Files
qbz/crates/qbz-ui/ui/settings/AppearanceSettings.slint
T
vicrodh b7549790a3 feat: interface-size (UI scale) presets
Webview-zoom-style presets: Extra small 0.8 / Small 0.9 / Default 1.0 /
Large 1.2 / Extra large 1.5, applied through SLINT_SCALE_FACTOR set at
the top of main() before any thread or the backend exists. The env var
overrides the compositor DPR, so the value is last observed DPR x
preset; the Default preset leaves the var unset (stock DPR handling,
including live monitor changes, stays intact). last_dpr is persisted
from the winit window (real compositor DPR) on the 5s renderer-sentinel
timer.

- const-bake guard: strip SLINT_SCALE_FACTOR in qbz-ui build.rs so the
  slint compiler never bakes the factor into the generated code
- new UiScale global + scale-divided window minimums keep the physical
  minimum constant across presets (XL fits a 1366x768 screen); the
  three window-geometry gates in main.rs use the process-active factor
- artwork decode targets scale with the preset: sharp covers at >=1.2,
  smaller decoded-cache footprint below 1.0
- Settings > Appearance "Interface size" row below Language (replaces
  the commented-out percentage placeholder and its orphan properties),
  restart-to-apply toast
- Typography.boost = 1.1 on Extra small only, so body text stays
  readable at the 0.8 factor; Small (0.9) needs no compensation
- i18n: new strings added to all 7 locale catalogs

Known accepted limitation: slight text softness at fractional scale
factors (owner-accepted; diagnostic notes live in
qbz-nix-docs/ui-scale-presets/).

cc
2026-07-02 08:36:40 -06:00

811 lines
32 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Appearance settings panel — theme, typography, language, window chrome,
// notifications, player views, system tray, and immersive options. A 1:1
// structural replica of the Tauri app's Settings > Appearance section.
//
// This is primarily an UNWIRED replica: rows are bound to AppearanceState
// and emit generic appearance-bool / appearance-select / appearance-text /
// appearance-action callbacks, but the Rust side does not yet persist or
// apply most of them. The single exception is the sidebar-playlist-collage
// toggle, which is wired straight through to SidebarState.playlist-collage
// (an already-functional flag). Conditional rows are `if`-gated on the same
// flags Tauri gates on (System theme, title-bar mode, etc.).
import { Theme } from "../foundation/semantic-colors.slint";
import { ThemeState } from "../foundation/theme-state.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { AppearanceState, SidebarState, ShellState, ToastState, MyQbzBrandingState , UiFocusState } from "../state.slint";
import { QbzToggle } from "../primitives/QbzToggle.slint";
import { QbzSelect } from "../primitives/QbzSelect.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { SettingRow } from "SettingRow.slint";
import { LineEdit } from "std-widgets.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;
}
// A small secondary-style button used for "Select Image…" / "Regenerate".
component SecondaryButton inherits Rectangle {
in property <string> label;
in property <bool> enabled: true;
callback clicked();
width: 160px;
height: 34px;
border-radius: Radius.sm;
border-width: 1px;
border-color: Theme.border-subtle;
background: ta.has-hover && root.enabled ? Theme.surface-hover : Theme.surface-elevated;
opacity: root.enabled ? 1.0 : 0.4;
Text {
text: root.label;
color: Theme.text-secondary;
font-size: Typography.body;
font-weight: Typography.medium;
horizontal-alignment: center;
vertical-alignment: center;
}
ta := TouchArea {
mouse-cursor: root.enabled ? pointer : default;
clicked => {
if (root.enabled) {
root.clicked();
}
}
}
}
// A collapsible sub-section header (Immersive Panels / Animation Frame Rate).
// Hidden: its only users were the IMMERSIVE block (now commented out). Kept
// commented for re-enable alongside that block; do NOT delete.
/*
component CollapsibleHeader inherits Rectangle {
in property <string> title;
in property <string> summary;
in property <bool> collapsed: true;
callback toggled();
height: 56px;
HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
horizontal-stretch: 1;
spacing: 3px;
Text {
text: root.title;
color: Theme.text-primary;
font-size: Typography.body;
font-weight: Typography.semibold;
}
Text {
text: root.summary;
color: Theme.text-muted;
font-size: 12px;
}
}
VerticalLayout {
alignment: center;
QbzIcon {
source: root.collapsed
? @image-url("../assets/icons/chevron-down.svg")
: @image-url("../assets/icons/chevron-up.svg");
width: 16px;
height: 16px;
tint: Theme.text-secondary;
}
}
}
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.toggled();
}
}
}
*/
export component AppearanceSettings inherits VerticalLayout {
spacing: 4px;
// Derived: title-bar customization rows are disabled when the title bar
// is hidden or the system title bar is used (mirrors Tauri).
property <bool> tb-locked: AppearanceState.hide-title-bar || AppearanceState.use-system-title-bar;
// Any nav-in-title-bar target enabled — gates the nav position dropdown.
property <bool> nav-tb-any:
AppearanceState.nav-tb-discover || AppearanceState.nav-tb-favorites
|| AppearanceState.nav-tb-library || AppearanceState.nav-tb-myqbz
|| AppearanceState.nav-tb-purchases;
// ===================================================================
// THEME
// ===================================================================
GroupHeader { text: @tr("THEME"); }
SettingRow {
label: @tr("Theme");
HorizontalLayout {
spacing: 8px;
// Theme list filter cycle (All / Dark / Light).
filter-btn := Rectangle {
width: 34px;
height: 34px;
border-radius: Radius.sm;
border-width: 1px;
border-color: Theme.border-subtle;
background: filter-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
QbzIcon {
source: AppearanceState.theme-filter == 1
? @image-url("../assets/icons/disc.svg")
: AppearanceState.theme-filter == 2
? @image-url("../assets/icons/disc.svg")
: @image-url("../assets/icons/disc.svg");
width: 16px;
height: 16px;
tint: Theme.text-secondary;
}
filter-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
AppearanceState.theme-filter = Math.mod(AppearanceState.theme-filter + 1, 3);
AppearanceState.appearance-select("theme-filter", AppearanceState.theme-filter);
}
}
}
QbzSelect {
menu-width: 200px;
options: AppearanceState.themes;
current-index: AppearanceState.theme-index;
selected(i) => {
AppearanceState.theme-index = i;
// The Rust handler ("theme" arm) maps this dropdown index to
// a stable theme id, persists the slug, and hot-switches the
// palette via Theme.set_c. ThemeState.mode is kept in sync
// only as a legacy index reference (no longer drives colors).
ThemeState.mode = i;
AppearanceState.appearance-select("theme", i);
}
}
}
}
SettingRow {
label: @tr("Album header gradient");
description: @tr("Use artwork-derived blur as a backdrop in album and artist detail views.");
QbzToggle {
checked: AppearanceState.album-header-gradient;
toggled(v) => {
AppearanceState.album-header-gradient = v;
AppearanceState.appearance-bool("album-header-gradient", v);
}
}
}
SettingRow {
label: @tr("Intelligent Search");
description: @tr("Smart search cache, ranking, and the search preview dropdown. On by default.");
QbzToggle {
checked: AppearanceState.intelligent-search;
toggled(v) => {
AppearanceState.intelligent-search = v;
AppearanceState.appearance-bool("intelligent-search", v);
}
}
}
// Auto-Theme controls — only when the "System" theme is selected.
if AppearanceState.theme-is-system: SettingRow {
unsupported: true;
label: @tr("Source");
description: @tr("Generate a color theme from your system wallpaper or a custom image");
QbzSelect {
menu-width: 200px;
options: AppearanceState.auto-theme-sources;
current-index: AppearanceState.auto-theme-source-index;
selected(i) => {
AppearanceState.auto-theme-source-index = i;
AppearanceState.appearance-select("auto-theme-source", i);
}
}
}
if AppearanceState.theme-is-system && AppearanceState.auto-theme-source-index == 2: SettingRow {
unsupported: true;
label: AppearanceState.auto-theme-custom-path != ""
? AppearanceState.auto-theme-custom-path
: @tr("Select Image...");
SecondaryButton {
label: @tr("Select Image...");
clicked => {
AppearanceState.appearance-action("auto-theme-select-image");
}
}
}
if AppearanceState.theme-is-system && AppearanceState.auto-theme-detected-de != "": SettingRow {
label: @tr("Detected: ") + AppearanceState.auto-theme-detected-de;
description: @tr("Experimental: theme may not match your system exactly.");
}
if AppearanceState.theme-is-system: SettingRow {
unsupported: true;
label: @tr("Regenerate");
SecondaryButton {
label: @tr("Regenerate");
enabled: !AppearanceState.auto-theme-generating;
clicked => {
AppearanceState.appearance-action("auto-theme-regenerate");
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// TYPOGRAPHY & LANGUAGE
// ===================================================================
GroupHeader { text: @tr("TYPOGRAPHY & LANGUAGE"); }
SettingRow {
label: @tr("Language");
QbzSelect {
menu-width: 200px;
options: AppearanceState.languages;
current-index: AppearanceState.language-index;
selected(i) => {
AppearanceState.language-index = i;
AppearanceState.appearance-select("language", i);
}
}
}
// Interface size — the working replacement for the old percentage "UI
// Scale" placeholder that was parked here (commented, unwired). Window
// scale-factor preset via SLINT_SCALE_FACTOR (main.rs); restart to apply.
SettingRow {
label: @tr("Interface size");
description: @tr("Scales the whole interface, like browser zoom. Small fits more content on screen, Large and Extra large improve readability (requires restart)");
QbzSelect {
menu-width: 200px;
options: AppearanceState.ui-scale-options;
current-index: AppearanceState.ui-scale-index;
selected(i) => {
AppearanceState.ui-scale-index = i;
AppearanceState.appearance-select("ui-scale", i);
}
}
}
SettingRow {
label: @tr("Immersive search");
description: @tr("What selecting a result in the Immersive search does. Disabled turns the in-immersive search off.");
QbzSelect {
menu-width: 200px;
options: AppearanceState.immersive-search-actions;
current-index: AppearanceState.immersive-search-action-index;
selected(i) => {
AppearanceState.immersive-search-action-index = i;
AppearanceState.appearance-select("immersive-search-action", i);
}
}
}
SettingRow {
label: @tr("Immersive default view");
description: @tr("Which immersive view opens by default. 'Remember last' restores your last view.");
QbzSelect {
menu-width: 200px;
options: AppearanceState.immersive-default-views;
current-index: AppearanceState.immersive-default-view-index;
selected(i) => {
AppearanceState.immersive-default-view-index = i;
AppearanceState.appearance-select("immersive-default-view", i);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// LIBRARY & VISUALS
// ===================================================================
GroupHeader { text: @tr("LIBRARY & VISUALS"); }
SettingRow {
label: @tr("Show navigation in sidebar");
description: @tr("Move the Discover, Library, Local Library and My QBZ sections out of the header and into the sidebar.");
QbzToggle {
checked: ShellState.nav-in-sidebar;
toggled(v) => {
ShellState.nav-in-sidebar = v;
ShellState.persist-nav(v);
}
}
}
// "My QBZ" nav branding — RENAME ONLY (DQ3, owner decision: no custom icon).
// The icon is fixed to QBZ's monochrome (tray) glyph. The label input
// commits on edit (blank coerces to "My QBZ" in Rust); the reset button
// clears the name back to the default. The icon-change backend (pick-icon /
// reset-icon / custom-icon) stays in Rust but is no longer surfaced.
SettingRow {
label: @tr("My QBZ");
description: @tr("Rename the My QBZ hub. Leave the name blank (or hit reset) to restore the default.");
HorizontalLayout {
spacing: 8px;
// Fixed icon preview — QBZ's monochrome glyph, tinted to text.
Rectangle {
width: 34px;
height: 34px;
border-radius: Radius.sm;
border-width: 1px;
border-color: Theme.border-subtle;
background: Theme.surface-elevated;
QbzIcon {
source: @image-url("../assets/icons/qbz-symbolic.svg");
width: 18px;
height: 18px;
tint: Theme.text-secondary;
}
}
// Label input — commits on edit; the placeholder is the default.
VerticalLayout {
alignment: center;
width: 150px;
name-edit := LineEdit {
text: MyQbzBrandingState.label;
placeholder-text: @tr("My QBZ");
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
edited(s) => {
MyQbzBrandingState.set-label(s);
}
}
}
// Reset name — a square glyph button (matches the Output-device
// refresh button), clears the custom name to the default.
VerticalLayout {
alignment: center;
reset-name := Rectangle {
width: 34px;
height: 34px;
border-radius: Radius.sm;
border-width: 1px;
border-color: Theme.border-subtle;
background: reset-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
QbzIcon {
width: 16px;
height: 16px;
source: @image-url("../assets/icons/rotate-ccw.svg");
tint: reset-ta.has-hover ? Theme.text-primary : Theme.text-muted;
}
reset-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
MyQbzBrandingState.set-label("");
}
}
}
}
}
}
SettingRow {
label: @tr("Show playlist cover collages in sidebar");
description: @tr("Render a 2×2 thumbnail of track covers next to each playlist. Disable on low-end machines to skip the extra images.");
QbzToggle {
// Wired: this toggle drives the already-functional sidebar collage flag.
checked: SidebarState.playlist-collage;
toggled(v) => {
SidebarState.playlist-collage = v;
AppearanceState.sidebar-playlist-collage = v;
AppearanceState.appearance-bool("sidebar-playlist-collage", v);
}
}
}
SettingRow {
label: @tr("Show album artwork in Local Library track list");
description: @tr("Display the album cover thumbnail next to each track in the Tracks and Folders views. Off by default — large libraries pay a per-row image-decode cost.");
QbzToggle {
checked: AppearanceState.local-library-track-artwork;
toggled(v) => {
AppearanceState.local-library-track-artwork = v;
AppearanceState.appearance-bool("local-library-track-artwork", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// NOTIFICATIONS
// ===================================================================
GroupHeader { text: @tr("NOTIFICATIONS"); }
SettingRow {
label: @tr("In-app toasts notifications");
QbzToggle {
checked: AppearanceState.in-app-toasts;
toggled(v) => {
AppearanceState.in-app-toasts = v;
ToastState.enabled = v;
AppearanceState.appearance-bool("in-app-toasts", v);
}
}
}
SettingRow {
label: @tr("System Notifications");
QbzToggle {
checked: AppearanceState.system-notifications;
toggled(v) => {
AppearanceState.system-notifications = v;
AppearanceState.appearance-bool("system-notifications", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// WINDOW TITLE
// ===================================================================
GroupHeader { text: @tr("WINDOW TITLE"); }
SettingRow {
label: @tr("Show track in window title");
QbzToggle {
checked: AppearanceState.window-title-show;
toggled(v) => {
AppearanceState.window-title-show = v;
AppearanceState.appearance-bool("window-title-show", v);
}
}
}
// Title-template row + the TITLE BAR and WINDOW CONTROLS blocks — hidden per
// owner request. The window title now uses a fixed format (see
// AppWindow.title in app.slint); the custom titlebar is the only chrome mode
// shipping for now. Code kept commented for re-enable; do NOT delete.
/*
if AppearanceState.window-title-show: SettingRow {
unsupported: true;
label: @tr("Title template");
description: @tr("Placeholders: {{artist}}, {{track}}, {{album}}");
HorizontalLayout {
width: 240px;
VerticalLayout {
alignment: center;
horizontal-stretch: 1;
LineEdit {
text: AppearanceState.window-title-template;
placeholder-text: "{artist} - {track}";
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
edited(s) => {
AppearanceState.window-title-template = s;
AppearanceState.appearance-text("window-title-template", s);
}
}
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// TITLE BAR
// ===================================================================
GroupHeader { text: @tr("TITLE BAR"); }
SettingRow {
unsupported: true;
label: @tr("Use system title bar");
description: @tr("Replace QBZ custom title bar with your system's native window decorations. Changing this will restart the app.");
QbzToggle {
checked: AppearanceState.use-system-title-bar;
toggled(v) => {
AppearanceState.use-system-title-bar = v;
AppearanceState.appearance-bool("use-system-title-bar", v);
}
}
}
SettingRow {
unsupported: true;
label: @tr("Hide title bar");
description: @tr("Hide the title bar completely (for tiling window manager users)");
enabled: !AppearanceState.use-system-title-bar;
QbzToggle {
checked: AppearanceState.hide-title-bar;
enabled: !AppearanceState.use-system-title-bar;
toggled(v) => {
AppearanceState.hide-title-bar = v;
AppearanceState.appearance-bool("hide-title-bar", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// WINDOW CONTROLS
// ===================================================================
GroupHeader { text: @tr("WINDOW CONTROLS"); }
SettingRow {
unsupported: true;
label: @tr("Window controls position");
description: @tr("Place the window control buttons on the left or right side of the title bar");
enabled: !root.tb-locked;
QbzSelect {
menu-width: 160px;
options: AppearanceState.wc-positions;
current-index: AppearanceState.wc-position-index;
enabled: !root.tb-locked;
selected(i) => {
AppearanceState.wc-position-index = i;
AppearanceState.appearance-select("wc-position", i);
}
}
}
SettingRow {
unsupported: true;
label: @tr("Show window controls");
description: @tr("Show minimize, maximize, and close buttons in the title bar. Disable if your window manager handles these.");
enabled: !root.tb-locked;
QbzToggle {
checked: AppearanceState.show-window-controls;
enabled: !root.tb-locked;
toggled(v) => {
AppearanceState.show-window-controls = v;
AppearanceState.appearance-bool("show-window-controls", v);
}
}
}
*/
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// PLAYER VIEWS / STARTUP
// ===================================================================
GroupHeader { text: @tr("PLAYER VIEWS"); }
SettingRow {
label: @tr("Show volume +/- buttons");
description: @tr("Add discrete plus and minus buttons next to the volume slider in the player bar.");
QbzToggle {
checked: AppearanceState.show-volume-steppers;
toggled(v) => {
AppearanceState.show-volume-steppers = v;
AppearanceState.appearance-bool("show-volume-steppers", v);
}
}
}
SettingRow {
label: @tr("Mini player default view");
QbzSelect {
menu-width: 200px;
options: AppearanceState.miniplayer-views;
current-index: AppearanceState.miniplayer-view-index;
selected(i) => {
AppearanceState.miniplayer-view-index = i;
AppearanceState.appearance-select("miniplayer-view", i);
}
}
}
SettingRow {
label: @tr("Startup page");
description: @tr("Choose which page to show when the app starts");
QbzSelect {
menu-width: 200px;
options: AppearanceState.startup-pages;
current-index: AppearanceState.startup-page-index;
selected(i) => {
AppearanceState.startup-page-index = i;
AppearanceState.appearance-select("startup-page", i);
}
}
}
SettingRow {
label: @tr("Show Purchases");
description: @tr("Show the Purchases section in the sidebar for browsing and downloading your purchased music");
QbzToggle {
checked: AppearanceState.show-purchases;
toggled(v) => {
AppearanceState.show-purchases = v;
AppearanceState.appearance-bool("show-purchases", v);
}
}
}
// Purchases title-bar placement — moves the Purchases nav entry into the
// custom title bar instead of the sidebar (mirrors Tauri `purchasesInTitlebar`,
// default OFF). Disabled when the custom title bar is hidden or the system
// title bar is in use (no custom title bar to place it in).
SettingRow {
label: @tr("Purchases in title bar");
description: @tr("Place the Purchases entry in the custom title bar instead of the sidebar");
enabled: !root.tb-locked;
QbzToggle {
checked: AppearanceState.nav-tb-purchases;
enabled: !root.tb-locked;
toggled(v) => {
AppearanceState.nav-tb-purchases = v;
AppearanceState.appearance-bool("nav-tb-purchases", v);
}
}
}
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// SYSTEM TRAY (macOS: "MENU BAR")
// ===================================================================
GroupHeader { text: AppearanceState.is-macos ? @tr("MENU BAR") : @tr("SYSTEM TRAY"); }
SettingRow {
label: @tr("Enable tray icon");
description: @tr("Show icon in system tray (requires restart)");
QbzToggle {
checked: AppearanceState.tray-enable;
toggled(v) => {
AppearanceState.tray-enable = v;
AppearanceState.appearance-bool("tray-enable", v);
}
}
}
// NOTE: a separate "Minimize to tray" toggle is intentionally absent.
// Redirecting the window-manager minimize button to the tray requires
// owning that button (custom window chrome), which is not built yet, and
// Wayland forbids a client from intercepting the compositor's minimize.
// Until custom chrome lands, the option is HIDDEN (not shown disabled) and
// this row reads "Close to tray". When custom chrome exists, merge the two
// into a single "Minimize/Close to tray" control.
SettingRow {
label: @tr("Close to tray");
description: AppearanceState.is-macos
? @tr("Keep playing in the menu bar instead of quitting when you close the window")
: @tr("Keep playing in the tray instead of quitting when you close the window");
enabled: AppearanceState.tray-enable;
QbzToggle {
checked: AppearanceState.tray-close-to-tray;
enabled: AppearanceState.tray-enable;
toggled(v) => {
AppearanceState.tray-close-to-tray = v;
AppearanceState.appearance-bool("tray-close-to-tray", v);
}
}
}
// macOS-only: switch the activation policy to .accessory while closed to
// the menu bar (no Dock icon). Off = Spotify-style (keep the Dock icon).
SettingRow {
visible: AppearanceState.is-macos;
label: @tr("Hide Dock icon when closed to menu bar");
description: @tr("Run as a menu-bar-only app while the window is closed. Off keeps the Dock icon (like Spotify)");
enabled: AppearanceState.tray-enable && AppearanceState.tray-close-to-tray;
QbzToggle {
checked: AppearanceState.tray-mac-hide-dock;
enabled: AppearanceState.tray-enable && AppearanceState.tray-close-to-tray;
toggled(v) => {
AppearanceState.tray-mac-hide-dock = v;
AppearanceState.appearance-bool("tray-mac-hide-dock", v);
}
}
}
SettingRow {
label: @tr("Tray icon variant");
description: @tr("Pick a mono glyph to match your panel (Plasma, GNOME's permanently dark top bar) or the full colour vinyl logo");
QbzSelect {
menu-width: 160px;
options: AppearanceState.tray-icon-themes;
current-index: AppearanceState.tray-icon-theme-index;
selected(i) => {
AppearanceState.tray-icon-theme-index = i;
AppearanceState.appearance-select("tray-icon-theme", i);
}
}
}
// IMMERSIVE block — hidden per owner request. Code kept commented for
// re-enable; do NOT delete.
/*
Rectangle { height: 12px; }
Divider { }
Rectangle { height: 12px; }
// ===================================================================
// IMMERSIVE
// ===================================================================
GroupHeader { text: @tr("IMMERSIVE"); }
SettingRow {
unsupported: true;
label: @tr("Background effect");
description: @tr("Controls how the blurred artwork background is rendered in immersive mode.");
QbzSelect {
menu-width: 160px;
options: AppearanceState.immersive-background-modes;
current-index: AppearanceState.immersive-background-mode-index;
selected(i) => {
AppearanceState.immersive-background-mode-index = i;
AppearanceState.appearance-select("immersive-background-mode", i);
}
}
}
// Collapsible: Immersive Panels (per-panel enable/disable lives behind
// this header in Tauri; here the header is replicated, body deferred).
CollapsibleHeader {
title: @tr("Immersive Panels");
summary: @tr("Enable or disable individual immersive view panels");
collapsed: AppearanceState.immersive-panels-collapsed;
toggled => {
AppearanceState.immersive-panels-collapsed = !AppearanceState.immersive-panels-collapsed;
}
}
if !AppearanceState.immersive-panels-collapsed: Text {
text: @tr("Disabled panels are hidden from the visualizer menu so you don't land on them by mistake.");
color: Theme.text-muted;
font-size: 12px;
wrap: word-wrap;
}
// Collapsible: Animation Frame Rate (per-panel FPS lives behind this
// header in Tauri; here the header is replicated, body deferred).
CollapsibleHeader {
title: @tr("Animation Frame Rate");
summary: @tr("Per-animation FPS control for immersive mode");
collapsed: AppearanceState.immersive-fps-collapsed;
toggled => {
AppearanceState.immersive-fps-collapsed = !AppearanceState.immersive-fps-collapsed;
}
}
if !AppearanceState.immersive-fps-collapsed: Text {
text: @tr("Configure frame rate per animation. Lower values save CPU. Requires re-entering immersive mode to apply.");
color: Theme.text-muted;
font-size: 12px;
wrap: word-wrap;
}
*/
// ===================================================================
// RENDERER (Linux only — visibility seeded from Rust; macOS is
// always Skia/Metal so the group never shows there)
// ===================================================================
if AppearanceState.renderer-setting-visible: Rectangle { height: 12px; }
if AppearanceState.renderer-setting-visible: Divider { }
if AppearanceState.renderer-setting-visible: Rectangle { height: 12px; }
if AppearanceState.renderer-setting-visible: GroupHeader { text: @tr("RENDERER"); }
if AppearanceState.renderer-setting-visible: SettingRow {
label: @tr("Rendering backend");
description: @tr("Auto picks the best renderer for your graphics hardware. Only change this if the app feels slow or renders incorrectly (requires restart)");
QbzSelect {
menu-width: 200px;
options: AppearanceState.renderer-options;
current-index: AppearanceState.renderer-index;
selected(i) => {
AppearanceState.renderer-index = i;
AppearanceState.appearance-select("renderer", i);
}
}
}
Rectangle { height: 40px; }
}