Introduce the Settings page, reachable from the Discover gear and the header menu. Two sub-sections backed by the existing UI-agnostic stores. - Audio panel: backend, output device and sample-rate dropdowns; toggles for exclusive mode, DAC passthrough, PipeWire force bit-perfect, gapless, normalization and quality fallback - Playback panel: autoplay mode, show context icon, remember session and resume position - settings.rs controller owns AudioSettingsStore + PlaybackPreferencesStore at the shared global path; persists changes and applies audio ones to the live Player (reload_settings, plus reinit_device for routing- critical changes); a backend switch re-enumerates output devices - QbzToggle (switch) and QbzSelect (PopupWindow dropdown) primitives, SettingRow layout helper, chevron-down icon - SettingsState global, ContentView::settings, settings-bool / settings-select callbacks wired through the shell cc
152 lines
4.8 KiB
Plaintext
152 lines
4.8 KiB
Plaintext
// Audio settings panel — output routing, bit-perfect options, and
|
|
// playback-quality preferences. All rows are 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 { 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 AudioSettings inherits VerticalLayout {
|
|
callback settings-bool(string, bool);
|
|
callback settings-select(string, int);
|
|
|
|
spacing: 4px;
|
|
|
|
GroupHeader { text: @tr("OUTPUT"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Audio backend");
|
|
description: @tr("The audio stack QBZ routes playback through.");
|
|
QbzSelect {
|
|
menu-width: 220px;
|
|
options: SettingsState.backends;
|
|
current-index: SettingsState.backend-index;
|
|
selected(i) => {
|
|
SettingsState.backend-index = i;
|
|
root.settings-select("backend", i);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Output device");
|
|
description: @tr("The DAC or sound device that receives audio.");
|
|
QbzSelect {
|
|
menu-width: 300px;
|
|
options: SettingsState.devices;
|
|
current-index: SettingsState.device-index;
|
|
selected(i) => {
|
|
SettingsState.device-index = i;
|
|
root.settings-select("device", i);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Preferred sample rate");
|
|
description: @tr("Force a sample rate, or let the track decide.");
|
|
QbzSelect {
|
|
menu-width: 170px;
|
|
options: SettingsState.sample-rates;
|
|
current-index: SettingsState.sample-rate-index;
|
|
selected(i) => {
|
|
SettingsState.sample-rate-index = i;
|
|
root.settings-select("sample-rate", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
Divider { }
|
|
Rectangle { height: 12px; }
|
|
|
|
GroupHeader { text: @tr("BIT-PERFECT"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Exclusive mode");
|
|
description: @tr("Lock the device so no other app can resample it.");
|
|
QbzToggle {
|
|
checked: SettingsState.exclusive-mode;
|
|
toggled(v) => {
|
|
SettingsState.exclusive-mode = v;
|
|
root.settings-bool("exclusive-mode", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("DAC passthrough");
|
|
description: @tr("Send the bitstream untouched to the DAC.");
|
|
QbzToggle {
|
|
checked: SettingsState.dac-passthrough;
|
|
toggled(v) => {
|
|
SettingsState.dac-passthrough = v;
|
|
root.settings-bool("dac-passthrough", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("PipeWire force bit-perfect");
|
|
description: @tr("Pin the PipeWire quantum and rate for the active track.");
|
|
QbzToggle {
|
|
checked: SettingsState.pw-force-bitperfect;
|
|
toggled(v) => {
|
|
SettingsState.pw-force-bitperfect = v;
|
|
root.settings-bool("pw-force-bitperfect", v);
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
Divider { }
|
|
Rectangle { height: 12px; }
|
|
|
|
GroupHeader { text: @tr("PLAYBACK QUALITY"); }
|
|
|
|
SettingRow {
|
|
label: @tr("Gapless playback");
|
|
description: @tr("Play consecutive same-format tracks without a gap.");
|
|
QbzToggle {
|
|
checked: SettingsState.gapless;
|
|
toggled(v) => {
|
|
SettingsState.gapless = v;
|
|
root.settings-bool("gapless", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Volume normalization");
|
|
description: @tr("Apply ReplayGain. Off keeps the pipeline bit-perfect.");
|
|
QbzToggle {
|
|
checked: SettingsState.normalization;
|
|
toggled(v) => {
|
|
SettingsState.normalization = v;
|
|
root.settings-bool("normalization", v);
|
|
}
|
|
}
|
|
}
|
|
SettingRow {
|
|
label: @tr("Allow quality fallback");
|
|
description: @tr("Drop to a lower tier when the requested one is unavailable.");
|
|
QbzToggle {
|
|
checked: SettingsState.allow-quality-fallback;
|
|
toggled(v) => {
|
|
SettingsState.allow-quality-fallback = v;
|
|
root.settings-bool("allow-quality-fallback", v);
|
|
}
|
|
}
|
|
}
|
|
}
|