feat(qbz-slint): header redesign, Lucide icons, scrollbar fixes
- Replace every Unicode glyph placeholder with Lucide icons via QbzIcon (player transport, carousel nav, album-card overlay, album-page actions, gear, search, checkbox, sidebar). - Header: the search field is centered to the window width and gets a search icon; the header and player bar now use the elevated surface, so all chrome (header, sidebar, player bar) shares one color over the darker content area. - The sidebar user card moves to the header as a user element (name + QBZ logo) that opens a dropdown — Settings, Keyboard Shortcuts, Documentation, Report an Issue, About, plus Log Out and Close. Log Out clears the session and returns to login; Close quits the app. - Content ScrollViews disable the horizontal scrollbar — no view scrolls horizontally, so the phantom reserved lane is gone. cc
This commit is contained in:
13 files changed
+389
-165
No files matched your search
@@ -10,10 +10,11 @@ import { Typography } from "../foundation/typography.slint";
|
||||
import { Radius } from "../foundation/radius.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { AlbumState, AlbumTrackItem, NavState } from "../state.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
component CircleAction inherits Rectangle {
|
||||
in property <string> glyph;
|
||||
in property <image> icon;
|
||||
in property <bool> primary: false;
|
||||
in property <length> diameter: 40px;
|
||||
callback clicked();
|
||||
@@ -25,12 +26,13 @@ component CircleAction inherits Rectangle {
|
||||
? (ta.has-hover ? Theme.accent-hover : Theme.accent)
|
||||
: (ta.has-hover ? Theme.surface-hover : Theme.surface-elevated);
|
||||
|
||||
Text {
|
||||
text: root.glyph;
|
||||
color: root.primary ? Theme.accent-text : Theme.text-secondary;
|
||||
font-size: 16px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
QbzIcon {
|
||||
source: root.icon;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: root.primary ? Theme.accent-text : Theme.text-secondary;
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
@@ -133,6 +135,7 @@ export component AlbumPageView inherits Rectangle {
|
||||
background: Theme.surface-main;
|
||||
|
||||
ScrollView {
|
||||
horizontal-scrollbar-policy: ScrollBarPolicy.always-off;
|
||||
VerticalLayout {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
@@ -151,11 +154,14 @@ export component AlbumPageView inherits Rectangle {
|
||||
}
|
||||
back-row := HorizontalLayout {
|
||||
spacing: 6px;
|
||||
Text {
|
||||
text: "‹";
|
||||
color: back.has-hover ? Theme.text-secondary : Theme.text-muted;
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/arrow-left.svg");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
tint: back.has-hover ? Theme.text-secondary : Theme.text-muted;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: @tr("Back");
|
||||
@@ -222,18 +228,22 @@ export component AlbumPageView inherits Rectangle {
|
||||
HorizontalLayout {
|
||||
alignment: start;
|
||||
spacing: 12px;
|
||||
CircleAction { glyph: "▶"; primary: true; diameter: 44px; }
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
CircleAction { glyph: "⇄"; }
|
||||
CircleAction {
|
||||
icon: @image-url("../assets/icons/play.svg");
|
||||
primary: true;
|
||||
diameter: 44px;
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
CircleAction { glyph: "♡"; }
|
||||
CircleAction { icon: @image-url("../assets/icons/shuffle.svg"); }
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
CircleAction { glyph: "⋯"; }
|
||||
CircleAction { icon: @image-url("../assets/icons/heart.svg"); }
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
CircleAction { icon: @image-url("../assets/icons/ellipsis.svg"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { LoginScreen } from "login/LoginScreen.slint";
|
||||
import { AppShell } from "shell/AppShell.slint";
|
||||
|
||||
// Re-export the state globals so the Rust layer can populate them.
|
||||
export { HomeState, AlbumState, NavState } from "state.slint";
|
||||
export { HomeState, AlbumState, NavState, SessionState } from "state.slint";
|
||||
|
||||
// Which top-level screen is shown. The app starts on `splash` while it
|
||||
// restores a saved session, then resolves to `shell` or `login`.
|
||||
@@ -32,6 +32,8 @@ export component AppWindow inherits Window {
|
||||
callback start-offline();
|
||||
callback open-tos();
|
||||
callback open-album(string);
|
||||
callback logout();
|
||||
callback close-app();
|
||||
|
||||
if root.screen == AppScreen.splash: SplashScreen { }
|
||||
|
||||
@@ -54,5 +56,11 @@ export component AppWindow inherits Window {
|
||||
open-album(id) => {
|
||||
root.open-album(id);
|
||||
}
|
||||
logout => {
|
||||
root.logout();
|
||||
}
|
||||
close-app => {
|
||||
root.close-app();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,14 @@ import { Typography } from "../foundation/typography.slint";
|
||||
import { Radius } from "../foundation/radius.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { AlbumCardItem } from "../state.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
|
||||
// Circular hover-overlay action button.
|
||||
component OverlayButton inherits Rectangle {
|
||||
in property <string> glyph;
|
||||
in property <image> icon;
|
||||
in property <bool> primary: false;
|
||||
in property <length> diameter: 36px;
|
||||
in property <length> glyph-size: 16px;
|
||||
in property <length> icon-size: 16px;
|
||||
|
||||
width: root.diameter;
|
||||
height: root.diameter;
|
||||
@@ -26,12 +27,13 @@ component OverlayButton inherits Rectangle {
|
||||
border-width: root.primary ? 0px : 1.5px;
|
||||
border-color: #ffffffe6;
|
||||
|
||||
Text {
|
||||
text: root.glyph;
|
||||
color: root.primary ? #000000 : #ffffff;
|
||||
font-size: root.glyph-size;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
QbzIcon {
|
||||
source: root.icon;
|
||||
width: root.icon-size;
|
||||
height: root.icon-size;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: root.primary ? #000000 : #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,17 +111,17 @@ export component AlbumCard inherits Rectangle {
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
OverlayButton { glyph: "♡"; }
|
||||
OverlayButton { icon: @image-url("../assets/icons/heart.svg"); }
|
||||
}
|
||||
OverlayButton {
|
||||
glyph: "▶";
|
||||
icon: @image-url("../assets/icons/play.svg");
|
||||
primary: true;
|
||||
diameter: 44px;
|
||||
glyph-size: 17px;
|
||||
icon-size: 18px;
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
OverlayButton { glyph: "⋯"; glyph-size: 18px; }
|
||||
OverlayButton { icon: @image-url("../assets/icons/ellipsis.svg"); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@ import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { DiscoverSection } from "../state.slint";
|
||||
import { AlbumCard } from "AlbumCard.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
|
||||
component NavButton inherits Rectangle {
|
||||
in property <string> glyph;
|
||||
in property <image> icon;
|
||||
in property <bool> enabled: true;
|
||||
callback clicked();
|
||||
|
||||
@@ -23,12 +24,13 @@ component NavButton inherits Rectangle {
|
||||
? Theme.surface-hover
|
||||
: Theme.surface-elevated;
|
||||
|
||||
Text {
|
||||
text: root.glyph;
|
||||
color: root.enabled ? Theme.text-primary : Theme.text-muted;
|
||||
font-size: 16px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
QbzIcon {
|
||||
source: root.icon;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: root.enabled ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
@@ -77,7 +79,7 @@ export component Carousel inherits VerticalLayout {
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavButton {
|
||||
glyph: "‹";
|
||||
icon: @image-url("../assets/icons/chevron-left.svg");
|
||||
enabled: root.current-page > 0;
|
||||
clicked => {
|
||||
root.page = root.current-page - 1;
|
||||
@@ -87,7 +89,7 @@ export component Carousel inherits VerticalLayout {
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
NavButton {
|
||||
glyph: "›";
|
||||
icon: @image-url("../assets/icons/chevron-right.svg");
|
||||
enabled: root.current-page < root.page-count - 1;
|
||||
clicked => {
|
||||
root.page = root.current-page + 1;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { HomeState } from "../state.slint";
|
||||
import { Carousel } from "Carousel.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
component Tab inherits Rectangle {
|
||||
@@ -50,8 +51,18 @@ component GenreButton inherits Rectangle {
|
||||
background: ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 14px;
|
||||
padding-left: 12px;
|
||||
padding-right: 14px;
|
||||
spacing: 7px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/list-filter.svg");
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
tint: Theme.text-secondary;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: @tr("Filter by genre");
|
||||
color: Theme.text-secondary;
|
||||
@@ -76,12 +87,13 @@ component GearButton inherits Rectangle {
|
||||
border-radius: 4px;
|
||||
background: ta.has-hover ? Theme.surface-hover : transparent;
|
||||
|
||||
Text {
|
||||
text: "⚙";
|
||||
color: ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
||||
font-size: 17px;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/settings.svg");
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: ta.has-hover ? Theme.text-primary : Theme.text-muted;
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
@@ -144,6 +156,7 @@ export component HomeView inherits Rectangle {
|
||||
// --- Scrollable sections -----------------------------------------
|
||||
ScrollView {
|
||||
vertical-stretch: 1;
|
||||
horizontal-scrollbar-policy: ScrollBarPolicy.always-off;
|
||||
VerticalLayout {
|
||||
padding: 32px;
|
||||
spacing: 28px;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Small square checkbox primitive matching the QBZ dark theme.
|
||||
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { QbzIcon } from "QbzIcon.slint";
|
||||
|
||||
export component QbzCheckbox inherits Rectangle {
|
||||
in-out property <bool> checked: false;
|
||||
@@ -13,14 +14,14 @@ export component QbzCheckbox inherits Rectangle {
|
||||
border-width: root.checked ? 0px : 1.5px;
|
||||
border-color: Theme.text-muted;
|
||||
|
||||
Text {
|
||||
text: "✓";
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/check.svg");
|
||||
visible: root.checked;
|
||||
color: Theme.accent-text;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: Theme.accent-text;
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
|
||||
@@ -13,13 +13,22 @@ import { AlbumPageView } from "../album/AlbumPageView.slint";
|
||||
|
||||
export component AppShell inherits Rectangle {
|
||||
callback open-album(string);
|
||||
callback logout();
|
||||
callback close-app();
|
||||
|
||||
background: Theme.surface-main;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
|
||||
HeaderBar { }
|
||||
HeaderBar {
|
||||
logout => {
|
||||
root.logout();
|
||||
}
|
||||
close-app => {
|
||||
root.close-app();
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
height: Layout.hairline;
|
||||
background: Theme.border-subtle;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Top header bar — primary navigation tabs and the search field.
|
||||
// Top header bar — primary navigation tabs, a window-centered search
|
||||
// field, and the user element with its dropdown menu.
|
||||
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { Layout } from "../foundation/layout.slint";
|
||||
import { Radius } from "../foundation/radius.slint";
|
||||
import { LineEdit } from "std-widgets.slint";
|
||||
import { SessionState } from "../state.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
|
||||
component NavTab inherits Rectangle {
|
||||
in property <string> label;
|
||||
@@ -37,35 +39,196 @@ component NavTab inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
export component HeaderBar inherits Rectangle {
|
||||
height: Layout.header-height;
|
||||
background: Theme.surface-main;
|
||||
component MenuItem inherits Rectangle {
|
||||
in property <image> icon;
|
||||
in property <string> label;
|
||||
callback clicked();
|
||||
|
||||
height: 34px;
|
||||
background: ta.has-hover ? Theme.surface-hover : transparent;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: Spacing.md;
|
||||
padding-right: Spacing.md;
|
||||
spacing: Spacing.xs;
|
||||
|
||||
padding-left: 12px;
|
||||
padding-right: 18px;
|
||||
spacing: 10px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: Spacing.xs;
|
||||
NavTab { label: "Discover"; active: true; }
|
||||
NavTab { label: "Library"; }
|
||||
NavTab { label: "Local Library"; }
|
||||
NavTab { label: "My QBZ"; }
|
||||
QbzIcon {
|
||||
source: root.icon;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
tint: Theme.text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: root.label;
|
||||
color: Theme.text-secondary;
|
||||
font-size: 13px;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
HorizontalLayout {
|
||||
}
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component HeaderBar inherits Rectangle {
|
||||
callback logout();
|
||||
callback close-app();
|
||||
|
||||
height: Layout.header-height;
|
||||
background: Theme.surface-card;
|
||||
|
||||
// --- Nav tabs (left) -------------------------------------------------
|
||||
VerticalLayout {
|
||||
x: Spacing.md;
|
||||
height: parent.height;
|
||||
alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: Spacing.xs;
|
||||
NavTab { label: "Discover"; active: true; }
|
||||
NavTab { label: "Library"; }
|
||||
NavTab { label: "Local Library"; }
|
||||
NavTab { label: "My QBZ"; }
|
||||
}
|
||||
}
|
||||
|
||||
// --- Search field, centered to the header width ----------------------
|
||||
Rectangle {
|
||||
width: 320px;
|
||||
height: 32px;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
border-radius: 6px;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-subtle;
|
||||
background: Theme.surface-elevated;
|
||||
|
||||
HorizontalLayout {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
spacing: 7px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/search.svg");
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
tint: Theme.text-muted;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: @tr("Search");
|
||||
color: Theme.text-muted;
|
||||
font-size: 13px;
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- User element + dropdown (right) ---------------------------------
|
||||
user-area := TouchArea {
|
||||
width: user-row.preferred-width + 16px;
|
||||
height: 36px;
|
||||
x: parent.width - self.width - Spacing.md;
|
||||
y: (parent.height - self.height) / 2;
|
||||
mouse-cursor: pointer;
|
||||
clicked => {
|
||||
menu.show();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: Radius.sm;
|
||||
background: user-area.has-hover ? Theme.surface-hover : transparent;
|
||||
}
|
||||
user-row := HorizontalLayout {
|
||||
x: 8px;
|
||||
height: parent.height;
|
||||
spacing: 8px;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: SessionState.user-name == ""
|
||||
? @tr("Guest")
|
||||
: SessionState.user-name;
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Image {
|
||||
source: @image-url("../assets/qbz-logo.png");
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
image-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu := PopupWindow {
|
||||
x: parent.width - 234px - Spacing.md;
|
||||
y: Layout.header-height - 4px;
|
||||
width: 234px;
|
||||
height: 254px;
|
||||
|
||||
Rectangle {
|
||||
background: Theme.surface-elevated;
|
||||
border-radius: Radius.md;
|
||||
border-width: 1px;
|
||||
border-color: Theme.border-subtle;
|
||||
drop-shadow-blur: 24px;
|
||||
drop-shadow-color: Theme.card-shadow;
|
||||
drop-shadow-offset-y: 6px;
|
||||
|
||||
VerticalLayout {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/settings.svg");
|
||||
label: @tr("Settings");
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/keyboard.svg");
|
||||
label: @tr("Keyboard Shortcuts");
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/book-open.svg");
|
||||
label: @tr("Documentation");
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/bug.svg");
|
||||
label: @tr("Report an Issue");
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/info.svg");
|
||||
label: @tr("About");
|
||||
}
|
||||
Rectangle {
|
||||
width: 360px;
|
||||
search := LineEdit {
|
||||
placeholder-text: "Search";
|
||||
height: 1px;
|
||||
background: Theme.border-subtle;
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/log-out.svg");
|
||||
label: @tr("Log Out");
|
||||
clicked => {
|
||||
root.logout();
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
icon: @image-url("../assets/icons/x.svg");
|
||||
label: @tr("Close");
|
||||
clicked => {
|
||||
root.close-app();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
// Bottom now-playing / transport bar.
|
||||
//
|
||||
// Shell-frame increment: controls are laid out and hover-reactive but
|
||||
// not yet wired to playback. Real playback state arrives in a later
|
||||
// milestone.
|
||||
// Controls are laid out and hover-reactive but not yet wired to
|
||||
// playback. Real playback state arrives in a later milestone.
|
||||
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { Layout } from "../foundation/layout.slint";
|
||||
import { Radius } from "../foundation/radius.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
|
||||
component GlyphButton inherits Rectangle {
|
||||
in property <string> glyph;
|
||||
in property <length> glyph-size: 18px;
|
||||
component IconButton inherits Rectangle {
|
||||
in property <image> icon;
|
||||
in property <length> icon-size: 18px;
|
||||
callback clicked();
|
||||
|
||||
width: 34px;
|
||||
@@ -20,12 +20,13 @@ component GlyphButton inherits Rectangle {
|
||||
border-radius: Radius.sm;
|
||||
background: ta.has-hover ? Theme.surface-hover : transparent;
|
||||
|
||||
Text {
|
||||
text: root.glyph;
|
||||
font-size: root.glyph-size;
|
||||
color: ta.has-hover ? Theme.text-primary : Theme.text-secondary;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
QbzIcon {
|
||||
source: root.icon;
|
||||
width: root.icon-size;
|
||||
height: root.icon-size;
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
tint: ta.has-hover ? Theme.text-primary : Theme.text-secondary;
|
||||
}
|
||||
|
||||
ta := TouchArea {
|
||||
@@ -38,7 +39,7 @@ component GlyphButton inherits Rectangle {
|
||||
|
||||
export component PlayerBar inherits Rectangle {
|
||||
height: Layout.player-bar-height;
|
||||
background: Theme.surface-main;
|
||||
background: Theme.surface-card;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: 0px;
|
||||
@@ -65,11 +66,14 @@ export component PlayerBar inherits Rectangle {
|
||||
alignment: center;
|
||||
HorizontalLayout {
|
||||
spacing: 2px;
|
||||
GlyphButton { glyph: "⇄"; }
|
||||
GlyphButton { glyph: "⏮"; }
|
||||
GlyphButton { glyph: "▶"; glyph-size: 22px; }
|
||||
GlyphButton { glyph: "⏭"; }
|
||||
GlyphButton { glyph: "↻"; }
|
||||
IconButton { icon: @image-url("../assets/icons/shuffle.svg"); }
|
||||
IconButton { icon: @image-url("../assets/icons/skip-back.svg"); }
|
||||
IconButton {
|
||||
icon: @image-url("../assets/icons/play.svg");
|
||||
icon-size: 22px;
|
||||
}
|
||||
IconButton { icon: @image-url("../assets/icons/skip-forward.svg"); }
|
||||
IconButton { icon: @image-url("../assets/icons/repeat.svg"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +98,7 @@ export component PlayerBar inherits Rectangle {
|
||||
font-size: Typography.body;
|
||||
}
|
||||
Text {
|
||||
text: "QBZ Slint POC";
|
||||
text: "QBZ MVP";
|
||||
color: Theme.text-muted;
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -108,8 +112,9 @@ export component PlayerBar inherits Rectangle {
|
||||
HorizontalLayout {
|
||||
alignment: end;
|
||||
spacing: Spacing.sm;
|
||||
GlyphButton { glyph: "♡"; }
|
||||
GlyphButton { glyph: "⤢"; }
|
||||
IconButton { icon: @image-url("../assets/icons/heart.svg"); }
|
||||
IconButton { icon: @image-url("../assets/icons/maximize-2.svg"); }
|
||||
IconButton { icon: @image-url("../assets/icons/volume-2.svg"); }
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
Rectangle {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Left navigation sidebar — playlists list plus the user card.
|
||||
// Left navigation sidebar — the playlists list.
|
||||
//
|
||||
// The playlist list is empty until real playlist data is wired (the
|
||||
// sidebar is a later milestone). No placeholder/fixture rows.
|
||||
// sidebar is a later milestone). The user element now lives in the
|
||||
// header, not at the bottom of the sidebar.
|
||||
|
||||
import { Theme } from "../foundation/semantic-colors.slint";
|
||||
import { Spacing } from "../foundation/spacing.slint";
|
||||
import { Typography } from "../foundation/typography.slint";
|
||||
import { Layout } from "../foundation/layout.slint";
|
||||
import { Radius } from "../foundation/radius.slint";
|
||||
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
||||
import { ScrollView } from "std-widgets.slint";
|
||||
|
||||
export component Sidebar inherits Rectangle {
|
||||
@@ -29,66 +29,25 @@ export component Sidebar inherits Rectangle {
|
||||
vertical-alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
}
|
||||
Text {
|
||||
text: "+";
|
||||
color: Theme.text-muted;
|
||||
font-size: 18px;
|
||||
vertical-alignment: center;
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
QbzIcon {
|
||||
source: @image-url("../assets/icons/plus.svg");
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
tint: Theme.text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
vertical-stretch: 1;
|
||||
horizontal-scrollbar-policy: ScrollBarPolicy.always-off;
|
||||
VerticalLayout {
|
||||
spacing: 2px;
|
||||
// Real playlist rows are populated when the sidebar
|
||||
// milestone wires playlist data. No fixtures.
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 56px;
|
||||
border-radius: Radius.md;
|
||||
background: Theme.surface-elevated;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: 10px;
|
||||
spacing: Spacing.sm;
|
||||
|
||||
Rectangle {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: Radius.pill;
|
||||
background: Theme.accent;
|
||||
Text {
|
||||
text: "Q";
|
||||
color: Theme.accent-text;
|
||||
font-weight: Typography.semibold;
|
||||
horizontal-alignment: center;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal-stretch: 1;
|
||||
Text {
|
||||
text: "QBZ User";
|
||||
color: Theme.text-primary;
|
||||
font-size: Typography.body;
|
||||
}
|
||||
Text {
|
||||
text: "QBZ MVP";
|
||||
color: Theme.text-muted;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: "⚙";
|
||||
color: Theme.text-muted;
|
||||
font-size: 16px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,3 +51,8 @@ export global AlbumState {
|
||||
export global NavState {
|
||||
in-out property <bool> showing-album: false;
|
||||
}
|
||||
|
||||
// Authenticated user, for the header user element.
|
||||
export global SessionState {
|
||||
in property <string> user-name: "";
|
||||
}
|
||||
Reference in new issue
Block a user