merge: integrate the Appearance theme fixes (options boxed under their pickers, adjustable Wallpaper blur) into pre-release
cc
This commit is contained in:
18 files changed
+547
-103
No files matched your search
@@ -89,6 +89,7 @@ on:
|
|||||||
- "scripts/test-array-model-qml.sh"
|
- "scripts/test-array-model-qml.sh"
|
||||||
- "scripts/test-keyed-model-qml.sh"
|
- "scripts/test-keyed-model-qml.sh"
|
||||||
- "scripts/test-multi-select-qml.sh"
|
- "scripts/test-multi-select-qml.sh"
|
||||||
|
- "scripts/test-settings-fieldset-qml.sh"
|
||||||
- "scripts/test-wallpaper-field-qml.sh"
|
- "scripts/test-wallpaper-field-qml.sh"
|
||||||
- "scripts/test-card-menu-qml.sh"
|
- "scripts/test-card-menu-qml.sh"
|
||||||
- "scripts/test-orbit-daemon.py"
|
- "scripts/test-orbit-daemon.py"
|
||||||
@@ -122,6 +123,7 @@ on:
|
|||||||
- "scripts/test-array-model-qml.sh"
|
- "scripts/test-array-model-qml.sh"
|
||||||
- "scripts/test-keyed-model-qml.sh"
|
- "scripts/test-keyed-model-qml.sh"
|
||||||
- "scripts/test-multi-select-qml.sh"
|
- "scripts/test-multi-select-qml.sh"
|
||||||
|
- "scripts/test-settings-fieldset-qml.sh"
|
||||||
- "scripts/test-wallpaper-field-qml.sh"
|
- "scripts/test-wallpaper-field-qml.sh"
|
||||||
- "scripts/test-card-menu-qml.sh"
|
- "scripts/test-card-menu-qml.sh"
|
||||||
- "scripts/test-orbit-daemon.py"
|
- "scripts/test-orbit-daemon.py"
|
||||||
|
|||||||
@@ -7723,3 +7723,11 @@ msgstr "System-Hintergrundbild"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} Titel werden aus dieser Playlist entfernt."
|
msgstr "{} Titel werden aus dieser Playlist entfernt."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Unschärfe des Hintergrundbilds"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Wie stark das Hintergrundbild hinter dem Fenster weichgezeichnet wird: 0 % lässt es scharf, 100 % ist die stärkste Unschärfe."
|
||||||
@@ -3378,3 +3378,11 @@ msgstr "System wallpaper"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} tracks will be removed from this playlist."
|
msgstr "{} tracks will be removed from this playlist."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Wallpaper blur"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
@@ -7729,3 +7729,11 @@ msgstr "Fondo del sistema"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "Se quitarán {} pistas de esta playlist."
|
msgstr "Se quitarán {} pistas de esta playlist."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Desenfoque del fondo de pantalla"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Qué tan desenfocado se ve el fondo de pantalla detrás de la ventana: 0 % lo deja nítido y 100 % es el desenfoque máximo."
|
||||||
@@ -7721,3 +7721,11 @@ msgstr "Fond d'écran du système"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} titres seront retirés de cette playlist."
|
msgstr "{} titres seront retirés de cette playlist."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Flou du fond d'écran"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Intensité du flou du fond d'écran derrière la fenêtre : 0 % le laisse net, 100 % est le flou maximal."
|
||||||
@@ -7704,3 +7704,11 @@ msgstr "システムの壁紙"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} 曲をこのプレイリストから削除します。"
|
msgstr "{} 曲をこのプレイリストから削除します。"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "壁紙のぼかし"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "ウィンドウの背後に見える壁紙のぼかしの強さ。0% でくっきり、100% で最も強くぼかします。"
|
||||||
@@ -7711,3 +7711,11 @@ msgstr "Systeemachtergrond"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} nummers worden uit deze afspeellijst verwijderd."
|
msgstr "{} nummers worden uit deze afspeellijst verwijderd."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Vervaging van de bureaubladachtergrond"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Hoe sterk de bureaubladachtergrond achter het venster wordt vervaagd: 0% houdt hem scherp, 100% is de sterkste vervaging."
|
||||||
@@ -7721,3 +7721,11 @@ msgstr "Papel de parede do sistema"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "{} faixas serão removidas desta playlist."
|
msgstr "{} faixas serão removidas desta playlist."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Desfoque do papel de parede"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Quanto o papel de parede atrás da janela fica desfocado: 0% o mantém nítido, 100% é o desfoque máximo."
|
||||||
@@ -7742,3 +7742,11 @@ msgstr "Обои системы"
|
|||||||
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
#: crates/qbz-qt/qml/views/PlaylistView.qml
|
||||||
msgid "{} tracks will be removed from this playlist."
|
msgid "{} tracks will be removed from this playlist."
|
||||||
msgstr "Из этого плейлиста будет удалено {} треков."
|
msgstr "Из этого плейлиста будет удалено {} треков."
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "Wallpaper blur"
|
||||||
|
msgstr "Размытие обоев"
|
||||||
|
|
||||||
|
#: crates/qbz-qt/qml/settings/AppearanceSettings.qml
|
||||||
|
msgid "How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur."
|
||||||
|
msgstr "Насколько размыты обои за окном: 0 % — чётко, 100 % — максимальное размытие."
|
||||||
@@ -628,6 +628,7 @@ fn main() {
|
|||||||
"qml/controls/IconTextButton.qml",
|
"qml/controls/IconTextButton.qml",
|
||||||
"qml/controls/SettingsButton.qml",
|
"qml/controls/SettingsButton.qml",
|
||||||
"qml/controls/SettingsDivider.qml",
|
"qml/controls/SettingsDivider.qml",
|
||||||
|
"qml/controls/SettingsFieldset.qml",
|
||||||
"qml/controls/SettingsSpacer.qml",
|
"qml/controls/SettingsSpacer.qml",
|
||||||
// Moved out of views/local/ on 2026-07-31: the album/track
|
// Moved out of views/local/ on 2026-07-31: the album/track
|
||||||
// CARD badges mount it too, and Slint keeps its counterpart in
|
// CARD badges mount it too, and Slint keeps its counterpart in
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
// SettingsFieldset — a bordered, collapsible group of settings rows that
|
||||||
|
// belong to ONE control above them (the options of the selected theme).
|
||||||
|
//
|
||||||
|
// The border and the faint fill are what say "these rows are part of the
|
||||||
|
// same thing"; the header's chevron (> collapsed, v expanded) hides the body.
|
||||||
|
// The component does not own the collapsed state: the host passes the
|
||||||
|
// persisted value in and writes it back from `toggleRequested`, the same way
|
||||||
|
// the sidebar sections and the media-server panels keep theirs across
|
||||||
|
// restarts (settingsBool -> ui_prefs -> settingsJson).
|
||||||
|
//
|
||||||
|
// Rows go straight in as children; the body is a Column with a side inset,
|
||||||
|
// so a SettingRow still takes the full inner width.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import com.blitzfc.qbz
|
||||||
|
import "../theme"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool kioskHost: false
|
||||||
|
property string title: ""
|
||||||
|
property bool collapsed: false
|
||||||
|
signal toggleRequested()
|
||||||
|
default property alias content: body.data
|
||||||
|
|
||||||
|
QbzTheme { id: theme }
|
||||||
|
|
||||||
|
readonly property int inset: kioskHost ? 16 : 12
|
||||||
|
|
||||||
|
width: parent ? parent.width : 0
|
||||||
|
height: header.height + (collapsed ? 0 : body.height + inset)
|
||||||
|
radius: theme.radiusMd
|
||||||
|
color: theme.alphaTier(3)
|
||||||
|
border.width: 1
|
||||||
|
border.color: theme.borderMuted
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: header
|
||||||
|
x: 1
|
||||||
|
y: 1
|
||||||
|
width: parent.width - 2
|
||||||
|
height: root.kioskHost ? 52 : 40
|
||||||
|
radius: theme.radiusMd
|
||||||
|
color: headerArea.containsMouse ? theme.elevatedHoverFill : "transparent"
|
||||||
|
activeFocusOnTab: true
|
||||||
|
border.width: activeFocus ? 2 : 0
|
||||||
|
border.color: theme.accent
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: root.title
|
||||||
|
Accessible.onPressAction: root.toggleRequested()
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
if (!event.isAutoRepeat
|
||||||
|
&& (event.key === Qt.Key_Space
|
||||||
|
|| event.key === Qt.Key_Return
|
||||||
|
|| event.key === Qt.Key_Enter)) {
|
||||||
|
root.toggleRequested()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: root.inset - 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 8
|
||||||
|
QbzIcon {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: root.collapsed ? "chevron-right" : "chevron-down"
|
||||||
|
width: root.kioskHost ? 20 : 16
|
||||||
|
height: root.kioskHost ? 20 : 16
|
||||||
|
tintName: "secondary"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.title
|
||||||
|
color: theme.textPrimary
|
||||||
|
font.pixelSize: root.kioskHost ? theme.fontBody * 1.2 : theme.fontBody
|
||||||
|
font.weight: theme.weightSemibold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: headerArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onPressed: header.forceActiveFocus()
|
||||||
|
onClicked: root.toggleRequested()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: body
|
||||||
|
visible: !root.collapsed
|
||||||
|
x: root.inset
|
||||||
|
y: header.y + header.height
|
||||||
|
width: root.width - 2 * root.inset
|
||||||
|
spacing: 4
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -98,6 +98,15 @@ Column {
|
|||||||
readonly property bool tbLocked: doc.hideTitleBar === true || doc.useSystemTitleBar === true
|
readonly property bool tbLocked: doc.hideTitleBar === true || doc.useSystemTitleBar === true
|
||||||
|
|
||||||
// ============================ THEME ==================================
|
// ============================ THEME ==================================
|
||||||
|
// ── EACH CHOICE'S OPTIONS SIT UNDER ITS OWN PICKER (2026-09-14) ────────
|
||||||
|
// The rows that exist only for one choice used to trail the whole group:
|
||||||
|
// Custom's editor and Auto's source rows came after Album header
|
||||||
|
// gradient, Compact header and Dynamic background, and Background image
|
||||||
|
// (an option of the Wallpaper background) ran straight into the Custom
|
||||||
|
// editor, so it read as part of the theme. Now each picker is followed by
|
||||||
|
// ITS options, boxed in a SettingsFieldset whose chevron state persists:
|
||||||
|
// the theme's right under Theme, the background's right under Dynamic
|
||||||
|
// background. The rows themselves are unchanged.
|
||||||
GroupHeader { kioskHost: root.kioskHost; text: QbzSession.tr("THEME", QbzSession.trRev) }
|
GroupHeader { kioskHost: root.kioskHost; text: QbzSession.tr("THEME", QbzSession.trRev) }
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
label: QbzSession.tr("Theme", QbzSession.trRev)
|
label: QbzSession.tr("Theme", QbzSession.trRev)
|
||||||
@@ -122,6 +131,113 @@ Column {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// The applied theme's display name, the legend of its options box.
|
||||||
|
readonly property string currentThemeLabel: {
|
||||||
|
for (let i = 0; i < themeEntries.length; i++) {
|
||||||
|
if (themeEntries[i].slug === QbzShell.themeSlug) return themeEntries[i].label
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-theme rows (the "auto" theme only).
|
||||||
|
SettingsFieldset { kioskHost: root.kioskHost;
|
||||||
|
visible: QbzShell.themeSlug === "auto"
|
||||||
|
title: root.currentThemeLabel
|
||||||
|
collapsed: root.doc.themeAutoCollapsed === true
|
||||||
|
onToggleRequested: QbzBridge.settingsBool("theme-auto-collapsed", root.doc.themeAutoCollapsed !== true)
|
||||||
|
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
label: QbzSession.tr("Source", QbzSession.trRev)
|
||||||
|
description: QbzSession.tr("Generate a color theme from your system wallpaper or a custom image", QbzSession.trRev)
|
||||||
|
QbzSelect { kioskHost: root.kioskHost;
|
||||||
|
menuWidth: 200
|
||||||
|
options: root.doc.autoThemeSources || []
|
||||||
|
currentIndex: root.doc.autoThemeSourceIndex || 0
|
||||||
|
onSelected: function (i) { QbzBridge.settingsSelect("auto-theme-source", i) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// "Custom Image" only (source index 2) — AppearanceSettings.slint:283-292.
|
||||||
|
// The row's LABEL is the picked path, which is how the reference shows
|
||||||
|
// what is currently in use; with nothing picked it repeats the button's
|
||||||
|
// text. Both strings already exist in the catalogs.
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
visible: (root.doc.autoThemeSourceIndex || 0) === 2
|
||||||
|
label: (root.doc.autoThemeImagePath || "") !== ""
|
||||||
|
? root.doc.autoThemeImagePath
|
||||||
|
: QbzSession.tr("Select Image...", QbzSession.trRev)
|
||||||
|
SettingsButton { kioskHost: root.kioskHost;
|
||||||
|
text: QbzSession.tr("Select Image...", QbzSession.trRev)
|
||||||
|
// The action seam for button rows is settingsString with an empty
|
||||||
|
// payload — the `library-pick-folder` precedent
|
||||||
|
// (LibraryFolderTable.qml:111).
|
||||||
|
onClicked: QbzBridge.settingsString("auto-theme-select-image", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The detected desktop, with the experimental caveat under it
|
||||||
|
// (`:294-297`). Hint only — no control, and it hides when the shared
|
||||||
|
// detector cannot name a desktop.
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
visible: (root.doc.autoThemeDetectedDe || "") !== ""
|
||||||
|
label: QbzSession.tr("Detected: ", QbzSession.trRev)
|
||||||
|
+ (root.doc.autoThemeDetectedDe || "")
|
||||||
|
description: QbzSession.tr("Experimental: theme may not match your system exactly.", QbzSession.trRev)
|
||||||
|
}
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
label: QbzSession.tr("Regenerate", QbzSession.trRev)
|
||||||
|
SettingsButton { kioskHost: root.kioskHost;
|
||||||
|
text: QbzSession.tr("Regenerate", QbzSession.trRev)
|
||||||
|
onClicked: QbzShell.themeSet(QbzShell.themeSlug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom-theme editor rows (the appended "Custom" theme only), 1:1 with
|
||||||
|
// AppearanceSettings.slint:313-425. "Start from current theme" re-seeds
|
||||||
|
// the base from the applied palette; the "Dark theme" toggle flips
|
||||||
|
// polarity; the grid below edits each base token and the rest of the
|
||||||
|
// palette is derived live in Rust on every change.
|
||||||
|
//
|
||||||
|
// There is deliberately NO save / save-as, NO delete, NO named-theme list
|
||||||
|
// and NO import/export: the model is ONE implicit custom theme that
|
||||||
|
// autosaves, and the reference has none of those affordances either.
|
||||||
|
SettingsFieldset { kioskHost: root.kioskHost;
|
||||||
|
visible: QbzShell.themeSlug === "custom"
|
||||||
|
title: root.currentThemeLabel
|
||||||
|
collapsed: root.doc.themeCustomCollapsed === true
|
||||||
|
onToggleRequested: {
|
||||||
|
// A picker left open inside a folded box would come back open.
|
||||||
|
customThemeEditor.closePicker()
|
||||||
|
QbzBridge.settingsBool("theme-custom-collapsed", root.doc.themeCustomCollapsed !== true)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
label: QbzSession.tr("Start from current theme", QbzSession.trRev)
|
||||||
|
description: QbzSession.tr("Copy the colors of the currently applied theme into the editor as a starting point.", QbzSession.trRev)
|
||||||
|
SettingsButton { kioskHost: root.kioskHost;
|
||||||
|
text: QbzSession.tr("Use current colors", QbzSession.trRev)
|
||||||
|
onClicked: {
|
||||||
|
QbzShell.customSeedFromCurrent()
|
||||||
|
customThemeEditor.closePicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
|
label: QbzSession.tr("Dark theme", QbzSession.trRev)
|
||||||
|
description: QbzSession.tr("Set the overall light or dark polarity. Affects derived shades, borders and overlays.", QbzSession.trRev)
|
||||||
|
QbzToggle { kioskHost: root.kioskHost;
|
||||||
|
checked: customThemeEditor.isDark
|
||||||
|
onToggled: function (v) { QbzShell.customToggleDark(v) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOT inside a SettingRow: that control hardcodes 52/64px and centres one
|
||||||
|
// child, which would clip the ~250px picker. The reference keeps the grid
|
||||||
|
// outside its own rows for the same reason.
|
||||||
|
CustomThemeEditor { kioskHost: root.kioskHost;
|
||||||
|
id: customThemeEditor
|
||||||
|
visible: QbzShell.themeSlug === "custom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
label: QbzSession.tr("Album header gradient", QbzSession.trRev)
|
label: QbzSession.tr("Album header gradient", QbzSession.trRev)
|
||||||
description: QbzSession.tr("Use artwork-derived blur as a backdrop in album and artist detail views.", QbzSession.trRev)
|
description: QbzSession.tr("Use artwork-derived blur as a backdrop in album and artist detail views.", QbzSession.trRev)
|
||||||
@@ -148,113 +264,65 @@ Column {
|
|||||||
onSelected: function (i) { QbzBridge.settingsSelect("app-background", i) }
|
onSelected: function (i) { QbzBridge.settingsSelect("app-background", i) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The picture behind the two Wallpaper modes: the desktop's, or one of
|
// The options of the chosen background: only the two Wallpaper modes
|
||||||
// the user's own (wallpaper_qt.rs).
|
// (3 Wallpaper, 4 Wallpaper, blurred) have any.
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
readonly property int backgroundIndex: root.doc.appBackgroundIndex || 0
|
||||||
visible: (root.doc.appBackgroundIndex || 0) >= 3
|
SettingsFieldset { kioskHost: root.kioskHost;
|
||||||
label: QbzSession.tr("Background image", QbzSession.trRev)
|
visible: root.backgroundIndex >= 3
|
||||||
description: (root.doc.appBackgroundImage || "") !== ""
|
title: (root.doc.appBackgroundModes || [])[root.backgroundIndex] || ""
|
||||||
? root.doc.appBackgroundImage
|
collapsed: root.doc.ambientOptionsCollapsed === true
|
||||||
: QbzSession.tr("The system wallpaper. Choose an image of your own instead.", QbzSession.trRev)
|
onToggleRequested: QbzBridge.settingsBool("ambient-options-collapsed", root.doc.ambientOptionsCollapsed !== true)
|
||||||
Row {
|
|
||||||
spacing: 8
|
|
||||||
SettingsButton { kioskHost: root.kioskHost;
|
|
||||||
text: QbzSession.tr("Choose image…", QbzSession.trRev)
|
|
||||||
onClicked: QbzBridge.settingsPickBackgroundImage()
|
|
||||||
}
|
|
||||||
SettingsButton { kioskHost: root.kioskHost;
|
|
||||||
visible: (root.doc.appBackgroundImage || "") !== ""
|
|
||||||
text: QbzSession.tr("System wallpaper", QbzSession.trRev)
|
|
||||||
onClicked: QbzBridge.settingsClearBackgroundImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Auto-theme rows (the "auto" theme only).
|
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
|
||||||
visible: QbzShell.themeSlug === "auto"
|
|
||||||
label: QbzSession.tr("Source", QbzSession.trRev)
|
|
||||||
description: QbzSession.tr("Generate a color theme from your system wallpaper or a custom image", QbzSession.trRev)
|
|
||||||
QbzSelect { kioskHost: root.kioskHost;
|
|
||||||
menuWidth: 200
|
|
||||||
options: root.doc.autoThemeSources || []
|
|
||||||
currentIndex: root.doc.autoThemeSourceIndex || 0
|
|
||||||
onSelected: function (i) { QbzBridge.settingsSelect("auto-theme-source", i) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// "Custom Image" only (source index 2) — AppearanceSettings.slint:283-292.
|
|
||||||
// The row's LABEL is the picked path, which is how the reference shows
|
|
||||||
// what is currently in use; with nothing picked it repeats the button's
|
|
||||||
// text. Both strings already exist in the catalogs.
|
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
|
||||||
visible: QbzShell.themeSlug === "auto"
|
|
||||||
&& (root.doc.autoThemeSourceIndex || 0) === 2
|
|
||||||
label: (root.doc.autoThemeImagePath || "") !== ""
|
|
||||||
? root.doc.autoThemeImagePath
|
|
||||||
: QbzSession.tr("Select Image...", QbzSession.trRev)
|
|
||||||
SettingsButton { kioskHost: root.kioskHost;
|
|
||||||
text: QbzSession.tr("Select Image...", QbzSession.trRev)
|
|
||||||
// The action seam for button rows is settingsString with an empty
|
|
||||||
// payload — the `library-pick-folder` precedent
|
|
||||||
// (LibraryFolderTable.qml:111).
|
|
||||||
onClicked: QbzBridge.settingsString("auto-theme-select-image", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The detected desktop, with the experimental caveat under it
|
|
||||||
// (`:294-297`). Hint only — no control, and it hides when the shared
|
|
||||||
// detector cannot name a desktop.
|
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
|
||||||
visible: QbzShell.themeSlug === "auto"
|
|
||||||
&& (root.doc.autoThemeDetectedDe || "") !== ""
|
|
||||||
label: QbzSession.tr("Detected: ", QbzSession.trRev)
|
|
||||||
+ (root.doc.autoThemeDetectedDe || "")
|
|
||||||
description: QbzSession.tr("Experimental: theme may not match your system exactly.", QbzSession.trRev)
|
|
||||||
}
|
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
|
||||||
visible: QbzShell.themeSlug === "auto"
|
|
||||||
label: QbzSession.tr("Regenerate", QbzSession.trRev)
|
|
||||||
SettingsButton { kioskHost: root.kioskHost;
|
|
||||||
text: QbzSession.tr("Regenerate", QbzSession.trRev)
|
|
||||||
onClicked: QbzShell.themeSet(QbzShell.themeSlug)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom-theme editor rows (the appended "Custom" theme only), 1:1 with
|
// The picture behind the two Wallpaper modes: the desktop's, or one of
|
||||||
// AppearanceSettings.slint:313-425. "Start from current theme" re-seeds
|
// the user's own (wallpaper_qt.rs).
|
||||||
// the base from the applied palette; the "Dark theme" toggle flips
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
// polarity; the grid below edits each base token and the rest of the
|
label: QbzSession.tr("Background image", QbzSession.trRev)
|
||||||
// palette is derived live in Rust on every change.
|
description: (root.doc.appBackgroundImage || "") !== ""
|
||||||
//
|
? root.doc.appBackgroundImage
|
||||||
// There is deliberately NO save / save-as, NO delete, NO named-theme list
|
: QbzSession.tr("The system wallpaper. Choose an image of your own instead.", QbzSession.trRev)
|
||||||
// and NO import/export: the model is ONE implicit custom theme that
|
Row {
|
||||||
// autosaves, and the reference has none of those affordances either.
|
spacing: 8
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
SettingsButton { kioskHost: root.kioskHost;
|
||||||
visible: QbzShell.themeSlug === "custom"
|
text: QbzSession.tr("Choose image…", QbzSession.trRev)
|
||||||
label: QbzSession.tr("Start from current theme", QbzSession.trRev)
|
onClicked: QbzBridge.settingsPickBackgroundImage()
|
||||||
description: QbzSession.tr("Copy the colors of the currently applied theme into the editor as a starting point.", QbzSession.trRev)
|
}
|
||||||
SettingsButton { kioskHost: root.kioskHost;
|
SettingsButton { kioskHost: root.kioskHost;
|
||||||
text: QbzSession.tr("Use current colors", QbzSession.trRev)
|
visible: (root.doc.appBackgroundImage || "") !== ""
|
||||||
onClicked: {
|
text: QbzSession.tr("System wallpaper", QbzSession.trRev)
|
||||||
QbzShell.customSeedFromCurrent()
|
onClicked: QbzBridge.settingsClearBackgroundImage()
|
||||||
customThemeEditor.closePicker()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// Wallpaper mode only: how blurred the X-ray view of the desktop is.
|
||||||
SettingRow { kioskHost: root.kioskHost;
|
// Qt's own range for MultiEffect.blur, 0.0-1.0, shown as 0-100 %; the
|
||||||
visible: QbzShell.themeSlug === "custom"
|
// default is the 0.75 the mode shipped with (settings_qt.rs). The drag
|
||||||
label: QbzSession.tr("Dark theme", QbzSession.trRev)
|
// moves the live background; the value is stored on release.
|
||||||
description: QbzSession.tr("Set the overall light or dark polarity. Affects derived shades, borders and overlays.", QbzSession.trRev)
|
SettingRow { kioskHost: root.kioskHost;
|
||||||
QbzToggle { kioskHost: root.kioskHost;
|
visible: root.backgroundIndex === 3
|
||||||
checked: customThemeEditor.isDark
|
label: QbzSession.tr("Wallpaper blur", QbzSession.trRev)
|
||||||
onToggled: function (v) { QbzShell.customToggleDark(v) }
|
description: QbzSession.tr("How much the wallpaper behind the window is blurred: 0% keeps it sharp, 100% is the strongest blur.", QbzSession.trRev)
|
||||||
|
Row {
|
||||||
|
spacing: 12
|
||||||
|
QbzSlider { kioskHost: root.kioskHost;
|
||||||
|
id: wallpaperBlurSlider
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
minimum: 0
|
||||||
|
maximum: 100
|
||||||
|
value: Math.round(QbzShell.wallpaperBlur * 100)
|
||||||
|
onChanged: function (v) { QbzShell.wallpaperBlur = v / 100 }
|
||||||
|
onReleased: function (v) { QbzBridge.settingsSlider("wallpaper-blur", v) }
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
text: wallpaperBlurSlider.value + "%"
|
||||||
|
color: theme.textSecondary
|
||||||
|
font.pixelSize: root.kioskHost ? theme.fontBody * 1.2 : theme.fontBody
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOT inside a SettingRow: that control hardcodes 52/64px and centres one
|
|
||||||
// child, which would clip the ~250px picker. The reference keeps the grid
|
|
||||||
// outside its own rows for the same reason.
|
|
||||||
CustomThemeEditor { kioskHost: root.kioskHost;
|
|
||||||
id: customThemeEditor
|
|
||||||
visible: QbzShell.themeSlug === "custom"
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsSpacer { }
|
SettingsSpacer { }
|
||||||
SettingsDivider { }
|
SettingsDivider { }
|
||||||
|
|||||||
@@ -140,7 +140,9 @@ Rectangle {
|
|||||||
visible: root.wallpaperModeOn
|
visible: root.wallpaperModeOn
|
||||||
source: root.wallpaperModeOn ? QbzShell.wallpaperUrl : ""
|
source: root.wallpaperModeOn ? QbzShell.wallpaperUrl : ""
|
||||||
hostWindow: root.hostWindow
|
hostWindow: root.hostWindow
|
||||||
blur: 0.75
|
// The user's choice (Settings > Appearance, 0-100 % of Qt's 0.0-1.0),
|
||||||
|
// live while that slider drags; 0.75 by default.
|
||||||
|
blur: QbzShell.wallpaperBlur
|
||||||
dim: theme.isDark ? QbzShell.ambientDim : 0.0
|
dim: theme.isDark ? QbzShell.ambientDim : 0.0
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -665,6 +665,30 @@ pub fn app_background_mode() -> i32 {
|
|||||||
.unwrap_or(0) as i32
|
.unwrap_or(0) as i32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Wallpaper mode's blur, in MultiEffect's own units: Qt documents
|
||||||
|
/// `blur` from 0.0 (no blur) to 1.0 (full blur), reached at the field's
|
||||||
|
/// `blurMax` of 64 (shell/WallpaperField.qml). 0.75 is the look the mode
|
||||||
|
/// shipped with and stays the default; Settings > Appearance edits it as
|
||||||
|
/// 0-100 %.
|
||||||
|
pub const WALLPAPER_BLUR_DEFAULT: f32 = 0.75;
|
||||||
|
|
||||||
|
pub fn wallpaper_blur() -> f32 {
|
||||||
|
clamp_wallpaper_blur(pref_f32("wallpaper_blur", WALLPAPER_BLUR_DEFAULT))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Qt's range, whatever a hand-edited ui_prefs.json holds; NaN is the default.
|
||||||
|
fn clamp_wallpaper_blur(value: f32) -> f32 {
|
||||||
|
if value.is_nan() {
|
||||||
|
return WALLPAPER_BLUR_DEFAULT;
|
||||||
|
}
|
||||||
|
value.clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Settings slider's 0-100 % step as the stored Qt value.
|
||||||
|
fn wallpaper_blur_from_percent(percent: i32) -> f32 {
|
||||||
|
percent.clamp(0, 100) as f32 / 100.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Live-tuning knobs (Slint AppearanceState defaults; the QBZ_BG_* envs are
|
/// Live-tuning knobs (Slint AppearanceState defaults; the QBZ_BG_* envs are
|
||||||
/// the same dev knobs the Slint seeds at startup).
|
/// the same dev knobs the Slint seeds at startup).
|
||||||
pub fn ambient_dim() -> f32 {
|
pub fn ambient_dim() -> f32 {
|
||||||
@@ -2188,6 +2212,15 @@ pub struct SettingsDoc {
|
|||||||
/// The user's own background image ("" = the desktop wallpaper).
|
/// The user's own background image ("" = the desktop wallpaper).
|
||||||
#[serde(rename = "appBackgroundImage")]
|
#[serde(rename = "appBackgroundImage")]
|
||||||
pub app_background_image: String,
|
pub app_background_image: String,
|
||||||
|
/// Persisted fieldset state of the Appearance > Theme groups (default
|
||||||
|
/// expanded): the Custom theme editor, the Auto theme rows, and the
|
||||||
|
/// options of the selected dynamic background.
|
||||||
|
#[serde(rename = "themeCustomCollapsed")]
|
||||||
|
pub theme_custom_collapsed: bool,
|
||||||
|
#[serde(rename = "themeAutoCollapsed")]
|
||||||
|
pub theme_auto_collapsed: bool,
|
||||||
|
#[serde(rename = "ambientOptionsCollapsed")]
|
||||||
|
pub ambient_options_collapsed: bool,
|
||||||
#[serde(rename = "autoThemeSources")]
|
#[serde(rename = "autoThemeSources")]
|
||||||
pub auto_theme_sources: Vec<String>,
|
pub auto_theme_sources: Vec<String>,
|
||||||
#[serde(rename = "autoThemeSourceIndex")]
|
#[serde(rename = "autoThemeSourceIndex")]
|
||||||
@@ -2748,6 +2781,9 @@ pub async fn publish_snapshot() {
|
|||||||
.position(|v| *v == pref_str("app_background", "off"))
|
.position(|v| *v == pref_str("app_background", "off"))
|
||||||
.unwrap_or(0) as i32,
|
.unwrap_or(0) as i32,
|
||||||
app_background_image: pref_str("app_background_image", ""),
|
app_background_image: pref_str("app_background_image", ""),
|
||||||
|
theme_custom_collapsed: pref_bool("theme_custom_collapsed", false),
|
||||||
|
theme_auto_collapsed: pref_bool("theme_auto_collapsed", false),
|
||||||
|
ambient_options_collapsed: pref_bool("ambient_options_collapsed", false),
|
||||||
auto_theme_sources: AUTO_THEME_SOURCE_LABELS
|
auto_theme_sources: AUTO_THEME_SOURCE_LABELS
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| qbz_i18n::t(l))
|
.map(|l| qbz_i18n::t(l))
|
||||||
@@ -3689,6 +3725,19 @@ pub async fn settings_bool(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str,
|
|||||||
save_pref("myqbz_collections_collapsed", serde_json::json!(value));
|
save_pref("myqbz_collections_collapsed", serde_json::json!(value));
|
||||||
Ok(Apply::None)
|
Ok(Apply::None)
|
||||||
}
|
}
|
||||||
|
// Appearance > Theme fieldsets, read back from settingsJson.
|
||||||
|
"theme-custom-collapsed" => {
|
||||||
|
save_pref("theme_custom_collapsed", serde_json::json!(value));
|
||||||
|
Ok(Apply::None)
|
||||||
|
}
|
||||||
|
"theme-auto-collapsed" => {
|
||||||
|
save_pref("theme_auto_collapsed", serde_json::json!(value));
|
||||||
|
Ok(Apply::None)
|
||||||
|
}
|
||||||
|
"ambient-options-collapsed" => {
|
||||||
|
save_pref("ambient_options_collapsed", serde_json::json!(value));
|
||||||
|
Ok(Apply::None)
|
||||||
|
}
|
||||||
"sidebar-playlist-collage" => {
|
"sidebar-playlist-collage" => {
|
||||||
save_pref("sidebar_playlist_collage", serde_json::json!(value));
|
save_pref("sidebar_playlist_collage", serde_json::json!(value));
|
||||||
// LIVE: the sidebar rows swap collage <-> list-music glyph off the
|
// LIVE: the sidebar rows swap collage <-> list-music glyph off the
|
||||||
@@ -4409,6 +4458,15 @@ pub async fn settings_select(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &st
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn settings_slider(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str, value: i32) {
|
pub async fn settings_slider(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str, value: i32) {
|
||||||
|
if key == "wallpaper-blur" {
|
||||||
|
// Persisted on the slider's release; the drag itself already moved
|
||||||
|
// QbzShell.wallpaperBlur for the live preview.
|
||||||
|
let blur = wallpaper_blur_from_percent(value);
|
||||||
|
// Written from the whole percent as f64: an f32 widened on the way to
|
||||||
|
// JSON would store 0.62 as 0.6200000047683716.
|
||||||
|
save_pref("wallpaper_blur", serde_json::json!(f64::from(value.clamp(0, 100)) / 100.0));
|
||||||
|
crate::shell_bridge::ui(move |mut b| b.as_mut().set_wallpaper_blur(blur));
|
||||||
|
}
|
||||||
if key == "buffer-seconds" {
|
if key == "buffer-seconds" {
|
||||||
let seconds = value.clamp(1, 10) as u8;
|
let seconds = value.clamp(1, 10) as u8;
|
||||||
match with_audio(|s| s.set_stream_buffer_seconds(seconds)) {
|
match with_audio(|s| s.set_stream_buffer_seconds(seconds)) {
|
||||||
@@ -4882,6 +4940,46 @@ mod local_tab_order_tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod wallpaper_blur_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_slider_percent_maps_onto_qts_blur_range() {
|
||||||
|
assert_eq!(wallpaper_blur_from_percent(0), 0.0);
|
||||||
|
assert_eq!(wallpaper_blur_from_percent(75), 0.75);
|
||||||
|
assert_eq!(wallpaper_blur_from_percent(100), 1.0);
|
||||||
|
assert_eq!(wallpaper_blur_from_percent(-20), 0.0);
|
||||||
|
assert_eq!(wallpaper_blur_from_percent(250), 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_hand_edited_value_is_held_to_qts_range() {
|
||||||
|
assert_eq!(clamp_wallpaper_blur(1.7), 1.0);
|
||||||
|
assert_eq!(clamp_wallpaper_blur(-0.2), 0.0);
|
||||||
|
assert_eq!(clamp_wallpaper_blur(0.4), 0.4);
|
||||||
|
assert_eq!(clamp_wallpaper_blur(f32::NAN), WALLPAPER_BLUR_DEFAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod theme_fieldset_doc_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_settings_document_names_the_fieldset_states_for_qml() {
|
||||||
|
let doc = SettingsDoc {
|
||||||
|
theme_custom_collapsed: true,
|
||||||
|
ambient_options_collapsed: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let json = serde_json::to_value(&doc).expect("settings document serializes");
|
||||||
|
assert_eq!(json["themeCustomCollapsed"], true);
|
||||||
|
assert_eq!(json["themeAutoCollapsed"], false);
|
||||||
|
assert_eq!(json["ambientOptionsCollapsed"], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod exclusive_gate_tests {
|
mod exclusive_gate_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -346,6 +346,10 @@ pub mod qbz_shell {
|
|||||||
#[qproperty(QString, ambient_accent)]
|
#[qproperty(QString, ambient_accent)]
|
||||||
// Look knobs (Slint AppearanceState defaults; QBZ_BG_* env seed).
|
// Look knobs (Slint AppearanceState defaults; QBZ_BG_* env seed).
|
||||||
#[qproperty(f32, ambient_dim)]
|
#[qproperty(f32, ambient_dim)]
|
||||||
|
/// The Wallpaper mode's blur, MultiEffect units 0.0-1.0
|
||||||
|
/// (settings_qt::wallpaper_blur). Settings writes it while the slider
|
||||||
|
/// drags, for a live preview, and persists it on release.
|
||||||
|
#[qproperty(f32, wallpaper_blur)]
|
||||||
/// Debug knob QBZ_BG_SCALE — RETIRED 2026-08-13: the ambient field
|
/// Debug knob QBZ_BG_SCALE — RETIRED 2026-08-13: the ambient field
|
||||||
/// renders inline now (no offscreen target to scale), and the knob
|
/// renders inline now (no offscreen target to scale), and the knob
|
||||||
/// measured as a non-lever anyway. Kept to avoid bridge churn.
|
/// measured as a non-lever anyway. Kept to avoid bridge churn.
|
||||||
@@ -923,6 +927,7 @@ pub struct QbzShellRust {
|
|||||||
ambient_secondary: QString,
|
ambient_secondary: QString,
|
||||||
ambient_accent: QString,
|
ambient_accent: QString,
|
||||||
ambient_dim: f32,
|
ambient_dim: f32,
|
||||||
|
wallpaper_blur: f32,
|
||||||
ambient_scale: f32,
|
ambient_scale: f32,
|
||||||
viz_tick_ms: i32,
|
viz_tick_ms: i32,
|
||||||
pane_layer: bool,
|
pane_layer: bool,
|
||||||
@@ -1043,6 +1048,7 @@ impl Default for QbzShellRust {
|
|||||||
ambient_secondary: QString::from("#9632ff"),
|
ambient_secondary: QString::from("#9632ff"),
|
||||||
ambient_accent: QString::from("#3fd9c8"),
|
ambient_accent: QString::from("#3fd9c8"),
|
||||||
ambient_dim: crate::settings_qt::ambient_dim(),
|
ambient_dim: crate::settings_qt::ambient_dim(),
|
||||||
|
wallpaper_blur: crate::settings_qt::wallpaper_blur(),
|
||||||
ambient_scale: crate::settings_qt::ambient_scale(),
|
ambient_scale: crate::settings_qt::ambient_scale(),
|
||||||
viz_tick_ms: crate::settings_qt::viz_tick_ms(),
|
viz_tick_ms: crate::settings_qt::viz_tick_ms(),
|
||||||
pane_layer: crate::settings_qt::pane_layer(),
|
pane_layer: crate::settings_qt::pane_layer(),
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// SettingsFieldset: the bordered, collapsible group that holds the options of
|
||||||
|
// ONE settings control (Appearance > Theme: the theme's editor, the Wallpaper
|
||||||
|
// background's image and blur). The host owns the persisted collapsed state;
|
||||||
|
// the fieldset shows or hides its rows and asks for the toggle.
|
||||||
|
import QtQuick
|
||||||
|
import QtTest
|
||||||
|
import com.blitzfc.qbz
|
||||||
|
import "../../crates/qbz-qt/qml/controls"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
width: 700; height: 600
|
||||||
|
|
||||||
|
property bool storedCollapsed: false
|
||||||
|
property int toggles: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: 600
|
||||||
|
SettingsFieldset {
|
||||||
|
id: fieldset
|
||||||
|
title: "Custom"
|
||||||
|
collapsed: root.storedCollapsed
|
||||||
|
onToggleRequested: {
|
||||||
|
root.toggles++
|
||||||
|
root.storedCollapsed = !root.storedCollapsed
|
||||||
|
}
|
||||||
|
Rectangle { id: rowA; width: parent.width; height: 52; color: "transparent" }
|
||||||
|
Rectangle { id: rowB; width: parent.width; height: 64; color: "transparent" }
|
||||||
|
}
|
||||||
|
Rectangle { id: below; width: 600; height: 20; color: "transparent" }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase {
|
||||||
|
name: "SettingsFieldset"
|
||||||
|
when: windowShown
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
root.storedCollapsed = false
|
||||||
|
root.toggles = 0
|
||||||
|
wait(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_expanded_shows_its_rows_inside_the_box() {
|
||||||
|
verify(rowA.visible && rowB.visible)
|
||||||
|
compare(rowA.width, fieldset.width - 2 * fieldset.inset, "rows take the inner width")
|
||||||
|
var bottom = rowB.mapToItem(fieldset, 0, rowB.height).y
|
||||||
|
verify(bottom <= fieldset.height, "the last row sits inside the border")
|
||||||
|
compare(below.y, fieldset.height, "the next setting starts after the box")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_header_click_asks_for_the_toggle_and_folds_the_body() {
|
||||||
|
var expandedHeight = fieldset.height
|
||||||
|
mouseClick(fieldset, 60, 20)
|
||||||
|
compare(root.toggles, 1)
|
||||||
|
verify(fieldset.collapsed)
|
||||||
|
verify(!rowA.visible && !rowB.visible, "a folded box hides its rows")
|
||||||
|
verify(fieldset.height < expandedHeight)
|
||||||
|
// Column lays out in the next polish pass.
|
||||||
|
tryCompare(below, "y", fieldset.height, 1000, "the settings below move up")
|
||||||
|
mouseClick(fieldset, 60, 20)
|
||||||
|
compare(root.toggles, 2)
|
||||||
|
verify(rowA.visible)
|
||||||
|
compare(fieldset.height, expandedHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_keyboard_toggles_from_the_focused_header() {
|
||||||
|
mouseClick(fieldset, 60, 20)
|
||||||
|
compare(root.toggles, 1)
|
||||||
|
keyClick(Qt.Key_Space)
|
||||||
|
compare(root.toggles, 2)
|
||||||
|
keyClick(Qt.Key_Return)
|
||||||
|
compare(root.toggles, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_a_click_on_a_row_does_not_toggle() {
|
||||||
|
var p = rowA.mapToItem(fieldset, 20, 20)
|
||||||
|
mouseClick(fieldset, p.x, p.y)
|
||||||
|
compare(root.toggles, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ bash scripts/test-library-folders-qml.sh
|
|||||||
bash scripts/test-array-model-qml.sh
|
bash scripts/test-array-model-qml.sh
|
||||||
bash scripts/test-keyed-model-qml.sh
|
bash scripts/test-keyed-model-qml.sh
|
||||||
bash scripts/test-multi-select-qml.sh
|
bash scripts/test-multi-select-qml.sh
|
||||||
|
bash scripts/test-settings-fieldset-qml.sh
|
||||||
bash scripts/test-wallpaper-field-qml.sh
|
bash scripts/test-wallpaper-field-qml.sh
|
||||||
bash scripts/test-card-menu-qml.sh
|
bash scripts/test-card-menu-qml.sh
|
||||||
bash scripts/test-orbit-qml.sh
|
bash scripts/test-orbit-qml.sh
|
||||||
|
|||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SettingsFieldset regression: the collapsible options box of Appearance >
|
||||||
|
# Theme shows its rows inside the border, folds them on a header click or key,
|
||||||
|
# moves the settings below, and leaves the persisted state to its host. Same
|
||||||
|
# run in CI and locally.
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.."
|
||||||
|
if [[ -n "${QT_ROOT_DIR:-}" ]]; then
|
||||||
|
qt_fieldset_bins="$QT_ROOT_DIR/bin"
|
||||||
|
else
|
||||||
|
qt_fieldset_qmake="${QMAKE:-}"
|
||||||
|
if [[ -z "$qt_fieldset_qmake" ]]; then
|
||||||
|
qt_fieldset_qmake="$(command -v qmake6 || command -v qmake)"
|
||||||
|
fi
|
||||||
|
qt_fieldset_bins="$("$qt_fieldset_qmake" -query QT_INSTALL_BINS)"
|
||||||
|
fi
|
||||||
|
QT_QPA_PLATFORM=offscreen QT_QUICK_BACKEND=software \
|
||||||
|
"$qt_fieldset_bins/qmltestrunner" \
|
||||||
|
-input scripts/qml-tests/tst_settings_fieldset.qml \
|
||||||
|
-import scripts/qml-tests/imports
|
||||||
Reference in new issue
Block a user