Files
qbz/crates/qbz-slint/ui/settings/PlaybackSettings.slint
T
vicrodh 99e3513d68 feat(slint): NowPlayingBar rebuild, Settings constraints, Discover fixes
NowPlayingBar (start of the playback-bar work):
- height raised to 110px
- SeekBar component: three-line track (total / cached / progress), a
  hover thumb, elapsed/remaining times, and a hover time bubble with a
  downward caret marking the seek position
- song card: background and border dropped, gains the extra height,
  album/artist links and the context icon kept (context icon now
  always shown)
- transport + secondary buttons copied from Tauri (shuffle, prev,
  play, next, repeat, add, favorite; cast, picture-in-picture,
  fullscreen, normalization; volume cluster). Queue/Lyrics stay in the
  header — and the header now orders them lyrics-then-queue.

Settings constraints (1:1 with Tauri):
- Exclusive Mode enabled only on ALSA; DAC Passthrough only on
  PipeWire; Lock Output Device disabled while DAC Passthrough is on;
  Gapless disabled while Streaming Only is on
- backend change cascades: forces off DAC passthrough / force-bit-
  perfect / exclusive / gapless as appropriate; DAC-passthrough and
  streaming-only cascades; initial-buffer slider right-aligned

Discover fixes:
- navigation [<] [>] pair homologated to the same top/left margin
  across every view
- album/artist description moved back into the metadata column (it now
  fills the width there) with uniform secondary text color
- genre filter button hugs its label instead of stretching
- Recently Played Albums and Tracks modules always render, with a
  placeholder until the play history has data
- wider spacing between Discover rows; the Discover gear is no longer
  a Settings shortcut (it is the per-tab sort/show-hide control)

cc
2026-05-18 21:22:40 -06:00

178 lines
6.0 KiB
Plaintext

// Playback settings panel — end-of-source behavior, session persistence,
// and streaming/caching options. 1:1 with the Tauri app. Backed by the
// SettingsState global and persisted through the Rust settings
// controller.
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { SettingsState } from "../state.slint";
import { QbzToggle } from "../primitives/QbzToggle.slint";
import { QbzSelect } from "../primitives/QbzSelect.slint";
import { QbzSlider } from "../primitives/QbzSlider.slint";
import { SettingRow } from "SettingRow.slint";
component GroupHeader inherits Text {
color: Theme.text-muted;
font-size: 11px;
letter-spacing: 1.5px;
font-weight: Typography.semibold;
}
component Divider inherits Rectangle {
height: 1px;
background: Theme.border-subtle;
}
export component PlaybackSettings inherits VerticalLayout {
callback settings-bool(string, bool);
callback settings-select(string, int);
callback settings-slider(string, int);
spacing: 4px;
GroupHeader { text: @tr("PLAYBACK"); }
SettingRow {
label: @tr("Continue playback after track ends");
description: @tr("Keep playing the rest of the album or playlist instead of stopping.");
QbzToggle {
checked: SettingsState.continue-playback;
toggled(v) => {
SettingsState.continue-playback = v;
root.settings-bool("continue-playback", v);
}
}
}
SettingRow {
label: @tr("Show track playing context");
description: @tr("Display the context-stack icon in the player.");
QbzToggle {
checked: SettingsState.show-context-icon;
toggled(v) => {
SettingsState.show-context-icon = v;
root.settings-bool("show-context-icon", v);
}
}
}
SettingRow {
label: @tr("Gapless playback");
description: @tr("Play consecutive same-format tracks without a gap.");
// Disabled while Streaming only is on — gapless needs local cache.
enabled: !SettingsState.streaming-only;
QbzToggle {
checked: SettingsState.gapless;
enabled: !SettingsState.streaming-only;
toggled(v) => {
SettingsState.gapless = v;
root.settings-bool("gapless", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
GroupHeader { text: @tr("SESSION"); }
SettingRow {
label: @tr("Restore session on startup");
description: @tr("Restore the queue and current track on the next launch.");
QbzToggle {
checked: SettingsState.persist-session;
toggled(v) => {
SettingsState.persist-session = v;
root.settings-bool("persist-session", v);
}
}
}
SettingRow {
label: @tr("Resume playback position");
description: @tr("Also seek back to where the saved track left off.");
enabled: SettingsState.persist-session;
QbzToggle {
checked: SettingsState.resume-position;
enabled: SettingsState.persist-session;
toggled(v) => {
SettingsState.resume-position = v;
root.settings-bool("resume-position", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
GroupHeader { text: @tr("STREAMING"); }
SettingRow {
label: @tr("Stream uncached tracks");
description: @tr("Start uncached tracks via streaming instead of waiting for the full download.");
QbzToggle {
checked: SettingsState.stream-uncached;
toggled(v) => {
SettingsState.stream-uncached = v;
root.settings-bool("stream-uncached", v);
}
}
}
if SettingsState.stream-uncached: SettingRow {
label: @tr("Initial buffer size");
description: @tr("Seconds of audio buffered before streaming playback starts.");
// Fixed width so the slider row right-aligns flush with the toggle
// and select rows instead of stretching across the row.
HorizontalLayout {
width: 200px;
spacing: 12px;
VerticalLayout {
alignment: center;
horizontal-stretch: 1;
QbzSlider {
minimum: 1;
maximum: 10;
value: SettingsState.buffer-seconds;
changed(v) => {
SettingsState.buffer-seconds = v;
root.settings-slider("buffer-seconds", v);
}
}
}
VerticalLayout {
alignment: center;
horizontal-stretch: 0;
Text {
text: SettingsState.buffer-seconds + @tr("s");
color: Theme.text-secondary;
font-size: Typography.body;
font-weight: Typography.medium;
}
}
}
}
SettingRow {
label: @tr("Streaming only");
description: @tr("Skip writing tracks to the local cache while streaming.");
QbzToggle {
checked: SettingsState.streaming-only;
toggled(v) => {
SettingsState.streaming-only = v;
root.settings-bool("streaming-only", v);
}
}
}
SettingRow {
label: @tr("When quality retries fail");
description: @tr("What to do when every quality tier for a track is unavailable.");
QbzSelect {
menu-width: 240px;
options: SettingsState.retry-behaviors;
current-index: SettingsState.retry-behavior-index;
selected(i) => {
SettingsState.retry-behavior-index = i;
root.settings-select("retry-behavior", i);
}
}
}
}