Implement Client-Side Decorations (CSD) with custom title bar

- Disable native window decorations in tauri.conf.json
- Create TitleBar component with minimize/maximize/close controls
- Add drag region for window movement
- Integrate TitleBar into main layout and LoginView
- Adjust content heights to account for 36px title bar
- Prepare for future mini player implementation
This commit is contained in:
vicrodh committed 2026-01-11 02:56:17 -06:00
1 parent 57bfc0eb48
commit 96dc524c92
5 files changed
+155 -11

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@
"height": 800,
"minWidth": 800,
"minHeight": 600,
"decorations": true,
"decorations": false,
"transparent": false,
"resizable": true
}
+1 -1
View File
@@ -197,7 +197,7 @@
background-color: var(--bg-secondary);
display: flex;
flex-direction: column;
height: calc(100vh - 104px);
height: calc(100vh - 140px); /* 104px NowPlayingBar + 36px TitleBar */
}
.content {
+123
View File
@@ -0,0 +1,123 @@
<script lang="ts">
import { Minus, Square, X, Copy } from 'lucide-svelte';
import { getCurrentWindow } from '@tauri-apps/api/window';
let isMaximized = $state(false);
const appWindow = getCurrentWindow();
// Check initial maximized state
appWindow.isMaximized().then((maximized) => {
isMaximized = maximized;
});
// Listen for window state changes
appWindow.onResized(async () => {
isMaximized = await appWindow.isMaximized();
});
function handleMinimize() {
appWindow.minimize();
}
function handleMaximize() {
appWindow.toggleMaximize();
}
function handleClose() {
appWindow.close();
}
</script>
<div class="titlebar" data-tauri-drag-region>
<div class="titlebar-content" data-tauri-drag-region>
<img src="/icons/AppIcons/android/48x48.png" alt="QBZ" class="titlebar-icon" />
<span class="titlebar-title">QBZ</span>
</div>
<div class="window-controls">
<button class="control-btn" onclick={handleMinimize} title="Minimize">
<Minus size={14} />
</button>
<button class="control-btn" onclick={handleMaximize} title={isMaximized ? "Restore" : "Maximize"}>
{#if isMaximized}
<Copy size={12} />
{:else}
<Square size={12} />
{/if}
</button>
<button class="control-btn close-btn" onclick={handleClose} title="Close">
<X size={14} />
</button>
</div>
</div>
<style>
.titlebar {
height: 36px;
min-height: 36px;
background-color: var(--bg-primary);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
user-select: none;
-webkit-user-select: none;
border-bottom: 1px solid var(--bg-tertiary);
}
.titlebar-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.titlebar-icon {
width: 20px;
height: 20px;
border-radius: 4px;
pointer-events: none;
}
.titlebar-title {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
pointer-events: none;
}
.window-controls {
display: flex;
align-items: center;
gap: 2px;
}
.control-btn {
width: 36px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 4px;
color: var(--text-muted);
cursor: pointer;
transition: all 100ms ease;
}
.control-btn:hover {
background-color: var(--bg-hover);
color: var(--text-primary);
}
.control-btn.close-btn:hover {
background-color: #e81123;
color: white;
}
.control-btn :global(svg) {
pointer-events: none;
}
</style>
+14 -7
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { invoke } from '@tauri-apps/api/core';
import TitleBar from '../TitleBar.svelte';
interface UserInfo {
userName: string;
@@ -129,8 +130,10 @@
}
</script>
<div class="login-view">
<div class="login-card">
<div class="login-wrapper">
<TitleBar />
<div class="login-view">
<div class="login-card">
<!-- Logo -->
<div class="logo">
<img src="/logo.png" alt="QBZ Logo" class="logo-img" />
@@ -201,16 +204,20 @@
Your credentials are sent directly to Qobuz and stored securely in your system's keyring.
</p>
{/if}
</div>
</div>
</div>
<style>
.login-wrapper {
display: flex;
flex-direction: column;
height: 100vh;
background-color: var(--bg-primary);
}
.login-view {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
+16 -2
View File
@@ -183,6 +183,7 @@
} from '$lib/stores/lyricsStore';
// Components
import TitleBar from '$lib/components/TitleBar.svelte';
import Sidebar from '$lib/components/Sidebar.svelte';
import AboutModal from '$lib/components/AboutModal.svelte';
import NowPlayingBar from '$lib/components/NowPlayingBar.svelte';
@@ -1025,6 +1026,10 @@
<LoginView onLoginSuccess={handleLoginSuccess} />
{:else}
<div class="app">
<!-- Custom Title Bar (CSD) -->
<TitleBar />
<div class="app-body">
<!-- Sidebar -->
<Sidebar
bind:this={sidebarRef}
@@ -1229,6 +1234,7 @@
onCast={openCastPicker}
/>
{/if}
</div><!-- end app-body -->
<!-- Queue Panel -->
<QueuePanel
@@ -1350,23 +1356,31 @@
<style>
.app {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
background-color: var(--bg-primary);
}
.app-body {
display: flex;
flex: 1;
min-height: 0;
overflow: hidden;
}
.content-area {
display: flex;
flex: 1;
min-width: 0;
height: calc(100vh - 104px);
height: calc(100vh - 140px); /* 104px NowPlayingBar + 36px TitleBar */
overflow: hidden;
}
.main-content {
flex: 1;
min-width: 0;
height: calc(100vh - 104px);
height: calc(100vh - 140px); /* 104px NowPlayingBar + 36px TitleBar */
overflow-y: auto;
padding: 24px 32px;
}