mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +00:00
feat(titlebar): theme-colored window bar on macOS (#1199)
* feat(titlebar): theme-colored window bar on macOS macOS showed the grey native title bar that ignores the app theme (#1198). Enable titleBarStyle: Overlay so the webview reaches the top edge and the native traffic lights float over the existing in-page title bar, which already paints with the active theme. Reuses the Linux title-bar grid: the bar is shown on macOS too (hidden in native fullscreen), without the custom window buttons since the traffic lights stay native. The GTK resize grips are now Linux-only. Renamed the stylesheet to drop the misleading linux-only name. * docs(changelog): macOS themed title bar * fix(titlebar): crisp macOS bar text — drop transform layer and shadow * fix(titlebar): drop now-playing text from the macOS bar
This commit is contained in:
@@ -156,6 +156,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
* Fifty strings that still fell back to English in the Russian UI are now translated — macOS in-place updater, device sync file migration, fullscreen lyrics, and statistics share-image export.
|
||||
* User-facing descriptions in Russian and English no longer mention WebKitGTK or Fisher–Yates internals; several Russian labels and section titles read more naturally (settings casing, smart playlists, track transitions, and home rails).
|
||||
|
||||
### macOS — themed window title bar
|
||||
|
||||
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1199](https://github.com/Psychotoxical/psysonic/pull/1199)**, suggested by [@bcorporaal](https://github.com/bcorporaal)
|
||||
|
||||
* On macOS the window's title bar now follows the active theme instead of the grey system bar; the native macOS window buttons stay in place, floating over the themed bar.
|
||||
|
||||
|
||||
## Fixed
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"fullscreen": false,
|
||||
"decorations": true,
|
||||
"transparent": false,
|
||||
"titleBarStyle": "Overlay",
|
||||
"visible": false,
|
||||
"backgroundColor": "#1e1e2e",
|
||||
"dragDropEnabled": false
|
||||
|
||||
+14
-4
@@ -56,7 +56,7 @@ import { useNowPlayingPrewarm } from '../hooks/useNowPlayingPrewarm';
|
||||
import { useOfflineAutoNav } from '../hooks/useOfflineAutoNav';
|
||||
import { useOfflineLibraryFilterSuspend } from '../hooks/useOfflineLibraryFilterSuspend';
|
||||
import { AppShellQueueResizerSeam } from '../components/AppShellQueueResizerSeam';
|
||||
import { IS_LINUX } from '../utils/platform';
|
||||
import { IS_LINUX, IS_MACOS } from '../utils/platform';
|
||||
import { useConnectionStatus } from '../hooks/useConnectionStatus';
|
||||
import { useIdlePlayQueuePull } from '../hooks/useIdlePlayQueuePull';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
@@ -75,7 +75,7 @@ import {
|
||||
} from '../utils/componentHelpers/appShellHelpers';
|
||||
|
||||
/**
|
||||
* The main webview's persistent layout: titlebar (Linux only) + sidebar +
|
||||
* The main webview's persistent layout: titlebar (Linux + macOS) + sidebar +
|
||||
* main content area (header + route host + offline banner) + queue panel +
|
||||
* player bar + fullscreen overlay + global modals + tray-tooltip / title
|
||||
* sync. Mounted under `<RequireAuth>` and shared across all routes.
|
||||
@@ -226,12 +226,22 @@ export function AppShell() {
|
||||
|
||||
const isMobilePlayer = isMobile && location.pathname === '/now-playing';
|
||||
|
||||
// Custom in-page titlebar. Linux: opt-in, native decorations off. macOS:
|
||||
// always on — `titleBarStyle: Overlay` lets the webview reach the top edge
|
||||
// with the native traffic lights floating over our themed bar, so the bar
|
||||
// follows the active theme instead of the grey system titlebar (#1198).
|
||||
// Hidden in native fullscreen (the OS chrome is gone there anyway).
|
||||
const showLinuxTitlebar = IS_LINUX && useCustomTitlebar && !isWindowFullscreen && !isTilingWm;
|
||||
const showMacTitlebar = IS_MACOS && !isWindowFullscreen;
|
||||
const showTitlebar = showLinuxTitlebar || showMacTitlebar;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`app-shell ${floatingPlayerBar ? 'floating-player' : ''}`}
|
||||
data-mobile={isMobile || undefined}
|
||||
data-mobile-player={isMobilePlayer || undefined}
|
||||
data-titlebar={(IS_LINUX && useCustomTitlebar && !isWindowFullscreen && !isTilingWm) || undefined}
|
||||
data-titlebar={showTitlebar || undefined}
|
||||
data-titlebar-platform={showMacTitlebar ? 'macos' : showLinuxTitlebar ? 'linux' : undefined}
|
||||
data-fullscreen={isWindowFullscreen || undefined}
|
||||
style={{
|
||||
'--sidebar-width': isMobile ? '0px' : (isSidebarCollapsed ? '72px' : 'clamp(200px, 15vw, 220px)'),
|
||||
@@ -241,7 +251,7 @@ export function AppShell() {
|
||||
} as React.CSSProperties}
|
||||
onContextMenu={e => e.preventDefault()}
|
||||
>
|
||||
{IS_LINUX && useCustomTitlebar && !isWindowFullscreen && !isTilingWm && <TitleBar />}
|
||||
{showTitlebar && <TitleBar />}
|
||||
{import.meta.env.DEV && isMobile && (
|
||||
<span className="dev-build-badge" aria-hidden>DEV</span>
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
import { Minus, Square, X } from 'lucide-react';
|
||||
import { usePlayerStore } from '../store/playerStore';
|
||||
import { useAuthStore } from '../store/authStore';
|
||||
import { IS_MACOS } from '../utils/platform';
|
||||
|
||||
export default function TitleBar() {
|
||||
const win = getCurrentWindow();
|
||||
@@ -13,6 +14,11 @@ export default function TitleBar() {
|
||||
|
||||
return (
|
||||
<div className="titlebar" data-tauri-drag-region>
|
||||
{/* macOS drops the now-playing label: subpixel antialiasing is disabled
|
||||
in the Overlay title-bar zone, so small text renders frayed. The bar
|
||||
stays a clean themed strip with the native traffic lights (#1198);
|
||||
the track is already shown in the player bar. */}
|
||||
{!IS_MACOS && (
|
||||
<div className="titlebar-track" data-tauri-drag-region>
|
||||
{currentTrack && (
|
||||
<>
|
||||
@@ -23,7 +29,11 @@ export default function TitleBar() {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* macOS keeps its native traffic lights (floating over the bar via
|
||||
titleBarStyle: Overlay); only Linux draws in-page window buttons. */}
|
||||
{!IS_MACOS && (
|
||||
<div className="titlebar-controls" data-btnstyle={windowButtonStyle}>
|
||||
{showMinimizeButton && (
|
||||
<button
|
||||
@@ -55,6 +65,7 @@ export default function TitleBar() {
|
||||
<X size={10} strokeWidth={2.5} aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -392,6 +392,7 @@ const CONTRIBUTOR_ENTRIES = [
|
||||
'Compact buttons — appearance toggle that switches action and toolbar buttons to icon-only across detail headers and browse views, app-wide (PR #1189)',
|
||||
'Playlist sort — sort a playlist by date added (newest/oldest) or any track column from a visible sort dropdown (PR #1191)',
|
||||
'Configurable artist backdrops — per-place source order + on/off (mainstage hero, artist page, fullscreen), with the mainstage hero showing the artist backdrop and prefetching upcoming ones (PR #1193)',
|
||||
'Themed window title bar on macOS — follows the active theme instead of the grey system bar, with the native window buttons floating over it (PR #1199)',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
+15
-8
@@ -1,4 +1,4 @@
|
||||
/* ─── Custom title bar (Linux only — decorations: false) ─── */
|
||||
/* ─── Custom title bar — Linux (decorations: false) + macOS (titleBarStyle: Overlay) ─── */
|
||||
:root {
|
||||
--titlebar-height: 32px;
|
||||
}
|
||||
@@ -19,9 +19,10 @@
|
||||
"sidebar main queue";
|
||||
}
|
||||
|
||||
/* Resize grips — replace the native GTK grips lost when decorations: false */
|
||||
.app-shell[data-titlebar]::before,
|
||||
.app-shell[data-titlebar]::after {
|
||||
/* Resize grips — Linux only; replace the native GTK grips lost when
|
||||
decorations: false. macOS keeps its native window-edge resize. */
|
||||
.app-shell[data-titlebar-platform='linux']::before,
|
||||
.app-shell[data-titlebar-platform='linux']::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
@@ -33,8 +34,8 @@
|
||||
opacity: 0.35;
|
||||
z-index: 9999;
|
||||
}
|
||||
.app-shell[data-titlebar]::before { left: 0; }
|
||||
.app-shell[data-titlebar]::after { right: 0; }
|
||||
.app-shell[data-titlebar-platform='linux']::before { left: 0; }
|
||||
.app-shell[data-titlebar-platform='linux']::after { right: 0; }
|
||||
|
||||
.titlebar {
|
||||
grid-area: titlebar;
|
||||
@@ -50,8 +51,14 @@
|
||||
|
||||
.titlebar-track {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
/* Centre without translateX: a transform promotes the label to its own
|
||||
compositing layer, which drops macOS subpixel antialiasing and lands it
|
||||
on half-pixels — the text ends up frayed/grainy. Auto-margins between
|
||||
left:0/right:0 keep it centred and pixel-snapped on both platforms. */
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: fit-content;
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -1,6 +1,6 @@
|
||||
@import './_intro.css';
|
||||
@import './dev-build-chrome.css';
|
||||
@import './custom-title-bar-linux-only-decorations-false.css';
|
||||
@import './custom-title-bar.css';
|
||||
@import './resizer-handles.css';
|
||||
@import './sidebar.css';
|
||||
@import './update-toast.css';
|
||||
|
||||
Reference in New Issue
Block a user