fix(qt): the now-playing audio button opens the Normalization + Gapless flyout, and reads the real state
It was not a flyout and it did not read the persisted value, which together
made normalization impossible to turn off.
- `shell/AudioSettingsMenu.qml` (new) ports the `audio-menu` PopupWindow both
bars mount (PlayerBar.slint:686-705, PlayerBarSmall.slint:635-712): 252 wide,
two 44px ToggleRows with 12px side padding and a 12px gap to the switch,
label text-primary 13px over hint text-muted 10px at spacing 1 — the numbers
are PlayerBar.slint's file-private ToggleRow (:80-115).
- `PlayerBar.qml` had `normTouched` / `normLocal` and toggled normalization on
click. `NowPlayingBarSmall.qml` was worse: `property bool normOn: false`,
local state that started at false no matter what was persisted, so the FIRST
click on that button always sent `normalization = true`. Both now just open
the flyout. `active` mirrors normalization on the full bar and is neutral on
the Small one, which is the reference's own asymmetry
(PlayerBarSmall.slint:628-630 states it out loud).
- Gapless had no now-playing control at all.
The local shadow state existed because the document never carried the truth:
`publish_settings()` ran ONLY on `navigate_to("settings")` and on a language
change, and no handler in `settings_qt` ever republished. So until the user
opened Settings the shell read an empty document, the control drew OFF while
the backend had it ON, and the first interaction wrote the wrong value back.
Slint has no equivalent hole — `SettingsState` is a global seeded at startup.
So: seed the document at shell entry, and republish after every audio apply.
Verified by effect, not by diff — the boot log now reports the snapshot as
5006 bytes carrying the persisted `normalization` and `gapless`, before any
view is opened.
Seeding at shell entry also seeds the now-playing stamp's two output LEDs,
which `publish_snapshot` derives from the same audio settings.
cc
This commit is contained in:
6 files changed
+172
-35
No files matched your search
@@ -0,0 +1,104 @@
|
||||
// Audio settings flyout — the port of the "Audio settings" PopupWindow that
|
||||
// BOTH now-playing bars mount (`shell/PlayerBar.slint:686-705` and
|
||||
// `shell/PlayerBarSmall.slint:635-712`). Two rows, Normalization and Gapless,
|
||||
// and nothing else: the Slint popup is 252x118 with exactly these two
|
||||
// ToggleRows (`PlayerBar.slint:80-115` is the file-private ToggleRow this
|
||||
// reproduces — 44px tall, 12px side padding, 12px gap to the switch, label
|
||||
// text-primary 13px over hint text-muted 10px, spacing 1).
|
||||
//
|
||||
// It replaces a direct-toggle button that both bars had shipped with LOCAL
|
||||
// state (`normLocal` / `property bool normOn: false`), which is what made
|
||||
// normalization impossible to turn off: the bars never learned the persisted
|
||||
// value, so the button drew OFF on a fresh launch and the first click SET IT
|
||||
// ON. The state now comes from the published document, which is seeded at
|
||||
// shell entry and republished after every apply.
|
||||
//
|
||||
// Mounted as a QbzContextMenu (the port's popup primitive, same as
|
||||
// ViewModeMenu next to it) rather than a raw Popup, so the panel chrome,
|
||||
// placement clamping and close policy are the ones every other flyout in the
|
||||
// bar already uses.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
QbzContextMenu {
|
||||
id: root
|
||||
// PlayerBar.slint:689 — `width: 252px`.
|
||||
menuWidth: 252
|
||||
|
||||
/// The live document both rows read. The host passes its already-parsed
|
||||
/// `settingsDoc` so this file never parses the JSON a second time.
|
||||
property var doc: ({})
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
"key": "normalization",
|
||||
"label": QbzSession.tr("Normalization", QbzSession.trRev),
|
||||
"hint": QbzSession.tr("Level loudness across tracks", QbzSession.trRev),
|
||||
"on": root.doc.normalization === true,
|
||||
},
|
||||
{
|
||||
"key": "gapless",
|
||||
"label": QbzSession.tr("Gapless", QbzSession.trRev),
|
||||
"hint": QbzSession.tr("Seamless track transitions", QbzSession.trRev),
|
||||
"on": root.doc.gapless === true,
|
||||
},
|
||||
]
|
||||
delegate: Item {
|
||||
required property var modelData
|
||||
width: parent ? parent.width : 0
|
||||
// ToggleRow.slint:86 — `height: 44px`.
|
||||
height: 44
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
// ToggleRow.slint:88-90 — padding 12/12, spacing 12.
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
spacing: 12
|
||||
|
||||
Column {
|
||||
width: parent.width - 12 - rowToggle.width
|
||||
height: parent.height
|
||||
// The label/hint pair is vertically centred as a block
|
||||
// (ToggleRow's inner VerticalLayout `alignment: center`),
|
||||
// so the Column is centred rather than filling.
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// ToggleRow.slint:93 — `spacing: 1px`.
|
||||
spacing: 1
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: modelData.label
|
||||
color: theme.textPrimary
|
||||
// ToggleRow.slint:98 — literal 13px, not a token.
|
||||
font.pixelSize: 13
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: modelData.hint
|
||||
color: theme.textMuted
|
||||
// ToggleRow.slint:103 — literal 10px.
|
||||
font.pixelSize: 10
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
QbzToggle {
|
||||
id: rowToggle
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: modelData.on
|
||||
onToggled: function (v) {
|
||||
QbzBridge.settingsBool(modelData.key, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,22 +352,23 @@ Rectangle {
|
||||
active: QbzPlayer.npCastActive
|
||||
onClicked: QbzCast.openPicker()
|
||||
}
|
||||
// Audio settings — Tauri's normalization toggle (2.0
|
||||
// groups normalization + gapless behind this button).
|
||||
// TODO(glue): `normalization` is settable through
|
||||
// settingsBool but NOT published in settingsJson, so the
|
||||
// state can only be tracked locally; TODO(qt-bridge): the
|
||||
// Slint two-toggle flyout is not ported — the click
|
||||
// toggles normalization, exactly like the Tauri button.
|
||||
// Audio settings — opens the Normalization + Gapless
|
||||
// flyout (PlayerBarSmall.slint:620-712). NOT a toggle, and
|
||||
// `active` is NEUTRAL here on purpose: the .slint says so
|
||||
// out loud at :628-630 — "this button opens a flyout, so
|
||||
// its colour should NOT reflect a toggle (normalization)".
|
||||
// The full bar DOES mirror it; that asymmetry is the
|
||||
// reference's, not a slip.
|
||||
//
|
||||
// It previously carried `property bool normOn: false` —
|
||||
// local state that started at false regardless of what was
|
||||
// persisted, so the first click on this button always sent
|
||||
// `true` and SWITCHED NORMALIZATION ON.
|
||||
QbzIconButton {
|
||||
id: smallNormBtn
|
||||
property bool normOn: false
|
||||
id: smallAudioBtn
|
||||
name: "settings-2"
|
||||
active: normOn
|
||||
onClicked: {
|
||||
normOn = !normOn
|
||||
QbzBridge.settingsBool("normalization", normOn)
|
||||
}
|
||||
active: false
|
||||
onClicked: smallAudioMenu.openBelowRight(smallAudioBtn)
|
||||
}
|
||||
QbzIconButton {
|
||||
id: smallViewBtn
|
||||
@@ -419,6 +420,20 @@ Rectangle {
|
||||
// The Now-Playing-view mode menu (phase 18 — shared with PlayerBar).
|
||||
ViewModeMenu { id: smallViewMenu }
|
||||
|
||||
// Audio settings flyout — Normalization + Gapless (PlayerBarSmall.slint's
|
||||
// `audio-menu` PopupWindow). Guarded parse, the PlaylistView.qml:55-61
|
||||
// idiom: a raw JSON.parse in a binding throws on the pre-publish frame and
|
||||
// takes the whole bar down with it.
|
||||
readonly property var settingsDoc: parseSettings()
|
||||
function parseSettings() {
|
||||
try {
|
||||
return JSON.parse(QbzBridge.settingsJson)
|
||||
} catch (e) {
|
||||
return ({})
|
||||
}
|
||||
}
|
||||
AudioSettingsMenu { id: smallAudioMenu; doc: root.settingsDoc }
|
||||
|
||||
// "Add to…" flyout behind the transport "+" (TransportControls.slint's
|
||||
// add-menu) — same seven entries, order and icons as the full bar.
|
||||
CardMenu {
|
||||
|
||||
@@ -134,21 +134,20 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Audio settings / normalization (Tauri's normalization button; Slint's
|
||||
// "Audio settings" flyout). `normalization` IS settable through
|
||||
// QbzBridge.settingsBool but is NOT published in settingsJson yet, so the
|
||||
// toggle keeps its own value once the user touches it and falls back to
|
||||
// the (currently absent) published field before that.
|
||||
// TODO(glue): publish `normalization` in SettingsDoc — see the report.
|
||||
property bool normTouched: false
|
||||
property bool normLocal: false
|
||||
readonly property bool normalizationOn: normTouched ? normLocal
|
||||
: (settingsDoc.normalization === true)
|
||||
function toggleNormalization() {
|
||||
normLocal = !normalizationOn
|
||||
normTouched = true
|
||||
QbzBridge.settingsBool("normalization", normLocal)
|
||||
}
|
||||
// Audio settings — the button OPENS A FLYOUT (Normalization + Gapless),
|
||||
// it is not itself a toggle: `PlayerBar.slint:666-706`. Its `active`
|
||||
// colour does mirror normalization here (`:677
|
||||
// active: SettingsState.normalization`) — the Small bar deliberately does
|
||||
// NOT, see its own note.
|
||||
//
|
||||
// The state is read from the PUBLISHED document. It used to be shadowed
|
||||
// locally (`normTouched` / `normLocal`) because settingsJson did not carry
|
||||
// it, and that is what made normalization impossible to turn off: on a
|
||||
// fresh launch the fallback read `undefined`, the button drew OFF while
|
||||
// the backend was ON, and the first click SET IT ON. The document is now
|
||||
// seeded at shell entry (main.rs enter_shell) and republished after every
|
||||
// apply (settings_qt::apply_audio), so there is nothing left to shadow.
|
||||
readonly property bool normalizationOn: settingsDoc.normalization === true
|
||||
|
||||
function fmt(secs) {
|
||||
var m = Math.floor(secs / 60)
|
||||
@@ -402,16 +401,14 @@ Rectangle {
|
||||
onClicked: viewMenu.openBelowRight(viewBtn)
|
||||
}
|
||||
|
||||
// Audio settings — Tauri's normalization toggle (2.0
|
||||
// groups normalization + gapless behind this button).
|
||||
// TODO(qt-bridge): the Slint two-toggle flyout is not
|
||||
// ported; the click toggles normalization directly, which
|
||||
// is exactly what the Tauri button did.
|
||||
// Audio settings — opens the Normalization + Gapless
|
||||
// flyout (PlayerBar.slint:666-706). NOT a toggle.
|
||||
QbzIconButton {
|
||||
id: audioBtn
|
||||
name: "settings-2"
|
||||
active: root.normalizationOn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.toggleNormalization()
|
||||
onClicked: audioMenu.openBelowRight(audioBtn)
|
||||
}
|
||||
|
||||
Item { width: 6; height: 1 }
|
||||
@@ -473,6 +470,11 @@ Rectangle {
|
||||
// The Now-Playing-view mode menu (shared with the Small bar).
|
||||
ViewModeMenu { id: viewMenu }
|
||||
|
||||
// Audio settings flyout — Normalization + Gapless (PlayerBar.slint's
|
||||
// `audio-menu` PopupWindow). Fed the already-parsed document so it does
|
||||
// not re-parse settingsJson on every open.
|
||||
AudioSettingsMenu { id: audioMenu; doc: root.settingsDoc }
|
||||
|
||||
// "Add to…" flyout behind the transport "+" (TransportControls.slint's
|
||||
// add-menu), on the shared CardMenu surface. Same seven entries, same
|
||||
// order, same icons.
|
||||
|
||||
Reference in new issue
Block a user