diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 2490b2c1..ae69b939 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -826,6 +826,17 @@ pub fn run() { })) .setup(|app| { + // ── Custom title bar on Linux ───────────────────────────────── + // Remove OS window decorations so the React TitleBar component + // takes over. macOS and Windows keep their native decorations. + #[cfg(target_os = "linux")] + { + use tauri::Manager; + if let Some(win) = app.get_webview_window("main") { + let _ = win.set_decorations(false); + } + } + // ── System tray ─────────────────────────────────────────────── // Always build on startup; the frontend calls toggle_tray_icon(false) // immediately after load if the user has disabled the tray icon. diff --git a/src/App.tsx b/src/App.tsx index c4b89f0e..26ddb851 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -51,6 +51,8 @@ import GenreDetail from './pages/GenreDetail'; import ExportPickerModal from './components/ExportPickerModal'; import ChangelogModal from './components/ChangelogModal'; import AppUpdater from './components/AppUpdater'; +import TitleBar from './components/TitleBar'; +import { IS_LINUX } from './utils/platform'; import { version } from '../package.json'; import { useConnectionStatus } from './hooks/useConnectionStatus'; import { useAuthStore } from './store/authStore'; @@ -248,16 +250,18 @@ function AppShell() { const isMobilePlayer = isMobile && location.pathname === '/now-playing'; return ( -
e.preventDefault()} > + {IS_LINUX && } {!isMobile && ( s.currentTrack); + const isPlaying = usePlayerStore(s => s.isPlaying); + + return ( +
+ Psysonic + +
+ {currentTrack && ( + <> + {isPlaying ? '▶' : '⏸'} + + {currentTrack.artist && `${currentTrack.artist} – `}{currentTrack.title} + + + )} +
+ +
+ + + +
+
+ ); +} diff --git a/src/styles/layout.css b/src/styles/layout.css index 245137d4..180152e5 100644 --- a/src/styles/layout.css +++ b/src/styles/layout.css @@ -27,6 +27,98 @@ background: var(--bg-app); } +/* ─── Custom title bar (Linux only — decorations: false) ─── */ +:root { + --titlebar-height: 32px; +} + +.app-shell[data-titlebar] { + grid-template-rows: var(--titlebar-height) 1fr var(--player-height); + grid-template-areas: + "titlebar titlebar titlebar" + "sidebar main queue" + "player player player"; +} + +.titlebar { + grid-area: titlebar; + display: flex; + align-items: center; + justify-content: space-between; + background: var(--bg-sidebar); + border-bottom: 1px solid var(--border-subtle); + padding: 0 6px 0 12px; + height: var(--titlebar-height); + user-select: none; +} + +.titlebar-title { + font-size: 12px; + font-weight: 600; + color: var(--text-muted); + letter-spacing: 0.02em; + pointer-events: none; + flex: 0 0 auto; +} + +.titlebar-track { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + gap: 6px; + max-width: 40%; + pointer-events: none; +} + +.titlebar-track-state { + font-size: 9px; + color: var(--accent); + flex-shrink: 0; +} + +.titlebar-track-text { + font-size: 12px; + color: var(--text-secondary); + max-width: 100%; +} + +.titlebar-controls { + display: flex; + gap: 2px; + flex: 0 0 auto; +} + +.titlebar-btn { + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 24px; + border: none; + background: transparent; + color: var(--text-muted); + border-radius: var(--radius-sm); + cursor: pointer; + transition: background var(--transition-fast), color var(--transition-fast); +} + +.titlebar-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.titlebar-btn-close:hover { + background: var(--danger); + color: #fff; +} + +/* Resizer handles must start below the titlebar */ +.app-shell[data-titlebar] .resizer { + top: var(--titlebar-height); +} + /* ─── Resizer Handles ─── */ .resizer { position: absolute; diff --git a/src/utils/platform.ts b/src/utils/platform.ts new file mode 100644 index 00000000..91e1c5e2 --- /dev/null +++ b/src/utils/platform.ts @@ -0,0 +1,2 @@ +/** True when running on Linux (WebKitGTK). Used to show the custom title bar. */ +export const IS_LINUX = navigator.platform.toLowerCase().includes('linux');