Files
qbz/crates/qbz-ui/ui/settings/AppearanceSettings.slint
T
vicrodh 1bbc98cc8e feat(ui): auto-theme settings UI + i18n
Repoint the existing auto-theme Settings scaffold from the "System" theme to
the new dynamic "Auto (dynamic)" option:

- state.slint: add theme-is-auto (distinct from theme-is-system, which stays
  the native OS-palette follow).
- AppearanceSettings.slint: gate the Source dropdown, image picker, detected-DE
  hint and Regenerate button on theme-is-auto, and drop the placeholder
  `unsupported` flag now that the rows are functional.
- translations: add "Auto theme generation failed" to all 7 catalogs and give
  the es "Wallpaper Sync" string a proper Mexican-neutral translation.

cc
2026-07-05 22:34:09 -06:00

815 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();
// 160px floor keeps the settings right-edge grid; longer translations grow.
width: Math.max(label-txt.preferred-width + 32px, 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;
label-txt := 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;
// ===================================================================
// 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 "Auto (dynamic)" theme is selected.
if AppearanceState.theme-is-auto: SettingRow {
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-auto && AppearanceState.auto-theme-source-index == 2: SettingRow {
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-auto && 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-auto: SettingRow {
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);
}
}
}
// Compact header nav — only meaningful while the nav lives in the header,
// so the row mounts only when the toggle above is OFF (ADR-010).
if !ShellState.nav-in-sidebar: SettingRow {
label: @tr("Compact header navigation");
description: @tr("Use the icon-only section navigation in the header even while the sidebar is open.");
QbzToggle {
checked: ShellState.nav-header-compact;
toggled(v) => {
ShellState.nav-header-compact = v;
ShellState.persist-nav-compact(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 — hidden per owner request. The window title uses a
// fixed format (see AppWindow.title in app.slint). Kept commented.
/*
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 (custom window chrome — the header IS the title bar: it is
// always present; custom mode adds window controls + drag to it and
// drops the system decorations. Linux defaults to system chrome; macOS
// defaults to the overlay (native traffic lights over the header).)
// ===================================================================
GroupHeader { text: @tr("TITLE BAR"); }
SettingRow {
label: @tr("Use system title bar");
description: @tr("Keep your system's native window decorations. Turn off to use the QBZ header as the title bar, with its own window controls and drag support. Takes effect after restarting QBZ.");
QbzToggle {
checked: AppearanceState.use-system-title-bar;
toggled(v) => {
AppearanceState.use-system-title-bar = v;
AppearanceState.appearance-bool("use-system-title-bar", v);
}
}
}
SettingRow {
label: @tr("Hide title bar");
description: @tr("Frameless window without window controls or header drag (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 (drawn in-app on Linux custom chrome; macOS keeps
// the native traffic lights so these rows are hidden there)
// ===================================================================
if !AppearanceState.is-macos: GroupHeader { text: @tr("WINDOW CONTROLS"); }
if !AppearanceState.is-macos: SettingRow {
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);
}
}
}
if !AppearanceState.is-macos: SettingRow {
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; }
}