refactor(qbz-qt): phase 23 part 1 — QML module layout + first five domain bridges
Two fronts of the modularization, both gated green before this commit. QML leaves the flat qml/ directory for seven modules: cards, controls, rows, settings, shell, theme, views. Only Main.qml and LoginScreen.qml stay at the root. build.rs matches the tree exactly (55 files), relative imports and asset paths were re-anchored for the new depth. The QbzBridge God-object starts breaking up: QbzSession, QbzShell, QbzPlayer, QbzQueue and QbzHome are now their own #[cxx_qt::bridge] singletons, each with its own OnceLock<CxxQtThread> and ui() hop, booted in order from Main.qml. Bridge files stay directly under src/ because cxx-qt-build only accepts rust_files from one directory (QTBUG-93443). bridge.rs keeps 5.5 domains (library, album/artist, settings, integrations, search, playlist) and dies once they are split out. cc
This commit is contained in:
72 files changed
+2152
-1690
No files matched your search
+55
-55
@@ -35,63 +35,63 @@ fn main() {
|
||||
.qt_module("QuickControls2")
|
||||
.qml_module(QmlModule {
|
||||
uri: "com.blitzfc.qbz",
|
||||
rust_files: &["src/bridge.rs"],
|
||||
rust_files: &["src/bridge.rs", "src/session_bridge.rs", "src/shell_bridge.rs", "src/player_bridge.rs", "src/queue_bridge.rs", "src/home_bridge.rs"],
|
||||
qml_files: &[
|
||||
"qml/Main.qml",
|
||||
"qml/LoginScreen.qml",
|
||||
"qml/AppShell.qml",
|
||||
"qml/AlbumCard.qml",
|
||||
"qml/QbzNavButton.qml",
|
||||
"qml/QbzIconButton.qml",
|
||||
"qml/QbzCircleAction.qml",
|
||||
"qml/QbzOfflinePlaceholder.qml",
|
||||
"qml/QbzEmptyState.qml",
|
||||
"qml/QbzSectionHeader.qml",
|
||||
"qml/QbzTabBar.qml",
|
||||
"qml/ArtistCard.qml",
|
||||
"qml/PlaylistCard.qml",
|
||||
"qml/TrackCard.qml",
|
||||
"qml/TrackRow.qml",
|
||||
"qml/LabelCard.qml",
|
||||
"qml/SlimCard.qml",
|
||||
"qml/CardMenu.qml",
|
||||
"qml/CardOverlayButton.qml",
|
||||
"qml/QualityMini.qml",
|
||||
"qml/AlbumView.qml",
|
||||
"qml/ArtistView.qml",
|
||||
"qml/SectionRail.qml",
|
||||
"qml/Sidebar.qml",
|
||||
"qml/HeaderBar.qml",
|
||||
"qml/NowPlayingBarSmall.qml",
|
||||
"qml/NowPlayingBar.qml",
|
||||
"qml/PlayerBar.qml",
|
||||
"qml/ViewModeMenu.qml",
|
||||
"qml/QueuePanel.qml",
|
||||
"qml/LyricsPanel.qml",
|
||||
"qml/HomeView.qml",
|
||||
"qml/LibraryView.qml",
|
||||
"qml/SettingsView.qml",
|
||||
"qml/AppearanceSettings.qml",
|
||||
"qml/IntegrationsSettings.qml",
|
||||
"qml/SettingRow.qml",
|
||||
"qml/GroupHeader.qml",
|
||||
"qml/SettingsDivider.qml",
|
||||
"qml/SettingsSpacer.qml",
|
||||
"qml/SettingsButton.qml",
|
||||
"qml/QbzToggle.qml",
|
||||
"qml/QbzSelect.qml",
|
||||
"qml/QbzSlider.qml",
|
||||
"qml/QbzLineEdit.qml",
|
||||
"qml/SearchView.qml",
|
||||
"qml/Cortinilla.qml",
|
||||
"qml/PlaylistView.qml",
|
||||
"qml/QbzTheme.qml",
|
||||
"qml/QbzIcon.qml",
|
||||
"qml/QbzScrollBar.qml",
|
||||
"qml/QbzSpinner.qml",
|
||||
"qml/RoundedImage.qml",
|
||||
"qml/AmbientField.qml",
|
||||
"qml/QbzContextMenu.qml",
|
||||
"qml/Main.qml",
|
||||
"qml/cards/AlbumCard.qml",
|
||||
"qml/cards/ArtistCard.qml",
|
||||
"qml/cards/LabelCard.qml",
|
||||
"qml/cards/PlaylistCard.qml",
|
||||
"qml/cards/SlimCard.qml",
|
||||
"qml/cards/TrackCard.qml",
|
||||
"qml/controls/CardMenu.qml",
|
||||
"qml/controls/CardOverlayButton.qml",
|
||||
"qml/controls/GroupHeader.qml",
|
||||
"qml/controls/QbzCircleAction.qml",
|
||||
"qml/controls/QbzContextMenu.qml",
|
||||
"qml/controls/QbzEmptyState.qml",
|
||||
"qml/controls/QbzIconButton.qml",
|
||||
"qml/controls/QbzLineEdit.qml",
|
||||
"qml/controls/QbzNavButton.qml",
|
||||
"qml/controls/QbzOfflinePlaceholder.qml",
|
||||
"qml/controls/QbzSectionHeader.qml",
|
||||
"qml/controls/QbzSelect.qml",
|
||||
"qml/controls/QbzSlider.qml",
|
||||
"qml/controls/QbzTabBar.qml",
|
||||
"qml/controls/QbzToggle.qml",
|
||||
"qml/controls/QualityMini.qml",
|
||||
"qml/controls/SettingRow.qml",
|
||||
"qml/controls/SettingsButton.qml",
|
||||
"qml/controls/SettingsDivider.qml",
|
||||
"qml/controls/SettingsSpacer.qml",
|
||||
"qml/rows/TrackRow.qml",
|
||||
"qml/settings/AppearanceSettings.qml",
|
||||
"qml/settings/IntegrationsSettings.qml",
|
||||
"qml/settings/SettingsView.qml",
|
||||
"qml/shell/AmbientField.qml",
|
||||
"qml/shell/AppShell.qml",
|
||||
"qml/shell/Cortinilla.qml",
|
||||
"qml/shell/HeaderBar.qml",
|
||||
"qml/shell/LyricsPanel.qml",
|
||||
"qml/shell/NowPlayingBar.qml",
|
||||
"qml/shell/NowPlayingBarSmall.qml",
|
||||
"qml/shell/PlayerBar.qml",
|
||||
"qml/shell/QueuePanel.qml",
|
||||
"qml/shell/Sidebar.qml",
|
||||
"qml/shell/ViewModeMenu.qml",
|
||||
"qml/theme/QbzIcon.qml",
|
||||
"qml/theme/QbzScrollBar.qml",
|
||||
"qml/theme/QbzSpinner.qml",
|
||||
"qml/theme/QbzTheme.qml",
|
||||
"qml/theme/RoundedImage.qml",
|
||||
"qml/views/AlbumView.qml",
|
||||
"qml/views/ArtistView.qml",
|
||||
"qml/views/HomeView.qml",
|
||||
"qml/views/LibraryView.qml",
|
||||
"qml/views/PlaylistView.qml",
|
||||
"qml/views/SearchView.qml",
|
||||
"qml/views/SectionRail.qml",
|
||||
],
|
||||
qrc_files: &qrc_refs,
|
||||
..Default::default()
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// captive-portal line), session-restore error box, "Start offline" link,
|
||||
// legal disclaimer.
|
||||
//
|
||||
// All user-visible strings go through QbzBridge.tr() with the EXACT msgids
|
||||
// All user-visible strings go through QbzSession.tr() with the EXACT msgids
|
||||
// of the Slint @tr() calls so the existing .po translations apply.
|
||||
// State comes from the QbzBridge singleton (Slint's LoginState +
|
||||
// OfflineState globals); actions call the bridge invokables.
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -67,7 +68,7 @@ Rectangle {
|
||||
Item { width: 1; height: theme.spacingSm }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("QBZ", QbzBridge.trRev)
|
||||
text: QbzSession.tr("QBZ", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontWordmark
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -76,7 +77,7 @@ Rectangle {
|
||||
Item { width: 1; height: 2 }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("QOBUZ™ PLAYER", QbzBridge.trRev)
|
||||
text: QbzSession.tr("QOBUZ™ PLAYER", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontSubtitle
|
||||
font.letterSpacing: 4
|
||||
@@ -115,20 +116,20 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("I have read the", QbzBridge.trRev)
|
||||
text: QbzSession.tr("I have read the", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
}
|
||||
Text {
|
||||
id: tosLink
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Qobuz™ Terms of Service", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Qobuz™ Terms of Service", QbzSession.trRev)
|
||||
color: theme.accent
|
||||
font.pixelSize: theme.fontBody
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.openTos()
|
||||
onClicked: QbzSession.openTos()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,14 +143,14 @@ Rectangle {
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: theme.radiusSm
|
||||
property bool canSignIn: root.tosAccepted && QbzBridge.loginPhase === 0
|
||||
property bool canSignIn: root.tosAccepted && QbzSession.loginPhase === 0
|
||||
opacity: canSignIn ? 1.0 : 0.5
|
||||
color: signInArea.containsMouse && canSignIn ? theme.accentHover : theme.accent
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Sign in with your browser", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Sign in with your browser", QbzSession.trRev)
|
||||
color: theme.accentText
|
||||
font.pixelSize: theme.fontButton
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -160,7 +161,7 @@ Rectangle {
|
||||
enabled: signInButton.canSignIn
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.signInViaBrowser()
|
||||
onClicked: QbzSession.signInViaBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,12 +172,12 @@ Rectangle {
|
||||
// focus, and the code exchange takes a few seconds — the
|
||||
// screen must narrate instead of sitting inert.
|
||||
Column {
|
||||
visible: QbzBridge.loginPhase === 1
|
||||
visible: QbzSession.loginPhase === 1
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Continue in your web browser — QBZ is waiting for you to finish signing in.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Continue in your web browser — QBZ is waiting for you to finish signing in.", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -184,7 +185,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("The browser may open in the background without taking focus — check your other windows.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("The browser may open in the background without taking focus — check your other windows.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -197,7 +198,7 @@ Rectangle {
|
||||
height: cancelText.implicitHeight + 3
|
||||
Text {
|
||||
id: cancelText
|
||||
text: QbzBridge.tr("Cancel", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Cancel", QbzSession.trRev)
|
||||
color: cancelArea.containsMouse ? theme.accent : theme.textMuted
|
||||
font.pixelSize: theme.fontLink
|
||||
}
|
||||
@@ -212,21 +213,21 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.cancelLogin()
|
||||
onClicked: QbzSession.cancelLogin()
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: QbzBridge.loginPhase === 2
|
||||
visible: QbzSession.loginPhase === 2
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Signing you in…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Signing you in…", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
// Sign-in failure box (phase 0 only).
|
||||
Rectangle {
|
||||
visible: QbzBridge.loginPhase === 0 && QbzBridge.loginError !== ""
|
||||
visible: QbzSession.loginPhase === 0 && QbzSession.loginError !== ""
|
||||
width: parent.width
|
||||
height: signInErrorColumn.implicitHeight + 24
|
||||
color: theme.surfaceElevated
|
||||
@@ -242,7 +243,7 @@ Rectangle {
|
||||
spacing: 4
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Sign-in failed", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Sign-in failed", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
@@ -250,7 +251,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.loginError
|
||||
text: QbzSession.loginError
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -264,7 +265,7 @@ Rectangle {
|
||||
// No connectivity: point the user at the Start-offline link
|
||||
// right below. Gated on has-previous-session too.
|
||||
Column {
|
||||
visible: QbzBridge.hasPreviousSession && QbzBridge.connectivity === 2
|
||||
visible: QbzSession.hasPreviousSession && QbzSession.connectivity === 2
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
Rectangle {
|
||||
@@ -283,16 +284,16 @@ Rectangle {
|
||||
spacing: 4
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("No internet connection — you can start in offline mode with your local library and downloads", QbzBridge.trRev)
|
||||
text: QbzSession.tr("No internet connection — you can start in offline mode with your local library and downloads", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
Text {
|
||||
visible: QbzBridge.captivePortal
|
||||
visible: QbzSession.captivePortal
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("A network sign-in page may be blocking the connection", QbzBridge.trRev)
|
||||
text: QbzSession.tr("A network sign-in page may be blocking the connection", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -313,7 +314,7 @@ Rectangle {
|
||||
// for a non-connectivity reason). Hidden while the
|
||||
// connectivity box shows — never two boxes at once.
|
||||
Column {
|
||||
visible: QbzBridge.restoreError !== "" && QbzBridge.connectivity !== 2
|
||||
visible: QbzSession.restoreError !== "" && QbzSession.connectivity !== 2
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
Rectangle {
|
||||
@@ -332,7 +333,7 @@ Rectangle {
|
||||
spacing: 4
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Could not restore your session", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Could not restore your session", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
@@ -340,7 +341,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.restoreError
|
||||
text: QbzSession.restoreError
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -360,7 +361,7 @@ Rectangle {
|
||||
height: offlineText.implicitHeight + 3
|
||||
Text {
|
||||
id: offlineText
|
||||
text: QbzBridge.tr("Start offline (no access to Qobuz™ services)", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Start offline (no access to Qobuz™ services)", QbzSession.trRev)
|
||||
color: offlineArea.containsMouse ? theme.accent : theme.textMuted
|
||||
font.pixelSize: theme.fontLink
|
||||
}
|
||||
@@ -375,7 +376,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.startOffline()
|
||||
onClicked: QbzSession.startOffline()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,13 +385,13 @@ Rectangle {
|
||||
// --- Legal disclaimer ------------------------------------
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("QBZ requires an active Qobuz™ subscription. Your credentials are sent directly to Qobuz™.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("QBZ requires an active Qobuz™ subscription. Your credentials are sent directly to Qobuz™.", QbzSession.trRev)
|
||||
+ "\n"
|
||||
+ QbzBridge.tr("QBZ can be used as an offline player without a Qobuz™ account (no access to the Qobuz™ library).", QbzBridge.trRev)
|
||||
+ QbzSession.tr("QBZ can be used as an offline player without a Qobuz™ account (no access to the Qobuz™ library).", QbzSession.trRev)
|
||||
+ "\n"
|
||||
+ QbzBridge.tr("This application uses the Qobuz API but is not certified by Qobuz.", QbzBridge.trRev)
|
||||
+ QbzSession.tr("This application uses the Qobuz API but is not certified by Qobuz.", QbzSession.trRev)
|
||||
+ " "
|
||||
+ QbzBridge.tr("Qobuz™ is a trademark of Qobuz. QBZ is an open-source application licensed under the MIT License and is not affiliated with, endorsed by, or certified by Qobuz.", QbzBridge.trRev)
|
||||
+ QbzSession.tr("Qobuz™ is a trademark of Qobuz. QBZ is an open-source application licensed under the MIT License and is not affiliated with, endorsed by, or certified by Qobuz.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
+25
-15
@@ -10,6 +10,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "theme"
|
||||
|
||||
ApplicationWindow {
|
||||
id: window
|
||||
@@ -28,7 +29,7 @@ ApplicationWindow {
|
||||
// with square corners and a square 1px hairline frame. The system
|
||||
// titlebar is the `use_system_title_bar` pref (ui_prefs.json; applied
|
||||
// at startup, restart semantics like Slint).
|
||||
flags: QbzBridge.systemTitleBar ? Qt.Window : (Qt.Window | Qt.FramelessWindowHint)
|
||||
flags: QbzShell.systemTitleBar ? Qt.Window : (Qt.Window | Qt.FramelessWindowHint)
|
||||
color: "#1a1a1a"
|
||||
|
||||
FontLoader { id: interRegular; source: "assets/fonts/Inter_18pt-Regular.ttf" }
|
||||
@@ -37,7 +38,16 @@ ApplicationWindow {
|
||||
FontLoader { id: interBold; source: "assets/fonts/Inter_18pt-Bold.ttf" }
|
||||
font.family: interRegular.status === FontLoader.Ready ? interRegular.name : "Sans Serif"
|
||||
|
||||
Component.onCompleted: QbzBridge.boot()
|
||||
// Phase 23: every domain singleton boots (registers its Qt-thread
|
||||
// hop; QbzSession.boot additionally fires the app boot sequence).
|
||||
Component.onCompleted: {
|
||||
QbzSession.boot()
|
||||
QbzShell.boot()
|
||||
QbzPlayer.boot()
|
||||
QbzQueue.boot()
|
||||
QbzHome.boot()
|
||||
QbzBridge.boot()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -48,10 +58,10 @@ ApplicationWindow {
|
||||
Loader {
|
||||
id: screenLoader
|
||||
anchors.fill: parent
|
||||
active: QbzBridge.screen !== "splash"
|
||||
source: QbzBridge.screen === "login"
|
||||
active: QbzSession.screen !== "splash"
|
||||
source: QbzSession.screen === "login"
|
||||
? "LoginScreen.qml"
|
||||
: (QbzBridge.screen === "shell" ? "AppShell.qml" : "")
|
||||
: (QbzSession.screen === "shell" ? "shell/AppShell.qml" : "")
|
||||
// Hand the host window down for drag/maximize/resize (custom chrome).
|
||||
onLoaded: if (screenLoader.item) screenLoader.item.hostWindow = window
|
||||
}
|
||||
@@ -63,7 +73,7 @@ ApplicationWindow {
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: "#14ffffff"
|
||||
visible: !QbzBridge.systemTitleBar
|
||||
visible: !QbzShell.systemTitleBar
|
||||
&& window.visibility !== Window.Maximized && window.visibility !== Window.FullScreen
|
||||
}
|
||||
|
||||
@@ -72,49 +82,49 @@ ApplicationWindow {
|
||||
MouseArea {
|
||||
anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; width: 6
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.LeftEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.right: parent.right; anchors.top: parent.top; anchors.bottom: parent.bottom; width: 6
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.RightEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.left: parent.left; anchors.right: parent.right; anchors.top: parent.top; height: 6
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.TopEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; height: 6
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.BottomEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.left: parent.left; anchors.top: parent.top; width: 12; height: 12
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.TopEdge | Qt.LeftEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.right: parent.right; anchors.top: parent.top; width: 12; height: 12
|
||||
cursorShape: Qt.SizeBDiagCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.TopEdge | Qt.RightEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.left: parent.left; anchors.bottom: parent.bottom; width: 12; height: 12
|
||||
cursorShape: Qt.SizeBDiagCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.BottomEdge | Qt.LeftEdge)
|
||||
}
|
||||
MouseArea {
|
||||
anchors.right: parent.right; anchors.bottom: parent.bottom; width: 12; height: 12
|
||||
cursorShape: Qt.SizeFDiagCursor
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
onPressed: window.startSystemResize(Qt.BottomEdge | Qt.RightEdge)
|
||||
}
|
||||
|
||||
@@ -123,7 +133,7 @@ ApplicationWindow {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#0f0f0f"
|
||||
visible: QbzBridge.screen === "splash"
|
||||
visible: QbzSession.screen === "splash"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -200,7 +202,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playAlbum(root.albumId)
|
||||
onClicked: QbzPlayer.playAlbum(root.albumId)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
@@ -236,12 +238,12 @@ Rectangle {
|
||||
menuWidth: 196
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Open album", QbzBridge.trRev), "icon": "library-big", "action": "open" },
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-plus", "action": "next" },
|
||||
{ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": root.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev), "icon": root.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
{ "label": QbzBridge.tr("Block this album", QbzBridge.trRev), "icon": "blind-eye", "action": "block" },
|
||||
{ "label": QbzSession.tr("Open album", QbzSession.trRev), "icon": "library-big", "action": "open" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-plus", "action": "next" },
|
||||
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": root.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev), "icon": root.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
{ "label": QbzSession.tr("Block this album", QbzSession.trRev), "icon": "blind-eye", "action": "block" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -279,9 +281,9 @@ Rectangle {
|
||||
albumMenu.close()
|
||||
var a = modelData.action
|
||||
if (a === "open") QbzBridge.openAlbum(root.albumId)
|
||||
else if (a === "play") QbzBridge.playAlbum(root.albumId)
|
||||
else if (a === "next") QbzBridge.enqueueAlbum(root.albumId, "next")
|
||||
else if (a === "queue") QbzBridge.enqueueAlbum(root.albumId, "later")
|
||||
else if (a === "play") QbzPlayer.playAlbum(root.albumId)
|
||||
else if (a === "next") QbzPlayer.enqueueAlbum(root.albumId, "next")
|
||||
else if (a === "queue") QbzPlayer.enqueueAlbum(root.albumId, "later")
|
||||
else if (a === "favorite") root.toggleFavorite()
|
||||
// "block" (no blacklist store): inert —
|
||||
// POC-NOTE.
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -127,7 +129,7 @@ Rectangle {
|
||||
name: "play-fill"
|
||||
primary: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.playArtistCard(root.item.id)
|
||||
onClicked: QbzPlayer.playArtistCard(root.item.id)
|
||||
}
|
||||
CardOverlayButton {
|
||||
id: agMore
|
||||
@@ -145,19 +147,19 @@ Rectangle {
|
||||
menuWidth: 196
|
||||
entries: {
|
||||
var m = [
|
||||
{ "label": QbzBridge.tr("Open artist", QbzBridge.trRev), "icon": "user", "action": "open" },
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Open artist", QbzSession.trRev), "icon": "user", "action": "open" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
]
|
||||
if (root.showFollow) m.push({
|
||||
"label": root.item.following ? QbzBridge.tr("Following", QbzBridge.trRev) : QbzBridge.tr("Follow", QbzBridge.trRev),
|
||||
"label": root.item.following ? QbzSession.tr("Following", QbzSession.trRev) : QbzSession.tr("Follow", QbzSession.trRev),
|
||||
"icon": root.item.following ? "check" : "user-plus",
|
||||
"action": "follow" })
|
||||
m.push({ "label": QbzBridge.tr("Not interested", QbzBridge.trRev), "icon": "thumbs-down", "action": "not-interested" })
|
||||
m.push({ "label": QbzSession.tr("Not interested", QbzSession.trRev), "icon": "thumbs-down", "action": "not-interested" })
|
||||
return m
|
||||
}
|
||||
onPicked: function (a) {
|
||||
if (a === "open") QbzBridge.openArtist(root.item.id)
|
||||
else if (a === "play") QbzBridge.playArtistCard(root.item.id)
|
||||
else if (a === "play") QbzPlayer.playArtistCard(root.item.id)
|
||||
// "follow": no artist-follow API (POC-NOTE — inert).
|
||||
// "not-interested": the reco dismissal store is not
|
||||
// open in the POC (POC-NOTE — inert).
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -98,7 +100,7 @@ Rectangle {
|
||||
name: "play-fill"
|
||||
primary: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.playPlaylistById(root.item.id)
|
||||
onClicked: QbzPlayer.playPlaylistById(root.item.id)
|
||||
}
|
||||
CardOverlayButton {
|
||||
id: plMore
|
||||
@@ -143,22 +145,22 @@ Rectangle {
|
||||
// PlaylistCard.slint's menu: queueing actions + favorite
|
||||
// (owned) / follow (foreign).
|
||||
entries: [
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-start", "action": "play-next" },
|
||||
{ "label": QbzBridge.tr("Play later", QbzBridge.trRev), "icon": "list-plus", "action": "play-later" },
|
||||
{ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "play-next" },
|
||||
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "play-later" },
|
||||
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": root.item.playlistOwned
|
||||
? (root.item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev))
|
||||
: (root.item.playlistFollowing ? QbzBridge.tr("Unfollow on Qobuz", QbzBridge.trRev) : QbzBridge.tr("Follow on Qobuz", QbzBridge.trRev)),
|
||||
? (root.item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev))
|
||||
: (root.item.playlistFollowing ? QbzSession.tr("Unfollow on Qobuz", QbzSession.trRev) : QbzSession.tr("Follow on Qobuz", QbzSession.trRev)),
|
||||
"icon": root.item.playlistOwned ? (root.item.isFavorite ? "heart-filled" : "heart")
|
||||
: (root.item.playlistFollowing ? "check" : "user-plus"),
|
||||
"action": "favorite" },
|
||||
]
|
||||
onPicked: function (a) {
|
||||
if (a === "play") QbzBridge.playPlaylistById(root.item.id)
|
||||
else if (a === "play-next") QbzBridge.enqueuePlaylistById(root.item.id, "next")
|
||||
else if (a === "play-later") QbzBridge.enqueuePlaylistById(root.item.id, "later")
|
||||
else if (a === "queue") QbzBridge.enqueuePlaylistById(root.item.id, "queue")
|
||||
if (a === "play") QbzPlayer.playPlaylistById(root.item.id)
|
||||
else if (a === "play-next") QbzPlayer.enqueuePlaylistById(root.item.id, "next")
|
||||
else if (a === "play-later") QbzPlayer.enqueuePlaylistById(root.item.id, "later")
|
||||
else if (a === "queue") QbzPlayer.enqueuePlaylistById(root.item.id, "queue")
|
||||
else if (a === "favorite") {
|
||||
if (root.item.playlistOwned) {
|
||||
root.item.isFavorite = !root.item.isFavorite
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property var card: ({})
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -63,7 +65,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playTrack(root.item.id)
|
||||
onClicked: QbzPlayer.playTrack(root.item.id)
|
||||
}
|
||||
// Hover overlay — favorite / play / more (y=120, h=44,
|
||||
// centered, spacing 12).
|
||||
@@ -89,7 +91,7 @@ Rectangle {
|
||||
name: "play-fill"
|
||||
primary: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.playTrack(root.item.id)
|
||||
onClicked: QbzPlayer.playTrack(root.item.id)
|
||||
}
|
||||
CardOverlayButton {
|
||||
id: moreBtn
|
||||
@@ -147,13 +149,13 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playTrack(root.item.id)
|
||||
onClicked: QbzPlayer.playTrack(root.item.id)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
height: 18
|
||||
text: QbzBridge.tr("Track", QbzBridge.trRev) + " • " + (root.item.artist || "")
|
||||
text: QbzSession.tr("Track", QbzSession.trRev) + " • " + (root.item.artist || "")
|
||||
color: root.item.artistId && tcArtistArea.containsMouse
|
||||
? theme.textPrimary : theme.textMuted
|
||||
font.pixelSize: theme.fontLink - 1
|
||||
@@ -175,22 +177,22 @@ Rectangle {
|
||||
// Track context-menu model (TrackCard.slint track-menu) + dispatch.
|
||||
function menuModel() {
|
||||
var m = [
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-start", "action": "next" },
|
||||
{ "label": QbzBridge.tr("Play later", QbzBridge.trRev), "icon": "list-plus", "action": "later" },
|
||||
{ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
|
||||
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" },
|
||||
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
||||
]
|
||||
if (root.item.artistId) m.push({ "label": QbzBridge.tr("Go to artist", QbzBridge.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (root.item.albumId) m.push({ "label": QbzBridge.tr("Go to album", QbzBridge.trRev), "icon": "disc", "action": "go-album" })
|
||||
m.push({ "label": root.item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev),
|
||||
if (root.item.artistId) m.push({ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (root.item.albumId) m.push({ "label": QbzSession.tr("Go to album", QbzSession.trRev), "icon": "disc", "action": "go-album" })
|
||||
m.push({ "label": root.item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev),
|
||||
"icon": root.item.isFavorite ? "heart-filled" : "heart", "action": "favorite" })
|
||||
return m
|
||||
}
|
||||
function trackAction(a) {
|
||||
if (a === "play") QbzBridge.playTrack(root.item.id)
|
||||
else if (a === "next") QbzBridge.enqueueTrack(root.item.id, "next")
|
||||
else if (a === "later") QbzBridge.enqueueTrack(root.item.id, "later")
|
||||
else if (a === "queue") QbzBridge.enqueueTrack(root.item.id, "queue")
|
||||
if (a === "play") QbzPlayer.playTrack(root.item.id)
|
||||
else if (a === "next") QbzPlayer.enqueueTrack(root.item.id, "next")
|
||||
else if (a === "later") QbzPlayer.enqueueTrack(root.item.id, "later")
|
||||
else if (a === "queue") QbzPlayer.enqueueTrack(root.item.id, "queue")
|
||||
else if (a === "go-artist") QbzBridge.openArtist(root.item.artistId)
|
||||
else if (a === "go-album") QbzBridge.openAlbum(root.item.albumId)
|
||||
else if (a === "favorite") {
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
QbzContextMenu {
|
||||
id: cmRoot
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string name: ""
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Text {
|
||||
QbzTheme { id: theme }
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string name: ""
|
||||
@@ -20,6 +20,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
property string iconName: ""
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string name: ""
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string text: ""
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string name: ""
|
||||
+7
-6
@@ -2,14 +2,15 @@
|
||||
// .slint), consolidated in phase 22 from the TWO verbatim copies
|
||||
// (HomeView + LibraryView). Arms: `induced` (offlineMode === 2) switches
|
||||
// the body copy; `showSettingsAction` mounts the induced-only "Open
|
||||
// Settings" button (POC-NOTE: navigates via QbzBridge.navigateTo —
|
||||
// Settings" button (POC-NOTE: navigates via QbzShell.navigateTo —
|
||||
// the Slint opens Settings the same way).
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
property bool induced: QbzBridge.offlineMode === 2
|
||||
property bool induced: QbzSession.offlineMode === 2
|
||||
property bool showSettingsAction: false
|
||||
signal settingsClicked()
|
||||
|
||||
@@ -26,7 +27,7 @@ Column {
|
||||
Item { width: 1; height: 18 }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("You're offline", QbzBridge.trRev)
|
||||
text: QbzSession.tr("You're offline", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -35,8 +36,8 @@ Column {
|
||||
Text {
|
||||
width: 420
|
||||
text: induced
|
||||
? QbzBridge.tr("Offline mode is enabled. Disable it in Settings to use Qobuz.", QbzBridge.trRev)
|
||||
: QbzBridge.tr("No internet connection. Your local library and downloads keep working.", QbzBridge.trRev)
|
||||
? QbzSession.tr("Offline mode is enabled. Disable it in Settings to use Qobuz.", QbzSession.trRev)
|
||||
: QbzSession.tr("No internet connection. Your local library and downloads keep working.", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -46,7 +47,7 @@ Column {
|
||||
SettingsButton {
|
||||
visible: showSettingsAction && induced
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("Open Settings", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Open Settings", QbzSession.trRev)
|
||||
onClicked: parent.settingsClicked()
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -15,6 +15,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
property string title: ""
|
||||
@@ -58,7 +59,7 @@ Item {
|
||||
anchors.centerIn: parent
|
||||
text: parent.parent.parent.viewAllLabel !== ""
|
||||
? parent.parent.parent.viewAllLabel
|
||||
: QbzBridge.tr("View all →", QbzBridge.trRev)
|
||||
: QbzSession.tr("View all →", QbzSession.trRev)
|
||||
color: parent.parent.parent.viewAllAccent ? theme.accent
|
||||
: vaArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 14
|
||||
@@ -11,6 +11,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property var options: []
|
||||
@@ -176,7 +177,7 @@ Rectangle {
|
||||
Text {
|
||||
visible: searchInput.text === ""
|
||||
anchors.fill: parent
|
||||
text: QbzBridge.tr("Search…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search…", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontBody
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
property int minimum: 0
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property bool checked: false
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
property string tier: ""
|
||||
@@ -15,7 +16,7 @@ Item {
|
||||
|
||||
Image {
|
||||
visible: tier === "hires"
|
||||
source: "assets/hi-res.svg"
|
||||
source: "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/hi-res.svg"
|
||||
width: 42
|
||||
height: 28
|
||||
anchors.centerIn: parent
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
property string label: ""
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
property string text: ""
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
QbzTheme { id: theme }
|
||||
File renamed without changes.
@@ -30,6 +30,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -64,7 +66,7 @@ Rectangle {
|
||||
color: hovered ? theme.surfaceHover : (zebra && number % 2 === 0 ? "#07ffffff" : "transparent")
|
||||
|
||||
readonly property bool hovered: trArea.containsMouse || favArea.containsMouse || moreArea.containsMouse
|
||||
readonly property bool isActive: QbzBridge.npTrackId === (item.id || "")
|
||||
readonly property bool isActive: QbzPlayer.npTrackId === (item.id || "")
|
||||
readonly property int cellsRight: 70 + 92 + (showFavorite ? 28 : 0) + (showDownload ? 28 : 0) + (showMenu ? 32 : 0)
|
||||
readonly property int cellsLeft: 32 + (showArtwork ? 36 : 0) + (showAlbum ? 220 : 0)
|
||||
readonly property int gaps: (3 + (showArtwork ? 1 : 0) + (showAlbum ? 1 : 0)
|
||||
@@ -105,15 +107,15 @@ Rectangle {
|
||||
width: 28
|
||||
height: 28
|
||||
radius: 14
|
||||
color: root.isActive && QbzBridge.npPlaying ? "transparent" : "#3dffffff"
|
||||
border.width: root.isActive && QbzBridge.npPlaying ? 1.5 : 0
|
||||
color: root.isActive && QbzPlayer.npPlaying ? "transparent" : "#3dffffff"
|
||||
border.width: root.isActive && QbzPlayer.npPlaying ? 1.5 : 0
|
||||
border.color: theme.accent
|
||||
QbzIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root.isActive && QbzBridge.npPlaying ? "pause" : "play-fill"
|
||||
name: root.isActive && QbzPlayer.npPlaying ? "pause" : "play-fill"
|
||||
width: 14
|
||||
height: 14
|
||||
tintName: root.isActive && QbzBridge.npPlaying ? "accent" : "primary"
|
||||
tintName: root.isActive && QbzPlayer.npPlaying ? "accent" : "primary"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,17 +301,17 @@ Rectangle {
|
||||
menuWidth: 220
|
||||
entries: {
|
||||
var m = [
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-start", "action": "next" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
|
||||
]
|
||||
if (root.menuShowLater) m.push({ "label": QbzBridge.tr("Play later", QbzBridge.trRev), "icon": "list-plus", "action": "later" })
|
||||
m.push({ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" })
|
||||
if (root.menuShowGoTo && root.item.artistId) m.push({ "label": QbzBridge.tr("Go to artist", QbzBridge.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (root.menuShowGoTo && root.item.albumId) m.push({ "label": QbzBridge.tr("Go to album", QbzBridge.trRev), "icon": "disc", "action": "go-album" })
|
||||
if (root.menuShowLater) m.push({ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" })
|
||||
m.push({ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" })
|
||||
if (root.menuShowGoTo && root.item.artistId) m.push({ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (root.menuShowGoTo && root.item.albumId) m.push({ "label": QbzSession.tr("Go to album", QbzSession.trRev), "icon": "disc", "action": "go-album" })
|
||||
if (root.menuShowFavorite) m.push({
|
||||
"label": root.item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev),
|
||||
"label": root.item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev),
|
||||
"icon": root.item.isFavorite ? "heart-filled" : "heart", "action": "favorite" })
|
||||
if (root.menuShowRemove) m.push({ "label": QbzBridge.tr("Remove from playlist", QbzBridge.trRev), "icon": "trash-2", "action": "remove" })
|
||||
if (root.menuShowRemove) m.push({ "label": QbzSession.tr("Remove from playlist", QbzSession.trRev), "icon": "trash-2", "action": "remove" })
|
||||
return m
|
||||
}
|
||||
onPicked: function (a) {
|
||||
@@ -346,14 +348,14 @@ Rectangle {
|
||||
|| Math.abs(mouse.y - root.downPos.y) > 6)) {
|
||||
root.dragging = true
|
||||
root.bodyDragStarted(root.number)
|
||||
QbzBridge.dragStart(root.item.id, root.item.title || "",
|
||||
QbzShell.dragStart(root.item.id, root.item.title || "",
|
||||
(root.item.artist || "") + " · " + (root.item.album || ""), g.x, g.y)
|
||||
}
|
||||
if (root.dragging) QbzBridge.dragMove(g.x, g.y)
|
||||
if (root.dragging) QbzShell.dragMove(g.x, g.y)
|
||||
}
|
||||
onReleased: function (mouse) {
|
||||
if (root.dragging) {
|
||||
QbzBridge.dragEnd()
|
||||
QbzShell.dragEnd()
|
||||
root.dragging = false
|
||||
mouse.accepted = true
|
||||
} else if (root.clickPlays) {
|
||||
+70
-68
@@ -4,7 +4,7 @@
|
||||
// settingsJson document (root.doc) and the settingsBool/Select/String
|
||||
// invokables — never local truth.
|
||||
//
|
||||
// The THEME row is live: QbzBridge.themeSlug / themeListJson / themeFilter
|
||||
// The THEME row is live: QbzShell.themeSlug / themeListJson / themeFilter
|
||||
// (theme_qt.rs) and themeSet() repaints the whole app through QbzTheme.qml
|
||||
// (the Slint theme::push_colors equivalent).
|
||||
//
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -38,7 +40,7 @@ Column {
|
||||
// --- Theme row state (theme_qt.rs via the bridge) --------------------
|
||||
readonly property var themeEntries: {
|
||||
try {
|
||||
return JSON.parse(QbzBridge.themeListJson)
|
||||
return JSON.parse(QbzShell.themeListJson)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
@@ -46,7 +48,7 @@ Column {
|
||||
// 0 All / 1 Dark / 2 Light; the Auto/Custom synthetics only list under
|
||||
// All (theme.rs:217-227).
|
||||
readonly property var filteredThemes: {
|
||||
const f = QbzBridge.themeFilter
|
||||
const f = QbzShell.themeFilter
|
||||
const out = []
|
||||
for (let i = 0; i < themeEntries.length; i++) {
|
||||
const e = themeEntries[i]
|
||||
@@ -60,16 +62,16 @@ Column {
|
||||
readonly property var filteredLabels: filteredThemes.map(function (e) { return e.label })
|
||||
readonly property int currentThemeIndex: {
|
||||
for (let i = 0; i < filteredThemes.length; i++) {
|
||||
if (filteredThemes[i].slug === QbzBridge.themeSlug) return i
|
||||
if (filteredThemes[i].slug === QbzShell.themeSlug) return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
readonly property bool tbLocked: doc.hideTitleBar === true || doc.useSystemTitleBar === true
|
||||
|
||||
// ============================ THEME ==================================
|
||||
GroupHeader { text: QbzBridge.tr("THEME", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("THEME", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Theme", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Theme", QbzSession.trRev)
|
||||
Row {
|
||||
spacing: 8
|
||||
// Filter cycle (All -> Dark -> Light): sun-moon / moon / sun.
|
||||
@@ -82,8 +84,8 @@ Column {
|
||||
color: fArea.containsMouse ? theme.surfaceHover : theme.surfaceElevated
|
||||
QbzIcon {
|
||||
anchors.centerIn: parent
|
||||
name: QbzBridge.themeFilter === 1 ? "moon"
|
||||
: QbzBridge.themeFilter === 2 ? "sun" : "sun-moon"
|
||||
name: QbzShell.themeFilter === 1 ? "moon"
|
||||
: QbzShell.themeFilter === 2 ? "sun" : "sun-moon"
|
||||
width: 16
|
||||
height: 16
|
||||
tintName: "secondary"
|
||||
@@ -93,7 +95,7 @@ Column {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.themeSetFilter((QbzBridge.themeFilter + 1) % 3)
|
||||
onClicked: QbzShell.themeSetFilter((QbzShell.themeFilter + 1) % 3)
|
||||
}
|
||||
}
|
||||
QbzSelect {
|
||||
@@ -103,21 +105,21 @@ Column {
|
||||
currentIndex: root.currentThemeIndex
|
||||
onSelected: function (i) {
|
||||
if (i >= 0 && i < root.filteredThemes.length)
|
||||
QbzBridge.themeSet(root.filteredThemes[i].slug)
|
||||
QbzShell.themeSet(root.filteredThemes[i].slug)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Album header gradient", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Use artwork-derived blur as a backdrop in album and artist detail views.", QbzBridge.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)
|
||||
QbzToggle {
|
||||
checked: root.doc.albumHeaderGradient === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("album-header-gradient", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Dynamic background", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Dynamic background", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: root.doc.appBackgroundModes || []
|
||||
@@ -126,7 +128,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Intelligent Search", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Intelligent Search", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.intelligentSearch === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("intelligent-search", v) }
|
||||
@@ -134,29 +136,29 @@ Column {
|
||||
}
|
||||
// Auto-theme rows (theme slug "auto" only).
|
||||
SettingRow {
|
||||
visible: QbzBridge.themeSlug === "auto"
|
||||
label: QbzBridge.tr("Source", QbzBridge.trRev)
|
||||
visible: QbzShell.themeSlug === "auto"
|
||||
label: QbzSession.tr("Source", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: [QbzBridge.tr("System Colors", QbzBridge.trRev), QbzBridge.tr("Wallpaper Sync", QbzBridge.trRev), QbzBridge.tr("Custom Image", QbzBridge.trRev)]
|
||||
options: [QbzSession.tr("System Colors", QbzSession.trRev), QbzSession.tr("Wallpaper Sync", QbzSession.trRev), QbzSession.tr("Custom Image", QbzSession.trRev)]
|
||||
currentIndex: root.doc.autoThemeSourceIndex || 0
|
||||
onSelected: function (i) { QbzBridge.settingsSelect("auto-theme-source", i) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
visible: QbzBridge.themeSlug === "auto"
|
||||
label: QbzBridge.tr("Regenerate", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Rebuild the palette from the source (System colors in the POC).", QbzBridge.trRev)
|
||||
visible: QbzShell.themeSlug === "auto"
|
||||
label: QbzSession.tr("Regenerate", QbzSession.trRev)
|
||||
description: QbzSession.tr("Rebuild the palette from the source (System colors in the POC).", QbzSession.trRev)
|
||||
SettingsButton {
|
||||
text: QbzBridge.tr("Regenerate", QbzBridge.trRev)
|
||||
onClicked: QbzBridge.themeSet(QbzBridge.themeSlug)
|
||||
text: QbzSession.tr("Regenerate", QbzSession.trRev)
|
||||
onClicked: QbzShell.themeSet(QbzShell.themeSlug)
|
||||
}
|
||||
}
|
||||
// Custom-theme rows (theme slug "custom" only) — no token editor.
|
||||
SettingRow {
|
||||
visible: QbzBridge.themeSlug === "custom"
|
||||
label: QbzBridge.tr("Custom theme", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Applied read-only from custom_theme.json — the token editor is not ported to the Qt frontend (POC).", QbzBridge.trRev)
|
||||
visible: QbzShell.themeSlug === "custom"
|
||||
label: QbzSession.tr("Custom theme", QbzSession.trRev)
|
||||
description: QbzSession.tr("Applied read-only from custom_theme.json — the token editor is not ported to the Qt frontend (POC).", QbzSession.trRev)
|
||||
}
|
||||
|
||||
SettingsSpacer { }
|
||||
@@ -164,9 +166,9 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ===================== TYPOGRAPHY & LANGUAGE =========================
|
||||
GroupHeader { text: QbzBridge.tr("TYPOGRAPHY & LANGUAGE", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("TYPOGRAPHY & LANGUAGE", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Language", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Language", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: root.doc.languages || []
|
||||
@@ -175,8 +177,8 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Interface size", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Restart to apply.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Interface size", QbzSession.trRev)
|
||||
description: QbzSession.tr("Restart to apply.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: root.doc.uiScales || []
|
||||
@@ -185,7 +187,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Immersive search", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Immersive search", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.immersiveSearchActions || []
|
||||
@@ -194,7 +196,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Immersive default view", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Immersive default view", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.immersiveDefaultViews || []
|
||||
@@ -208,9 +210,9 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ======================= LIBRARY & VISUALS ===========================
|
||||
GroupHeader { text: QbzBridge.tr("LIBRARY & VISUALS", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("LIBRARY & VISUALS", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show navigation in sidebar", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show navigation in sidebar", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.navInSidebar === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("nav-in-sidebar", v) }
|
||||
@@ -219,26 +221,26 @@ Column {
|
||||
SettingRow {
|
||||
// ADR-010: only mounted when navigation is NOT in the sidebar.
|
||||
visible: root.doc.navInSidebar !== true
|
||||
label: QbzBridge.tr("Compact header navigation", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Compact header navigation", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.navHeaderCompact === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("nav-header-compact", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("My QBZ", QbzBridge.trRev)
|
||||
label: QbzSession.tr("My QBZ", QbzSession.trRev)
|
||||
Row {
|
||||
spacing: 8
|
||||
RoundedImage {
|
||||
width: 34
|
||||
height: 34
|
||||
source: "assets/qbz-logo.png"
|
||||
source: "../assets/qbz-logo.png"
|
||||
radius: theme.radiusSm
|
||||
}
|
||||
QbzLineEdit {
|
||||
width: 150
|
||||
text: root.doc.myQbzLabel || ""
|
||||
placeholder: QbzBridge.tr("My QBZ", QbzBridge.trRev)
|
||||
placeholder: QbzSession.tr("My QBZ", QbzSession.trRev)
|
||||
onCommitted: function (v) { QbzBridge.settingsString("myqbz-label", v) }
|
||||
}
|
||||
SettingsButton {
|
||||
@@ -248,28 +250,28 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show playlist cover collages in sidebar", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show playlist cover collages in sidebar", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.sidebarPlaylistCollage === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("sidebar-playlist-collage", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show album artwork in Local Library track list", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show album artwork in Local Library track list", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.localLibraryTrackArtwork === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("local-library-track-artwork", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Play indicator animation", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Play indicator animation", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.playIndicatorAnimation === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("play-indicator-animation", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Invert swipe navigation direction", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Invert swipe navigation direction", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.invertSwipeNavigation === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("invert-swipe-navigation", v) }
|
||||
@@ -281,16 +283,16 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ========================= NOTIFICATIONS =============================
|
||||
GroupHeader { text: QbzBridge.tr("NOTIFICATIONS", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("NOTIFICATIONS", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("In-app toasts notifications", QbzBridge.trRev)
|
||||
label: QbzSession.tr("In-app toasts notifications", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.inAppToasts === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("in-app-toasts", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("System Notifications", QbzBridge.trRev)
|
||||
label: QbzSession.tr("System Notifications", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.systemNotifications === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("system-notifications", v) }
|
||||
@@ -302,9 +304,9 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ========================= WINDOW TITLE ==============================
|
||||
GroupHeader { text: QbzBridge.tr("WINDOW TITLE", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("WINDOW TITLE", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show track in window title", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show track in window title", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.windowTitleShow === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("window-title-show", v) }
|
||||
@@ -316,17 +318,17 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ========================== TITLE BAR ================================
|
||||
GroupHeader { text: QbzBridge.tr("TITLE BAR", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("TITLE BAR", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Use system title bar", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Restart to apply.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Use system title bar", QbzSession.trRev)
|
||||
description: QbzSession.tr("Restart to apply.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.useSystemTitleBar === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("use-system-title-bar", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Hide title bar", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Hide title bar", QbzSession.trRev)
|
||||
rowEnabled: root.doc.useSystemTitleBar !== true
|
||||
QbzToggle {
|
||||
enabled: root.doc.useSystemTitleBar !== true
|
||||
@@ -340,9 +342,9 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ======================= WINDOW CONTROLS =============================
|
||||
GroupHeader { text: QbzBridge.tr("WINDOW CONTROLS", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("WINDOW CONTROLS", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Window controls position", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Window controls position", QbzSession.trRev)
|
||||
rowEnabled: !root.tbLocked
|
||||
QbzSelect {
|
||||
enabled: !root.tbLocked
|
||||
@@ -353,7 +355,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show window controls", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show window controls", QbzSession.trRev)
|
||||
rowEnabled: !root.tbLocked
|
||||
QbzToggle {
|
||||
enabled: !root.tbLocked
|
||||
@@ -367,16 +369,16 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ========================= PLAYER VIEWS ==============================
|
||||
GroupHeader { text: QbzBridge.tr("PLAYER VIEWS", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("PLAYER VIEWS", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show volume +/- buttons", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show volume +/- buttons", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.showVolumeSteppers === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("show-volume-steppers", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Mini player default view", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Mini player default view", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.miniDefaultViews || []
|
||||
@@ -385,7 +387,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Startup page", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Startup page", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.startupPages || []
|
||||
@@ -394,14 +396,14 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show Purchases", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show Purchases", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.showPurchases === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("show-purchases", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Purchases in title bar", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Purchases in title bar", QbzSession.trRev)
|
||||
rowEnabled: !root.tbLocked
|
||||
QbzToggle {
|
||||
enabled: !root.tbLocked
|
||||
@@ -415,16 +417,16 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ========================== SYSTEM TRAY ==============================
|
||||
GroupHeader { text: QbzBridge.tr("SYSTEM TRAY", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("SYSTEM TRAY", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Enable tray icon", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Enable tray icon", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.trayEnable === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("tray-enable", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Close to tray", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Close to tray", QbzSession.trRev)
|
||||
rowEnabled: root.doc.trayEnable === true
|
||||
QbzToggle {
|
||||
enabled: root.doc.trayEnable === true
|
||||
@@ -433,7 +435,7 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Tray icon variant", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Tray icon variant", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: root.doc.trayIconThemes || []
|
||||
@@ -447,10 +449,10 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// =========================== RENDERER ================================
|
||||
GroupHeader { text: QbzBridge.tr("RENDERER", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("RENDERER", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Rendering backend", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Restart to apply.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Rendering backend", QbzSession.trRev)
|
||||
description: QbzSession.tr("Restart to apply.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.renderers || []
|
||||
@@ -459,8 +461,8 @@ Column {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Preferred GPU", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Restart to apply.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Preferred GPU", QbzSession.trRev)
|
||||
description: QbzSession.tr("Restart to apply.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 260
|
||||
options: root.doc.gpuPowers || []
|
||||
+37
-35
@@ -14,6 +14,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -25,10 +27,10 @@ Column {
|
||||
spacing: 4
|
||||
|
||||
// ======================= RECOMMENDATIONS =============================
|
||||
GroupHeader { text: QbzBridge.tr("RECOMMENDATIONS", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("RECOMMENDATIONS", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show Recommendations in Discover", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("A personalized Discover tab built from your listening. For the best results connect Last.fm and ListenBrainz below — MusicBrainz is used automatically to match releases.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show Recommendations in Discover", QbzSession.trRev)
|
||||
description: QbzSession.tr("A personalized Discover tab built from your listening. For the best results connect Last.fm and ListenBrainz below — MusicBrainz is used automatically to match releases.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.showRecommendations === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("show-recommendations", v) }
|
||||
@@ -40,10 +42,10 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// =========================== METADATA ================================
|
||||
GroupHeader { text: QbzBridge.tr("METADATA", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("METADATA", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("MusicBrainz", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Enable artist relationships and enhanced metadata from MusicBrainz. No telemetry — it only matches releases to enrich artist pages and playlist song suggestions. Turn off to disable those sections.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("MusicBrainz", QbzSession.trRev)
|
||||
description: QbzSession.tr("Enable artist relationships and enhanced metadata from MusicBrainz. No telemetry — it only matches releases to enrich artist pages and playlist song suggestions. Turn off to disable those sections.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.musicbrainzEnabled === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("musicbrainz", v) }
|
||||
@@ -68,7 +70,7 @@ Column {
|
||||
spacing: 3
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("SCROBBLERS", QbzBridge.trRev)
|
||||
text: QbzSession.tr("SCROBBLERS", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.letterSpacing: 1.5
|
||||
@@ -77,7 +79,7 @@ Column {
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Send your plays to Last.fm and ListenBrainz. Works for Qobuz, local, and Plex tracks.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Send your plays to Last.fm and ListenBrainz. Works for Qobuz, local, and Plex tracks.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -124,12 +126,12 @@ Column {
|
||||
spacing: 4
|
||||
|
||||
// ------------------------- LAST.FM ------------------------------
|
||||
GroupHeader { text: QbzBridge.tr("LAST.FM", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("LAST.FM", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Scrobble to Last.fm", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Scrobble to Last.fm", QbzSession.trRev)
|
||||
description: root.doc.lastfmAuthed === true
|
||||
? QbzBridge.tr("Signed in as {}.", QbzBridge.trRev).replace("{}", root.doc.lastfmUsername || "")
|
||||
: QbzBridge.tr("Connect your Last.fm account to enable scrobbling.", QbzBridge.trRev)
|
||||
? QbzSession.tr("Signed in as {}.", QbzSession.trRev).replace("{}", root.doc.lastfmUsername || "")
|
||||
: QbzSession.tr("Connect your Last.fm account to enable scrobbling.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.lastfmEnabled === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("lastfm-enable", v) }
|
||||
@@ -137,34 +139,34 @@ Column {
|
||||
}
|
||||
SettingRow {
|
||||
visible: root.doc.lastfmAuthed !== true
|
||||
label: QbzBridge.tr("Connect", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Authorize QBZ in your browser, then click Finish.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Connect", QbzSession.trRev)
|
||||
description: QbzSession.tr("Authorize QBZ in your browser, then click Finish.", QbzSession.trRev)
|
||||
Row {
|
||||
spacing: 8
|
||||
SettingsButton {
|
||||
text: root.doc.lastfmBusy === true ? QbzBridge.tr("Working...", QbzBridge.trRev) : QbzBridge.tr("Connect Last.fm", QbzBridge.trRev)
|
||||
text: root.doc.lastfmBusy === true ? QbzSession.tr("Working...", QbzSession.trRev) : QbzSession.tr("Connect Last.fm", QbzSession.trRev)
|
||||
enabled: root.doc.lastfmBusy !== true
|
||||
onClicked: QbzBridge.integrationsAction("lastfm-connect")
|
||||
}
|
||||
SettingsButton {
|
||||
visible: (root.doc.lastfmAuthUrl || "") !== ""
|
||||
text: QbzBridge.tr("Open authorize page", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Open authorize page", QbzSession.trRev)
|
||||
onClicked: QbzBridge.integrationsAction("lastfm-open-auth-url")
|
||||
}
|
||||
SettingsButton {
|
||||
visible: (root.doc.lastfmAuthUrl || "") !== ""
|
||||
text: QbzBridge.tr("Finish", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Finish", QbzSession.trRev)
|
||||
onClicked: QbzBridge.integrationsAction("lastfm-finish")
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
visible: root.doc.lastfmAuthed === true
|
||||
label: QbzBridge.tr("Disconnect Last.fm", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Sign out of Last.fm.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Disconnect Last.fm", QbzSession.trRev)
|
||||
description: QbzSession.tr("Sign out of Last.fm.", QbzSession.trRev)
|
||||
SettingsButton {
|
||||
danger: true
|
||||
text: QbzBridge.tr("Disconnect", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Disconnect", QbzSession.trRev)
|
||||
onClicked: QbzBridge.integrationsAction("lastfm-disconnect")
|
||||
}
|
||||
}
|
||||
@@ -172,12 +174,12 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// ----------------------- LISTENBRAINZ ----------------------------
|
||||
GroupHeader { text: QbzBridge.tr("LISTENBRAINZ", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("LISTENBRAINZ", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Scrobble to ListenBrainz", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Scrobble to ListenBrainz", QbzSession.trRev)
|
||||
description: root.doc.listenbrainzAuthed === true
|
||||
? QbzBridge.tr("Signed in as {}.", QbzBridge.trRev).replace("{}", root.doc.listenbrainzUsername || "")
|
||||
: QbzBridge.tr("Paste your ListenBrainz user token to enable scrobbling.", QbzBridge.trRev)
|
||||
? QbzSession.tr("Signed in as {}.", QbzSession.trRev).replace("{}", root.doc.listenbrainzUsername || "")
|
||||
: QbzSession.tr("Paste your ListenBrainz user token to enable scrobbling.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.listenbrainzEnabled === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("listenbrainz-enable", v) }
|
||||
@@ -185,21 +187,21 @@ Column {
|
||||
}
|
||||
SettingRow {
|
||||
visible: root.doc.listenbrainzAuthed !== true
|
||||
label: QbzBridge.tr("User token", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("From listenbrainz.org/settings.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("User token", QbzSession.trRev)
|
||||
description: QbzSession.tr("From listenbrainz.org/settings.", QbzSession.trRev)
|
||||
QbzLineEdit {
|
||||
isPassword: true
|
||||
placeholder: QbzBridge.tr("ListenBrainz token", QbzBridge.trRev)
|
||||
placeholder: QbzSession.tr("ListenBrainz token", QbzSession.trRev)
|
||||
onCommitted: function (v) { QbzBridge.settingsString("listenbrainz-token", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
visible: root.doc.listenbrainzAuthed === true
|
||||
label: QbzBridge.tr("Disconnect ListenBrainz", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Sign out of ListenBrainz.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Disconnect ListenBrainz", QbzSession.trRev)
|
||||
description: QbzSession.tr("Sign out of ListenBrainz.", QbzSession.trRev)
|
||||
SettingsButton {
|
||||
danger: true
|
||||
text: QbzBridge.tr("Disconnect", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Disconnect", QbzSession.trRev)
|
||||
onClicked: QbzBridge.integrationsAction("listenbrainz-disconnect")
|
||||
}
|
||||
}
|
||||
@@ -222,17 +224,17 @@ Column {
|
||||
SettingsSpacer { }
|
||||
|
||||
// =========================== DISCORD =================================
|
||||
GroupHeader { text: QbzBridge.tr("DISCORD", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("DISCORD", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Discord Rich Presence", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Show what you're listening to as your Discord status. Opt-in — Discord must be running.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Discord Rich Presence", QbzSession.trRev)
|
||||
description: QbzSession.tr("Show what you're listening to as your Discord status. Opt-in — Discord must be running.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.discordEnabled === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("discord-rpc", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Flatpak socket access", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Flatpak install and the presence isn't showing? Grant access to Discord's IPC socket, then restart QBZ:\nflatpak override --user --filesystem=xdg-run/discord-ipc-0 com.blitzfc.qbz", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Flatpak socket access", QbzSession.trRev)
|
||||
description: QbzSession.tr("Flatpak install and the presence isn't showing? Grant access to Discord's IPC socket, then restart QBZ:\nflatpak override --user --filesystem=xdg-run/discord-ipc-0 com.blitzfc.qbz", QbzSession.trRev)
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -64,7 +66,7 @@ Item {
|
||||
x: 32
|
||||
// padding-top 11 + 12px gap below the (0px) NavButtons row.
|
||||
y: 23
|
||||
text: QbzBridge.tr("Settings", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Settings", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontTitle
|
||||
font.weight: theme.weightBold
|
||||
@@ -131,49 +133,49 @@ Item {
|
||||
|
||||
SubNavItem {
|
||||
name: "volume-2"
|
||||
label: QbzBridge.tr("Audio", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Audio", QbzSession.trRev)
|
||||
active: root.section === 0
|
||||
onClicked: root.section = 0
|
||||
}
|
||||
SubNavItem {
|
||||
name: "play-fill"
|
||||
label: QbzBridge.tr("Playback", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Playback", QbzSession.trRev)
|
||||
active: root.section === 1
|
||||
onClicked: root.section = 1
|
||||
}
|
||||
SubNavItem {
|
||||
name: "layers"
|
||||
label: QbzBridge.tr("Appearance", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Appearance", QbzSession.trRev)
|
||||
active: root.section === 2
|
||||
onClicked: root.section = 2
|
||||
}
|
||||
SubNavItem {
|
||||
name: "cloud-download"
|
||||
label: QbzBridge.tr("Offline", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Offline", QbzSession.trRev)
|
||||
active: root.section === 3
|
||||
onClicked: root.section = 3
|
||||
}
|
||||
SubNavItem {
|
||||
name: "hard-drive"
|
||||
label: QbzBridge.tr("Local Library", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Local Library", QbzSession.trRev)
|
||||
active: root.section === 4
|
||||
onClicked: root.section = 4
|
||||
}
|
||||
SubNavItem {
|
||||
name: "blind-eye"
|
||||
label: QbzBridge.tr("Blacklist", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Blacklist", QbzSession.trRev)
|
||||
active: root.section === 5
|
||||
onClicked: root.section = 5
|
||||
}
|
||||
SubNavItem {
|
||||
name: "refresh-cw"
|
||||
label: QbzBridge.tr("Integrations", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Integrations", QbzSession.trRev)
|
||||
active: root.section === 6
|
||||
onClicked: root.section = 6
|
||||
}
|
||||
SubNavItem {
|
||||
name: "bug"
|
||||
label: QbzBridge.tr("Developer", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Developer", QbzSession.trRev)
|
||||
active: root.section === 7
|
||||
onClicked: root.section = 7
|
||||
}
|
||||
@@ -186,7 +188,7 @@ Item {
|
||||
Item { width: 1; height: parent.height - 8 * 42 - 38 - 4 }
|
||||
SubNavItem {
|
||||
name: "cloud-upload"
|
||||
label: QbzBridge.tr("Share logs", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Share logs", QbzSession.trRev)
|
||||
active: false
|
||||
// POC-NOTE: LogViewerState (copy + upload viewer) is
|
||||
// not ported — the row is inert.
|
||||
@@ -222,10 +224,10 @@ Item {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("STREAMING", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("STREAMING", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Streaming quality", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("The quality tier QBZ requests for playback.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Streaming quality", QbzSession.trRev)
|
||||
description: QbzSession.tr("The quality tier QBZ requests for playback.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 200
|
||||
options: root.doc.streamingQualities || []
|
||||
@@ -234,8 +236,8 @@ Item {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Limit quality to device", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Cap the requested streaming quality at your output device's limit. Applies to local playback only, never to casting.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Limit quality to device", QbzSession.trRev)
|
||||
description: QbzSession.tr("Cap the requested streaming quality at your output device's limit. Applies to local playback only, never to casting.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.limitQualityToDevice === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("limit-quality-to-device", v) }
|
||||
@@ -250,10 +252,10 @@ Item {
|
||||
SettingsDivider { }
|
||||
SettingsSpacer { }
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("OUTPUT", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("OUTPUT", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Audio backend", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("The audio stack QBZ routes playback through.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Audio backend", QbzSession.trRev)
|
||||
description: QbzSession.tr("The audio stack QBZ routes playback through.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.backends || []
|
||||
@@ -265,8 +267,8 @@ Item {
|
||||
// WarningBanner is skipped (backendIsJack is
|
||||
// published for a future port).
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Output device", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("The DAC or sound device that receives audio.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Output device", QbzSession.trRev)
|
||||
description: QbzSession.tr("The DAC or sound device that receives audio.", QbzSession.trRev)
|
||||
Row {
|
||||
spacing: 8
|
||||
// Refresh / release: frees a device QBZ
|
||||
@@ -306,8 +308,8 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.backendIsAlsa === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("ALSA plugin", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("How ALSA opens the device — hw is bit-perfect, plughw converts.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("ALSA plugin", QbzSession.trRev)
|
||||
description: QbzSession.tr("How ALSA opens the device — hw is bit-perfect, plughw converts.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 220
|
||||
options: root.doc.alsaPlugins || []
|
||||
@@ -318,8 +320,8 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.backendIsAlsa === true && root.doc.alsaPluginIsHw === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("Hardware volume control", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Use the ALSA mixer for volume instead of software gain.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Hardware volume control", QbzSession.trRev)
|
||||
description: QbzSession.tr("Use the ALSA mixer for volume instead of software gain.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.alsaHardwareVolume === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("alsa-hardware-volume", v) }
|
||||
@@ -328,8 +330,8 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.backendIsAlsa === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("DSD playback", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("How DSD tracks reach the DAC. WARNING: choose DoP or Native only if your DAC supports it — on any other DAC they play as loud noise. Volume is fixed and seeking is disabled in DoP/Native mode. Native additionally needs kernel support for the DAC.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("DSD playback", QbzSession.trRev)
|
||||
description: QbzSession.tr("How DSD tracks reach the DAC. WARNING: choose DoP or Native only if your DAC supports it — on any other DAC they play as loud noise. Volume is fixed and seeking is disabled in DoP/Native mode. Native additionally needs kernel support for the DAC.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 280
|
||||
options: root.doc.dsdModes || []
|
||||
@@ -342,10 +344,10 @@ Item {
|
||||
SettingsDivider { }
|
||||
SettingsSpacer { }
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("BIT-PERFECT", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("BIT-PERFECT", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Exclusive mode", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Lock the device so no other app can resample it.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Exclusive mode", QbzSession.trRev)
|
||||
description: QbzSession.tr("Lock the device so no other app can resample it.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.backendIsAlsa === true
|
||||
QbzToggle {
|
||||
checked: root.doc.exclusiveMode === true
|
||||
@@ -354,16 +356,16 @@ Item {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Reserve DAC while running", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Hold the device reserved so other apps can't grab it.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Reserve DAC while running", QbzSession.trRev)
|
||||
description: QbzSession.tr("Hold the device reserved so other apps can't grab it.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.reserveDac === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("reserve-dac", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("DAC passthrough", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Send the bitstream untouched to the DAC.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("DAC passthrough", QbzSession.trRev)
|
||||
description: QbzSession.tr("Send the bitstream untouched to the DAC.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.backendIsPipewire === true
|
||||
QbzToggle {
|
||||
checked: root.doc.dacPassthrough === true
|
||||
@@ -374,16 +376,16 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.dacPassthrough === true && root.doc.backendIsPipewire === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("Force bit-perfect", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Pin the PipeWire quantum and rate for the active track.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Force bit-perfect", QbzSession.trRev)
|
||||
description: QbzSession.tr("Pin the PipeWire quantum and rate for the active track.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.pwForceBitperfect === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("pw-force-bitperfect", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Allow quality fallback", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Drop to a lower tier when the requested one is unavailable.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Allow quality fallback", QbzSession.trRev)
|
||||
description: QbzSession.tr("Drop to a lower tier when the requested one is unavailable.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.allowQualityFallback === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("allow-quality-fallback", v) }
|
||||
@@ -397,10 +399,10 @@ Item {
|
||||
SettingsDivider { }
|
||||
SettingsSpacer { }
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("STARTUP", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("STARTUP", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Sync audio settings on startup", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Reload saved audio settings into the player when QBZ launches.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Sync audio settings on startup", QbzSession.trRev)
|
||||
description: QbzSession.tr("Reload saved audio settings into the player when QBZ launches.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.syncAudioOnStartup === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("sync-audio-on-startup", v) }
|
||||
@@ -409,8 +411,8 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.backendIsPipewire === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("Lock output device", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Keep external routing intact — skip switching the default sink.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Lock output device", QbzSession.trRev)
|
||||
description: QbzSession.tr("Keep external routing intact — skip switching the default sink.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.dacPassthrough !== true
|
||||
QbzToggle {
|
||||
checked: root.doc.skipSinkSwitch === true
|
||||
@@ -432,7 +434,7 @@ Item {
|
||||
Text {
|
||||
id: resetLabel
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Reset to defaults", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Reset to defaults", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
@@ -453,26 +455,26 @@ Item {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("PLAYBACK", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("PLAYBACK", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Continue playback after track ends", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Keep playing the rest of the album or playlist instead of stopping.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Continue playback after track ends", QbzSession.trRev)
|
||||
description: QbzSession.tr("Keep playing the rest of the album or playlist instead of stopping.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.continuePlayback === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("continue-playback", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Show track playing context", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Display the context-stack icon in the player.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Show track playing context", QbzSession.trRev)
|
||||
description: QbzSession.tr("Display the context-stack icon in the player.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.showContextIcon === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("show-context-icon", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Gapless playback", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Play consecutive same-format tracks without a gap.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Gapless playback", QbzSession.trRev)
|
||||
description: QbzSession.tr("Play consecutive same-format tracks without a gap.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.streamingOnly !== true
|
||||
QbzToggle {
|
||||
checked: root.doc.gapless === true
|
||||
@@ -485,18 +487,18 @@ Item {
|
||||
SettingsDivider { }
|
||||
SettingsSpacer { }
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("SESSION", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("SESSION", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Restore session on startup", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Restore the queue and current track on the next launch.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Restore session on startup", QbzSession.trRev)
|
||||
description: QbzSession.tr("Restore the queue and current track on the next launch.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.persistSession === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("persist-session", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Resume playback position", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Also seek back to where the saved track left off.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Resume playback position", QbzSession.trRev)
|
||||
description: QbzSession.tr("Also seek back to where the saved track left off.", QbzSession.trRev)
|
||||
rowEnabled: root.doc.persistSession === true
|
||||
QbzToggle {
|
||||
checked: root.doc.resumePosition === true
|
||||
@@ -505,8 +507,8 @@ Item {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Auto-connect Qobuz Connect on startup", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Choose whether Qobuz Connect activates automatically when QBZ launches.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Auto-connect Qobuz Connect on startup", QbzSession.trRev)
|
||||
description: QbzSession.tr("Choose whether Qobuz Connect activates automatically when QBZ launches.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 240
|
||||
options: root.doc.qconnectStartupModes || []
|
||||
@@ -515,8 +517,8 @@ Item {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Qobuz Connect device name", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("The name other Qobuz Connect apps see for this device. Applies on the next connection.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Qobuz Connect device name", QbzSession.trRev)
|
||||
description: QbzSession.tr("The name other Qobuz Connect apps see for this device. Applies on the next connection.", QbzSession.trRev)
|
||||
QbzLineEdit {
|
||||
text: root.doc.qconnectDeviceName || ""
|
||||
placeholder: root.doc.qconnectDeviceNameDefault || ""
|
||||
@@ -528,10 +530,10 @@ Item {
|
||||
SettingsDivider { }
|
||||
SettingsSpacer { }
|
||||
|
||||
GroupHeader { text: QbzBridge.tr("STREAMING", QbzBridge.trRev) }
|
||||
GroupHeader { text: QbzSession.tr("STREAMING", QbzSession.trRev) }
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Stream uncached tracks", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Start uncached tracks via streaming instead of waiting for the full download.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Stream uncached tracks", QbzSession.trRev)
|
||||
description: QbzSession.tr("Start uncached tracks via streaming instead of waiting for the full download.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.streamUncached === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("stream-uncached", v) }
|
||||
@@ -540,8 +542,8 @@ Item {
|
||||
SettingRow {
|
||||
visible: root.doc.streamUncached === true
|
||||
height: visible ? (description === "" ? 52 : 64) : 0
|
||||
label: QbzBridge.tr("Initial buffer size", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Seconds of audio buffered before streaming playback starts.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Initial buffer size", QbzSession.trRev)
|
||||
description: QbzSession.tr("Seconds of audio buffered before streaming playback starts.", QbzSession.trRev)
|
||||
Row {
|
||||
spacing: 12
|
||||
Item { width: 1; height: 1 }
|
||||
@@ -554,7 +556,7 @@ Item {
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: (root.doc.bufferSeconds || 1) + QbzBridge.tr("s", QbzBridge.trRev)
|
||||
text: (root.doc.bufferSeconds || 1) + QbzSession.tr("s", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
@@ -562,16 +564,16 @@ Item {
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("Streaming only", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("Skip writing tracks to the local cache while streaming.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Streaming only", QbzSession.trRev)
|
||||
description: QbzSession.tr("Skip writing tracks to the local cache while streaming.", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.streamingOnly === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("streaming-only", v) }
|
||||
}
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzBridge.tr("When quality retries fail", QbzBridge.trRev)
|
||||
description: QbzBridge.tr("What to do when every quality tier for a track is unavailable.", QbzBridge.trRev)
|
||||
label: QbzSession.tr("When quality retries fail", QbzSession.trRev)
|
||||
description: QbzSession.tr("What to do when every quality tier for a track is unavailable.", QbzSession.trRev)
|
||||
QbzSelect {
|
||||
menuWidth: 240
|
||||
options: root.doc.retryBehaviors || []
|
||||
@@ -606,14 +608,14 @@ Item {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
GroupHeader {
|
||||
text: root.section === 3 ? QbzBridge.tr("OFFLINE", QbzBridge.trRev)
|
||||
: root.section === 4 ? QbzBridge.tr("LOCAL LIBRARY", QbzBridge.trRev)
|
||||
: root.section === 5 ? QbzBridge.tr("BLACKLIST", QbzBridge.trRev)
|
||||
: QbzBridge.tr("DEVELOPER", QbzBridge.trRev)
|
||||
text: root.section === 3 ? QbzSession.tr("OFFLINE", QbzSession.trRev)
|
||||
: root.section === 4 ? QbzSession.tr("LOCAL LIBRARY", QbzSession.trRev)
|
||||
: root.section === 5 ? QbzSession.tr("BLACKLIST", QbzSession.trRev)
|
||||
: QbzSession.tr("DEVELOPER", QbzSession.trRev)
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("This section is not ported to the Qt frontend yet.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("This section is not ported to the Qt frontend yet.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -5,7 +5,7 @@
|
||||
// wandering orbits (10-18s, t scaled 0.75), additive-blended over a black
|
||||
// base so near blobs fuse like the metaball field, plus the shader's
|
||||
// vertical edge shade. Colors come from the ambient_qt album triad
|
||||
// (QbzBridge.ambientPrimary/Secondary/Accent, recomputed on track change —
|
||||
// (QbzShell.ambientPrimary/Secondary/Accent, recomputed on track change —
|
||||
// the field crossfades because Canvas repaints are continuous, ~0.6s of
|
||||
// visible blend per the blob softness).
|
||||
//
|
||||
@@ -25,9 +25,9 @@ Canvas {
|
||||
id: root
|
||||
|
||||
// The album triad (hex strings from ambient_qt).
|
||||
property color primary: QbzBridge.ambientPrimary
|
||||
property color secondary: QbzBridge.ambientSecondary
|
||||
property color accent: QbzBridge.ambientAccent
|
||||
property color primary: QbzShell.ambientPrimary
|
||||
property color secondary: QbzShell.ambientSecondary
|
||||
property color accent: QbzShell.ambientAccent
|
||||
// Set false by the host to freeze the clock (inactive / hidden).
|
||||
property bool running: true
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -25,7 +26,7 @@ Rectangle {
|
||||
|
||||
// App-wide dynamic background (phase 14): active when the mode pref is
|
||||
// on AND a track is loaded (D4: no track -> opaque theme restored).
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
// The bottom-most visual layer (AppShell.slint:206-242, declared FIRST
|
||||
// so every chrome surface paints above it): the ambient album-colored
|
||||
// field + the dark dim scrim that keeps chrome text legible
|
||||
@@ -39,7 +40,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: root.ambientOn
|
||||
color: "#000000"
|
||||
opacity: QbzBridge.ambientDim
|
||||
opacity: QbzShell.ambientDim
|
||||
}
|
||||
|
||||
// The host ApplicationWindow (custom chrome: drag / maximize / resize).
|
||||
@@ -65,7 +66,7 @@ Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
// Mode-aware height (AppShell.slint:396): Small collapses to one
|
||||
// header-tall row; New/Classic/Large keep the full 112px.
|
||||
height: QbzBridge.npbMode === 2 ? theme.npbSmallHeight : 112
|
||||
height: QbzShell.npbMode === 2 ? theme.npbSmallHeight : 112
|
||||
}
|
||||
|
||||
Sidebar {
|
||||
@@ -85,7 +86,7 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.top: header.bottom
|
||||
anchors.bottom: npb.top
|
||||
width: (QbzBridge.queueOpen || QbzBridge.lyricsOpen) ? theme.queuePanelWidth : 0
|
||||
width: (QbzShell.queueOpen || QbzShell.lyricsOpen) ? theme.queuePanelWidth : 0
|
||||
clip: true
|
||||
color: root.ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
|
||||
|
||||
@@ -97,16 +98,16 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: QbzBridge.lyricsOpen
|
||||
? (QbzBridge.queueOpen ? parent.height / 2 : 0)
|
||||
height: QbzShell.lyricsOpen
|
||||
? (QbzShell.queueOpen ? parent.height / 2 : 0)
|
||||
: parent.height
|
||||
visible: QbzBridge.queueOpen
|
||||
visible: QbzShell.queueOpen
|
||||
}
|
||||
Rectangle {
|
||||
visible: QbzBridge.queueOpen && QbzBridge.lyricsOpen
|
||||
visible: QbzShell.queueOpen && QbzShell.lyricsOpen
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
y: QbzBridge.lyricsOpen && QbzBridge.queueOpen ? parent.height / 2 : 0
|
||||
y: QbzShell.lyricsOpen && QbzShell.queueOpen ? parent.height / 2 : 0
|
||||
height: 1
|
||||
color: theme.borderSubtle
|
||||
}
|
||||
@@ -114,10 +115,10 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: QbzBridge.lyricsOpen
|
||||
? (QbzBridge.queueOpen ? parent.height / 2 - 1 : parent.height)
|
||||
height: QbzShell.lyricsOpen
|
||||
? (QbzShell.queueOpen ? parent.height / 2 - 1 : parent.height)
|
||||
: 0
|
||||
visible: QbzBridge.lyricsOpen
|
||||
visible: QbzShell.lyricsOpen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,13 +143,13 @@ Rectangle {
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: QbzBridge.currentView === "home" ? "HomeView.qml"
|
||||
: QbzBridge.currentView === "library" ? "LibraryView.qml"
|
||||
: QbzBridge.currentView === "album" ? "AlbumView.qml"
|
||||
: QbzBridge.currentView === "artist" ? "ArtistView.qml"
|
||||
: QbzBridge.currentView === "settings" ? "SettingsView.qml"
|
||||
: QbzBridge.currentView === "search" ? "SearchView.qml"
|
||||
: QbzBridge.currentView === "playlist" ? "PlaylistView.qml" : ""
|
||||
source: QbzShell.currentView === "home" ? "../views/HomeView.qml"
|
||||
: QbzShell.currentView === "library" ? "../views/LibraryView.qml"
|
||||
: QbzShell.currentView === "album" ? "../views/AlbumView.qml"
|
||||
: QbzShell.currentView === "artist" ? "../views/ArtistView.qml"
|
||||
: QbzShell.currentView === "settings" ? "../settings/SettingsView.qml"
|
||||
: QbzShell.currentView === "search" ? "../views/SearchView.qml"
|
||||
: QbzShell.currentView === "playlist" ? "../views/PlaylistView.qml" : ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +159,7 @@ Rectangle {
|
||||
// AppShell.slint:747). Only while Large is ACTIVE (mode 3 + sidebar
|
||||
// open). The spectrum band above the cover is NOT rendered — it needs
|
||||
// the FFT visualizer tap, which the POC does not wire (POC-NOTE).
|
||||
readonly property bool largeActive: QbzBridge.npbMode === 3 && QbzBridge.sidebarState === 0
|
||||
readonly property bool largeActive: QbzShell.npbMode === 3 && QbzShell.sidebarState === 0
|
||||
Rectangle {
|
||||
visible: root.largeActive
|
||||
x: 16
|
||||
@@ -184,13 +185,13 @@ Rectangle {
|
||||
color: ambientOn ? theme.surfaceMainA22 : theme.surfaceMain
|
||||
clip: true
|
||||
RoundedImage {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
anchors.fill: parent
|
||||
source: QbzBridge.npArtworkPath
|
||||
source: QbzPlayer.npArtworkPath
|
||||
radius: theme.radiusMd
|
||||
}
|
||||
QbzIcon {
|
||||
visible: !QbzBridge.npHasTrack
|
||||
visible: !QbzPlayer.npHasTrack
|
||||
name: "music"
|
||||
width: 66
|
||||
height: 66
|
||||
@@ -297,9 +298,9 @@ Rectangle {
|
||||
// the sidebar: "N tracks" for a group, or title + "artist · album" for
|
||||
// one. Visual only (never blocks the pointer).
|
||||
Rectangle {
|
||||
visible: QbzBridge.dragActive
|
||||
x: QbzBridge.dragX + 10
|
||||
y: QbzBridge.dragY + 14
|
||||
visible: QbzShell.dragActive
|
||||
x: QbzShell.dragX + 10
|
||||
y: QbzShell.dragY + 14
|
||||
width: ghostCol.width + 28
|
||||
height: ghostCol.height + 16
|
||||
radius: 8
|
||||
@@ -311,16 +312,16 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
Text {
|
||||
text: QbzBridge.dragCount > 1
|
||||
? QbzBridge.dragCount + " " + QbzBridge.tr("tracks", QbzBridge.trRev)
|
||||
: QbzBridge.dragTitle
|
||||
text: QbzShell.dragCount > 1
|
||||
? QbzShell.dragCount + " " + QbzSession.tr("tracks", QbzSession.trRev)
|
||||
: QbzShell.dragTitle
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 12
|
||||
font.weight: theme.weightMedium
|
||||
}
|
||||
Text {
|
||||
visible: QbzBridge.dragCount === 1 && QbzBridge.dragSubtitle !== ""
|
||||
text: QbzBridge.dragSubtitle
|
||||
visible: QbzShell.dragCount === 1 && QbzShell.dragSubtitle !== ""
|
||||
text: QbzShell.dragSubtitle
|
||||
color: "#8cffffff"
|
||||
font.pixelSize: 10
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -58,8 +59,6 @@ Item {
|
||||
// Activity restarts the countdown (keystroke or arrow move).
|
||||
function onCortinillaJsonChanged() { if (root.visible && !root.panelHovered) idleClose.restart() }
|
||||
function onSelectedIndexChanged() { if (root.visible && !root.panelHovered) idleClose.restart() }
|
||||
// A page change dismisses (AppShell's tracked-view close hook).
|
||||
function onCurrentViewChanged() { QbzBridge.cortinillaDismiss() }
|
||||
// Keyboard scroll-into-view: the controller publishes the selected
|
||||
// row's content-space top-y; nudge the Flickable so it is visible.
|
||||
function onCortinillaScrollYChanged() {
|
||||
@@ -72,6 +71,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: QbzShell
|
||||
// A page change dismisses (AppShell's tracked-view close hook).
|
||||
function onCurrentViewChanged() { QbzBridge.cortinillaDismiss() }
|
||||
}
|
||||
|
||||
// --- Click-outside scrims (any click outside the panel dismisses) ----
|
||||
// 1) everything BELOW the header.
|
||||
MouseArea {
|
||||
@@ -201,7 +206,7 @@ Item {
|
||||
Text {
|
||||
x: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("No results for", QbzBridge.trRev) + " “" + (root.doc.query || "") + "”"
|
||||
text: QbzSession.tr("No results for", QbzSession.trRev) + " “" + (root.doc.query || "") + "”"
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -217,7 +222,7 @@ Item {
|
||||
Text {
|
||||
x: 10
|
||||
height: 22
|
||||
text: QbzBridge.tr("Top result", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Top result", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -253,7 +258,7 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("View more", QbzBridge.trRev)
|
||||
text: QbzSession.tr("View more", QbzSession.trRev)
|
||||
color: vmArea.containsMouse ? theme.textPrimary : theme.accent
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightMedium
|
||||
@@ -6,7 +6,7 @@
|
||||
// Center: the search field, absolutely centered on the window (VISUAL
|
||||
// replica — the cortinilla/live-search is out of scope; POC-NOTE).
|
||||
// Right: the tri-state offline status badge with its flyout (recovery
|
||||
// "Sign in" wired to QbzBridge.recoveryLogin) and the app menu (user block
|
||||
// "Sign in" wired to QbzSession.recoveryLogin) and the app menu (user block
|
||||
// + Log Out + Close; the other Slint menu items are POC-NOTE stubs).
|
||||
//
|
||||
// POC-NOTE: the custom window-chrome parts (drag surface, drawn
|
||||
@@ -16,13 +16,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// surface-card @ 0.5 while the ambient background is active (phase 14,
|
||||
// HeaderBar.slint's with-alpha(app-background-surface-alpha)).
|
||||
color: ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
|
||||
// The host ApplicationWindow (custom chrome); null in previews.
|
||||
@@ -38,7 +40,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
// Inert under the system titlebar (the native chrome owns
|
||||
// drag/double-click) — phase 12 titlebar toggle.
|
||||
enabled: !QbzBridge.systemTitleBar
|
||||
enabled: !QbzShell.systemTitleBar
|
||||
property bool dragStarted: false
|
||||
onPressed: dragStarted = false
|
||||
onPositionChanged: {
|
||||
@@ -57,9 +59,9 @@ Rectangle {
|
||||
|
||||
// Slint OfflineState.badge-state: 0 hidden / 1 hard offline / 2 manual /
|
||||
// 3 logged out (wins over the others).
|
||||
readonly property int badgeState: QbzBridge.offlineSession ? 3
|
||||
: QbzBridge.offlineMode === 2 ? 2
|
||||
: QbzBridge.offlineMode === 1 ? 1 : 0
|
||||
readonly property int badgeState: QbzSession.offlineSession ? 3
|
||||
: QbzSession.offlineMode === 2 ? 2
|
||||
: QbzSession.offlineMode === 1 ? 1 : 0
|
||||
|
||||
|
||||
|
||||
@@ -74,26 +76,26 @@ Rectangle {
|
||||
QbzNavButton {
|
||||
name: "panel-left"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.cycleSidebar()
|
||||
onClicked: QbzShell.cycleSidebar()
|
||||
}
|
||||
QbzNavButton {
|
||||
name: "chevron-left"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
btnEnabled: QbzBridge.canBack
|
||||
onClicked: QbzBridge.navigateBack()
|
||||
btnEnabled: QbzShell.canBack
|
||||
onClicked: QbzShell.navigateBack()
|
||||
}
|
||||
QbzNavButton {
|
||||
name: "chevron-right"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
btnEnabled: QbzBridge.canForward
|
||||
onClicked: QbzBridge.navigateForward()
|
||||
btnEnabled: QbzShell.canForward
|
||||
onClicked: QbzShell.navigateForward()
|
||||
}
|
||||
|
||||
// Compact section nav — only while the sidebar is fully closed
|
||||
// (Cerrado), so the sections stay reachable. POC-NOTE: these open
|
||||
// dropdown menus in Slint; here they are inert visual replicas.
|
||||
Row {
|
||||
visible: QbzBridge.sidebarState === 2
|
||||
visible: QbzShell.sidebarState === 2
|
||||
height: parent.height
|
||||
spacing: 2
|
||||
|
||||
@@ -102,13 +104,13 @@ Rectangle {
|
||||
name: "compass"
|
||||
btnSize: 30; iconSize: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !QbzBridge.offline
|
||||
visible: !QbzSession.offline
|
||||
}
|
||||
QbzIconButton { activeBackground: true
|
||||
name: "music-library-2"
|
||||
btnSize: 30; iconSize: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !QbzBridge.offline
|
||||
visible: !QbzSession.offline
|
||||
}
|
||||
QbzIconButton { activeBackground: true
|
||||
name: "hard-drive"
|
||||
@@ -236,7 +238,7 @@ Rectangle {
|
||||
visible: searchInput.text === "" && !searchInput.activeFocus
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 30
|
||||
text: QbzBridge.tr("Search", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -250,7 +252,7 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
height: parent.height
|
||||
text: "⏎ " + QbzBridge.tr("Enter", QbzBridge.trRev)
|
||||
text: "⏎ " + QbzSession.tr("Enter", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -285,7 +287,7 @@ Rectangle {
|
||||
Row {
|
||||
id: rightControls
|
||||
// Shifts left of the drawn window controls (3x34 + 2x2 = 106px).
|
||||
x: root.width - width - theme.spacingMd + 2 - (QbzBridge.systemTitleBar ? 0 : 110)
|
||||
x: root.width - width - theme.spacingMd + 2 - (QbzShell.systemTitleBar ? 0 : 110)
|
||||
y: (root.height - height) / 2
|
||||
height: 36
|
||||
spacing: 4
|
||||
@@ -318,9 +320,9 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
height: parent.height
|
||||
text: root.badgeState === 3 ? QbzBridge.tr("Logged out", QbzBridge.trRev)
|
||||
: root.badgeState === 2 ? QbzBridge.tr("Offline (manual)", QbzBridge.trRev)
|
||||
: QbzBridge.tr("Offline (hard)", QbzBridge.trRev)
|
||||
text: root.badgeState === 3 ? QbzSession.tr("Logged out", QbzSession.trRev)
|
||||
: root.badgeState === 2 ? QbzSession.tr("Offline (manual)", QbzSession.trRev)
|
||||
: QbzSession.tr("Offline (hard)", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 11
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -363,14 +365,14 @@ Rectangle {
|
||||
Text {
|
||||
width: parent.width - 23
|
||||
text: root.badgeState === 3
|
||||
? (QbzBridge.connectivity === 2
|
||||
? QbzBridge.tr("You're signed out. Sign-in needs a connection.", QbzBridge.trRev)
|
||||
: QbzBridge.connectivity === 1
|
||||
? QbzBridge.tr("Connection available — sign back in to Qobuz.", QbzBridge.trRev)
|
||||
: QbzBridge.tr("You're signed out — sign back in to Qobuz.", QbzBridge.trRev))
|
||||
? (QbzSession.connectivity === 2
|
||||
? QbzSession.tr("You're signed out. Sign-in needs a connection.", QbzSession.trRev)
|
||||
: QbzSession.connectivity === 1
|
||||
? QbzSession.tr("Connection available — sign back in to Qobuz.", QbzSession.trRev)
|
||||
: QbzSession.tr("You're signed out — sign back in to Qobuz.", QbzSession.trRev))
|
||||
: root.badgeState === 2
|
||||
? QbzBridge.tr("Offline mode is enabled. Disable it in Settings to use Qobuz.", QbzBridge.trRev)
|
||||
: QbzBridge.tr("No internet connection — your local library and downloads keep working.", QbzBridge.trRev)
|
||||
? QbzSession.tr("Offline mode is enabled. Disable it in Settings to use Qobuz.", QbzSession.trRev)
|
||||
: QbzSession.tr("No internet connection — your local library and downloads keep working.", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -389,23 +391,23 @@ Rectangle {
|
||||
radius: theme.radiusSm
|
||||
border.width: 1
|
||||
border.color: theme.borderSubtle
|
||||
opacity: QbzBridge.connectivity === 2 ? 0.4 : 1.0
|
||||
opacity: QbzSession.connectivity === 2 ? 0.4 : 1.0
|
||||
color: signInArea.containsMouse ? theme.surfaceHover : theme.surfaceElevated
|
||||
Text {
|
||||
id: signInText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Sign in", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Sign in", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
id: signInArea
|
||||
anchors.fill: parent
|
||||
enabled: QbzBridge.connectivity !== 2
|
||||
enabled: QbzSession.connectivity !== 2
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
QbzBridge.recoveryLogin()
|
||||
QbzSession.recoveryLogin()
|
||||
badgeFlyout.close()
|
||||
}
|
||||
}
|
||||
@@ -428,7 +430,7 @@ Rectangle {
|
||||
// --- Drawn window controls (WindowControls.slint, right placement:
|
||||
// minimize · maximize · close; close gets the danger-red hover) ------
|
||||
Row {
|
||||
visible: !QbzBridge.systemTitleBar
|
||||
visible: !QbzShell.systemTitleBar
|
||||
x: root.width - width - 8
|
||||
y: (root.height - height) / 2
|
||||
height: 26
|
||||
@@ -532,15 +534,15 @@ Rectangle {
|
||||
bottomPadding: 10
|
||||
spacing: 2
|
||||
Text {
|
||||
text: QbzBridge.sessionUserName === ""
|
||||
? QbzBridge.tr("Guest", QbzBridge.trRev) : QbzBridge.sessionUserName
|
||||
text: QbzSession.sessionUserName === ""
|
||||
? QbzSession.tr("Guest", QbzSession.trRev) : QbzSession.sessionUserName
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightSemibold
|
||||
}
|
||||
Text {
|
||||
text: QbzBridge.sessionSubscription === ""
|
||||
? QbzBridge.tr("Not signed in", QbzBridge.trRev) : QbzBridge.sessionSubscription
|
||||
text: QbzSession.sessionSubscription === ""
|
||||
? QbzSession.tr("Not signed in", QbzSession.trRev) : QbzSession.sessionSubscription
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -603,25 +605,25 @@ Rectangle {
|
||||
|
||||
AppMenuItem {
|
||||
name: "search"
|
||||
label: QbzBridge.tr("Intelligent search", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Intelligent search", QbzSession.trRev)
|
||||
checkedItem: QbzBridge.intelligentSearch
|
||||
onClicked: QbzBridge.toggleIntelligentSearch()
|
||||
}
|
||||
AppMenuItem {
|
||||
name: "layout-grid"
|
||||
label: QbzBridge.tr("Ambient background", QbzBridge.trRev)
|
||||
checkedItem: QbzBridge.ambientMode > 0
|
||||
label: QbzSession.tr("Ambient background", QbzSession.trRev)
|
||||
checkedItem: QbzShell.ambientMode > 0
|
||||
onClicked: {
|
||||
QbzBridge.toggleAmbientBackground()
|
||||
QbzShell.toggleAmbientBackground()
|
||||
// Applies LIVE (pure QML layering); the check flips now.
|
||||
}
|
||||
}
|
||||
AppMenuItem {
|
||||
name: "layout-grid"
|
||||
label: QbzBridge.tr("Use system title bar", QbzBridge.trRev)
|
||||
checkedItem: QbzBridge.systemTitleBarPref
|
||||
label: QbzSession.tr("Use system title bar", QbzSession.trRev)
|
||||
checkedItem: QbzShell.systemTitleBarPref
|
||||
onClicked: {
|
||||
QbzBridge.toggleSystemTitleBar()
|
||||
QbzShell.toggleSystemTitleBar()
|
||||
// Keep the menu open so the check state reads; the
|
||||
// applied mode changes on the next launch (1:1 Slint).
|
||||
}
|
||||
@@ -629,29 +631,29 @@ Rectangle {
|
||||
Text {
|
||||
width: parent.width
|
||||
leftPadding: 37
|
||||
text: QbzBridge.tr("Takes effect after restarting QBZ.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Takes effect after restarting QBZ.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
}
|
||||
AppMenuItem {
|
||||
name: "settings-2"
|
||||
label: QbzBridge.tr("Settings", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Settings", QbzSession.trRev)
|
||||
onClicked: {
|
||||
appMenu.close()
|
||||
QbzBridge.navigateTo("settings")
|
||||
QbzShell.navigateTo("settings")
|
||||
}
|
||||
}
|
||||
AppMenuItem {
|
||||
name: "log-out"
|
||||
label: QbzBridge.tr("Log Out", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Log Out", QbzSession.trRev)
|
||||
onClicked: {
|
||||
appMenu.close()
|
||||
QbzBridge.logout()
|
||||
QbzSession.logout()
|
||||
}
|
||||
}
|
||||
AppMenuItem {
|
||||
name: "x"
|
||||
label: QbzBridge.tr("Close", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Close", QbzSession.trRev)
|
||||
// POC-NOTE: the Slint app hides to tray / follows the
|
||||
// platform close behavior; the POC just quits.
|
||||
onClicked: Qt.quit()
|
||||
@@ -5,7 +5,7 @@
|
||||
// trigger + close X), body by status (loading / no-lyrics / offline-miss
|
||||
// / error / the lines view), source attribution line. Synced rendering:
|
||||
// LINE-level highlight + auto-follow driven by the 1 Hz position feed
|
||||
// (QbzBridge.npElapsedSecs; the Slint engine ticks ~30 Hz and can fill
|
||||
// (QbzPlayer.npElapsedSecs; the Slint engine ticks ~30 Hz and can fill
|
||||
// word-level karaoke — both noted POC-NOTEs). Lines with timestamps are
|
||||
// click-to-seek (LyricsLinesView behavior).
|
||||
//
|
||||
@@ -18,20 +18,21 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: "transparent"
|
||||
clip: true
|
||||
|
||||
readonly property var doc: JSON.parse(QbzBridge.lyricsJson)
|
||||
readonly property var doc: JSON.parse(QbzShell.lyricsJson)
|
||||
readonly property var lines: doc.lines || []
|
||||
readonly property bool synced: doc.synced === true
|
||||
|
||||
// Active line (line-level sync, 1 Hz cadence).
|
||||
readonly property int activeIndex: {
|
||||
if (!synced) return -1
|
||||
var nowMs = (QbzBridge.npElapsedSecs || 0) * 1000
|
||||
var nowMs = (QbzPlayer.npElapsedSecs || 0) * 1000
|
||||
var idx = -1
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].timeMs !== null && lines[i].timeMs <= nowMs) idx = i
|
||||
@@ -71,16 +72,16 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
Text {
|
||||
text: QbzBridge.tr("LYRICS", QbzBridge.trRev)
|
||||
text: QbzSession.tr("LYRICS", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
font.letterSpacing: 1.5
|
||||
}
|
||||
Text {
|
||||
visible: QbzBridge.npTitle !== ""
|
||||
visible: QbzPlayer.npTitle !== ""
|
||||
width: parent.width
|
||||
text: QbzBridge.npTitle + " - " + QbzBridge.npArtist
|
||||
text: QbzPlayer.npTitle + " - " + QbzPlayer.npArtist
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
@@ -131,7 +132,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.toggleLyrics()
|
||||
onClicked: QbzShell.toggleLyrics()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +156,7 @@ Rectangle {
|
||||
QbzSpinner { size: 24; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("Fetching lyrics...", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Fetching lyrics...", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
}
|
||||
@@ -169,7 +170,7 @@ Rectangle {
|
||||
spacing: 12
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("No lyrics available", QbzBridge.trRev)
|
||||
text: QbzSession.tr("No lyrics available", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -180,7 +181,7 @@ Rectangle {
|
||||
spacing: 2
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Missing lyrics? You can contribute them to LRCLIB:", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Missing lyrics? You can contribute them to LRCLIB:", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 10
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -207,7 +208,7 @@ Rectangle {
|
||||
visible: doc.status === 5
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 32
|
||||
text: QbzBridge.tr("Lyrics unavailable offline", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Lyrics unavailable offline", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLegal
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -266,8 +267,8 @@ Rectangle {
|
||||
enabled: modelData.timeMs !== null
|
||||
cursorShape: modelData.timeMs !== null ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (modelData.timeMs !== null && QbzBridge.npDurationSecs > 0)
|
||||
QbzBridge.seek(modelData.timeMs / 1000 / QbzBridge.npDurationSecs)
|
||||
if (modelData.timeMs !== null && QbzPlayer.npDurationSecs > 0)
|
||||
QbzPlayer.seek(modelData.timeMs / 1000 / QbzPlayer.npDurationSecs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +297,7 @@ Rectangle {
|
||||
bottomPadding: 5
|
||||
spacing: 3
|
||||
Text {
|
||||
text: QbzBridge.tr("Lyrics source", QbzBridge.trRev) + ":"
|
||||
text: QbzSession.tr("Lyrics source", QbzSession.trRev) + ":"
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 9
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -11,6 +11,6 @@ Item {
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
source: QbzBridge.npbMode === 2 ? "NowPlayingBarSmall.qml" : "PlayerBar.qml"
|
||||
source: QbzShell.npbMode === 2 ? "NowPlayingBarSmall.qml" : "PlayerBar.qml"
|
||||
}
|
||||
}
|
||||
+54
-52
@@ -21,13 +21,15 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// surface-card @ 0.5 while the ambient background is active (phase 14,
|
||||
// PlayerBarSmall.slint).
|
||||
color: ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
|
||||
QbzTheme { id: theme }
|
||||
@@ -88,7 +90,7 @@ Rectangle {
|
||||
|
||||
// Buffered / cache line.
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(Math.max(QbzBridge.npCacheProgress, 0), 1)
|
||||
width: parent.width * Math.min(Math.max(QbzPlayer.npCacheProgress, 0), 1)
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.textMuted
|
||||
@@ -96,7 +98,7 @@ Rectangle {
|
||||
}
|
||||
// Playback progress line.
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1)
|
||||
width: parent.width * Math.min(Math.max(QbzPlayer.npProgress, 0), 1)
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.accent
|
||||
@@ -107,7 +109,7 @@ Rectangle {
|
||||
height: 12
|
||||
radius: 6
|
||||
color: theme.textPrimary
|
||||
x: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1) - width / 2
|
||||
x: parent.width * Math.min(Math.max(QbzPlayer.npProgress, 0), 1) - width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: seekArea.containsMouse ? 1.0 : 0.0
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
@@ -121,8 +123,8 @@ Rectangle {
|
||||
anchors.verticalCenter: seekTrack.verticalCenter
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
enabled: QbzBridge.npHasTrack
|
||||
onClicked: QbzBridge.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
enabled: QbzPlayer.npHasTrack
|
||||
onClicked: QbzPlayer.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +151,7 @@ Rectangle {
|
||||
// title elides at exactly the free space.
|
||||
Item {
|
||||
height: parent.height
|
||||
width: parent.width - (QbzBridge.npHasTrack ? 59 : 0)
|
||||
width: parent.width - (QbzPlayer.npHasTrack ? 59 : 0)
|
||||
|
||||
Rectangle {
|
||||
id: artBox
|
||||
@@ -164,13 +166,13 @@ Rectangle {
|
||||
|
||||
RoundedImage {
|
||||
anchors.fill: parent
|
||||
source: QbzBridge.npArtworkPath
|
||||
source: QbzPlayer.npArtworkPath
|
||||
radius: 6
|
||||
}
|
||||
// Idle (no track): a STATIC album glyph
|
||||
// placeholder (50% of the art size, muted).
|
||||
QbzIcon {
|
||||
visible: !QbzBridge.npHasTrack
|
||||
visible: !QbzPlayer.npHasTrack
|
||||
name: "music"
|
||||
width: 18.5
|
||||
height: 18.5
|
||||
@@ -193,17 +195,17 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.npHasTrack
|
||||
? QbzBridge.npTitle : QbzBridge.tr("Nothing playing", QbzBridge.trRev)
|
||||
text: QbzPlayer.npHasTrack
|
||||
? QbzPlayer.npTitle : QbzSession.tr("Nothing playing", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: 13
|
||||
font.weight: theme.weightMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
width: parent.width
|
||||
text: QbzBridge.npArtist
|
||||
text: QbzPlayer.npArtist
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
@@ -214,7 +216,7 @@ Rectangle {
|
||||
// Separator: song-card | time column (8px toward the
|
||||
// card, 10px toward the timer).
|
||||
Sep {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
gapLeft: 8
|
||||
gapRight: 10
|
||||
}
|
||||
@@ -222,18 +224,18 @@ Rectangle {
|
||||
// Time column — fixed 40px 2-row block (elapsed /
|
||||
// total), monospace for stable digit positions.
|
||||
Column {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
width: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 1
|
||||
Text {
|
||||
text: root.fmt(QbzBridge.npElapsedSecs)
|
||||
text: root.fmt(QbzPlayer.npElapsedSecs)
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 10
|
||||
color: theme.textMuted
|
||||
}
|
||||
Text {
|
||||
text: root.fmt(QbzBridge.npDurationSecs)
|
||||
text: root.fmt(QbzPlayer.npDurationSecs)
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 10
|
||||
color: theme.textMuted
|
||||
@@ -257,19 +259,19 @@ Rectangle {
|
||||
|
||||
QbzIconButton {
|
||||
name: "info"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
// POC-NOTE: track-info panel is out of scope.
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "shuffle"
|
||||
active: QbzBridge.npShuffle
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
onClicked: QbzBridge.toggleShuffle()
|
||||
active: QbzPlayer.npShuffle
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
onClicked: QbzPlayer.toggleShuffle()
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "skip-back"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
onClicked: QbzBridge.previous()
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
onClicked: QbzPlayer.previous()
|
||||
}
|
||||
// Play/pause — the Classic plain glyph (play-circle:
|
||||
// false in Small): 34px, 20px glyph, text-primary,
|
||||
@@ -278,11 +280,11 @@ Rectangle {
|
||||
width: 34
|
||||
height: 34
|
||||
radius: theme.radiusSm
|
||||
opacity: QbzBridge.npHasTrack ? 1.0 : 0.3
|
||||
color: (playArea.containsMouse && QbzBridge.npHasTrack)
|
||||
opacity: QbzPlayer.npHasTrack ? 1.0 : 0.3
|
||||
color: (playArea.containsMouse && QbzPlayer.npHasTrack)
|
||||
? theme.surfaceHover : "transparent"
|
||||
QbzIcon {
|
||||
name: QbzBridge.npPlaying ? "pause" : "play-fill"
|
||||
name: QbzPlayer.npPlaying ? "pause" : "play-fill"
|
||||
width: 20
|
||||
height: 20
|
||||
anchors.centerIn: parent
|
||||
@@ -292,24 +294,24 @@ Rectangle {
|
||||
id: playArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: QbzBridge.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzBridge.npHasTrack) QbzBridge.togglePlay()
|
||||
cursorShape: QbzPlayer.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzPlayer.npHasTrack) QbzPlayer.togglePlay()
|
||||
}
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "skip-forward"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
onClicked: QbzBridge.next()
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
onClicked: QbzPlayer.next()
|
||||
}
|
||||
QbzIconButton {
|
||||
name: QbzBridge.npRepeatMode === 2 ? "repeat-1" : "repeat"
|
||||
active: QbzBridge.npRepeatMode !== 0
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
onClicked: QbzBridge.cycleRepeat()
|
||||
name: QbzPlayer.npRepeatMode === 2 ? "repeat-1" : "repeat"
|
||||
active: QbzPlayer.npRepeatMode !== 0
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
onClicked: QbzPlayer.cycleRepeat()
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "plus"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
// POC-NOTE: the grouped "Add to…" flyout is out of
|
||||
// scope.
|
||||
}
|
||||
@@ -337,23 +339,23 @@ Rectangle {
|
||||
// POC-NOTE: row 2 (backend/mode LEDs) is not rendered —
|
||||
// the POC model carries no output-backend state.
|
||||
Column {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 1
|
||||
Row {
|
||||
spacing: 5
|
||||
layoutDirection: Qt.RightToLeft
|
||||
Text {
|
||||
text: QbzBridge.npQualityLabel
|
||||
text: QbzPlayer.npQualityLabel
|
||||
font.pixelSize: 9
|
||||
font.weight: theme.weightSemibold
|
||||
color: theme.textPrimary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
text: QbzBridge.npQualityTier === "hires" ? "HI-RES"
|
||||
: QbzBridge.npQualityTier === "mp3" ? "MP3"
|
||||
: QbzBridge.npQualityTier === "lossless" ? "LOSSLESS" : "CD"
|
||||
text: QbzPlayer.npQualityTier === "hires" ? "HI-RES"
|
||||
: QbzPlayer.npQualityTier === "mp3" ? "MP3"
|
||||
: QbzPlayer.npQualityTier === "lossless" ? "LOSSLESS" : "CD"
|
||||
font.pixelSize: 9
|
||||
font.weight: theme.weightBold
|
||||
font.letterSpacing: 0.3
|
||||
@@ -365,7 +367,7 @@ Rectangle {
|
||||
// Separator: AudioStamp | icon cluster (10px toward the
|
||||
// stamp, 8px toward the buttons).
|
||||
Sep {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
gapLeft: 10
|
||||
gapRight: 8
|
||||
}
|
||||
@@ -385,17 +387,17 @@ Rectangle {
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "mic-vocal"
|
||||
active: QbzBridge.lyricsOpen
|
||||
onClicked: QbzBridge.toggleLyrics()
|
||||
active: QbzShell.lyricsOpen
|
||||
onClicked: QbzShell.toggleLyrics()
|
||||
}
|
||||
|
||||
Item { width: 4; height: 1 }
|
||||
|
||||
// Volume — mute icon + (WIDE) inline horizontal slider.
|
||||
QbzIconButton {
|
||||
name: QbzBridge.npMuted ? "volume-x" : "volume-2"
|
||||
active: QbzBridge.npMuted
|
||||
onClicked: QbzBridge.toggleMute()
|
||||
name: QbzPlayer.npMuted ? "volume-x" : "volume-2"
|
||||
active: QbzPlayer.npMuted
|
||||
onClicked: QbzPlayer.toggleMute()
|
||||
}
|
||||
Item {
|
||||
visible: root.volumeWide
|
||||
@@ -409,7 +411,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: theme.surfaceElevated
|
||||
Rectangle {
|
||||
width: parent.width * QbzBridge.npVolume
|
||||
width: parent.width * QbzPlayer.npVolume
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.accent
|
||||
@@ -419,16 +421,16 @@ Rectangle {
|
||||
height: 16
|
||||
radius: 8
|
||||
color: theme.textPrimary
|
||||
x: parent.width * QbzBridge.npVolume - width / 2
|
||||
x: parent.width * QbzPlayer.npVolume - width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPressed: QbzPlayer.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPositionChanged: if (pressed)
|
||||
QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
QbzPlayer.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,8 +439,8 @@ Rectangle {
|
||||
// Queue panel toggle.
|
||||
QbzIconButton {
|
||||
name: "list-ordered"
|
||||
active: QbzBridge.queueOpen
|
||||
onClicked: QbzBridge.toggleQueue()
|
||||
active: QbzShell.queueOpen
|
||||
onClicked: QbzShell.toggleQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,15 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool largeActive: QbzBridge.npbMode === 3 && QbzBridge.sidebarState === 0
|
||||
readonly property bool isClassic: QbzBridge.npbMode === 1
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
readonly property bool largeActive: QbzShell.npbMode === 3 && QbzShell.sidebarState === 0
|
||||
readonly property bool isClassic: QbzShell.npbMode === 1
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
@@ -110,8 +112,8 @@ Rectangle {
|
||||
layoutDirection: Qt.RightToLeft
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Text {
|
||||
visible: QbzBridge.npQualityLabel !== ""
|
||||
text: QbzBridge.npQualityLabel
|
||||
visible: QbzPlayer.npQualityLabel !== ""
|
||||
text: QbzPlayer.npQualityLabel
|
||||
font.pixelSize: 9
|
||||
font.weight: theme.weightSemibold
|
||||
color: theme.textPrimary
|
||||
@@ -121,8 +123,8 @@ Rectangle {
|
||||
QualityMini {
|
||||
// The stamp shows the cd glyph for ANY non-hires tier with
|
||||
// a label (not just "cd") — map that exactly.
|
||||
tier: QbzBridge.npQualityTier === "hires" ? "hires"
|
||||
: (QbzBridge.npQualityLabel !== "" ? "cd" : "")
|
||||
tier: QbzPlayer.npQualityTier === "hires" ? "hires"
|
||||
: (QbzPlayer.npQualityLabel !== "" ? "cd" : "")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
@@ -166,13 +168,13 @@ Rectangle {
|
||||
color: theme.surfaceElevated
|
||||
clip: true
|
||||
RoundedImage {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
anchors.fill: parent
|
||||
source: QbzBridge.npArtworkPath
|
||||
source: QbzPlayer.npArtworkPath
|
||||
radius: 6
|
||||
}
|
||||
QbzIcon {
|
||||
visible: !QbzBridge.npHasTrack
|
||||
visible: !QbzPlayer.npHasTrack
|
||||
name: "music"
|
||||
width: artSize * 0.5
|
||||
height: artSize * 0.5
|
||||
@@ -181,7 +183,7 @@ Rectangle {
|
||||
}
|
||||
// Fetch overlay (resolving/downloading).
|
||||
Rectangle {
|
||||
visible: QbzBridge.npHasTrack && QbzBridge.npLoading
|
||||
visible: QbzPlayer.npHasTrack && QbzPlayer.npLoading
|
||||
anchors.fill: parent
|
||||
radius: 6
|
||||
color: "#aa000000"
|
||||
@@ -202,19 +204,19 @@ Rectangle {
|
||||
spacing: 4
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.npHasTrack ? QbzBridge.npTitle : QbzBridge.tr("Nothing playing", QbzBridge.trRev)
|
||||
text: QbzPlayer.npHasTrack ? QbzPlayer.npTitle : QbzSession.tr("Nothing playing", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Row {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
spacing: 7
|
||||
height: 18
|
||||
// Context-stack icon ("Show track playing context" pref).
|
||||
Rectangle {
|
||||
visible: QbzBridge.showContextIcon
|
||||
visible: QbzPlayer.showContextIcon
|
||||
width: 16
|
||||
height: 18
|
||||
radius: 3
|
||||
@@ -231,21 +233,21 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (QbzBridge.npAlbumId !== "") QbzBridge.openAlbum(QbzBridge.npAlbumId)
|
||||
onClicked: if (QbzPlayer.npAlbumId !== "") QbzBridge.openAlbum(QbzPlayer.npAlbumId)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.npArtist
|
||||
color: artistArea.containsMouse && QbzBridge.npArtistId !== "" ? theme.accent : theme.textMuted
|
||||
text: QbzPlayer.npArtist
|
||||
color: artistArea.containsMouse && QbzPlayer.npArtistId !== "" ? theme.accent : theme.textMuted
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
id: artistArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: QbzBridge.npArtistId !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzBridge.npArtistId !== "") QbzBridge.openArtist(QbzBridge.npArtistId)
|
||||
cursorShape: QbzPlayer.npArtistId !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzPlayer.npArtistId !== "") QbzBridge.openArtist(QbzPlayer.npArtistId)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
@@ -257,16 +259,16 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.npAlbum
|
||||
color: albumArea.containsMouse && QbzBridge.npAlbumId !== "" ? theme.accent : theme.textMuted
|
||||
text: QbzPlayer.npAlbum
|
||||
color: albumArea.containsMouse && QbzPlayer.npAlbumId !== "" ? theme.accent : theme.textMuted
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
MouseArea {
|
||||
id: albumArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: QbzBridge.npAlbumId !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzBridge.npAlbumId !== "") QbzBridge.openAlbum(QbzBridge.npAlbumId)
|
||||
cursorShape: QbzPlayer.npAlbumId !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzPlayer.npAlbumId !== "") QbzBridge.openAlbum(QbzPlayer.npAlbumId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +276,7 @@ Rectangle {
|
||||
}
|
||||
AudioStamp {
|
||||
id: stamp
|
||||
visible: showBadges && QbzBridge.npHasTrack
|
||||
visible: showBadges && QbzPlayer.npHasTrack
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -290,27 +292,27 @@ Rectangle {
|
||||
|
||||
QbzIconButton {
|
||||
name: "shuffle"
|
||||
active: QbzBridge.npShuffle
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
active: QbzPlayer.npShuffle
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.toggleShuffle()
|
||||
onClicked: QbzPlayer.toggleShuffle()
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "skip-back"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.previous()
|
||||
onClicked: QbzPlayer.previous()
|
||||
}
|
||||
Rectangle {
|
||||
width: playCircle ? 44 : 32
|
||||
height: playCircle ? 44 : 32
|
||||
radius: width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: QbzBridge.npHasTrack ? 1.0 : 0.3
|
||||
color: playCircle ? (playArea.containsMouse && QbzBridge.npHasTrack ? theme.accentHover : theme.accent)
|
||||
: ((playArea.containsMouse && QbzBridge.npHasTrack) ? theme.surfaceHover : "transparent")
|
||||
opacity: QbzPlayer.npHasTrack ? 1.0 : 0.3
|
||||
color: playCircle ? (playArea.containsMouse && QbzPlayer.npHasTrack ? theme.accentHover : theme.accent)
|
||||
: ((playArea.containsMouse && QbzPlayer.npHasTrack) ? theme.surfaceHover : "transparent")
|
||||
QbzIcon {
|
||||
name: QbzBridge.npPlaying ? "pause" : "play-fill"
|
||||
name: QbzPlayer.npPlaying ? "pause" : "play-fill"
|
||||
width: playCircle ? 20 : 16
|
||||
height: playCircle ? 20 : 16
|
||||
anchors.centerIn: parent
|
||||
@@ -320,22 +322,22 @@ Rectangle {
|
||||
id: playArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: QbzBridge.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzBridge.npHasTrack) QbzBridge.togglePlay()
|
||||
cursorShape: QbzPlayer.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: if (QbzPlayer.npHasTrack) QbzPlayer.togglePlay()
|
||||
}
|
||||
}
|
||||
QbzIconButton {
|
||||
name: "skip-forward"
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.next()
|
||||
onClicked: QbzPlayer.next()
|
||||
}
|
||||
QbzIconButton {
|
||||
name: QbzBridge.npRepeatMode === 2 ? "repeat-1" : "repeat"
|
||||
active: QbzBridge.npRepeatMode > 0
|
||||
btnEnabled: QbzBridge.npHasTrack
|
||||
name: QbzPlayer.npRepeatMode === 2 ? "repeat-1" : "repeat"
|
||||
active: QbzPlayer.npRepeatMode > 0
|
||||
btnEnabled: QbzPlayer.npHasTrack
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.cycleRepeat()
|
||||
onClicked: QbzPlayer.cycleRepeat()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,8 +357,8 @@ Rectangle {
|
||||
Text {
|
||||
x: root.dockWidth > 0 ? root.dockWidth + 6 : 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: QbzBridge.npHasTrack
|
||||
text: root.fmt(QbzBridge.npElapsedSecs)
|
||||
visible: QbzPlayer.npHasTrack
|
||||
text: root.fmt(QbzPlayer.npElapsedSecs)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
}
|
||||
@@ -364,31 +366,31 @@ Rectangle {
|
||||
id: seekTrack
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: (root.dockWidth > 0 ? root.dockWidth + 6 : 6) + (QbzBridge.npHasTrack ? 46 : 0)
|
||||
anchors.rightMargin: 6 + (QbzBridge.npHasTrack ? 46 : 0)
|
||||
anchors.leftMargin: (root.dockWidth > 0 ? root.dockWidth + 6 : 6) + (QbzPlayer.npHasTrack ? 46 : 0)
|
||||
anchors.rightMargin: 6 + (QbzPlayer.npHasTrack ? 46 : 0)
|
||||
height: 4
|
||||
radius: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: theme.surfaceElevated
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(Math.max(QbzBridge.npCacheProgress, 0), 1)
|
||||
width: parent.width * Math.min(Math.max(QbzPlayer.npCacheProgress, 0), 1)
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.borderMuted
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1)
|
||||
width: parent.width * Math.min(Math.max(QbzPlayer.npProgress, 0), 1)
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.accent
|
||||
}
|
||||
Rectangle {
|
||||
visible: QbzBridge.npHasTrack
|
||||
visible: QbzPlayer.npHasTrack
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
color: theme.textPrimary
|
||||
x: parent.width * Math.min(Math.max(QbzBridge.npProgress, 0), 1) - width / 2
|
||||
x: parent.width * Math.min(Math.max(QbzPlayer.npProgress, 0), 1) - width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
@@ -396,8 +398,8 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: QbzBridge.npHasTrack
|
||||
text: "-" + root.fmt(Math.max(0, QbzBridge.npDurationSecs - QbzBridge.npElapsedSecs))
|
||||
visible: QbzPlayer.npHasTrack
|
||||
text: "-" + root.fmt(Math.max(0, QbzPlayer.npDurationSecs - QbzPlayer.npElapsedSecs))
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
}
|
||||
@@ -406,9 +408,9 @@ Rectangle {
|
||||
anchors.right: seekTrack.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
cursorShape: QbzBridge.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: if (QbzBridge.npHasTrack) QbzBridge.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPositionChanged: if (pressed && QbzBridge.npHasTrack) QbzBridge.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
cursorShape: QbzPlayer.npHasTrack ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onPressed: if (QbzPlayer.npHasTrack) QbzPlayer.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPositionChanged: if (pressed && QbzPlayer.npHasTrack) QbzPlayer.seek(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +491,7 @@ Rectangle {
|
||||
// cluster at a fixed 12px gap (the badges move out of the
|
||||
// song card in Large).
|
||||
AudioStamp {
|
||||
visible: root.largeActive && QbzBridge.npHasTrack
|
||||
visible: root.largeActive && QbzPlayer.npHasTrack
|
||||
showLeds: false
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -509,17 +511,17 @@ Rectangle {
|
||||
// Lyrics.
|
||||
QbzIconButton {
|
||||
name: "mic-vocal"
|
||||
active: QbzBridge.lyricsOpen
|
||||
onClicked: QbzBridge.toggleLyrics()
|
||||
active: QbzShell.lyricsOpen
|
||||
onClicked: QbzShell.toggleLyrics()
|
||||
}
|
||||
|
||||
Item { width: 6; height: 1 }
|
||||
|
||||
// Volume.
|
||||
QbzIconButton {
|
||||
name: QbzBridge.npMuted ? "volume-x" : "volume-2"
|
||||
active: QbzBridge.npMuted
|
||||
onClicked: QbzBridge.toggleMute()
|
||||
name: QbzPlayer.npMuted ? "volume-x" : "volume-2"
|
||||
active: QbzPlayer.npMuted
|
||||
onClicked: QbzPlayer.toggleMute()
|
||||
}
|
||||
Item {
|
||||
width: 81
|
||||
@@ -532,7 +534,7 @@ Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: theme.surfaceElevated
|
||||
Rectangle {
|
||||
width: parent.width * QbzBridge.npVolume
|
||||
width: parent.width * QbzPlayer.npVolume
|
||||
height: parent.height
|
||||
radius: 2
|
||||
color: theme.accent
|
||||
@@ -542,16 +544,16 @@ Rectangle {
|
||||
height: 16
|
||||
radius: 8
|
||||
color: theme.textPrimary
|
||||
x: parent.width * QbzBridge.npVolume - width / 2
|
||||
x: parent.width * QbzPlayer.npVolume - width / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPressed: QbzPlayer.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
onPositionChanged: if (pressed)
|
||||
QbzBridge.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
QbzPlayer.setVolume(Math.min(Math.max(mouseX / width, 0), 1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,8 +562,8 @@ Rectangle {
|
||||
// Queue panel toggle.
|
||||
QbzIconButton {
|
||||
name: "list-ordered"
|
||||
active: QbzBridge.queueOpen
|
||||
onClicked: QbzBridge.toggleQueue()
|
||||
active: QbzShell.queueOpen
|
||||
onClicked: QbzShell.toggleQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
// live search filter, row actions (play / remove / remove-all-after /
|
||||
// heart), drag reorder (basic press-drag), footer (count line + Clear /
|
||||
// stubs + search field), History tab (thumbnail rows, click replays as a
|
||||
// fresh single-track queue), exact empty states. Data: QbzBridge.queueJson
|
||||
// fresh single-track queue), exact empty states. Data: QbzQueue.queueJson
|
||||
// (queue_qt.rs QueueDoc).
|
||||
//
|
||||
// POC-NOTEs: save-as-playlist (opens a picker modal upstream — inert),
|
||||
@@ -17,6 +17,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
@@ -25,7 +27,7 @@ Rectangle {
|
||||
|
||||
// Queue (0) / History (1) — Slint QueueState.tab.
|
||||
property int tab: 0
|
||||
readonly property var doc: JSON.parse(QbzBridge.queueJson)
|
||||
readonly property var doc: JSON.parse(QbzQueue.queueJson)
|
||||
readonly property var currentRow: doc.current || null
|
||||
readonly property var upcoming: doc.upcoming || []
|
||||
readonly property var historyRows: doc.history || []
|
||||
@@ -55,7 +57,7 @@ Rectangle {
|
||||
var i
|
||||
for (i = 0; i < upcoming.length; i++) if (upcoming[i].artUrl) urls.push(upcoming[i].artUrl)
|
||||
for (i = 0; i < historyRows.length; i++) if (historyRows[i].artUrl) urls.push(historyRows[i].artUrl)
|
||||
if (urls.length > 0) QbzBridge.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
}
|
||||
|
||||
component QueueTab: Text {
|
||||
@@ -89,7 +91,7 @@ Rectangle {
|
||||
property bool inQueue: true
|
||||
|
||||
readonly property bool hovered: qrArea.containsMouse || menuArea.containsMouse
|
||||
readonly property bool isActive: inQueue && QbzBridge.npTrackId !== "" && QbzBridge.npTrackId === row.id
|
||||
readonly property bool isActive: inQueue && QbzPlayer.npTrackId !== "" && QbzPlayer.npTrackId === row.id
|
||||
|
||||
height: 44
|
||||
radius: theme.radiusSm
|
||||
@@ -205,9 +207,9 @@ Rectangle {
|
||||
menuWidth: 196
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Remove from queue", QbzBridge.trRev), "icon": "trash-2", "action": "remove" },
|
||||
{ "label": QbzBridge.tr("Remove all after", QbzBridge.trRev), "icon": "list-x", "action": "remove-after" },
|
||||
{ "label": row.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev), "icon": row.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
{ "label": QbzSession.tr("Remove from queue", QbzSession.trRev), "icon": "trash-2", "action": "remove" },
|
||||
{ "label": QbzSession.tr("Remove all after", QbzSession.trRev), "icon": "list-x", "action": "remove-after" },
|
||||
{ "label": row.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev), "icon": row.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -238,11 +240,11 @@ Rectangle {
|
||||
onClicked: {
|
||||
rowMenu.close()
|
||||
var a = modelData.action
|
||||
if (a === "remove") QbzBridge.queueRemoveUpcoming(rowIndex)
|
||||
else if (a === "remove-after") QbzBridge.queueRemoveAllAfter(rowIndex)
|
||||
if (a === "remove") QbzQueue.queueRemoveUpcoming(rowIndex)
|
||||
else if (a === "remove-after") QbzQueue.queueRemoveAllAfter(rowIndex)
|
||||
else if (a === "favorite") {
|
||||
row.isFavorite = !row.isFavorite
|
||||
QbzBridge.queueToggleFavorite("track", row.id)
|
||||
QbzQueue.queueToggleFavorite("track", row.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,8 +260,8 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (inQueue) QbzBridge.queuePlayUpcoming(rowIndex)
|
||||
else QbzBridge.queuePlayHistory(rowIndex)
|
||||
if (inQueue) QbzQueue.queuePlayUpcoming(rowIndex)
|
||||
else QbzQueue.queuePlayHistory(rowIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +285,7 @@ Rectangle {
|
||||
spacing: 14
|
||||
|
||||
QueueTab {
|
||||
text: QbzBridge.tr("Queue", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Queue", QbzSession.trRev)
|
||||
active: root.tab === 0
|
||||
onClicked: root.tab = 0
|
||||
}
|
||||
@@ -295,7 +297,7 @@ Rectangle {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
QueueTab {
|
||||
text: QbzBridge.tr("History", QbzBridge.trRev)
|
||||
text: QbzSession.tr("History", QbzSession.trRev)
|
||||
active: root.tab === 1
|
||||
onClicked: root.tab = 1
|
||||
}
|
||||
@@ -321,7 +323,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.toggleQueue()
|
||||
onClicked: QbzShell.toggleQueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,7 +359,7 @@ Rectangle {
|
||||
width: parent.width - 20
|
||||
spacing: 10
|
||||
Text {
|
||||
text: QbzBridge.tr("NOW PLAYING", QbzBridge.trRev)
|
||||
text: QbzSession.tr("NOW PLAYING", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -430,7 +432,7 @@ Rectangle {
|
||||
onClicked: {
|
||||
if (root.currentRow) {
|
||||
root.currentRow.isFavorite = !root.currentRow.isFavorite
|
||||
QbzBridge.queueToggleFavorite("track", root.currentRow.id)
|
||||
QbzQueue.queueToggleFavorite("track", root.currentRow.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,7 +447,7 @@ Rectangle {
|
||||
width: parent.width - 20
|
||||
spacing: 8
|
||||
Text {
|
||||
text: QbzBridge.tr("UP NEXT", QbzBridge.trRev)
|
||||
text: QbzSession.tr("UP NEXT", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -474,7 +476,7 @@ Rectangle {
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData.section === "next-in-queue"
|
||||
? QbzBridge.tr("Next in queue", QbzBridge.trRev) : QbzBridge.tr("Next up", QbzBridge.trRev)
|
||||
? QbzSession.tr("Next in queue", QbzSession.trRev) : QbzSession.tr("Next up", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -495,7 +497,7 @@ Rectangle {
|
||||
if (root.dragFrom >= 0) {
|
||||
var target = Math.max(0, Math.min(root.upcoming.length - 1,
|
||||
Math.floor((parent.mapToItem(null, mouseX, mouseY).y - queueBody.mapToItem(null, 0, 0).y - 40) / 44)))
|
||||
QbzBridge.queueMoveTrack((doc.page || 0) * 40 + root.dragFrom, (doc.page || 0) * 40 + target)
|
||||
QbzQueue.queueMoveTrack((doc.page || 0) * 40 + root.dragFrom, (doc.page || 0) * 40 + target)
|
||||
root.dragFrom = -1
|
||||
}
|
||||
}
|
||||
@@ -518,11 +520,11 @@ Rectangle {
|
||||
name: "chevron-left"
|
||||
iconSize: 15
|
||||
btnEnabled: (doc.page || 0) > 0
|
||||
onClicked: QbzBridge.queueSetPage((doc.page || 0) - 1)
|
||||
onClicked: QbzQueue.queueSetPage((doc.page || 0) - 1)
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Page {} of {}", QbzBridge.trRev).replace("{}", (doc.page || 0) + 1).replace("{}", doc.pageCount || 1)
|
||||
text: QbzSession.tr("Page {} of {}", QbzSession.trRev).replace("{}", (doc.page || 0) + 1).replace("{}", doc.pageCount || 1)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -530,7 +532,7 @@ Rectangle {
|
||||
name: "chevron-right"
|
||||
iconSize: 15
|
||||
btnEnabled: (doc.page || 0) < (doc.pageCount || 1) - 1
|
||||
onClicked: QbzBridge.queueSetPage((doc.page || 0) + 1)
|
||||
onClicked: QbzQueue.queueSetPage((doc.page || 0) + 1)
|
||||
}
|
||||
}
|
||||
Item { width: (parent.width - paginator.width) / 2; height: 1 }
|
||||
@@ -545,7 +547,7 @@ Rectangle {
|
||||
spacing: 6
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Your queue is empty", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Your queue is empty", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightMedium
|
||||
@@ -553,7 +555,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: QbzBridge.tr("Play an album or track to get started", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Play an album or track to get started", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -579,7 +581,7 @@ Rectangle {
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: QbzBridge.tr("RECENTLY PLAYED", QbzBridge.trRev)
|
||||
text: QbzSession.tr("RECENTLY PLAYED", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -599,7 +601,7 @@ Rectangle {
|
||||
visible: root.historyRows.length === 0
|
||||
width: parent.width
|
||||
topPadding: 48 - 10
|
||||
text: QbzBridge.tr("Nothing played yet", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Nothing played yet", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontBody
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
@@ -622,8 +624,8 @@ Rectangle {
|
||||
rightPadding: theme.spacingMd
|
||||
topPadding: 8
|
||||
text: (doc.upcomingTotal === 1
|
||||
? QbzBridge.tr("Showing {} of {} track.", QbzBridge.trRev)
|
||||
: QbzBridge.tr("Showing {} of {} tracks.", QbzBridge.trRev))
|
||||
? QbzSession.tr("Showing {} of {} track.", QbzSession.trRev)
|
||||
: QbzSession.tr("Showing {} of {} tracks.", QbzSession.trRev))
|
||||
.replace("{}", doc.pageEnd || 0).replace("{}", doc.upcomingTotal || 0)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
@@ -639,7 +641,7 @@ Rectangle {
|
||||
// Action 1 — Clear queue (wired).
|
||||
QbzIconButton { btnSize: 30
|
||||
name: "trash-list"
|
||||
onClicked: QbzBridge.queueClear()
|
||||
onClicked: QbzQueue.queueClear()
|
||||
}
|
||||
// Action 2 — Save as playlist (INERT: opens a picker modal
|
||||
// upstream — POC-NOTE).
|
||||
@@ -674,11 +676,11 @@ Rectangle {
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
clip: true
|
||||
onTextEdited: QbzBridge.queueSetSearch(text)
|
||||
onTextEdited: QbzQueue.queueSetSearch(text)
|
||||
Text {
|
||||
visible: parent.text === ""
|
||||
anchors.fill: parent
|
||||
text: QbzBridge.tr("Search queue", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search queue", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -2,7 +2,7 @@
|
||||
//
|
||||
// Three states (ShellState.sidebar-state): 0 = open 240px (icon + label),
|
||||
// 1 = mini 64px (icons only), 2 = closed 0px. Width animates 160ms
|
||||
// ease-in-out; the header's panel-left button cycles (QbzBridge.cycleSidebar).
|
||||
// ease-in-out; the header's panel-left button cycles (QbzShell.cycleSidebar).
|
||||
//
|
||||
// Top-level section nav rows (Discover / Library / Local Library / My QBZ)
|
||||
// replicate SidebarNavRow: 34px rows, radius 6, 16px icons, 13px/w500
|
||||
@@ -20,11 +20,12 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool mini: QbzBridge.sidebarState === 1
|
||||
property bool mini: QbzShell.sidebarState === 1
|
||||
// The current section — Discover (home) is the only real view.
|
||||
property string activeNav: "discover"
|
||||
// Playlist tree state (phase 7).
|
||||
@@ -32,7 +33,7 @@ Rectangle {
|
||||
property bool playlistsCollapsed: false
|
||||
property string activePlaylistId: ""
|
||||
// Flattened entries from the bridge + the url-keyed cover map.
|
||||
readonly property var entries: parseEntries(QbzBridge.sidebarJson)
|
||||
readonly property var entries: parseEntries(QbzShell.sidebarJson)
|
||||
property var coverMap: ({})
|
||||
function parseEntries(json) {
|
||||
var e = JSON.parse(json)
|
||||
@@ -41,7 +42,7 @@ Rectangle {
|
||||
for (var i = 0; i < e.length; i++) {
|
||||
for (var j = 0; j < e[i].covers.length; j++) urls.push(e[i].covers[j])
|
||||
}
|
||||
if (urls.length > 0) QbzBridge.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -56,12 +57,12 @@ Rectangle {
|
||||
|
||||
QbzTheme { id: theme }
|
||||
|
||||
width: QbzBridge.sidebarState === 2 ? 0
|
||||
: QbzBridge.sidebarState === 1 ? theme.sidebarMiniWidth
|
||||
width: QbzShell.sidebarState === 2 ? 0
|
||||
: QbzShell.sidebarState === 1 ? theme.sidebarMiniWidth
|
||||
: theme.sidebarOpenWidth
|
||||
// surface-card @ 0.5 while the ambient background is active (phase 14).
|
||||
color: ambientOn ? theme.surfaceCardA50 : theme.surfaceCard
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
// Square edges; clip keeps content from spilling while the width
|
||||
// animates (same as the Slint root).
|
||||
@@ -143,34 +144,34 @@ Rectangle {
|
||||
NavRow {
|
||||
navId: "discover"
|
||||
name: "compass"
|
||||
label: QbzBridge.tr("Discover", QbzBridge.trRev)
|
||||
visible: !QbzBridge.offline
|
||||
label: QbzSession.tr("Discover", QbzSession.trRev)
|
||||
visible: !QbzSession.offline
|
||||
onClicked: {
|
||||
root.activeNav = "discover"
|
||||
QbzBridge.navigateTo("home")
|
||||
QbzShell.navigateTo("home")
|
||||
}
|
||||
}
|
||||
NavRow {
|
||||
navId: "library"
|
||||
name: "music-library-2"
|
||||
label: QbzBridge.tr("Library", QbzBridge.trRev)
|
||||
visible: !QbzBridge.offline
|
||||
label: QbzSession.tr("Library", QbzSession.trRev)
|
||||
visible: !QbzSession.offline
|
||||
onClicked: {
|
||||
root.activeNav = "library"
|
||||
QbzBridge.navigateTo("library")
|
||||
QbzShell.navigateTo("library")
|
||||
}
|
||||
}
|
||||
NavRow {
|
||||
navId: "local"
|
||||
name: "hard-drive"
|
||||
label: QbzBridge.tr("Local Library", QbzBridge.trRev)
|
||||
label: QbzSession.tr("Local Library", QbzSession.trRev)
|
||||
onClicked: root.activeNav = "local"
|
||||
}
|
||||
NavRow {
|
||||
navId: "myqbz"
|
||||
name: "qbz-symbolic"
|
||||
// Slint: MyQbzBrandingState.label, default "My QBZ".
|
||||
label: QbzBridge.tr("My QBZ", QbzBridge.trRev)
|
||||
label: QbzSession.tr("My QBZ", QbzSession.trRev)
|
||||
onClicked: root.activeNav = "myqbz"
|
||||
}
|
||||
}
|
||||
@@ -193,7 +194,7 @@ Rectangle {
|
||||
visible: !root.searchOpen
|
||||
width: parent.width - 4 * 26
|
||||
height: parent.height
|
||||
text: QbzBridge.tr("PLAYLISTS", QbzBridge.trRev)
|
||||
text: QbzSession.tr("PLAYLISTS", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 10
|
||||
font.letterSpacing: 1
|
||||
@@ -216,11 +217,11 @@ Rectangle {
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
clip: true
|
||||
onTextEdited: QbzBridge.sidebarSearch(text)
|
||||
onTextEdited: QbzShell.sidebarSearch(text)
|
||||
Text {
|
||||
visible: searchEdit.text === ""
|
||||
anchors.fill: parent
|
||||
text: QbzBridge.tr("Search playlists", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search playlists", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -250,7 +251,7 @@ Rectangle {
|
||||
root.searchOpen = !root.searchOpen
|
||||
if (!root.searchOpen) {
|
||||
searchEdit.text = ""
|
||||
QbzBridge.sidebarSearch("")
|
||||
QbzShell.sidebarSearch("")
|
||||
} else {
|
||||
searchEdit.forceActiveFocus()
|
||||
}
|
||||
@@ -277,7 +278,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.createPlaylist()
|
||||
onClicked: QbzShell.createPlaylist()
|
||||
}
|
||||
}
|
||||
// Sort / more (...) menu.
|
||||
@@ -324,7 +325,7 @@ Rectangle {
|
||||
spacing: 6
|
||||
QbzIcon { name: "arrow-up-down"; width: 13; height: 13; tintName: "muted" }
|
||||
Text {
|
||||
text: QbzBridge.tr("Sort by", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Sort by", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -332,11 +333,11 @@ Rectangle {
|
||||
}
|
||||
Repeater {
|
||||
model: [
|
||||
{ "opt": "name", "label": QbzBridge.tr("Name (A-Z)", QbzBridge.trRev) },
|
||||
{ "opt": "recent", "label": QbzBridge.tr("Recent", QbzBridge.trRev) },
|
||||
{ "opt": "tracks", "label": QbzBridge.tr("# of tracks", QbzBridge.trRev) },
|
||||
{ "opt": "playcount", "label": QbzBridge.tr("Play Count", QbzBridge.trRev) },
|
||||
{ "opt": "custom", "label": QbzBridge.tr("Custom", QbzBridge.trRev) },
|
||||
{ "opt": "name", "label": QbzSession.tr("Name (A-Z)", QbzSession.trRev) },
|
||||
{ "opt": "recent", "label": QbzSession.tr("Recent", QbzSession.trRev) },
|
||||
{ "opt": "tracks", "label": QbzSession.tr("# of tracks", QbzSession.trRev) },
|
||||
{ "opt": "playcount", "label": QbzSession.tr("Play Count", QbzSession.trRev) },
|
||||
{ "opt": "custom", "label": QbzSession.tr("Custom", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -355,12 +356,12 @@ Rectangle {
|
||||
text: modelData.label
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
font.weight: QbzBridge.sidebarSortBy === modelData.opt ? theme.weightSemibold : theme.weightRegular
|
||||
font.weight: QbzShell.sidebarSortBy === modelData.opt ? theme.weightSemibold : theme.weightRegular
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
QbzIcon {
|
||||
visible: QbzBridge.sidebarSortBy === modelData.opt
|
||||
name: QbzBridge.sidebarSortAsc ? "chevron-up" : "chevron-down"
|
||||
visible: QbzShell.sidebarSortBy === modelData.opt
|
||||
name: QbzShell.sidebarSortAsc ? "chevron-up" : "chevron-down"
|
||||
width: 13
|
||||
height: 13
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -373,7 +374,7 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
QbzBridge.sidebarSetSort(modelData.opt)
|
||||
QbzShell.sidebarSetSort(modelData.opt)
|
||||
sortMenu.close()
|
||||
}
|
||||
}
|
||||
@@ -385,9 +386,9 @@ Rectangle {
|
||||
// of scope).
|
||||
Repeater {
|
||||
model: [
|
||||
{ "icon": "folder-plus", "label": QbzBridge.tr("New folder", QbzBridge.trRev) },
|
||||
{ "icon": "library-big", "label": QbzBridge.tr("Manage playlists", QbzBridge.trRev) },
|
||||
{ "icon": "import", "label": QbzBridge.tr("Import", QbzBridge.trRev) },
|
||||
{ "icon": "folder-plus", "label": QbzSession.tr("New folder", QbzSession.trRev) },
|
||||
{ "icon": "library-big", "label": QbzSession.tr("Manage playlists", QbzSession.trRev) },
|
||||
{ "icon": "import", "label": QbzSession.tr("Import", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -472,19 +473,19 @@ Rectangle {
|
||||
// window-coord pointer and claims/releases it.
|
||||
property bool dropHot: false
|
||||
function recomputeDrop() {
|
||||
if (isFolder || !QbzBridge.dragActive) {
|
||||
if (dropHot) { QbzBridge.dragSetOver(""); dropHot = false }
|
||||
if (isFolder || !QbzShell.dragActive) {
|
||||
if (dropHot) { QbzShell.dragSetOver(""); dropHot = false }
|
||||
return
|
||||
}
|
||||
const tl = mapToItem(null, 0, 0)
|
||||
const hot = QbzBridge.dragX >= tl.x && QbzBridge.dragX <= tl.x + width
|
||||
&& QbzBridge.dragY >= tl.y && QbzBridge.dragY <= tl.y + height
|
||||
if (hot && !dropHot) QbzBridge.dragSetOver(modelData.id)
|
||||
else if (!hot && dropHot) QbzBridge.dragSetOver("")
|
||||
const hot = QbzShell.dragX >= tl.x && QbzShell.dragX <= tl.x + width
|
||||
&& QbzShell.dragY >= tl.y && QbzShell.dragY <= tl.y + height
|
||||
if (hot && !dropHot) QbzShell.dragSetOver(modelData.id)
|
||||
else if (!hot && dropHot) QbzShell.dragSetOver("")
|
||||
dropHot = hot
|
||||
}
|
||||
Connections {
|
||||
target: QbzBridge
|
||||
target: QbzShell
|
||||
function onDragXChanged() { recomputeDrop() }
|
||||
function onDragYChanged() { recomputeDrop() }
|
||||
function onDragActiveChanged() { recomputeDrop() }
|
||||
@@ -604,7 +605,7 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (isFolder) {
|
||||
QbzBridge.sidebarToggleFolder(modelData.id)
|
||||
QbzShell.sidebarToggleFolder(modelData.id)
|
||||
} else {
|
||||
root.activePlaylistId = modelData.id
|
||||
QbzBridge.openPlaylist(modelData.id)
|
||||
@@ -616,9 +617,9 @@ Rectangle {
|
||||
|
||||
// Empty state.
|
||||
Text {
|
||||
visible: !root.mini && root.entries.length === 0 && !QbzBridge.offline
|
||||
visible: !root.mini && root.entries.length === 0 && !QbzSession.offline
|
||||
width: plColumn.width
|
||||
text: QbzBridge.tr("No playlists yet.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("No playlists yet.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
@@ -7,6 +7,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
QbzContextMenu {
|
||||
id: root
|
||||
@@ -14,10 +16,10 @@ QbzContextMenu {
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("New", QbzBridge.trRev), "icon": "panel-left", "mode": 0 },
|
||||
{ "label": QbzBridge.tr("Classic", QbzBridge.trRev), "icon": "panel-right-close", "mode": 1 },
|
||||
{ "label": QbzBridge.tr("Small", QbzBridge.trRev), "icon": "rows-3", "mode": 2 },
|
||||
{ "label": QbzBridge.tr("Large", QbzBridge.trRev), "icon": "layout-grid", "mode": 3 },
|
||||
{ "label": QbzSession.tr("New", QbzSession.trRev), "icon": "panel-left", "mode": 0 },
|
||||
{ "label": QbzSession.tr("Classic", QbzSession.trRev), "icon": "panel-right-close", "mode": 1 },
|
||||
{ "label": QbzSession.tr("Small", QbzSession.trRev), "icon": "rows-3", "mode": 2 },
|
||||
{ "label": QbzSession.tr("Large", QbzSession.trRev), "icon": "layout-grid", "mode": 3 },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -46,7 +48,7 @@ QbzContextMenu {
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QbzIcon {
|
||||
visible: QbzBridge.npbMode === modelData.mode
|
||||
visible: QbzShell.npbMode === modelData.mode
|
||||
name: "check"
|
||||
width: 13
|
||||
height: 13
|
||||
@@ -61,7 +63,7 @@ QbzContextMenu {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.close()
|
||||
QbzBridge.npbSetMode(modelData.mode)
|
||||
QbzShell.npbSetMode(modelData.mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,9 +84,9 @@ QbzContextMenu {
|
||||
}
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Miniplayer", QbzBridge.trRev), "icon": "picture-in-picture-2" },
|
||||
{ "label": QbzBridge.tr("Immersive", QbzBridge.trRev), "icon": "maximize-2" },
|
||||
{ "label": QbzBridge.tr("Kiosk mode", QbzBridge.trRev), "icon": "hard-drive" },
|
||||
{ "label": QbzSession.tr("Miniplayer", QbzSession.trRev), "icon": "picture-in-picture-2" },
|
||||
{ "label": QbzSession.tr("Immersive", QbzSession.trRev), "icon": "maximize-2" },
|
||||
{ "label": QbzSession.tr("Kiosk mode", QbzSession.trRev), "icon": "hard-drive" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -21,7 +21,10 @@ Image {
|
||||
// "black" (for glyphs on white discs).
|
||||
property string tintName: "secondary"
|
||||
|
||||
source: "assets/icons/" + tintName + "/" + name + ".svg"
|
||||
// Absolute qrc URL: relative URLs in a shared component resolve
|
||||
// against the CONSUMER's document depth (phase-23 dir layout — root
|
||||
// consumers underflowed one level).
|
||||
source: name === "" ? "" : "qrc:/qt/qml/com/blitzfc/qbz/qml/assets/icons/" + tintName + "/" + name + ".svg"
|
||||
sourceSize: Qt.size(Math.max(1, Math.round(width * 2)), Math.max(1, Math.round(height * 2)))
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: false
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,5 +1,5 @@
|
||||
// Shared theme tokens (phase 19: LIVE) — the color tokens now bind to
|
||||
// QbzBridge.themeJson, the serialized qbz-theme ThemeColors for the
|
||||
// QbzShell.themeJson, the serialized qbz-theme ThemeColors for the
|
||||
// persisted ui_prefs `theme` slug (theme_qt.rs — the Qt equivalent of the
|
||||
// Slint theme::push_colors: switching the theme republishes the document
|
||||
// and every consumer repaints, no restart). Until the first publish lands
|
||||
@@ -40,9 +40,9 @@ QtObject {
|
||||
})
|
||||
|
||||
readonly property var _doc: {
|
||||
if (QbzBridge.themeJson === "") return _fallback
|
||||
if (QbzShell.themeJson === "") return _fallback
|
||||
try {
|
||||
return JSON.parse(QbzBridge.themeJson)
|
||||
return JSON.parse(QbzShell.themeJson)
|
||||
} catch (e) {
|
||||
return _fallback
|
||||
}
|
||||
File renamed without changes.
@@ -15,13 +15,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../rows"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// Transparent while the ambient background is active (phase 14 —
|
||||
// HomeView.slint:163: the frosted content panel shows through).
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
// Round to the AppShell content-frame bezel (Radius.md): QML clips
|
||||
// are rectangular, so the frame's own rounding never reaches the
|
||||
@@ -83,7 +86,7 @@ Rectangle {
|
||||
for (var i = 0; i < more.length; i++) if (more[i].artUrl) urls.push(more[i].artUrl)
|
||||
var sug = album.suggestions || []
|
||||
for (i = 0; i < sug.length; i++) if (sug[i].artUrl) urls.push(sug[i].artUrl)
|
||||
if (urls.length > 0) QbzBridge.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
}
|
||||
|
||||
// Ghost CircleAction (secondary, on-surface variant): elevated disc,
|
||||
@@ -295,7 +298,7 @@ Rectangle {
|
||||
Item { visible: (header.description || "") !== (header.descriptionShort || ""); width: 1; height: 4 }
|
||||
Text {
|
||||
visible: (header.description || "") !== (header.descriptionShort || "")
|
||||
text: QbzBridge.tr("Read more", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Read more", QbzSession.trRev)
|
||||
color: readMoreArea.containsMouse ? theme.accentHover : theme.accent
|
||||
font.pixelSize: theme.fontLegal
|
||||
MouseArea {
|
||||
@@ -306,7 +309,7 @@ Rectangle {
|
||||
onClicked: {
|
||||
var shell = root.parent
|
||||
while (shell && shell.openTextModal === undefined) shell = shell.parent
|
||||
if (shell) shell.openTextModal(QbzBridge.tr("About this album", QbzBridge.trRev), header.description || "")
|
||||
if (shell) shell.openTextModal(QbzSession.tr("About this album", QbzSession.trRev), header.description || "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,13 +336,13 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playAlbum(header.id)
|
||||
onClicked: QbzPlayer.playAlbum(header.id)
|
||||
}
|
||||
}
|
||||
QbzCircleAction {
|
||||
name: "shuffle"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: QbzBridge.playAlbumShuffled(header.id)
|
||||
onClicked: QbzPlayer.playAlbumShuffled(header.id)
|
||||
}
|
||||
QbzCircleAction {
|
||||
name: header.isFavorite ? "heart-filled" : "heart"
|
||||
@@ -390,7 +393,7 @@ Rectangle {
|
||||
QbzSpinner { size: 36; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("Loading album…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Loading album…", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -409,7 +412,7 @@ Rectangle {
|
||||
spacing: 7
|
||||
Image {
|
||||
visible: header.qualityTier === "hires"
|
||||
source: "assets/hi-res.svg"
|
||||
source: "../assets/hi-res.svg"
|
||||
width: 42
|
||||
height: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -456,7 +459,7 @@ Rectangle {
|
||||
Text {
|
||||
visible: parent.text === ""
|
||||
anchors.fill: parent
|
||||
text: QbzBridge.tr("Search tracks...", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search tracks...", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -498,9 +501,9 @@ Rectangle {
|
||||
rightPadding: 12
|
||||
spacing: 16
|
||||
Text { text: "#"; width: 32; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: QbzBridge.tr("Title", QbzBridge.trRev); width: parent.width - 32 - 80 - 80 - 28 - 28 - 32 - 5 * 16 - 24; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight }
|
||||
Text { text: QbzBridge.tr("Duration", QbzBridge.trRev); width: 80; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; horizontalAlignment: Text.AlignHCenter; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: QbzBridge.tr("Quality", QbzBridge.trRev); width: 80; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; horizontalAlignment: Text.AlignHCenter; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: QbzSession.tr("Title", QbzSession.trRev); width: parent.width - 32 - 80 - 80 - 28 - 28 - 32 - 5 * 16 - 24; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; anchors.verticalCenter: parent.verticalCenter; elide: Text.ElideRight }
|
||||
Text { text: QbzSession.tr("Duration", QbzSession.trRev); width: 80; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; horizontalAlignment: Text.AlignHCenter; anchors.verticalCenter: parent.verticalCenter }
|
||||
Text { text: QbzSession.tr("Quality", QbzSession.trRev); width: 80; color: theme.textMuted; font.pixelSize: 13; font.letterSpacing: 0.5; horizontalAlignment: Text.AlignHCenter; anchors.verticalCenter: parent.verticalCenter }
|
||||
Item { width: 28; height: 1; QbzIcon { name: "heart"; width: 14; height: 14; anchors.centerIn: parent; tintName: "muted" } }
|
||||
Item { width: 28; height: 1; QbzIcon { name: "cloud-download"; width: 14; height: 14; anchors.centerIn: parent; tintName: "muted" } }
|
||||
Item { width: 32; height: 1 }
|
||||
@@ -524,7 +527,7 @@ Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Disc", QbzBridge.trRev) + " " + hdr.disc
|
||||
text: QbzSession.tr("Disc", QbzSession.trRev) + " " + hdr.disc
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -586,9 +589,9 @@ Rectangle {
|
||||
downloadGlyph: true
|
||||
menuShowLater: false
|
||||
menuShowGoTo: false
|
||||
onPlayRequested: QbzBridge.playAlbumFrom(header.id, item.id)
|
||||
onPlayRequested: QbzPlayer.playAlbumFrom(header.id, item.id)
|
||||
onEnqueueRequested: function (m) {
|
||||
QbzBridge.enqueueAlbumTrack(header.id, item.id, m === "next" ? "next" : "later")
|
||||
QbzPlayer.enqueueAlbumTrack(header.id, item.id, m === "next" ? "next" : "later")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -605,7 +608,7 @@ Rectangle {
|
||||
visible: (header.label || "") !== ""
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
SidebarHeading { text: QbzBridge.tr("LABEL", QbzBridge.trRev) }
|
||||
SidebarHeading { text: QbzSession.tr("LABEL", QbzSession.trRev) }
|
||||
SidebarCard {
|
||||
name: header.label || ""
|
||||
iconName: "disc"
|
||||
@@ -618,7 +621,7 @@ Rectangle {
|
||||
visible: awards.length > 0
|
||||
width: parent.width
|
||||
spacing: 8
|
||||
SidebarHeading { text: QbzBridge.tr("AWARDS", QbzBridge.trRev) }
|
||||
SidebarHeading { text: QbzSession.tr("AWARDS", QbzSession.trRev) }
|
||||
Repeater {
|
||||
model: awards
|
||||
delegate: SidebarCard {
|
||||
@@ -638,14 +641,14 @@ Rectangle {
|
||||
Item { visible: (album.moreFromArtist || []).length > 0; width: 1; height: 40 }
|
||||
SectionRail {
|
||||
visible: (album.moreFromArtist || []).length > 0
|
||||
title: QbzBridge.tr("From the same artist", QbzBridge.trRev)
|
||||
title: QbzSession.tr("From the same artist", QbzSession.trRev)
|
||||
items: album.moreFromArtist || []
|
||||
coverMap: root.coverMap
|
||||
}
|
||||
Item { visible: (album.suggestions || []).length > 0; width: 1; height: 40 }
|
||||
SectionRail {
|
||||
visible: (album.suggestions || []).length > 0
|
||||
title: QbzBridge.tr("Listening suggestions", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Listening suggestions", QbzSession.trRev)
|
||||
items: album.suggestions || []
|
||||
coverMap: root.coverMap
|
||||
}
|
||||
@@ -667,11 +670,11 @@ Rectangle {
|
||||
menuWidth: 196
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-plus", "action": "next" },
|
||||
{ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": header.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev), "icon": header.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
{ "label": header.isPinned ? QbzBridge.tr("Unpin", QbzBridge.trRev) : QbzBridge.tr("Pin", QbzBridge.trRev), "icon": header.isPinned ? "pin-filled" : "pin", "action": "pin" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-plus", "action": "next" },
|
||||
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": header.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev), "icon": header.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
{ "label": header.isPinned ? QbzSession.tr("Unpin", QbzSession.trRev) : QbzSession.tr("Pin", QbzSession.trRev), "icon": header.isPinned ? "pin-filled" : "pin", "action": "pin" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -702,9 +705,9 @@ Rectangle {
|
||||
onClicked: {
|
||||
albumMenu.close()
|
||||
var a = modelData.action
|
||||
if (a === "play") QbzBridge.playAlbum(header.id)
|
||||
else if (a === "next") QbzBridge.enqueueAlbum(header.id, "next")
|
||||
else if (a === "queue") QbzBridge.enqueueAlbum(header.id, "later")
|
||||
if (a === "play") QbzPlayer.playAlbum(header.id)
|
||||
else if (a === "next") QbzPlayer.enqueueAlbum(header.id, "next")
|
||||
else if (a === "queue") QbzPlayer.enqueueAlbum(header.id, "later")
|
||||
else if (a === "favorite") {
|
||||
header.isFavorite = !header.isFavorite
|
||||
QbzBridge.libraryToggleFavorite("album", header.id)
|
||||
@@ -17,13 +17,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../cards"
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// Transparent while the ambient background is active (phase 14 —
|
||||
// HomeView.slint:163: the frosted content panel shows through).
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
// Round to the AppShell content-frame bezel (Radius.md): QML clips
|
||||
// are rectangular, so the frame's own rounding never reaches the
|
||||
@@ -53,13 +56,13 @@ Rectangle {
|
||||
// JUMP TO tabs from the present sections (ArtistState.jump-tabs).
|
||||
readonly property var jumpTabs: {
|
||||
var tabs = []
|
||||
if ((artist.bio || "") !== "") tabs.push({ "id": "about", "label": QbzBridge.tr("About", QbzBridge.trRev) })
|
||||
if (topTracks.length > 0) tabs.push({ "id": "popular-tracks", "label": QbzBridge.tr("Popular Tracks", QbzBridge.trRev) })
|
||||
if ((artist.bio || "") !== "") tabs.push({ "id": "about", "label": QbzSession.tr("About", QbzSession.trRev) })
|
||||
if (topTracks.length > 0) tabs.push({ "id": "popular-tracks", "label": QbzSession.tr("Popular Tracks", QbzSession.trRev) })
|
||||
for (var i = 0; i < releaseSections.length; i++) {
|
||||
if (releaseSections[i].releaseType !== "other")
|
||||
tabs.push({ "id": releaseSections[i].releaseType, "label": releaseSections[i].title })
|
||||
}
|
||||
if (appearsOn.length > 0) tabs.push({ "id": "appears-on", "label": QbzBridge.tr("Appears On", QbzBridge.trRev) })
|
||||
if (appearsOn.length > 0) tabs.push({ "id": "appears-on", "label": QbzSession.tr("Appears On", QbzSession.trRev) })
|
||||
return tabs
|
||||
}
|
||||
|
||||
@@ -97,7 +100,7 @@ Rectangle {
|
||||
var urls = []
|
||||
var items = libraryTab.libItems || []
|
||||
for (var i = 0; i < items.length; i++) if (items[i].imageUrl) urls.push(items[i].imageUrl)
|
||||
if (urls.length > 0) QbzBridge.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
}
|
||||
|
||||
function dispatchCovers() {
|
||||
@@ -108,7 +111,7 @@ Rectangle {
|
||||
for (i = 0; i < releaseSections.length; i++)
|
||||
for (j = 0; j < releaseSections[i].cards.length; j++)
|
||||
if (releaseSections[i].cards[j].artUrl) urls.push(releaseSections[i].cards[j].artUrl)
|
||||
if (urls.length > 0) QbzBridge.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
if (urls.length > 0) QbzShell.sidebarArtworkWindow(JSON.stringify(urls))
|
||||
}
|
||||
|
||||
function scrollToSection(id) {
|
||||
@@ -130,7 +133,7 @@ Rectangle {
|
||||
property int rowIndex: 0
|
||||
property bool showAlbum: true
|
||||
|
||||
readonly property bool isActive: QbzBridge.npTrackId !== "" && QbzBridge.npTrackId === row.id
|
||||
readonly property bool isActive: QbzPlayer.npTrackId !== "" && QbzPlayer.npTrackId === row.id
|
||||
readonly property bool hovered: trArea.containsMouse || favArea.containsMouse || moreArea.containsMouse
|
||||
|
||||
width: parent ? parent.width : 0
|
||||
@@ -195,7 +198,7 @@ Rectangle {
|
||||
QbzIcon {
|
||||
visible: trArea.containsMouse || isActive
|
||||
anchors.centerIn: parent
|
||||
name: isActive && QbzBridge.npPlaying ? "pause" : "play-fill"
|
||||
name: isActive && QbzPlayer.npPlaying ? "pause" : "play-fill"
|
||||
width: 16
|
||||
height: 16
|
||||
tintName: "primary"
|
||||
@@ -203,7 +206,7 @@ Rectangle {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playArtistTrack(row.id)
|
||||
onClicked: QbzPlayer.playArtistTrack(row.id)
|
||||
}
|
||||
}
|
||||
// Title + artist.
|
||||
@@ -338,7 +341,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onDoubleClicked: QbzBridge.playArtistTrack(row.id)
|
||||
onDoubleClicked: QbzPlayer.playArtistTrack(row.id)
|
||||
onClicked: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
@@ -405,7 +408,7 @@ Rectangle {
|
||||
id: seeAll
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 28
|
||||
text: QbzBridge.tr("See discography", QbzBridge.trRev)
|
||||
text: QbzSession.tr("See discography", QbzSession.trRev)
|
||||
color: seeAllArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -432,7 +435,7 @@ Rectangle {
|
||||
spacing: 6
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Newest", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Newest", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -481,7 +484,7 @@ Rectangle {
|
||||
Text {
|
||||
id: loadMoreText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Load more", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Load more", QbzSession.trRev)
|
||||
color: loadMoreArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -566,7 +569,7 @@ Rectangle {
|
||||
Item { visible: artist.bioTruncated === true; width: 1; height: 4 }
|
||||
Text {
|
||||
visible: artist.bioTruncated === true
|
||||
text: QbzBridge.tr("Read more", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Read more", QbzSession.trRev)
|
||||
color: readMoreArea.containsMouse ? theme.accentHover : theme.accent
|
||||
font.pixelSize: theme.fontLegal
|
||||
MouseArea {
|
||||
@@ -630,8 +633,8 @@ Rectangle {
|
||||
spacing: 4
|
||||
Repeater {
|
||||
model: [
|
||||
{ "id": "catalog", "label": QbzBridge.tr("From catalog", QbzBridge.trRev), "count": 0 },
|
||||
{ "id": "library", "label": QbzBridge.tr("In library", QbzBridge.trRev), "count": artist.libraryCount || 0 },
|
||||
{ "id": "catalog", "label": QbzSession.tr("From catalog", QbzSession.trRev), "count": 0 },
|
||||
{ "id": "library", "label": QbzSession.tr("In library", QbzSession.trRev), "count": artist.libraryCount || 0 },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -738,7 +741,7 @@ Rectangle {
|
||||
QbzSpinner { size: 36; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: QbzBridge.tr("Loading artist…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Loading artist…", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -761,7 +764,7 @@ Rectangle {
|
||||
Text {
|
||||
width: parent.width - 44 - 32 - 32 - 3 * 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Popular Tracks", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Popular Tracks", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -778,7 +781,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playArtistTop(false)
|
||||
onClicked: QbzPlayer.playArtistTop(false)
|
||||
}
|
||||
}
|
||||
QbzCircleAction {
|
||||
@@ -813,7 +816,7 @@ Rectangle {
|
||||
color: "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.topTracksExpanded ? QbzBridge.tr("View less", QbzBridge.trRev) : QbzBridge.tr("Load more", QbzBridge.trRev)
|
||||
text: root.topTracksExpanded ? QbzSession.tr("View less", QbzSession.trRev) : QbzSession.tr("Load more", QbzSession.trRev)
|
||||
color: loadMoreTopArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
MouseArea {
|
||||
@@ -834,7 +837,7 @@ Rectangle {
|
||||
spacing: 12
|
||||
Item { width: 1; height: 32 }
|
||||
Text {
|
||||
text: QbzBridge.tr("Latest release", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Latest release", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -873,7 +876,7 @@ Rectangle {
|
||||
spacing: 0
|
||||
Item { width: 1; height: 32 }
|
||||
Text {
|
||||
text: QbzBridge.tr("Appears On", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Appears On", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -897,7 +900,7 @@ Rectangle {
|
||||
color: "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.appearsOnExpanded ? QbzBridge.tr("View less", QbzBridge.trRev) : QbzBridge.tr("Load more", QbzBridge.trRev)
|
||||
text: root.appearsOnExpanded ? QbzSession.tr("View less", QbzSession.trRev) : QbzSession.tr("Load more", QbzSession.trRev)
|
||||
color: loadMoreAppArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
MouseArea {
|
||||
@@ -918,7 +921,7 @@ Rectangle {
|
||||
spacing: 12
|
||||
Item { width: 1; height: 32 }
|
||||
Text {
|
||||
text: QbzBridge.tr("Playlists", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Playlists", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -998,7 +1001,7 @@ Rectangle {
|
||||
Text {
|
||||
id: otherToggle
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.otherExpanded ? QbzBridge.tr("Hide", QbzBridge.trRev) : QbzBridge.tr("Show", QbzBridge.trRev)
|
||||
text: root.otherExpanded ? QbzSession.tr("Hide", QbzSession.trRev) : QbzSession.tr("Show", QbzSession.trRev)
|
||||
color: otherToggleArea.containsMouse ? theme.textPrimary : theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
MouseArea {
|
||||
@@ -1056,7 +1059,7 @@ Rectangle {
|
||||
|
||||
Text {
|
||||
visible: libTracks.length > 0
|
||||
text: QbzBridge.tr("Tracks", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Tracks", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -1080,7 +1083,7 @@ Rectangle {
|
||||
Item { visible: libraryTab.libAlbums.length > 0; width: 1; height: 24 }
|
||||
Text {
|
||||
visible: libraryTab.libAlbums.length > 0
|
||||
text: QbzBridge.tr("Albums", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Albums", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -1151,8 +1154,8 @@ Rectangle {
|
||||
spacing: 14
|
||||
Repeater {
|
||||
model: [
|
||||
{ "id": "network", "label": QbzBridge.tr("Network", QbzBridge.trRev) },
|
||||
{ "id": "magazine", "label": QbzBridge.tr("Magazine", QbzBridge.trRev) },
|
||||
{ "id": "network", "label": QbzSession.tr("Network", QbzSession.trRev) },
|
||||
{ "id": "magazine", "label": QbzSession.tr("Magazine", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Column {
|
||||
required property var modelData
|
||||
@@ -1231,7 +1234,7 @@ Rectangle {
|
||||
bottomPadding: 6
|
||||
spacing: 4
|
||||
Text {
|
||||
text: QbzBridge.tr("LABELS", QbzBridge.trRev)
|
||||
text: QbzSession.tr("LABELS", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -1239,7 +1242,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
visible: labels.length === 0
|
||||
text: QbzBridge.tr("No label info", QbzBridge.trRev)
|
||||
text: QbzSession.tr("No label info", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -1262,7 +1265,7 @@ Rectangle {
|
||||
bottomPadding: 6
|
||||
spacing: 4
|
||||
Text {
|
||||
text: QbzBridge.tr("SIMILAR ARTISTS", QbzBridge.trRev)
|
||||
text: QbzSession.tr("SIMILAR ARTISTS", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -1270,7 +1273,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
visible: similarArtists.length === 0 && QbzBridge.artistLoading
|
||||
text: QbzBridge.tr("Loading…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Loading…", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -1310,7 +1313,7 @@ Rectangle {
|
||||
id: radioPopup
|
||||
menuWidth: 180
|
||||
Repeater {
|
||||
model: [QbzBridge.tr("QBZ Radio", QbzBridge.trRev), QbzBridge.tr("Qobuz Radio", QbzBridge.trRev)]
|
||||
model: [QbzSession.tr("QBZ Radio", QbzSession.trRev), QbzSession.tr("Qobuz Radio", QbzSession.trRev)]
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
width: parent ? parent.width : 0
|
||||
@@ -1347,10 +1350,10 @@ Rectangle {
|
||||
menuWidth: 224
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Create Artist Collection", QbzBridge.trRev), "icon": "library-big", "action": "stub" },
|
||||
{ "label": QbzBridge.tr("Artist Scene", QbzBridge.trRev), "icon": "map-pin", "action": "stub" },
|
||||
{ "label": QbzBridge.tr("Share", QbzBridge.trRev), "icon": "link", "action": "stub" },
|
||||
{ "label": artist.isPinned ? QbzBridge.tr("Unpin", QbzBridge.trRev) : QbzBridge.tr("Pin", QbzBridge.trRev), "icon": artist.isPinned ? "pin-filled" : "pin", "action": "pin" },
|
||||
{ "label": QbzSession.tr("Create Artist Collection", QbzSession.trRev), "icon": "library-big", "action": "stub" },
|
||||
{ "label": QbzSession.tr("Artist Scene", QbzSession.trRev), "icon": "map-pin", "action": "stub" },
|
||||
{ "label": QbzSession.tr("Share", QbzSession.trRev), "icon": "link", "action": "stub" },
|
||||
{ "label": artist.isPinned ? QbzSession.tr("Unpin", QbzSession.trRev) : QbzSession.tr("Pin", QbzSession.trRev), "icon": artist.isPinned ? "pin-filled" : "pin", "action": "pin" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -1401,7 +1404,7 @@ Rectangle {
|
||||
QbzIcon { name: "blind-eye"; width: 15; height: 15; anchors.verticalCenter: parent.verticalCenter; tintName: "secondary" }
|
||||
Text {
|
||||
height: parent.height
|
||||
text: QbzBridge.tr("Blacklist artist", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Blacklist artist", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -1423,10 +1426,10 @@ Rectangle {
|
||||
menuWidth: 224
|
||||
Repeater {
|
||||
model: [
|
||||
{ "label": QbzBridge.tr("Play all next", QbzBridge.trRev), "icon": "list-start", "action": "next-all" },
|
||||
{ "label": QbzBridge.tr("Add all to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue-all" },
|
||||
{ "label": QbzBridge.tr("Shuffle all", QbzBridge.trRev), "icon": "shuffle", "action": "shuffle-all" },
|
||||
{ "label": QbzBridge.tr("Add all to playlist", QbzBridge.trRev), "icon": "list-music", "action": "playlist-all" },
|
||||
{ "label": QbzSession.tr("Play all next", QbzSession.trRev), "icon": "list-start", "action": "next-all" },
|
||||
{ "label": QbzSession.tr("Add all to queue", QbzSession.trRev), "icon": "list-end", "action": "queue-all" },
|
||||
{ "label": QbzSession.tr("Shuffle all", QbzSession.trRev), "icon": "shuffle", "action": "shuffle-all" },
|
||||
{ "label": QbzSession.tr("Add all to playlist", QbzSession.trRev), "icon": "list-music", "action": "playlist-all" },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -1457,9 +1460,9 @@ Rectangle {
|
||||
onClicked: {
|
||||
topMenu.close()
|
||||
var a = modelData.action
|
||||
if (a === "shuffle-all") QbzBridge.playArtistTop(true)
|
||||
else if (a === "next-all") QbzBridge.playArtistTop(false)
|
||||
else if (a === "queue-all") QbzBridge.enqueueArtistTop()
|
||||
if (a === "shuffle-all") QbzPlayer.playArtistTop(true)
|
||||
else if (a === "next-all") QbzPlayer.playArtistTop(false)
|
||||
else if (a === "queue-all") QbzPlayer.enqueueArtistTop()
|
||||
// playlist-all: inert (no picker) — POC-NOTE.
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// (+ Carousel / AlbumCard / SlimCarousel / SlimCard / ArtistCarousel /
|
||||
// PlaylistCarousel / HomeSkeleton / OfflinePlaceholder).
|
||||
//
|
||||
// Data: QbzBridge.homeSectionsJson (one JSON document — see bridge.rs),
|
||||
// Data: QbzHome.homeSectionsJson (one JSON document — see bridge.rs),
|
||||
// published by src/home_qt.rs; artwork file:// paths resolve through the
|
||||
// qbz-cache image cache. Section kinds: "album" | "playlist" | "slim" |
|
||||
// "artists" | "pinned" | "recentPlaceholder". Rail ORDER + VISIBILITY
|
||||
@@ -25,13 +25,16 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../cards"
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// Transparent while the ambient background is active (phase 14 —
|
||||
// HomeView.slint:163: the frosted content panel shows through).
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
// Round to the AppShell content-frame bezel (Radius.md): QML clips
|
||||
// are rectangular, so the frame's own rounding never reaches the
|
||||
@@ -42,9 +45,9 @@ Rectangle {
|
||||
|
||||
// Reparsed whenever Rust republishes the JSON documents (one per
|
||||
// Discover tab — phase 13).
|
||||
readonly property var sections: JSON.parse(QbzBridge.homeSectionsJson)
|
||||
readonly property var editorSections: JSON.parse(QbzBridge.editorSectionsJson)
|
||||
readonly property var forYouSections: JSON.parse(QbzBridge.forYouSectionsJson)
|
||||
readonly property var sections: JSON.parse(QbzHome.homeSectionsJson)
|
||||
readonly property var editorSections: JSON.parse(QbzHome.editorSectionsJson)
|
||||
readonly property var forYouSections: JSON.parse(QbzHome.forYouSectionsJson)
|
||||
property string activeTab: "home"
|
||||
|
||||
// ============================ shared components =======================
|
||||
@@ -331,22 +334,22 @@ Rectangle {
|
||||
spacing: 32
|
||||
MixTile {
|
||||
badge: "qobuz"; mixName: "DailyQ"
|
||||
desc: QbzBridge.tr("Elevate your day with a customized selection of music.", QbzBridge.trRev)
|
||||
desc: QbzSession.tr("Elevate your day with a customized selection of music.", QbzSession.trRev)
|
||||
c0: "#1e3a8a"; c1: "#6366f1"; c2: "#c084fc"
|
||||
}
|
||||
MixTile {
|
||||
badge: "qobuz"; mixName: "WeeklyQ"
|
||||
desc: QbzBridge.tr("Take a weekly journey with a fresh mix every Friday.", QbzBridge.trRev)
|
||||
desc: QbzSession.tr("Take a weekly journey with a fresh mix every Friday.", QbzSession.trRev)
|
||||
c0: "#065f46"; c1: "#10b981"; c2: "#fbbf24"
|
||||
}
|
||||
MixTile {
|
||||
badge: "qbz"; mixName: "FavQ"
|
||||
desc: QbzBridge.tr("A fresh shuffle from your personal library.", QbzBridge.trRev)
|
||||
desc: QbzSession.tr("A fresh shuffle from your personal library.", QbzSession.trRev)
|
||||
c0: "#7f1d1d"; c1: "#ef4444"; c2: "#fb923c"
|
||||
}
|
||||
MixTile {
|
||||
badge: "qbz"; mixName: "TopQ"
|
||||
desc: QbzBridge.tr("Discover new music from your most-played playlists.", QbzBridge.trRev)
|
||||
desc: QbzSession.tr("Discover new music from your most-played playlists.", QbzSession.trRev)
|
||||
c0: "#1f2937"; c1: "#4b5563"; c2: "#fbbf24"
|
||||
}
|
||||
}
|
||||
@@ -485,18 +488,18 @@ Rectangle {
|
||||
// ============================ offline gate ============================
|
||||
// (OfflinePlaceholder.slint replica; mounted INSTEAD of the view.)
|
||||
QbzOfflinePlaceholder {
|
||||
visible: QbzBridge.offline
|
||||
visible: QbzSession.offline
|
||||
anchors.centerIn: parent
|
||||
// The induced-only "Open Settings" arm (Slint) is wired:
|
||||
showSettingsAction: true
|
||||
onSettingsClicked: QbzBridge.navigateTo("settings")
|
||||
onSettingsClicked: QbzShell.navigateTo("settings")
|
||||
}
|
||||
|
||||
// ============================ the view ================================
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
visible: !QbzBridge.offline
|
||||
visible: !QbzSession.offline
|
||||
|
||||
// --- Toolbar (fixed 56px) ---------------------------------------
|
||||
Item {
|
||||
@@ -512,10 +515,10 @@ Rectangle {
|
||||
|
||||
QbzTabBar {
|
||||
tabs: [
|
||||
{ "id": "home", "label": QbzBridge.tr("Home", QbzBridge.trRev) },
|
||||
{ "id": "editorPicks", "label": QbzBridge.tr("Editor's Picks", QbzBridge.trRev) },
|
||||
{ "id": "forYou", "label": QbzBridge.tr("For You", QbzBridge.trRev) },
|
||||
{ "id": "recommendations", "label": QbzBridge.tr("Recommendations", QbzBridge.trRev) },
|
||||
{ "id": "home", "label": QbzSession.tr("Home", QbzSession.trRev) },
|
||||
{ "id": "editorPicks", "label": QbzSession.tr("Editor's Picks", QbzSession.trRev) },
|
||||
{ "id": "forYou", "label": QbzSession.tr("For You", QbzSession.trRev) },
|
||||
{ "id": "recommendations", "label": QbzSession.tr("Recommendations", QbzSession.trRev) },
|
||||
]
|
||||
activeId: root.activeTab
|
||||
// Data is per-tab JSON (no refetch on switch); scroll
|
||||
@@ -552,7 +555,7 @@ Rectangle {
|
||||
tintName: "secondary"
|
||||
}
|
||||
Text {
|
||||
text: QbzBridge.tr("Filter by genre", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Filter by genre", QbzSession.trRev)
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -619,7 +622,7 @@ Rectangle {
|
||||
|
||||
// Loading skeleton (HomeSkeleton: two shimmer rows).
|
||||
Column {
|
||||
visible: QbzBridge.homeLoading && root.sections.length === 0
|
||||
visible: QbzHome.homeLoading && root.sections.length === 0
|
||||
width: parent.width
|
||||
spacing: 40
|
||||
property bool phase: false
|
||||
@@ -636,7 +639,7 @@ Rectangle {
|
||||
// Error state with retry (the Slint Home has no error
|
||||
// arm; the box mirrors the FavoritesView Retry button).
|
||||
Rectangle {
|
||||
visible: QbzBridge.homeError !== ""
|
||||
visible: QbzHome.homeError !== ""
|
||||
width: parent.width
|
||||
height: errorColumn.height + 28
|
||||
radius: theme.radiusSm
|
||||
@@ -648,7 +651,7 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
Text {
|
||||
text: QbzBridge.homeError
|
||||
text: QbzHome.homeError
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -662,7 +665,7 @@ Rectangle {
|
||||
Text {
|
||||
id: retryText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Retry", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Retry", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontLegal
|
||||
}
|
||||
@@ -671,7 +674,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.reloadHome()
|
||||
onClicked: QbzHome.reloadHome()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -708,7 +711,7 @@ Rectangle {
|
||||
width: parent.width - 64
|
||||
spacing: 10
|
||||
Text {
|
||||
text: QbzBridge.tr("Recommendations", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Recommendations", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontSection
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -22,13 +22,17 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../cards"
|
||||
import "../controls"
|
||||
import "../rows"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
// Transparent while the ambient background is active (phase 14 —
|
||||
// HomeView.slint:163: the frosted content panel shows through).
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
|
||||
// Round to the AppShell content-frame bezel (Radius.md): QML clips
|
||||
// are rectangular, so the frame's own rounding never reaches the
|
||||
@@ -291,22 +295,22 @@ Rectangle {
|
||||
// TrackCard.qml).
|
||||
function trackMenuModel(item) {
|
||||
var m = [
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzBridge.tr("Play next", QbzBridge.trRev), "icon": "list-start", "action": "next" },
|
||||
{ "label": QbzBridge.tr("Play later", QbzBridge.trRev), "icon": "list-plus", "action": "later" },
|
||||
{ "label": QbzBridge.tr("Add to queue", QbzBridge.trRev), "icon": "list-end", "action": "queue" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": QbzSession.tr("Play next", QbzSession.trRev), "icon": "list-start", "action": "next" },
|
||||
{ "label": QbzSession.tr("Play later", QbzSession.trRev), "icon": "list-plus", "action": "later" },
|
||||
{ "label": QbzSession.tr("Add to queue", QbzSession.trRev), "icon": "list-end", "action": "queue" },
|
||||
]
|
||||
if (item.artistId !== "") m.push({ "label": QbzBridge.tr("Go to artist", QbzBridge.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (item.albumId !== "") m.push({ "label": QbzBridge.tr("Go to album", QbzBridge.trRev), "icon": "disc", "action": "go-album" })
|
||||
m.push({ "label": item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev),
|
||||
if (item.artistId !== "") m.push({ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" })
|
||||
if (item.albumId !== "") m.push({ "label": QbzSession.tr("Go to album", QbzSession.trRev), "icon": "disc", "action": "go-album" })
|
||||
m.push({ "label": item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev),
|
||||
"icon": item.isFavorite ? "heart-filled" : "heart", "action": "favorite" })
|
||||
return m
|
||||
}
|
||||
function trackAction(item, a) {
|
||||
if (a === "play") QbzBridge.playTrack(item.id)
|
||||
else if (a === "next") QbzBridge.enqueueTrack(item.id, "next")
|
||||
else if (a === "later") QbzBridge.enqueueTrack(item.id, "later")
|
||||
else if (a === "queue") QbzBridge.enqueueTrack(item.id, "queue")
|
||||
if (a === "play") QbzPlayer.playTrack(item.id)
|
||||
else if (a === "next") QbzPlayer.enqueueTrack(item.id, "next")
|
||||
else if (a === "later") QbzPlayer.enqueueTrack(item.id, "later")
|
||||
else if (a === "queue") QbzPlayer.enqueueTrack(item.id, "queue")
|
||||
else if (a === "go-artist") QbzBridge.openArtist(item.artistId)
|
||||
else if (a === "go-album") QbzBridge.openAlbum(item.albumId)
|
||||
else if (a === "favorite") {
|
||||
@@ -330,7 +334,7 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (item.kind === "track") QbzBridge.playTrack(item.id)
|
||||
if (item.kind === "track") QbzPlayer.playTrack(item.id)
|
||||
else if (item.kind === "album") QbzBridge.openAlbum(item.id)
|
||||
else if (item.kind === "artist") QbzBridge.openArtist(item.id)
|
||||
// playlist/label pages: out of scope (POC-NOTE).
|
||||
@@ -370,8 +374,8 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (item.kind === "track") QbzBridge.playTrack(item.id)
|
||||
else if (item.kind === "album") QbzBridge.playAlbum(item.id)
|
||||
if (item.kind === "track") QbzPlayer.playTrack(item.id)
|
||||
else if (item.kind === "album") QbzPlayer.playAlbum(item.id)
|
||||
// playlist play: out of scope (POC-NOTE).
|
||||
}
|
||||
}
|
||||
@@ -398,7 +402,7 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (item.kind === "track") QbzBridge.playTrack(item.id)
|
||||
if (item.kind === "track") QbzPlayer.playTrack(item.id)
|
||||
else if (item.kind === "album") QbzBridge.openAlbum(item.id)
|
||||
else if (item.kind === "artist") QbzBridge.openArtist(item.id)
|
||||
}
|
||||
@@ -443,11 +447,11 @@ Rectangle {
|
||||
tintName: "muted"
|
||||
}
|
||||
Text {
|
||||
text: item.kind === "track" ? QbzBridge.tr("Track", QbzBridge.trRev)
|
||||
: item.kind === "album" ? QbzBridge.tr("Album", QbzBridge.trRev)
|
||||
: item.kind === "artist" ? QbzBridge.tr("Artist", QbzBridge.trRev)
|
||||
: item.kind === "playlist" ? QbzBridge.tr("Playlist", QbzBridge.trRev)
|
||||
: QbzBridge.tr("Label", QbzBridge.trRev)
|
||||
text: item.kind === "track" ? QbzSession.tr("Track", QbzSession.trRev)
|
||||
: item.kind === "album" ? QbzSession.tr("Album", QbzSession.trRev)
|
||||
: item.kind === "artist" ? QbzSession.tr("Artist", QbzSession.trRev)
|
||||
: item.kind === "playlist" ? QbzSession.tr("Playlist", QbzSession.trRev)
|
||||
: QbzSession.tr("Label", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 10
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -514,17 +518,17 @@ Rectangle {
|
||||
menuWidth: 196
|
||||
entries: item.kind === "track" ? root.trackMenuModel(item)
|
||||
: item.kind === "album" ? [
|
||||
{ "label": QbzBridge.tr("Open album", QbzBridge.trRev), "icon": "library-big", "action": "open" },
|
||||
{ "label": QbzBridge.tr("Play", QbzBridge.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev),
|
||||
{ "label": QbzSession.tr("Open album", QbzSession.trRev), "icon": "library-big", "action": "open" },
|
||||
{ "label": QbzSession.tr("Play", QbzSession.trRev), "icon": "play-fill", "action": "play" },
|
||||
{ "label": item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev),
|
||||
"icon": item.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
]
|
||||
: item.kind === "artist" ? [
|
||||
{ "label": QbzBridge.tr("Go to artist", QbzBridge.trRev), "icon": "user", "action": "go-artist" },
|
||||
{ "label": QbzSession.tr("Go to artist", QbzSession.trRev), "icon": "user", "action": "go-artist" },
|
||||
]
|
||||
: [
|
||||
{ "label": QbzBridge.tr("Open", QbzBridge.trRev), "icon": "list-music", "action": "open" },
|
||||
{ "label": item.isFavorite ? QbzBridge.tr("Remove from Library", QbzBridge.trRev) : QbzBridge.tr("Add to Library", QbzBridge.trRev),
|
||||
{ "label": QbzSession.tr("Open", QbzSession.trRev), "icon": "list-music", "action": "open" },
|
||||
{ "label": item.isFavorite ? QbzSession.tr("Remove from Library", QbzSession.trRev) : QbzSession.tr("Add to Library", QbzSession.trRev),
|
||||
"icon": item.isFavorite ? "heart-filled" : "heart", "action": "favorite" },
|
||||
]
|
||||
onPicked: function (a) {
|
||||
@@ -533,7 +537,7 @@ Rectangle {
|
||||
if (item.kind === "album") QbzBridge.openAlbum(item.id)
|
||||
// playlist/label pages: out of scope (POC-NOTE).
|
||||
} else if (a === "play") {
|
||||
if (item.kind === "album") QbzBridge.playAlbum(item.id)
|
||||
if (item.kind === "album") QbzPlayer.playAlbum(item.id)
|
||||
} else if (a === "go-artist") {
|
||||
QbzBridge.openArtist(item.id)
|
||||
} else if (a === "favorite") {
|
||||
@@ -550,16 +554,16 @@ Rectangle {
|
||||
// Offline gate (OfflinePlaceholder replica; the Slint offline RAIL is
|
||||
// out of scope — see header note).
|
||||
QbzOfflinePlaceholder {
|
||||
visible: QbzBridge.offline
|
||||
visible: QbzSession.offline
|
||||
anchors.centerIn: parent
|
||||
showSettingsAction: true
|
||||
onSettingsClicked: QbzBridge.navigateTo("settings")
|
||||
onSettingsClicked: QbzShell.navigateTo("settings")
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
visible: !QbzBridge.offline
|
||||
visible: !QbzSession.offline
|
||||
|
||||
// --- Toolbar: tab menu (left) + per-tab controls (right) ---------
|
||||
Item {
|
||||
@@ -583,12 +587,12 @@ Rectangle {
|
||||
underline: true
|
||||
activeId: root.activeTab
|
||||
tabs: [
|
||||
{ "id": "all", "label": QbzBridge.tr("All", QbzBridge.trRev), "count": counts.all || 0 },
|
||||
{ "id": "tracks", "label": QbzBridge.tr("Tracks", QbzBridge.trRev), "count": counts.tracks || 0 },
|
||||
{ "id": "albums", "label": QbzBridge.tr("Albums", QbzBridge.trRev), "count": counts.albums || 0 },
|
||||
{ "id": "artists", "label": QbzBridge.tr("Artists", QbzBridge.trRev), "count": counts.artists || 0 },
|
||||
{ "id": "playlists", "label": QbzBridge.tr("Playlists", QbzBridge.trRev), "count": counts.playlists || 0 },
|
||||
{ "id": "labels", "label": QbzBridge.tr("Labels", QbzBridge.trRev), "count": counts.labels || 0 },
|
||||
{ "id": "all", "label": QbzSession.tr("All", QbzSession.trRev), "count": counts.all || 0 },
|
||||
{ "id": "tracks", "label": QbzSession.tr("Tracks", QbzSession.trRev), "count": counts.tracks || 0 },
|
||||
{ "id": "albums", "label": QbzSession.tr("Albums", QbzSession.trRev), "count": counts.albums || 0 },
|
||||
{ "id": "artists", "label": QbzSession.tr("Artists", QbzSession.trRev), "count": counts.artists || 0 },
|
||||
{ "id": "playlists", "label": QbzSession.tr("Playlists", QbzSession.trRev), "count": counts.playlists || 0 },
|
||||
{ "id": "labels", "label": QbzSession.tr("Labels", QbzSession.trRev), "count": counts.labels || 0 },
|
||||
]
|
||||
onSelected: function (id) { root.activeTab = id }
|
||||
}
|
||||
@@ -608,7 +612,7 @@ Rectangle {
|
||||
spacing: 8
|
||||
height: parent.height
|
||||
SearchBox {
|
||||
placeholder: QbzBridge.tr("Search your library", QbzBridge.trRev)
|
||||
placeholder: QbzSession.tr("Search your library", QbzSession.trRev)
|
||||
onSearchEdited: {
|
||||
root.search = text
|
||||
allSearchDebounce.restart()
|
||||
@@ -646,10 +650,10 @@ Rectangle {
|
||||
spacing: 6
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Sort", QbzBridge.trRev) + ": " + (
|
||||
root.sortBy === "title" ? QbzBridge.tr("Title", QbzBridge.trRev)
|
||||
: root.sortBy === "artist" ? QbzBridge.tr("Artist", QbzBridge.trRev)
|
||||
: QbzBridge.tr("Date added", QbzBridge.trRev))
|
||||
text: QbzSession.tr("Sort", QbzSession.trRev) + ": " + (
|
||||
root.sortBy === "title" ? QbzSession.tr("Title", QbzSession.trRev)
|
||||
: root.sortBy === "artist" ? QbzSession.tr("Artist", QbzSession.trRev)
|
||||
: QbzSession.tr("Date added", QbzSession.trRev))
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -673,9 +677,9 @@ Rectangle {
|
||||
menuWidth: 172
|
||||
Repeater {
|
||||
model: [
|
||||
{ "field": "date", "label": QbzBridge.tr("Date added", QbzBridge.trRev) },
|
||||
{ "field": "title", "label": QbzBridge.tr("Title", QbzBridge.trRev) },
|
||||
{ "field": "artist", "label": QbzBridge.tr("Artist", QbzBridge.trRev) },
|
||||
{ "field": "date", "label": QbzSession.tr("Date added", QbzSession.trRev) },
|
||||
{ "field": "title", "label": QbzSession.tr("Title", QbzSession.trRev) },
|
||||
{ "field": "artist", "label": QbzSession.tr("Artist", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -732,7 +736,7 @@ Rectangle {
|
||||
// ===== Other-tab toolbars =====
|
||||
SearchBox {
|
||||
visible: root.activeTab !== "all"
|
||||
placeholder: QbzBridge.tr("Search", QbzBridge.trRev)
|
||||
placeholder: QbzSession.tr("Search", QbzSession.trRev)
|
||||
onSearchEdited: root.tabSearch = text
|
||||
}
|
||||
// Albums sort popup (real, JS-side).
|
||||
@@ -750,7 +754,7 @@ Rectangle {
|
||||
spacing: 6
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Sort", QbzBridge.trRev) + ": " + root.albumsSort
|
||||
text: QbzSession.tr("Sort", QbzSession.trRev) + ": " + root.albumsSort
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 12
|
||||
}
|
||||
@@ -807,8 +811,8 @@ Rectangle {
|
||||
spacing: 4
|
||||
Repeater {
|
||||
model: [
|
||||
{ "id": "favorites", "label": QbzBridge.tr("Library", QbzBridge.trRev) },
|
||||
{ "id": "following", "label": QbzBridge.tr("Following", QbzBridge.trRev) },
|
||||
{ "id": "favorites", "label": QbzSession.tr("Library", QbzSession.trRev) },
|
||||
{ "id": "following", "label": QbzSession.tr("Following", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -861,7 +865,7 @@ Rectangle {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
Text {
|
||||
text: QbzBridge.tr("Couldn't load your Library.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Couldn't load your Library.", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontBody
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -882,7 +886,7 @@ Rectangle {
|
||||
Text {
|
||||
id: retryText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Retry", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Retry", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontLegal
|
||||
}
|
||||
@@ -1009,8 +1013,8 @@ Rectangle {
|
||||
TrackRow {
|
||||
item: modelData
|
||||
number: index + 1
|
||||
onPlayRequested: QbzBridge.playTrack(item.id)
|
||||
onEnqueueRequested: function (m) { QbzBridge.enqueueTrack(item.id, m) }
|
||||
onPlayRequested: QbzPlayer.playTrack(item.id)
|
||||
onEnqueueRequested: function (m) { QbzPlayer.enqueueTrack(item.id, m) }
|
||||
}
|
||||
}
|
||||
Component {
|
||||
@@ -31,11 +31,13 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
radius: 12
|
||||
|
||||
QbzTheme { id: theme }
|
||||
@@ -67,13 +69,13 @@ Rectangle {
|
||||
}
|
||||
|
||||
function sortLabel() {
|
||||
return sortField === "title" ? QbzBridge.tr("Title", QbzBridge.trRev)
|
||||
: sortField === "artist" ? QbzBridge.tr("Artist", QbzBridge.trRev)
|
||||
: sortField === "album" ? QbzBridge.tr("Album", QbzBridge.trRev)
|
||||
: sortField === "duration" ? QbzBridge.tr("Duration", QbzBridge.trRev)
|
||||
: sortField === "added" ? QbzBridge.tr("Date added", QbzBridge.trRev)
|
||||
: sortField === "custom" ? QbzBridge.tr("Custom", QbzBridge.trRev)
|
||||
: QbzBridge.tr("Default", QbzBridge.trRev)
|
||||
return sortField === "title" ? QbzSession.tr("Title", QbzSession.trRev)
|
||||
: sortField === "artist" ? QbzSession.tr("Artist", QbzSession.trRev)
|
||||
: sortField === "album" ? QbzSession.tr("Album", QbzSession.trRev)
|
||||
: sortField === "duration" ? QbzSession.tr("Duration", QbzSession.trRev)
|
||||
: sortField === "added" ? QbzSession.tr("Date added", QbzSession.trRev)
|
||||
: sortField === "custom" ? QbzSession.tr("Custom", QbzSession.trRev)
|
||||
: QbzSession.tr("Default", QbzSession.trRev)
|
||||
}
|
||||
|
||||
// --- Reorder state (the view-level half of the shared drag) ----------
|
||||
@@ -85,14 +87,14 @@ Rectangle {
|
||||
// in the same turn it clears active, so the value must be captured
|
||||
// WHILE the drag is live).
|
||||
Connections {
|
||||
target: QbzBridge
|
||||
target: QbzShell
|
||||
function onDragOverPlaylistIdChanged() {
|
||||
if (QbzBridge.dragActive) root.reorderDropPlaylist = QbzBridge.dragOverPlaylistId
|
||||
if (QbzShell.dragActive) root.reorderDropPlaylist = QbzShell.dragOverPlaylistId
|
||||
}
|
||||
function onDragXChanged() { root.updateSlot() }
|
||||
function onDragYChanged() { root.updateSlot() }
|
||||
function onDragActiveChanged() {
|
||||
if (QbzBridge.dragActive) return
|
||||
if (QbzShell.dragActive) return
|
||||
// Drag ended (main.rs drag_end already ran — it handled a
|
||||
// sidebar-playlist drop, if any). A release INSIDE the list and
|
||||
// NOT on a sidebar playlist reorders.
|
||||
@@ -112,16 +114,16 @@ Rectangle {
|
||||
if (!trackList) return false
|
||||
const tl = trackList.mapToItem(null, 0, 0)
|
||||
const br = trackList.mapToItem(null, trackList.width, trackList.height)
|
||||
return QbzBridge.dragX >= tl.x && QbzBridge.dragX <= br.x
|
||||
&& QbzBridge.dragY >= tl.y && QbzBridge.dragY <= br.y
|
||||
return QbzShell.dragX >= tl.x && QbzShell.dragX <= br.x
|
||||
&& QbzShell.dragY >= tl.y && QbzShell.dragY <= br.y
|
||||
}
|
||||
function slotFromPointer() {
|
||||
const tl = trackList.mapToItem(null, 0, 0)
|
||||
return Math.max(0, Math.min(tracks.length,
|
||||
Math.round((QbzBridge.dragY - tl.y + trackList.contentY) / 50)))
|
||||
Math.round((QbzShell.dragY - tl.y + trackList.contentY) / 50)))
|
||||
}
|
||||
function updateSlot() {
|
||||
if (root.reorderFrom >= 0 && QbzBridge.dragActive) {
|
||||
if (root.reorderFrom >= 0 && QbzShell.dragActive) {
|
||||
root.reorderOver = root.pointerInList() ? root.slotFromPointer() : -1
|
||||
}
|
||||
}
|
||||
@@ -180,7 +182,7 @@ Rectangle {
|
||||
width: parent.width - 150 - 24
|
||||
spacing: 0
|
||||
Text {
|
||||
text: QbzBridge.tr("Playlist", QbzBridge.trRev).toUpperCase()
|
||||
text: QbzSession.tr("Playlist", QbzSession.trRev).toUpperCase()
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 11
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -207,7 +209,7 @@ Rectangle {
|
||||
Text {
|
||||
visible: (doc.description || "") !== (doc.descriptionShort || "")
|
||||
height: 18
|
||||
text: QbzBridge.tr("Read more", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Read more", QbzSession.trRev)
|
||||
color: rmArea.containsMouse ? theme.accentHover : theme.accent
|
||||
font.pixelSize: theme.fontLegal
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -221,7 +223,7 @@ Rectangle {
|
||||
}
|
||||
Item { width: 1; height: 8 }
|
||||
Text {
|
||||
text: (doc.owner || "") + " • " + (doc.trackCount || 0) + " " + QbzBridge.tr("tracks", QbzBridge.trRev)
|
||||
text: (doc.owner || "") + " • " + (doc.trackCount || 0) + " " + QbzSession.tr("tracks", QbzSession.trRev)
|
||||
+ " • " + (doc.totalDuration || "")
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontLegal
|
||||
@@ -304,7 +306,7 @@ Rectangle {
|
||||
Text {
|
||||
visible: parent.text === ""
|
||||
anchors.fill: parent
|
||||
text: QbzBridge.tr("Search tracks", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search tracks", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -329,7 +331,7 @@ Rectangle {
|
||||
Text {
|
||||
width: parent.width - 13 - 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Sort", QbzBridge.trRev) + ": " + root.sortLabel()
|
||||
text: QbzSession.tr("Sort", QbzSession.trRev) + ": " + root.sortLabel()
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: theme.fontLegal
|
||||
elide: Text.ElideRight
|
||||
@@ -348,13 +350,13 @@ Rectangle {
|
||||
menuWidth: 172
|
||||
Repeater {
|
||||
model: [
|
||||
{ "field": "default", "label": QbzBridge.tr("Default", QbzBridge.trRev) },
|
||||
{ "field": "title", "label": QbzBridge.tr("Title", QbzBridge.trRev) },
|
||||
{ "field": "artist", "label": QbzBridge.tr("Artist", QbzBridge.trRev) },
|
||||
{ "field": "album", "label": QbzBridge.tr("Album", QbzBridge.trRev) },
|
||||
{ "field": "duration", "label": QbzBridge.tr("Duration", QbzBridge.trRev) },
|
||||
{ "field": "added", "label": QbzBridge.tr("Date added", QbzBridge.trRev) },
|
||||
{ "field": "custom", "label": QbzBridge.tr("Custom", QbzBridge.trRev), "ownerOnly": true },
|
||||
{ "field": "default", "label": QbzSession.tr("Default", QbzSession.trRev) },
|
||||
{ "field": "title", "label": QbzSession.tr("Title", QbzSession.trRev) },
|
||||
{ "field": "artist", "label": QbzSession.tr("Artist", QbzSession.trRev) },
|
||||
{ "field": "album", "label": QbzSession.tr("Album", QbzSession.trRev) },
|
||||
{ "field": "duration", "label": QbzSession.tr("Duration", QbzSession.trRev) },
|
||||
{ "field": "added", "label": QbzSession.tr("Date added", QbzSession.trRev) },
|
||||
{ "field": "custom", "label": QbzSession.tr("Custom", QbzSession.trRev), "ownerOnly": true },
|
||||
]
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
@@ -413,7 +415,7 @@ Rectangle {
|
||||
}
|
||||
Text {
|
||||
visible: !root.loading && root.allTracks.length === 0
|
||||
text: QbzBridge.tr("This playlist is empty.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("This playlist is empty.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontBody
|
||||
}
|
||||
@@ -426,10 +428,10 @@ Rectangle {
|
||||
spacing: 14
|
||||
Text { text: "#"; width: 32; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
||||
Rectangle { width: 36; height: 1; color: "transparent" }
|
||||
Text { text: QbzBridge.tr("Title", QbzBridge.trRev); width: parent.width - 32 - 36 - 220 - 70 - 92 - 28 - 28 - 32 - 8 * 14; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
||||
Text { text: QbzBridge.tr("Album", QbzBridge.trRev); width: 220; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
||||
Text { text: QbzBridge.tr("Duration", QbzBridge.trRev); width: 70; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
||||
Text { text: QbzBridge.tr("Quality", QbzBridge.trRev); width: 92; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
||||
Text { text: QbzSession.tr("Title", QbzSession.trRev); width: parent.width - 32 - 36 - 220 - 70 - 92 - 28 - 28 - 32 - 8 * 14; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
||||
Text { text: QbzSession.tr("Album", QbzSession.trRev); width: 220; color: theme.textMuted; font.pixelSize: theme.fontLegal }
|
||||
Text { text: QbzSession.tr("Duration", QbzSession.trRev); width: 70; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
||||
Text { text: QbzSession.tr("Quality", QbzSession.trRev); width: 92; color: theme.textMuted; font.pixelSize: theme.fontLegal; horizontalAlignment: Text.AlignHCenter }
|
||||
Rectangle { width: 28; height: 1; color: "transparent" }
|
||||
Rectangle { width: 28; height: 1; color: "transparent" }
|
||||
Rectangle { width: 32; height: 1; color: "transparent" }
|
||||
@@ -518,7 +520,7 @@ Rectangle {
|
||||
contentItem: Column {
|
||||
spacing: 14
|
||||
Text {
|
||||
text: QbzBridge.tr("Edit playlist", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Edit playlist", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontHeading
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -555,7 +557,7 @@ Rectangle {
|
||||
Text {
|
||||
id: delText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Delete", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Delete", QbzSession.trRev)
|
||||
color: "#ef4444"
|
||||
font.pixelSize: 13
|
||||
font.weight: theme.weightMedium
|
||||
@@ -579,7 +581,7 @@ Rectangle {
|
||||
Text {
|
||||
id: saveText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Save", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Save", QbzSession.trRev)
|
||||
color: "#ffffff"
|
||||
font.pixelSize: 13
|
||||
font.weight: theme.weightMedium
|
||||
@@ -24,11 +24,14 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import com.blitzfc.qbz
|
||||
import "../cards"
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: ambientOn ? "transparent" : theme.surfaceMain
|
||||
readonly property bool ambientOn: QbzBridge.ambientMode > 0 && QbzBridge.npHasTrack
|
||||
readonly property bool ambientOn: QbzShell.ambientMode > 0 && QbzPlayer.npHasTrack
|
||||
radius: 12
|
||||
|
||||
QbzTheme { id: theme }
|
||||
@@ -94,7 +97,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playTrack(card.id)
|
||||
onClicked: QbzPlayer.playTrack(card.id)
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
@@ -102,7 +105,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.playTrack(card.id)
|
||||
onClicked: QbzPlayer.playTrack(card.id)
|
||||
}
|
||||
}
|
||||
Item { width: 1; height: 6 }
|
||||
@@ -119,7 +122,7 @@ Rectangle {
|
||||
Text {
|
||||
width: 200
|
||||
height: 18
|
||||
text: QbzBridge.tr("Track", QbzBridge.trRev) + " • " + (card.artist || "")
|
||||
text: QbzSession.tr("Track", QbzSession.trRev) + " • " + (card.artist || "")
|
||||
color: theme.textMuted
|
||||
font.pixelSize: theme.fontLink - 1
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
@@ -157,7 +160,7 @@ Rectangle {
|
||||
Text {
|
||||
id: lmText
|
||||
anchors.centerIn: parent
|
||||
text: QbzBridge.tr("Load more", QbzBridge.trRev) + " (" + loaded + " / " + total + ")"
|
||||
text: QbzSession.tr("Load more", QbzSession.trRev) + " (" + loaded + " / " + total + ")"
|
||||
color: theme.textSecondary
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -227,7 +230,7 @@ Rectangle {
|
||||
Text {
|
||||
x: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: QbzBridge.tr("Search", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Search", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontSection
|
||||
font.weight: theme.weightBold
|
||||
@@ -238,11 +241,11 @@ Rectangle {
|
||||
anchors.rightMargin: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 16
|
||||
FilterRadio { label: QbzBridge.tr("Main Artist", QbzBridge.trRev); selected: root.filterIndex === 1; onPicked: QbzBridge.searchFilterChanged(1) }
|
||||
FilterRadio { label: QbzBridge.tr("Performer", QbzBridge.trRev); selected: root.filterIndex === 2; onPicked: QbzBridge.searchFilterChanged(2) }
|
||||
FilterRadio { label: QbzBridge.tr("Composer", QbzBridge.trRev); selected: root.filterIndex === 3; onPicked: QbzBridge.searchFilterChanged(3) }
|
||||
FilterRadio { label: QbzBridge.tr("Label", QbzBridge.trRev); selected: root.filterIndex === 4; onPicked: QbzBridge.searchFilterChanged(4) }
|
||||
FilterRadio { label: QbzBridge.tr("Release Name", QbzBridge.trRev); selected: root.filterIndex === 5; onPicked: QbzBridge.searchFilterChanged(5) }
|
||||
FilterRadio { label: QbzSession.tr("Main Artist", QbzSession.trRev); selected: root.filterIndex === 1; onPicked: QbzBridge.searchFilterChanged(1) }
|
||||
FilterRadio { label: QbzSession.tr("Performer", QbzSession.trRev); selected: root.filterIndex === 2; onPicked: QbzBridge.searchFilterChanged(2) }
|
||||
FilterRadio { label: QbzSession.tr("Composer", QbzSession.trRev); selected: root.filterIndex === 3; onPicked: QbzBridge.searchFilterChanged(3) }
|
||||
FilterRadio { label: QbzSession.tr("Label", QbzSession.trRev); selected: root.filterIndex === 4; onPicked: QbzBridge.searchFilterChanged(4) }
|
||||
FilterRadio { label: QbzSession.tr("Release Name", QbzSession.trRev); selected: root.filterIndex === 5; onPicked: QbzBridge.searchFilterChanged(5) }
|
||||
Rectangle {
|
||||
visible: root.filterIndex !== 0
|
||||
width: 24
|
||||
@@ -274,11 +277,11 @@ Rectangle {
|
||||
spacing: 22
|
||||
Repeater {
|
||||
model: [
|
||||
{ "t": 0, "label": QbzBridge.tr("All", QbzBridge.trRev) },
|
||||
{ "t": 1, "label": QbzBridge.tr("Albums", QbzBridge.trRev) },
|
||||
{ "t": 2, "label": QbzBridge.tr("Tracks", QbzBridge.trRev) },
|
||||
{ "t": 3, "label": QbzBridge.tr("Artists", QbzBridge.trRev) },
|
||||
{ "t": 4, "label": QbzBridge.tr("Playlists", QbzBridge.trRev) },
|
||||
{ "t": 0, "label": QbzSession.tr("All", QbzSession.trRev) },
|
||||
{ "t": 1, "label": QbzSession.tr("Albums", QbzSession.trRev) },
|
||||
{ "t": 2, "label": QbzSession.tr("Tracks", QbzSession.trRev) },
|
||||
{ "t": 3, "label": QbzSession.tr("Artists", QbzSession.trRev) },
|
||||
{ "t": 4, "label": QbzSession.tr("Playlists", QbzSession.trRev) },
|
||||
]
|
||||
delegate: Column {
|
||||
required property var modelData
|
||||
@@ -344,7 +347,7 @@ Rectangle {
|
||||
spacing: 8
|
||||
QbzIcon { name: "award"; width: 18; height: 18; tintName: "warning" }
|
||||
Text {
|
||||
text: QbzBridge.tr("Most popular", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Most popular", QbzSession.trRev)
|
||||
color: theme.textPrimary
|
||||
font.pixelSize: theme.fontSection
|
||||
font.weight: theme.weightSemibold
|
||||
@@ -387,7 +390,7 @@ Rectangle {
|
||||
width: parent.width - 224
|
||||
spacing: 12
|
||||
QbzSectionHeader {
|
||||
title: QbzBridge.tr("Artists", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Artists", QbzSession.trRev)
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
@@ -414,7 +417,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
spacing: 12
|
||||
QbzSectionHeader {
|
||||
title: QbzBridge.tr("Artists", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Artists", QbzSession.trRev)
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
@@ -441,7 +444,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
spacing: 12
|
||||
QbzSectionHeader {
|
||||
title: QbzBridge.tr("Albums", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Albums", QbzSession.trRev)
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
@@ -505,7 +508,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
QbzSectionHeader {
|
||||
title: QbzBridge.tr("Tracks", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Tracks", QbzSession.trRev)
|
||||
showViewAll: root.tab === 0 && (root.doc.tracksTotal || 0) > root.previewCap
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
@@ -517,8 +520,8 @@ Rectangle {
|
||||
item: modelData
|
||||
number: index + 1
|
||||
menuShowFavorite: false
|
||||
onPlayRequested: QbzBridge.playTrack(item.id)
|
||||
onEnqueueRequested: function (m) { QbzBridge.enqueueTrack(item.id, m) }
|
||||
onPlayRequested: QbzPlayer.playTrack(item.id)
|
||||
onEnqueueRequested: function (m) { QbzPlayer.enqueueTrack(item.id, m) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +559,7 @@ Rectangle {
|
||||
width: parent.width
|
||||
spacing: 12
|
||||
QbzSectionHeader {
|
||||
title: QbzBridge.tr("Playlists", QbzBridge.trRev)
|
||||
title: QbzSession.tr("Playlists", QbzSession.trRev)
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
@@ -607,7 +610,7 @@ Rectangle {
|
||||
spacing: 18
|
||||
QbzSpinner { size: 36; anchors.horizontalCenter: parent.horizontalCenter }
|
||||
Text {
|
||||
text: QbzBridge.tr("Searching…", QbzBridge.trRev)
|
||||
text: QbzSession.tr("Searching…", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 13
|
||||
}
|
||||
@@ -617,7 +620,7 @@ Rectangle {
|
||||
visible: !root.loading && !root.hasResults
|
||||
x: 32
|
||||
y: 8
|
||||
text: QbzBridge.tr("No results.", QbzBridge.trRev)
|
||||
text: QbzSession.tr("No results.", QbzSession.trRev)
|
||||
color: theme.textMuted
|
||||
font.pixelSize: 14
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
import QtQuick
|
||||
import com.blitzfc.qbz
|
||||
import "../controls"
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
id: root
|
||||
@@ -272,7 +272,7 @@ pub fn update_for_artwork(artwork_url: &str) {
|
||||
.flatten();
|
||||
if let Some((primary, secondary, accent)) = triad {
|
||||
log::info!("[qbz-qt] ambient palette: {primary} / {secondary} / {accent}");
|
||||
crate::ui(move |mut b| {
|
||||
crate::shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_ambient_primary(QString::from(primary.as_str()));
|
||||
b.as_mut()
|
||||
.set_ambient_secondary(QString::from(secondary.as_str()));
|
||||
|
||||
+4
-784
@@ -17,12 +17,6 @@ pub mod qbz_bridge {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
|
||||
include!("cxx-qt-lib/qvariant.h");
|
||||
type QVariant = cxx_qt_lib::QVariant;
|
||||
|
||||
include!("cxx-qt-lib/qlist.h");
|
||||
type QList_QVariant = cxx_qt_lib::QList<cxx_qt_lib::QVariant>;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
@@ -30,83 +24,6 @@ pub mod qbz_bridge {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// "splash" | "login" | "shell"
|
||||
#[qproperty(QString, screen)]
|
||||
// Login narration: 0 idle / 1 waiting-for-browser / 2 authenticating.
|
||||
#[qproperty(i32, login_phase)]
|
||||
// Last SIGN-IN failure message ("" = none). Mirrors Slint
|
||||
// `LoginState.error`.
|
||||
#[qproperty(QString, login_error)]
|
||||
// Session-RESTORE failure message ("" = none). Mirrors Slint
|
||||
// `OfflineState.login-error`. (The Slint app has two separate fields;
|
||||
// keeping both here so the two login-screen boxes stay distinct.)
|
||||
#[qproperty(QString, restore_error)]
|
||||
// Offline-MODE engine snapshot (Slint `OfflineState`):
|
||||
#[qproperty(bool, offline)]
|
||||
// 0 online / 1 real offline / 2 induced offline.
|
||||
#[qproperty(i32, offline_mode)]
|
||||
// 0 unknown / 1 up / 2 down.
|
||||
#[qproperty(i32, connectivity)]
|
||||
#[qproperty(bool, captive_portal)]
|
||||
#[qproperty(bool, has_previous_session)]
|
||||
#[qproperty(bool, show_recovery_banner)]
|
||||
#[qproperty(bool, offline_session)]
|
||||
// Shell session header:
|
||||
#[qproperty(QString, session_user_name)]
|
||||
#[qproperty(QString, session_subscription)]
|
||||
|
||||
// --- Shell chrome (Slint ShellState) ----------------------------
|
||||
// Three-state sidebar: 0 = open (240px), 1 = mini (64px), 2 = closed.
|
||||
#[qproperty(i32, sidebar_state)]
|
||||
#[qproperty(bool, queue_open)]
|
||||
// Content view id — only "home" exists (phase 3 adds the rest).
|
||||
#[qproperty(QString, current_view)]
|
||||
// Nav history (src/nav_qt.rs):
|
||||
#[qproperty(bool, can_back)]
|
||||
#[qproperty(bool, can_forward)]
|
||||
|
||||
// --- Now playing (Slint NowPlayingState; np_ prefix) ------------
|
||||
// POC: fed by a static NowPlayingModel (src/now_playing.rs) with
|
||||
// empty-state defaults; phase 4 swaps the data source for the real
|
||||
// player poll.
|
||||
#[qproperty(bool, np_has_track)]
|
||||
#[qproperty(QString, np_title)]
|
||||
#[qproperty(QString, np_artist)]
|
||||
#[qproperty(QString, np_artwork_path)]
|
||||
#[qproperty(i32, np_elapsed_secs)]
|
||||
#[qproperty(i32, np_duration_secs)]
|
||||
#[qproperty(f32, np_progress)]
|
||||
#[qproperty(f32, np_cache_progress)]
|
||||
#[qproperty(bool, np_playing)]
|
||||
#[qproperty(bool, np_loading)]
|
||||
#[qproperty(f32, np_volume)]
|
||||
#[qproperty(bool, np_muted)]
|
||||
#[qproperty(bool, np_shuffle)]
|
||||
// 0 off / 1 all / 2 one.
|
||||
#[qproperty(i32, np_repeat_mode)]
|
||||
// "hires" | "mp3" | "lossless" | "cd" (AudioStamp tier mapping).
|
||||
#[qproperty(QString, np_quality_tier)]
|
||||
// e.g. "24-bit / 96 kHz" (AudioStamp detail line).
|
||||
#[qproperty(QString, np_quality_label)]
|
||||
|
||||
// --- Queue panel -------------------------------------------------
|
||||
// POC: empty until phase 4 (QML shows the empty states).
|
||||
#[qproperty(QList_QVariant, queue_model)]
|
||||
|
||||
// --- Discover > Home ---------------------------------------------
|
||||
#[qproperty(bool, home_loading)]
|
||||
#[qproperty(QString, home_error)]
|
||||
// POC-NOTE: the brief's QVariantList-of-QVariantMap shape is not
|
||||
// expressible in cxx-qt-lib 0.7.3 (no QVariantValue impls for
|
||||
// QMap/QList — a QVariant can't hold a nested map/list), so the
|
||||
// sections travel as ONE JSON document; QML JSON.parse()s it into
|
||||
// the exact same {id, title, kind, items:[{...}]} object graph.
|
||||
#[qproperty(QString, home_sections_json)]
|
||||
// Editor's Picks / For You tab section lists (phase 13; same
|
||||
// HomeSection shape, ordered by each tab's discover prefs).
|
||||
#[qproperty(QString, editor_sections_json)]
|
||||
#[qproperty(QString, for_you_sections_json)]
|
||||
|
||||
// --- Library view --------------------------------------------------
|
||||
#[qproperty(bool, library_loading)]
|
||||
#[qproperty(QString, library_error)]
|
||||
@@ -124,83 +41,12 @@ pub mod qbz_bridge {
|
||||
#[qproperty(QString, artist_json)]
|
||||
// The id backing the current "album"/"artist" view ("" = none).
|
||||
#[qproperty(QString, view_param_id)]
|
||||
// Now-playing track id (playing-row indicator in track lists).
|
||||
#[qproperty(QString, np_track_id)]
|
||||
#[qproperty(QString, np_album)]
|
||||
#[qproperty(QString, np_album_id)]
|
||||
#[qproperty(QString, np_artist_id)]
|
||||
// Now-playing bar mode (phase 18): 0 New / 1 Classic / 2 Small /
|
||||
// 3 Large — the ui_prefs npb_mode key, live-switchable.
|
||||
#[qproperty(i32, npb_mode)]
|
||||
// "Show track playing context" pref (Playback settings) — feeds the
|
||||
// SongCard layers icon.
|
||||
#[qproperty(bool, show_context_icon)]
|
||||
|
||||
// --- Theme (phase 19) --------------------------------------------
|
||||
// ONE JSON token document (theme_qt.rs ThemeTokens: 30 colors + 24
|
||||
// alpha tiers + ambient derivations + isDark) — QbzTheme.qml binds
|
||||
// to it, so a theme switch repaints the whole app live (the Slint
|
||||
// theme::push_colors equivalent).
|
||||
#[qproperty(QString, theme_json)]
|
||||
// The persisted ui_prefs theme slug ("oled", "auto", "custom", ...).
|
||||
#[qproperty(QString, theme_slug)]
|
||||
// The dropdown catalog (36 registry themes + Auto/Custom rows).
|
||||
#[qproperty(QString, theme_list_json)]
|
||||
// Dropdown filter: 0 All / 1 Dark / 2 Light (ui_prefs theme_filter).
|
||||
#[qproperty(i32, theme_filter)]
|
||||
// --- i18n (phase 20) ----------------------------------------------
|
||||
// Translation revision: bumped on every live language switch. Every
|
||||
// QML `QbzBridge.tr(msgid, QbzBridge.trRev)` binding depends on it,
|
||||
// so all ~380 call sites re-evaluate through the new catalog.
|
||||
#[qproperty(i32, tr_rev)]
|
||||
|
||||
// --- Lyrics panel (phase 9) ----------------------------------------
|
||||
#[qproperty(bool, lyrics_open)]
|
||||
// One JSON document (lyrics_qt.rs LyricsDoc: status/lines/synced/
|
||||
// provider/error).
|
||||
#[qproperty(QString, lyrics_json)]
|
||||
|
||||
// --- Queue panel (phase 9) -----------------------------------------
|
||||
// One JSON document (queue_qt.rs QueueDoc: current/upcoming/history
|
||||
// + pagination + #442 section markers). Supersedes `queueModel`.
|
||||
#[qproperty(QString, queue_json)]
|
||||
|
||||
// --- Sidebar playlist tree ---------------------------------------
|
||||
// One JSON document: the flattened entries (folders + playlists,
|
||||
// expand/sort/search applied Rust-side — sidebar_qt.rs).
|
||||
#[qproperty(QString, sidebar_json)]
|
||||
#[qproperty(QString, sidebar_sort_by)]
|
||||
#[qproperty(bool, sidebar_sort_asc)]
|
||||
|
||||
// --- Settings (phase 10) -----------------------------------------
|
||||
// One JSON document (settings_qt.rs SettingsDoc: audio + playback
|
||||
// row states, select option lists, output-device groups).
|
||||
#[qproperty(QString, settings_json)]
|
||||
|
||||
// --- Window chrome (phase 12) --------------------------------------
|
||||
// The APPLIED titlebar mode (the ui_prefs `use_system_title_bar`
|
||||
// value read at startup — drives the window flags; never mutated at
|
||||
// runtime, matching the Slint restart semantics).
|
||||
#[qproperty(bool, system_title_bar)]
|
||||
// The PERSISTED pref as it stands NOW (the app-menu check state;
|
||||
// flips live when the user toggles, applies on the next launch).
|
||||
#[qproperty(bool, system_title_bar_pref)]
|
||||
|
||||
// --- Ambient background (phase 14) --------------------------------
|
||||
// 0 = off, 1 = on (the ambient look; the owner's store carries
|
||||
// "ambient"). Live — toggling applies immediately (pure QML
|
||||
// layering). Combined in QML with npHasTrack for the D4 no-track
|
||||
// rule.
|
||||
#[qproperty(i32, ambient_mode)]
|
||||
// Album-art triad (ambient_qt.rs), pushed on track change.
|
||||
#[qproperty(QString, ambient_primary)]
|
||||
#[qproperty(QString, ambient_secondary)]
|
||||
#[qproperty(QString, ambient_accent)]
|
||||
// Look knobs (Slint AppearanceState defaults; QBZ_BG_* env seed).
|
||||
#[qproperty(f32, ambient_dim)]
|
||||
#[qproperty(f32, ambient_surface_alpha)]
|
||||
#[qproperty(f32, ambient_bar_alpha)]
|
||||
|
||||
// --- Search (phase 15) ---------------------------------------------
|
||||
// Cortinilla (live dropdown): open/loading flags, ONE JSON payload
|
||||
// (search_qt.rs CortinillaData: query/top/sections with controller
|
||||
@@ -221,159 +67,14 @@ pub mod qbz_bridge {
|
||||
// rows + ownership/follow/pin/sort/search state).
|
||||
#[qproperty(QString, playlist_json)]
|
||||
|
||||
// --- Drag & drop (phase 17) ----------------------------------------
|
||||
// The shared drag state (DragState in state.slint): the ghost reads
|
||||
// count/title/subtitle + the window-coord pointer; sidebar playlist
|
||||
// rows self-detect the drop via dragX/dragY + over-playlist-id.
|
||||
#[qproperty(bool, drag_active)]
|
||||
#[qproperty(i32, drag_count)]
|
||||
#[qproperty(QString, drag_title)]
|
||||
#[qproperty(QString, drag_subtitle)]
|
||||
#[qproperty(f32, drag_x)]
|
||||
#[qproperty(f32, drag_y)]
|
||||
#[qproperty(QString, drag_over_playlist_id)]
|
||||
|
||||
type QbzBridge = super::QbzBridgeRust;
|
||||
|
||||
/// Called once from Main.qml's Component.onCompleted: registers the
|
||||
/// Qt thread handle and kicks off the boot sequence (offline engine
|
||||
/// start + silent session restore).
|
||||
/// TEMP (phase 23 split): registers the QbzBridge Qt-thread
|
||||
/// hop while the remaining domains still live here. Removed
|
||||
/// when the last domain moves out.
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Login screen primary button: system-browser OAuth (no webview).
|
||||
#[qinvokable]
|
||||
fn sign_in_via_browser(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Cancel link (phase 1): aborts the in-flight OAuth task.
|
||||
#[qinvokable]
|
||||
fn cancel_login(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// "Start offline" link: unauthenticated offline session.
|
||||
#[qinvokable]
|
||||
fn start_offline(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Recovery badge "Sign in": same browser flow.
|
||||
#[qinvokable]
|
||||
fn recovery_login(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Terms-of-Service link: opens the system browser.
|
||||
#[qinvokable]
|
||||
fn open_tos(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Shell logout: back to the login screen.
|
||||
#[qinvokable]
|
||||
fn logout(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// i18n lookup against the shared gettext catalog (qbz-i18n), so the
|
||||
/// QML texts reuse the EXISTING .po translations via the same msgids
|
||||
/// the Slint `@tr()` calls use. `rev` is the trRev binding
|
||||
/// dependency (phase 20) — unused in Rust, it only forces QML
|
||||
/// re-evaluation when the language changes live.
|
||||
#[qinvokable]
|
||||
fn tr(self: &QbzBridge, msgid: QString, rev: i32) -> QString;
|
||||
|
||||
// --- Shell chrome -------------------------------------------------
|
||||
/// Header panel-left button: cycle the sidebar open -> mini ->
|
||||
/// closed -> open (Slint `ShellState.cycle-sidebar()`).
|
||||
#[qinvokable]
|
||||
fn cycle_sidebar(self: Pin<&mut QbzBridge>);
|
||||
/// NPB queue button / queue panel close.
|
||||
#[qinvokable]
|
||||
fn toggle_queue(self: Pin<&mut QbzBridge>);
|
||||
/// Header history buttons.
|
||||
#[qinvokable]
|
||||
fn navigate_back(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn navigate_forward(self: Pin<&mut QbzBridge>);
|
||||
|
||||
// --- Transport (phase 4 wires the player; POC log-and-noop, except
|
||||
// the pure-UI toggles which mutate the NowPlayingModel) -----------
|
||||
#[qinvokable]
|
||||
fn toggle_play(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn next(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn previous(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn seek(self: Pin<&mut QbzBridge>, frac: f32);
|
||||
#[qinvokable]
|
||||
fn set_volume(self: Pin<&mut QbzBridge>, volume: f32);
|
||||
#[qinvokable]
|
||||
fn toggle_mute(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn toggle_shuffle(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn cycle_repeat(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Home view retry button / manual refresh: refetch the discover
|
||||
/// index + rails and republish `homeSectionsJson`.
|
||||
#[qinvokable]
|
||||
fn reload_home(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Album-card click on Home: resolve the album's tracks, enqueue,
|
||||
/// and play through the core's resolved path.
|
||||
#[qinvokable]
|
||||
fn play_album(self: Pin<&mut QbzBridge>, album_id: QString);
|
||||
|
||||
/// AlbumView header Shuffle.
|
||||
#[qinvokable]
|
||||
fn play_album_shuffled(self: Pin<&mut QbzBridge>, album_id: QString);
|
||||
/// AlbumView row play: the album starting AT this track.
|
||||
#[qinvokable]
|
||||
fn play_album_from(self: Pin<&mut QbzBridge>, album_id: QString, track_id: QString);
|
||||
/// AlbumView row "Play next" ("next") / "Add to queue" ("later").
|
||||
#[qinvokable]
|
||||
fn enqueue_album_track(self: Pin<&mut QbzBridge>, album_id: QString, track_id: QString, mode: QString);
|
||||
/// ArtistView Popular Tracks row play (whole list as the queue).
|
||||
#[qinvokable]
|
||||
fn play_artist_track(self: Pin<&mut QbzBridge>, track_id: QString);
|
||||
/// ArtistView "Play all" (shuffle=false) / "Shuffle all" (true).
|
||||
#[qinvokable]
|
||||
fn play_artist_top(self: Pin<&mut QbzBridge>, shuffle: bool);
|
||||
/// ArtistView ⋯ "Add all to queue" — appends the top-tracks queue.
|
||||
#[qinvokable]
|
||||
fn enqueue_artist_top(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// NPB lyrics button / lyrics panel close X.
|
||||
#[qinvokable]
|
||||
fn toggle_lyrics(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Queue panel: tabs/pagination/search.
|
||||
#[qinvokable]
|
||||
fn queue_set_page(self: Pin<&mut QbzBridge>, page: i32);
|
||||
#[qinvokable]
|
||||
fn queue_set_search(self: Pin<&mut QbzBridge>, query: QString);
|
||||
/// Row actions.
|
||||
#[qinvokable]
|
||||
fn queue_play_upcoming(self: Pin<&mut QbzBridge>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_remove_upcoming(self: Pin<&mut QbzBridge>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_remove_all_after(self: Pin<&mut QbzBridge>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_move_track(self: Pin<&mut QbzBridge>, from: i32, to: i32);
|
||||
#[qinvokable]
|
||||
fn queue_play_history(self: Pin<&mut QbzBridge>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_toggle_favorite(self: Pin<&mut QbzBridge>, kind: QString, id: QString);
|
||||
/// Footer: Clear queue.
|
||||
#[qinvokable]
|
||||
fn queue_clear(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Sidebar navigation: record a content view ("home" | "library")
|
||||
/// and lazy-load its data on first visit.
|
||||
#[qinvokable]
|
||||
fn navigate_to(self: Pin<&mut QbzBridge>, view: QString);
|
||||
|
||||
/// Track-row click (Library): play the track as a 1-element queue.
|
||||
#[qinvokable]
|
||||
fn play_track(self: Pin<&mut QbzBridge>, track_id: QString);
|
||||
/// Library track context menus: Play next ("next") / Play later
|
||||
/// ("later") / Add to queue ("queue") on a single feed track.
|
||||
#[qinvokable]
|
||||
fn enqueue_track(self: Pin<&mut QbzBridge>, track_id: QString, mode: QString);
|
||||
|
||||
/// Library retry button / manual refresh.
|
||||
#[qinvokable]
|
||||
fn reload_library(self: Pin<&mut QbzBridge>);
|
||||
@@ -400,10 +101,6 @@ pub mod qbz_bridge {
|
||||
#[qsignal]
|
||||
fn library_favorite_changed(self: Pin<&mut QbzBridge>, key: QString, value: bool);
|
||||
|
||||
/// AlbumCard ⋯ menu: Play next ("next") / Add to queue ("later").
|
||||
#[qinvokable]
|
||||
fn enqueue_album(self: Pin<&mut QbzBridge>, album_id: QString, mode: QString);
|
||||
|
||||
/// AlbumCard pin badge: toggle pin (album/artist/playlist).
|
||||
#[qinvokable]
|
||||
fn toggle_pin(self: Pin<&mut QbzBridge>, kind: QString, id: QString, title: QString, subtitle: QString, artwork_url: QString);
|
||||
@@ -411,25 +108,6 @@ pub mod qbz_bridge {
|
||||
#[qsignal]
|
||||
fn pin_changed(self: Pin<&mut QbzBridge>, key: QString, value: bool);
|
||||
|
||||
/// Sidebar tree: rebuild + republish after load / sort / search /
|
||||
/// folder toggle.
|
||||
#[qinvokable]
|
||||
fn reload_sidebar(self: Pin<&mut QbzBridge>);
|
||||
#[qinvokable]
|
||||
fn sidebar_set_sort(self: Pin<&mut QbzBridge>, option: QString);
|
||||
#[qinvokable]
|
||||
fn sidebar_search(self: Pin<&mut QbzBridge>, query: QString);
|
||||
#[qinvokable]
|
||||
fn sidebar_toggle_folder(self: Pin<&mut QbzBridge>, id: QString);
|
||||
/// Sidebar "+" — create an empty playlist (single core call), then
|
||||
/// reload the tree.
|
||||
#[qinvokable]
|
||||
fn create_playlist(self: Pin<&mut QbzBridge>);
|
||||
/// Sidebar cover dispatch: JSON array of cover URLS (the tree's
|
||||
/// collage is url-keyed, unlike the feed's artKey).
|
||||
#[qinvokable]
|
||||
fn sidebar_artwork_window(self: Pin<&mut QbzBridge>, urls_json: QString);
|
||||
|
||||
/// Open the album detail view (pushes "album" on the nav stack).
|
||||
#[qinvokable]
|
||||
fn open_album(self: Pin<&mut QbzBridge>, album_id: QString);
|
||||
@@ -439,15 +117,6 @@ pub mod qbz_bridge {
|
||||
/// ArtistView per-section "Load more" — the next releases page.
|
||||
#[qinvokable]
|
||||
fn load_release_section(self: Pin<&mut QbzBridge>, artist_id: QString, release_type: QString, offset: i32);
|
||||
/// Artist-card overlay play (ArtistGridCard): Popular tracks,
|
||||
/// falling back to the studio discography (playback.rs play_artist).
|
||||
#[qinvokable]
|
||||
fn play_artist_card(self: Pin<&mut QbzBridge>, artist_id: QString);
|
||||
/// Now-Playing-view flyout: switch the bar mode (0 New / 1 Classic /
|
||||
/// 2 Small / 3 Large) — persists ui_prefs.npb_mode; Large forces the
|
||||
/// sidebar open (the Slint "large" arm).
|
||||
#[qinvokable]
|
||||
fn npb_set_mode(self: Pin<&mut QbzBridge>, mode: i32);
|
||||
/// Emitted with the next page of a releases bucket.
|
||||
#[qsignal]
|
||||
fn release_section_ready(self: Pin<&mut QbzBridge>, release_type: QString, cards_json: QString, has_more: bool);
|
||||
@@ -473,33 +142,12 @@ pub mod qbz_bridge {
|
||||
#[qinvokable]
|
||||
fn refresh_devices(self: Pin<&mut QbzBridge>);
|
||||
|
||||
// --- Theme (phase 19) ---------------------------------------------
|
||||
/// Appearance > Theme row: persist the picked slug + republish
|
||||
/// `themeJson` (live switch).
|
||||
#[qinvokable]
|
||||
fn theme_set(self: Pin<&mut QbzBridge>, slug: QString);
|
||||
/// Appearance > theme filter cycle button (0 All / 1 Dark / 2 Light).
|
||||
#[qinvokable]
|
||||
fn theme_set_filter(self: Pin<&mut QbzBridge>, index: i32);
|
||||
|
||||
// --- Integrations (phase 19) ---------------------------------------
|
||||
/// Non-toggle integration actions (integrations_qt.rs): Last.fm
|
||||
/// connect/open-auth-url/finish/disconnect, ListenBrainz disconnect.
|
||||
#[qinvokable]
|
||||
fn integrations_action(self: Pin<&mut QbzBridge>, action: QString);
|
||||
|
||||
/// App-menu chrome toggle: flip the persisted `use_system_title_bar`
|
||||
/// pref (applies on the next launch — the window flags are fixed at
|
||||
/// creation, 1:1 Slint). Updates `systemTitleBarPref` only.
|
||||
#[qinvokable]
|
||||
fn toggle_system_title_bar(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// App-menu ambient toggle: flip the persisted `app_background` pref
|
||||
/// off <-> "ambient" and apply LIVE (pure QML layering — no restart,
|
||||
/// unlike the titlebar).
|
||||
#[qinvokable]
|
||||
fn toggle_ambient_background(self: Pin<&mut QbzBridge>);
|
||||
|
||||
// --- Search (phase 15) ---------------------------------------------
|
||||
/// Header field keystrokes (QML-debounced 220ms, >= 2 chars): drive
|
||||
/// the cortinilla live query.
|
||||
@@ -572,25 +220,8 @@ pub mod qbz_bridge {
|
||||
#[qinvokable]
|
||||
fn playlist_reorder(self: Pin<&mut QbzBridge>, from: i32, slot: i32);
|
||||
|
||||
// --- Drag & drop (phase 17) ----------------------------------------
|
||||
/// Track-row press-drag start (row id, ghost texts, window coords).
|
||||
#[qinvokable]
|
||||
fn drag_start(self: Pin<&mut QbzBridge>, track_id: QString, title: QString, subtitle: QString, x: f32, y: f32);
|
||||
#[qinvokable]
|
||||
fn drag_move(self: Pin<&mut QbzBridge>, x: f32, y: f32);
|
||||
/// A sidebar playlist row claims / releases the drop target.
|
||||
#[qinvokable]
|
||||
fn drag_set_over(self: Pin<&mut QbzBridge>, playlist_id: QString);
|
||||
/// Release: add the dragged track(s) to the over-playlist target.
|
||||
#[qinvokable]
|
||||
fn drag_end(self: Pin<&mut QbzBridge>);
|
||||
|
||||
/// Card-level playlist actions (LibPlaylistCard overlay + menu).
|
||||
#[qinvokable]
|
||||
fn play_playlist_by_id(self: Pin<&mut QbzBridge>, playlist_id: QString);
|
||||
#[qinvokable]
|
||||
fn enqueue_playlist_by_id(self: Pin<&mut QbzBridge>, playlist_id: QString, mode: QString);
|
||||
#[qinvokable]
|
||||
fn playlist_set_follow_by_id(self: Pin<&mut QbzBridge>, playlist_id: QString, follow: bool);
|
||||
}
|
||||
|
||||
@@ -600,90 +231,22 @@ pub mod qbz_bridge {
|
||||
use core::pin::Pin;
|
||||
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::{QList, QString, QVariant};
|
||||
|
||||
type QListQVariant = QList<QVariant>;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
/// Rust side of the bridge. All fields are driven exclusively through the
|
||||
/// generated `set_*` methods on the Qt thread; the struct itself is plain
|
||||
/// storage (as required by cxx-qt's Default-constructed qobjects).
|
||||
pub struct QbzBridgeRust {
|
||||
screen: QString,
|
||||
login_phase: i32,
|
||||
login_error: QString,
|
||||
restore_error: QString,
|
||||
offline: bool,
|
||||
offline_mode: i32,
|
||||
connectivity: i32,
|
||||
captive_portal: bool,
|
||||
has_previous_session: bool,
|
||||
show_recovery_banner: bool,
|
||||
offline_session: bool,
|
||||
session_user_name: QString,
|
||||
session_subscription: QString,
|
||||
sidebar_state: i32,
|
||||
queue_open: bool,
|
||||
current_view: QString,
|
||||
can_back: bool,
|
||||
can_forward: bool,
|
||||
np_has_track: bool,
|
||||
np_title: QString,
|
||||
np_artist: QString,
|
||||
np_artwork_path: QString,
|
||||
np_elapsed_secs: i32,
|
||||
np_duration_secs: i32,
|
||||
np_progress: f32,
|
||||
np_cache_progress: f32,
|
||||
np_playing: bool,
|
||||
np_loading: bool,
|
||||
np_volume: f32,
|
||||
np_muted: bool,
|
||||
np_shuffle: bool,
|
||||
np_repeat_mode: i32,
|
||||
np_quality_tier: QString,
|
||||
np_quality_label: QString,
|
||||
queue_model: QListQVariant,
|
||||
home_loading: bool,
|
||||
home_error: QString,
|
||||
home_sections_json: QString,
|
||||
editor_sections_json: QString,
|
||||
for_you_sections_json: QString,
|
||||
library_loading: bool,
|
||||
library_error: QString,
|
||||
library_json: QString,
|
||||
library_counts_json: QString,
|
||||
sidebar_json: QString,
|
||||
sidebar_sort_by: QString,
|
||||
sidebar_sort_asc: bool,
|
||||
album_loading: bool,
|
||||
album_json: QString,
|
||||
artist_loading: bool,
|
||||
artist_json: QString,
|
||||
view_param_id: QString,
|
||||
np_track_id: QString,
|
||||
np_album: QString,
|
||||
np_album_id: QString,
|
||||
np_artist_id: QString,
|
||||
npb_mode: i32,
|
||||
show_context_icon: bool,
|
||||
theme_json: QString,
|
||||
theme_slug: QString,
|
||||
theme_list_json: QString,
|
||||
theme_filter: i32,
|
||||
tr_rev: i32,
|
||||
queue_json: QString,
|
||||
lyrics_open: bool,
|
||||
lyrics_json: QString,
|
||||
settings_json: QString,
|
||||
system_title_bar: bool,
|
||||
system_title_bar_pref: bool,
|
||||
ambient_mode: i32,
|
||||
ambient_primary: QString,
|
||||
ambient_secondary: QString,
|
||||
ambient_accent: QString,
|
||||
ambient_dim: f32,
|
||||
ambient_surface_alpha: f32,
|
||||
ambient_bar_alpha: f32,
|
||||
cortinilla_open: bool,
|
||||
cortinilla_loading: bool,
|
||||
cortinilla_json: QString,
|
||||
@@ -692,96 +255,21 @@ pub struct QbzBridgeRust {
|
||||
search_json: QString,
|
||||
intelligent_search: bool,
|
||||
playlist_json: QString,
|
||||
drag_active: bool,
|
||||
drag_count: i32,
|
||||
drag_title: QString,
|
||||
drag_subtitle: QString,
|
||||
drag_x: f32,
|
||||
drag_y: f32,
|
||||
drag_over_playlist_id: QString,
|
||||
}
|
||||
|
||||
impl Default for QbzBridgeRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
screen: QString::from("splash"),
|
||||
current_view: QString::from("home"),
|
||||
// A derive would zero these — the model's sane defaults instead.
|
||||
np_volume: 1.0,
|
||||
np_quality_tier: QString::from("cd"),
|
||||
login_phase: 0,
|
||||
login_error: QString::default(),
|
||||
restore_error: QString::default(),
|
||||
offline: false,
|
||||
offline_mode: 0,
|
||||
connectivity: 0,
|
||||
captive_portal: false,
|
||||
has_previous_session: false,
|
||||
show_recovery_banner: false,
|
||||
offline_session: false,
|
||||
session_user_name: QString::default(),
|
||||
session_subscription: QString::default(),
|
||||
sidebar_state: 0,
|
||||
queue_open: false,
|
||||
can_back: false,
|
||||
can_forward: false,
|
||||
np_has_track: false,
|
||||
np_title: QString::default(),
|
||||
np_artist: QString::default(),
|
||||
np_artwork_path: QString::default(),
|
||||
np_elapsed_secs: 0,
|
||||
np_duration_secs: 0,
|
||||
np_progress: 0.0,
|
||||
np_cache_progress: 0.0,
|
||||
np_playing: false,
|
||||
np_loading: false,
|
||||
np_muted: false,
|
||||
np_shuffle: false,
|
||||
np_repeat_mode: 0,
|
||||
np_quality_label: QString::default(),
|
||||
queue_model: QListQVariant::default(),
|
||||
home_loading: false,
|
||||
home_error: QString::default(),
|
||||
home_sections_json: QString::from("[]"),
|
||||
editor_sections_json: QString::from("[]"),
|
||||
for_you_sections_json: QString::from("[]"),
|
||||
library_loading: false,
|
||||
library_error: QString::default(),
|
||||
library_json: QString::from("[]"),
|
||||
library_counts_json: QString::from("{}"),
|
||||
sidebar_json: QString::from("[]"),
|
||||
sidebar_sort_by: QString::from("name"),
|
||||
sidebar_sort_asc: true,
|
||||
album_loading: false,
|
||||
album_json: QString::from("{}"),
|
||||
artist_loading: false,
|
||||
artist_json: QString::from("{}"),
|
||||
view_param_id: QString::default(),
|
||||
np_track_id: QString::default(),
|
||||
np_album: QString::default(),
|
||||
np_album_id: QString::default(),
|
||||
np_artist_id: QString::default(),
|
||||
npb_mode: crate::settings_qt::npb_mode_index(),
|
||||
show_context_icon: crate::settings_qt::show_context_icon(),
|
||||
theme_json: QString::from(crate::theme_qt::theme_json().as_str()),
|
||||
theme_slug: QString::from(crate::theme_qt::current_slug().as_str()),
|
||||
theme_list_json: QString::from(crate::theme_qt::theme_list_json().as_str()),
|
||||
theme_filter: crate::theme_qt::theme_filter(),
|
||||
tr_rev: 0,
|
||||
queue_json: QString::from("{}"),
|
||||
lyrics_open: false,
|
||||
lyrics_json: QString::from("{}"),
|
||||
settings_json: QString::from("{}"),
|
||||
system_title_bar: crate::settings_qt::use_system_title_bar(),
|
||||
system_title_bar_pref: crate::settings_qt::use_system_title_bar(),
|
||||
ambient_mode: crate::settings_qt::app_background_mode(),
|
||||
// The Slint ImmersiveState default triad (pre-artwork colors).
|
||||
ambient_primary: QString::from("#00dcc8"),
|
||||
ambient_secondary: QString::from("#9632ff"),
|
||||
ambient_accent: QString::from("#3fd9c8"),
|
||||
ambient_dim: crate::settings_qt::ambient_dim(),
|
||||
ambient_surface_alpha: crate::settings_qt::ambient_surface_alpha(),
|
||||
ambient_bar_alpha: crate::settings_qt::ambient_bar_alpha(),
|
||||
cortinilla_open: false,
|
||||
cortinilla_loading: false,
|
||||
cortinilla_json: QString::from("{}"),
|
||||
@@ -790,201 +278,13 @@ impl Default for QbzBridgeRust {
|
||||
search_json: QString::from("{}"),
|
||||
intelligent_search: crate::search_qt::intelligent_search_pref(),
|
||||
playlist_json: QString::from("{}"),
|
||||
drag_active: false,
|
||||
drag_count: 0,
|
||||
drag_title: QString::default(),
|
||||
drag_subtitle: QString::default(),
|
||||
drag_x: 0.0,
|
||||
drag_y: 0.0,
|
||||
drag_over_playlist_id: QString::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_bridge::QbzBridge {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
// The very first invokable: hand the Qt-thread handle to the process
|
||||
// global so tokio tasks can queue property updates back here.
|
||||
crate::register_qt_thread(self.qt_thread());
|
||||
crate::on_boot();
|
||||
}
|
||||
|
||||
pub fn sign_in_via_browser(self: Pin<&mut Self>) {
|
||||
crate::start_login();
|
||||
}
|
||||
|
||||
pub fn cancel_login(self: Pin<&mut Self>) {
|
||||
crate::cancel_login();
|
||||
}
|
||||
|
||||
pub fn start_offline(self: Pin<&mut Self>) {
|
||||
crate::start_offline();
|
||||
}
|
||||
|
||||
pub fn recovery_login(self: Pin<&mut Self>) {
|
||||
crate::start_login();
|
||||
}
|
||||
|
||||
pub fn open_tos(self: Pin<&mut Self>) {
|
||||
// Same URL the Slint shell opens (crates/qbz/src/main.rs
|
||||
// QOBUZ_TOS_URL). `open` spawns xdg-open detached, so this is safe to
|
||||
// run on the Qt thread.
|
||||
if let Err(e) = open::that(crate::QOBUZ_TOS_URL) {
|
||||
log::error!("[qbz-qt] failed to open Terms of Service: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logout(self: Pin<&mut Self>) {
|
||||
crate::do_logout();
|
||||
}
|
||||
|
||||
pub fn tr(&self, msgid: QString, rev: i32) -> QString {
|
||||
let _ = rev; // binding dependency only (see the bridge declaration)
|
||||
QString::from(&qbz_i18n::t(&msgid.to_string()))
|
||||
}
|
||||
|
||||
pub fn cycle_sidebar(mut self: Pin<&mut Self>) {
|
||||
let next = (self.sidebar_state() + 1) % 3;
|
||||
self.as_mut().set_sidebar_state(next);
|
||||
}
|
||||
|
||||
pub fn toggle_queue(mut self: Pin<&mut Self>) {
|
||||
let next = !self.queue_open();
|
||||
self.as_mut().set_queue_open(next);
|
||||
}
|
||||
|
||||
pub fn navigate_back(self: Pin<&mut Self>) {
|
||||
crate::nav_qt::back();
|
||||
}
|
||||
|
||||
pub fn navigate_forward(self: Pin<&mut Self>) {
|
||||
crate::nav_qt::forward();
|
||||
}
|
||||
|
||||
pub fn toggle_play(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_play();
|
||||
}
|
||||
|
||||
pub fn next(self: Pin<&mut Self>) {
|
||||
crate::transport_next();
|
||||
}
|
||||
|
||||
pub fn previous(self: Pin<&mut Self>) {
|
||||
crate::transport_previous();
|
||||
}
|
||||
|
||||
pub fn seek(self: Pin<&mut Self>, frac: f32) {
|
||||
crate::transport_seek(frac);
|
||||
}
|
||||
|
||||
pub fn set_volume(self: Pin<&mut Self>, volume: f32) {
|
||||
crate::transport_set_volume(volume);
|
||||
}
|
||||
|
||||
pub fn toggle_mute(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_mute();
|
||||
}
|
||||
|
||||
pub fn toggle_shuffle(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_shuffle();
|
||||
}
|
||||
|
||||
pub fn cycle_repeat(self: Pin<&mut Self>) {
|
||||
crate::transport_cycle_repeat();
|
||||
}
|
||||
|
||||
pub fn reload_home(self: Pin<&mut Self>) {
|
||||
crate::reload_home();
|
||||
}
|
||||
|
||||
pub fn play_album(self: Pin<&mut Self>, album_id: QString) {
|
||||
crate::play_album(album_id.to_string());
|
||||
}
|
||||
|
||||
pub fn queue_set_page(self: Pin<&mut Self>, page: i32) {
|
||||
crate::queue_set_page(page);
|
||||
}
|
||||
|
||||
pub fn queue_set_search(self: Pin<&mut Self>, query: QString) {
|
||||
crate::queue_set_search(query.to_string());
|
||||
}
|
||||
|
||||
pub fn queue_play_upcoming(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_play_upcoming(index);
|
||||
}
|
||||
|
||||
pub fn queue_remove_upcoming(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_remove_upcoming(index);
|
||||
}
|
||||
|
||||
pub fn queue_remove_all_after(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_remove_all_after(index);
|
||||
}
|
||||
|
||||
pub fn queue_move_track(self: Pin<&mut Self>, from: i32, to: i32) {
|
||||
crate::queue_move_track(from, to);
|
||||
}
|
||||
|
||||
pub fn queue_play_history(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_play_history(index);
|
||||
}
|
||||
|
||||
pub fn queue_toggle_favorite(self: Pin<&mut Self>, kind: QString, id: QString) {
|
||||
crate::queue_toggle_favorite(kind.to_string(), id.to_string());
|
||||
}
|
||||
|
||||
pub fn queue_clear(self: Pin<&mut Self>) {
|
||||
crate::queue_clear();
|
||||
}
|
||||
|
||||
pub fn toggle_lyrics(self: Pin<&mut Self>) {
|
||||
crate::toggle_lyrics();
|
||||
}
|
||||
|
||||
pub fn play_album_shuffled(self: Pin<&mut Self>, album_id: QString) {
|
||||
crate::play_album_shuffled(album_id.to_string());
|
||||
}
|
||||
|
||||
pub fn play_album_from(self: Pin<&mut Self>, album_id: QString, track_id: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_album_from_track(album_id.to_string(), tid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_album_track(self: Pin<&mut Self>, album_id: QString, track_id: QString, mode: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_album_track(album_id.to_string(), tid, mode.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_artist_track(self: Pin<&mut Self>, track_id: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_artist_track(tid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_artist_top(self: Pin<&mut Self>, shuffle: bool) {
|
||||
crate::play_artist_top(shuffle);
|
||||
}
|
||||
|
||||
pub fn enqueue_artist_top(self: Pin<&mut Self>) {
|
||||
crate::enqueue_artist_top();
|
||||
}
|
||||
|
||||
pub fn navigate_to(self: Pin<&mut Self>, view: QString) {
|
||||
crate::navigate_to(&view.to_string());
|
||||
}
|
||||
|
||||
pub fn play_track(self: Pin<&mut Self>, track_id: QString) {
|
||||
if let Ok(id) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_track(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_track(self: Pin<&mut Self>, track_id: QString, mode: QString) {
|
||||
if let Ok(id) = track_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_track(id, mode.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload_library(self: Pin<&mut Self>) {
|
||||
@@ -999,10 +299,6 @@ impl qbz_bridge::QbzBridge {
|
||||
crate::library_toggle_favorite(kind.to_string(), id.to_string());
|
||||
}
|
||||
|
||||
pub fn enqueue_album(self: Pin<&mut Self>, album_id: QString, mode: QString) {
|
||||
crate::enqueue_album(album_id.to_string(), mode.to_string());
|
||||
}
|
||||
|
||||
pub fn toggle_pin(self: Pin<&mut Self>, kind: QString, id: QString, title: QString, subtitle: QString, artwork_url: QString) {
|
||||
crate::toggle_pin(
|
||||
kind.to_string(),
|
||||
@@ -1013,30 +309,6 @@ impl qbz_bridge::QbzBridge {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn reload_sidebar(self: Pin<&mut Self>) {
|
||||
crate::reload_sidebar();
|
||||
}
|
||||
|
||||
pub fn sidebar_set_sort(self: Pin<&mut Self>, option: QString) {
|
||||
crate::sidebar_set_sort(&option.to_string());
|
||||
}
|
||||
|
||||
pub fn sidebar_search(self: Pin<&mut Self>, query: QString) {
|
||||
crate::sidebar_set_search(&query.to_string());
|
||||
}
|
||||
|
||||
pub fn sidebar_toggle_folder(self: Pin<&mut Self>, id: QString) {
|
||||
crate::sidebar_toggle_folder(&id.to_string());
|
||||
}
|
||||
|
||||
pub fn create_playlist(self: Pin<&mut Self>) {
|
||||
crate::create_playlist();
|
||||
}
|
||||
|
||||
pub fn sidebar_artwork_window(self: Pin<&mut Self>, urls_json: QString) {
|
||||
crate::sidebar_artwork_window(urls_json.to_string());
|
||||
}
|
||||
|
||||
pub fn open_album(self: Pin<&mut Self>, album_id: QString) {
|
||||
crate::open_album(album_id.to_string());
|
||||
}
|
||||
@@ -1049,22 +321,6 @@ impl qbz_bridge::QbzBridge {
|
||||
crate::load_release_section(artist_id.to_string(), release_type.to_string(), offset);
|
||||
}
|
||||
|
||||
pub fn play_artist_card(self: Pin<&mut Self>, artist_id: QString) {
|
||||
crate::play_artist_card(artist_id.to_string());
|
||||
}
|
||||
|
||||
pub fn npb_set_mode(self: Pin<&mut Self>, mode: i32) {
|
||||
crate::npb_set_mode(mode);
|
||||
}
|
||||
|
||||
pub fn theme_set(self: Pin<&mut Self>, slug: QString) {
|
||||
crate::theme_set(slug.to_string());
|
||||
}
|
||||
|
||||
pub fn theme_set_filter(self: Pin<&mut Self>, index: i32) {
|
||||
crate::theme_set_filter(index);
|
||||
}
|
||||
|
||||
pub fn integrations_action(self: Pin<&mut Self>, action: QString) {
|
||||
crate::integrations_action(action.to_string());
|
||||
}
|
||||
@@ -1093,14 +349,6 @@ impl qbz_bridge::QbzBridge {
|
||||
crate::refresh_devices();
|
||||
}
|
||||
|
||||
pub fn toggle_system_title_bar(self: Pin<&mut Self>) {
|
||||
crate::toggle_system_title_bar();
|
||||
}
|
||||
|
||||
pub fn toggle_ambient_background(self: Pin<&mut Self>) {
|
||||
crate::toggle_ambient_background();
|
||||
}
|
||||
|
||||
pub fn search_live(self: Pin<&mut Self>, query: QString) {
|
||||
crate::search_live(query.to_string());
|
||||
}
|
||||
@@ -1205,34 +453,6 @@ impl qbz_bridge::QbzBridge {
|
||||
crate::playlist_qt::reorder_track(from.max(0) as usize, slot.max(0) as usize);
|
||||
}
|
||||
|
||||
pub fn drag_start(self: Pin<&mut Self>, track_id: QString, title: QString, subtitle: QString, x: f32, y: f32) {
|
||||
crate::drag_start(track_id.to_string(), title.to_string(), subtitle.to_string(), x, y);
|
||||
}
|
||||
|
||||
pub fn drag_move(self: Pin<&mut Self>, x: f32, y: f32) {
|
||||
crate::drag_move(x, y);
|
||||
}
|
||||
|
||||
pub fn drag_set_over(self: Pin<&mut Self>, playlist_id: QString) {
|
||||
crate::drag_set_over(playlist_id.to_string());
|
||||
}
|
||||
|
||||
pub fn drag_end(self: Pin<&mut Self>) {
|
||||
crate::drag_end();
|
||||
}
|
||||
|
||||
pub fn play_playlist_by_id(self: Pin<&mut Self>, playlist_id: QString) {
|
||||
if let Ok(pid) = playlist_id.to_string().parse::<u64>() {
|
||||
crate::play_playlist_by_id(pid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_playlist_by_id(self: Pin<&mut Self>, playlist_id: QString, mode: QString) {
|
||||
if let Ok(pid) = playlist_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_playlist_by_id(pid, mode.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn playlist_set_follow_by_id(self: Pin<&mut Self>, playlist_id: QString, follow: bool) {
|
||||
if let Ok(pid) = playlist_id.to_string().parse::<u64>() {
|
||||
crate::playlist_set_follow_by_id(pid, follow);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
//! QbzHome — Discover > Home domain bridge (phase 23 split of the QbzBridge
|
||||
//! God-object; the pattern is documented in main.rs). Props: the three tab
|
||||
//! section documents + loading/error. Invokable: reloadHome (the retry
|
||||
//! button / manual refresh).
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_home {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// --- Discover > Home ---------------------------------------------
|
||||
#[qproperty(bool, home_loading)]
|
||||
#[qproperty(QString, home_error)]
|
||||
// POC-NOTE: the brief's QVariantList-of-QVariantMap shape is not
|
||||
// expressible in cxx-qt-lib 0.7.3 (no QVariantValue impls for
|
||||
// QMap/QList — a QVariant can't hold a nested map/list), so the
|
||||
// sections travel as ONE JSON document; QML JSON.parse()s it into
|
||||
// the exact same {id, title, kind, items:[{...}]} object graph.
|
||||
#[qproperty(QString, home_sections_json)]
|
||||
// Editor's Picks / For You tab section lists (phase 13; same
|
||||
// HomeSection shape, ordered by each tab's discover prefs).
|
||||
#[qproperty(QString, editor_sections_json)]
|
||||
#[qproperty(QString, for_you_sections_json)]
|
||||
|
||||
type QbzHome = super::QbzHomeRust;
|
||||
|
||||
/// Registers this object's Qt-thread hop (Main.qml boots EVERY
|
||||
/// domain singleton; only QbzSession.boot also fires crate::on_boot).
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzHome>);
|
||||
|
||||
/// Home view retry button / manual refresh: refetch the discover
|
||||
/// index + rails and republish `homeSectionsJson`.
|
||||
#[qinvokable]
|
||||
fn reload_home(self: Pin<&mut QbzHome>);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzHome {}
|
||||
}
|
||||
|
||||
use qbz_home::QbzHome;
|
||||
|
||||
/// Rust side of the home bridge (plain storage, phase-1 pattern).
|
||||
pub struct QbzHomeRust {
|
||||
home_loading: bool,
|
||||
home_error: QString,
|
||||
home_sections_json: QString,
|
||||
editor_sections_json: QString,
|
||||
for_you_sections_json: QString,
|
||||
}
|
||||
|
||||
impl Default for QbzHomeRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
home_loading: false,
|
||||
home_error: QString::default(),
|
||||
home_sections_json: QString::from("[]"),
|
||||
editor_sections_json: QString::from("[]"),
|
||||
for_you_sections_json: QString::from("[]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The UI hop (bridges/mod.rs pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzHome>> = OnceLock::new();
|
||||
|
||||
/// Queue a home-bridge mutation onto the Qt event loop (no-op before
|
||||
/// boot registers the thread).
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzHome>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_home::QbzHome {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] home Qt thread already registered");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reload_home(self: Pin<&mut Self>) {
|
||||
crate::reload_home();
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ async fn service(runtime: &Arc<AppRuntime<LoggingAdapter>>) -> Option<Arc<Lyrics
|
||||
|
||||
fn publish(doc: LyricsDoc) {
|
||||
let json = serde_json::to_string(&doc).unwrap_or_else(|_| "{}".into());
|
||||
crate::ui(move |mut b| {
|
||||
crate::shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_lyrics_json(QString::from(json.as_str()));
|
||||
});
|
||||
}
|
||||
|
||||
+50
-26
@@ -7,6 +7,24 @@
|
||||
//! async work; results hop back to Qt through the bridge's `CxxQtThread`.
|
||||
|
||||
mod auth_qt;
|
||||
// Per-domain QML bridge singletons (phase 23 — the QbzBridge God-object
|
||||
// split). THE PATTERN (phase-1, replicated per domain file):
|
||||
// 1. one #[cxx_qt::bridge] mod per file (crate root — cxx-qt-build
|
||||
// accepts rust_files from ONE directory only, QTBUG-93443) with a
|
||||
// single #[qml_element] #[qml_singleton] QObject + its *Rust storage;
|
||||
// 2. `static QT_THREAD: OnceLock<CxxQtThread<QbzDomain>>` per file;
|
||||
// 3. the singleton's `boot()` invokable registers `self.qt_thread()` —
|
||||
// Main.qml boots EVERY singleton (they instantiate lazily); only
|
||||
// QbzSession.boot also fires crate::on_boot();
|
||||
// 4. `pub(crate) fn ui(f)` queues a property mutation onto that
|
||||
// object's Qt event loop (no-op pre-registration).
|
||||
// All singletons live in the SAME QML module (com.blitzfc.qbz); the
|
||||
// invokables stay one-line forwards into the domain controllers below.
|
||||
mod session_bridge;
|
||||
mod shell_bridge;
|
||||
mod player_bridge;
|
||||
mod queue_bridge;
|
||||
mod home_bridge;
|
||||
mod artwork_qt;
|
||||
mod bridge;
|
||||
mod home_qt;
|
||||
@@ -112,8 +130,8 @@ pub(crate) fn on_boot() {
|
||||
}
|
||||
match auth_qt::restore_saved_session(&runtime).await {
|
||||
Ok(Some(session)) => enter_shell(session),
|
||||
Ok(None) => ui(|mut b| b.as_mut().set_screen(QString::from("login"))),
|
||||
Err(e) => ui(move |mut b| {
|
||||
Ok(None) => session_bridge::ui(|mut b| b.as_mut().set_screen(QString::from("login"))),
|
||||
Err(e) => session_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_restore_error(QString::from(e.as_str()));
|
||||
b.as_mut().set_screen(QString::from("login"));
|
||||
}),
|
||||
@@ -139,7 +157,7 @@ fn enter_shell(session: auth_qt::SessionInfo) {
|
||||
// Phase 10: seed the playback request tier from the persisted
|
||||
// streaming-quality pref (Settings > Audio writes it live after this).
|
||||
playback_qt::set_streaming_quality(&settings_qt::streaming_quality());
|
||||
ui(move |mut b| {
|
||||
session_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_session_user_name(QString::from(session.display_name.as_str()));
|
||||
b.as_mut().set_session_subscription(QString::from(session.subscription.as_str()));
|
||||
b.as_mut().set_has_previous_session(true);
|
||||
@@ -160,7 +178,7 @@ pub(crate) fn start_login() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
ui(|mut b| {
|
||||
session_bridge::ui(|mut b| {
|
||||
b.as_mut().set_login_error(QString::from(""));
|
||||
b.as_mut().set_login_phase(1);
|
||||
});
|
||||
@@ -172,13 +190,13 @@ pub(crate) fn start_login() {
|
||||
auth_qt::LoginPhase::WaitingForBrowser => 1,
|
||||
auth_qt::LoginPhase::Authenticating => 2,
|
||||
};
|
||||
ui(move |mut b| b.as_mut().set_login_phase(value));
|
||||
session_bridge::ui(move |mut b| b.as_mut().set_login_phase(value));
|
||||
})
|
||||
.await;
|
||||
LOGIN_TASK.lock().unwrap().take();
|
||||
match result {
|
||||
Ok(session) => enter_shell(session),
|
||||
Err(e) => ui(move |mut b| {
|
||||
Err(e) => session_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_login_phase(0);
|
||||
b.as_mut().set_login_error(QString::from(e.as_str()));
|
||||
}),
|
||||
@@ -192,7 +210,7 @@ pub(crate) fn cancel_login() {
|
||||
if let Some(task) = LOGIN_TASK.lock().unwrap().take() {
|
||||
task.abort();
|
||||
}
|
||||
ui(|mut b| b.as_mut().set_login_phase(0));
|
||||
session_bridge::ui(|mut b| b.as_mut().set_login_phase(0));
|
||||
}
|
||||
|
||||
/// "Start offline": unauthenticated offline session -> shell placeholder.
|
||||
@@ -206,7 +224,7 @@ pub(crate) fn start_offline() {
|
||||
} else {
|
||||
format!("Offline (user {user_id})")
|
||||
};
|
||||
ui(move |mut b| {
|
||||
session_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_session_user_name(QString::from(name.as_str()));
|
||||
b.as_mut().set_session_subscription(QString::from(""));
|
||||
b.as_mut().set_login_error(QString::from(""));
|
||||
@@ -219,7 +237,7 @@ pub(crate) fn start_offline() {
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("[qbz-qt] failed to enter offline mode: {e}");
|
||||
ui(move |mut b| {
|
||||
session_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_login_error(QString::from(e.as_str()));
|
||||
});
|
||||
}
|
||||
@@ -237,21 +255,27 @@ pub(crate) fn do_logout() {
|
||||
// A later login must re-fetch Home + Library (new user, fresh data).
|
||||
*HOME_LOADED.lock().unwrap() = false;
|
||||
*LIBRARY_LOADED.lock().unwrap() = false;
|
||||
ui(|mut b| {
|
||||
session_bridge::ui(|mut b| {
|
||||
b.as_mut().set_session_user_name(QString::from(""));
|
||||
b.as_mut().set_session_subscription(QString::from(""));
|
||||
b.as_mut().set_login_error(QString::from(""));
|
||||
b.as_mut().set_restore_error(QString::from(""));
|
||||
b.as_mut().set_login_phase(0);
|
||||
b.as_mut().set_screen(QString::from("login"));
|
||||
});
|
||||
home_bridge::ui(|mut b| {
|
||||
b.as_mut().set_home_sections_json(QString::from("[]"));
|
||||
b.as_mut().set_home_error(QString::from(""));
|
||||
b.as_mut().set_home_loading(false);
|
||||
});
|
||||
ui(|mut b| {
|
||||
b.as_mut().set_library_json(QString::from("[]"));
|
||||
b.as_mut().set_library_counts_json(QString::from("{}"));
|
||||
b.as_mut().set_library_error(QString::from(""));
|
||||
b.as_mut().set_library_loading(false);
|
||||
});
|
||||
shell_bridge::ui(|mut b| {
|
||||
b.as_mut().set_sidebar_json(QString::from("[]"));
|
||||
b.as_mut().set_screen(QString::from("login"));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -308,7 +332,7 @@ fn publish_sidebar() {
|
||||
let json = serde_json::to_string(&entries).unwrap_or_else(|_| "[]".into());
|
||||
log::debug!("[qbz-qt] sidebar published: {} entries ({} bytes)", entries.len(), json.len());
|
||||
let (sort_by, sort_asc) = sidebar_qt::sort_state();
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_sidebar_json(QString::from(json.as_str()));
|
||||
b.as_mut().set_sidebar_sort_by(QString::from(sort_by.as_str()));
|
||||
b.as_mut().set_sidebar_sort_asc(sort_asc);
|
||||
@@ -494,7 +518,7 @@ static LYRICS_OPEN: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBoo
|
||||
|
||||
pub(crate) fn toggle_lyrics() {
|
||||
let open = !LYRICS_OPEN.swap(!LYRICS_OPEN.load(std::sync::atomic::Ordering::SeqCst), std::sync::atomic::Ordering::SeqCst);
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_lyrics_open(open);
|
||||
});
|
||||
if open {
|
||||
@@ -734,7 +758,7 @@ pub(crate) fn drag_start(track_id: String, title: String, subtitle: String, x: f
|
||||
let id = track_id.parse::<u64>().unwrap_or(0);
|
||||
*DRAGGED.lock().unwrap() = if id > 0 { vec![id] } else { Vec::new() };
|
||||
log::info!("[qbz-qt][drag] start {track_id} ({title})");
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_drag_count(if id > 0 { 1 } else { 0 });
|
||||
b.as_mut().set_drag_title(QString::from(title.as_str()));
|
||||
b.as_mut().set_drag_subtitle(QString::from(subtitle.as_str()));
|
||||
@@ -746,7 +770,7 @@ pub(crate) fn drag_start(track_id: String, title: String, subtitle: String, x: f
|
||||
}
|
||||
|
||||
pub(crate) fn drag_move(x: f32, y: f32) {
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_drag_x(x);
|
||||
b.as_mut().set_drag_y(y);
|
||||
});
|
||||
@@ -754,7 +778,7 @@ pub(crate) fn drag_move(x: f32, y: f32) {
|
||||
|
||||
pub(crate) fn drag_set_over(playlist_id: String) {
|
||||
*DRAG_OVER.lock().unwrap() = playlist_id.clone();
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
b.as_mut()
|
||||
.set_drag_over_playlist_id(QString::from(playlist_id.as_str()));
|
||||
});
|
||||
@@ -762,7 +786,7 @@ pub(crate) fn drag_set_over(playlist_id: String) {
|
||||
|
||||
pub(crate) fn drag_end() {
|
||||
let pid = std::mem::take(&mut *DRAG_OVER.lock().unwrap());
|
||||
ui(|mut b| {
|
||||
shell_bridge::ui(|mut b| {
|
||||
b.as_mut().set_drag_active(false);
|
||||
b.as_mut().set_drag_over_playlist_id(QString::default());
|
||||
});
|
||||
@@ -810,7 +834,7 @@ pub(crate) fn playlist_set_follow_by_id(playlist_id: u64, follow: bool) {
|
||||
pub(crate) fn npb_set_mode(mode: i32) {
|
||||
let mode = settings_qt::set_npb_mode(mode);
|
||||
log::info!("[qbz-qt] npb_mode -> {mode}");
|
||||
ui(move |mut b| {
|
||||
shell_bridge::ui(move |mut b| {
|
||||
if mode == 3 {
|
||||
b.as_mut().set_sidebar_state(0);
|
||||
}
|
||||
@@ -930,7 +954,7 @@ pub(crate) fn apply_language(code: String) {
|
||||
};
|
||||
qbz_i18n::set_language(&resolved);
|
||||
log::info!("[qbz-qt] language -> {code} (resolved: {resolved})");
|
||||
ui(move |mut b| {
|
||||
session_bridge::ui(move |mut b| {
|
||||
let next = b.as_ref().tr_rev() + 1;
|
||||
b.as_mut().set_tr_rev(next);
|
||||
});
|
||||
@@ -1072,7 +1096,7 @@ pub(crate) fn theme_set(slug: String) {
|
||||
pub(crate) fn theme_set_filter(index: i32) {
|
||||
let index = index.clamp(0, 2);
|
||||
theme_qt::set_theme_filter(index);
|
||||
ui(move |mut b| b.as_mut().set_theme_filter(index));
|
||||
shell_bridge::ui(move |mut b| b.as_mut().set_theme_filter(index));
|
||||
}
|
||||
|
||||
/// Integrations panel non-toggle actions (Last.fm connect flow, LB/LFM
|
||||
@@ -1089,7 +1113,7 @@ pub(crate) fn integrations_action(action: String) {
|
||||
pub(crate) fn toggle_system_title_bar() {
|
||||
let next = settings_qt::toggle_system_title_bar();
|
||||
log::info!("[qbz-qt] use_system_title_bar -> {next} (applies on next launch)");
|
||||
ui(move |mut b| b.as_mut().set_system_title_bar_pref(next));
|
||||
shell_bridge::ui(move |mut b| b.as_mut().set_system_title_bar_pref(next));
|
||||
}
|
||||
|
||||
/// App-menu ambient toggle: persist the flipped pref and apply LIVE (the
|
||||
@@ -1097,7 +1121,7 @@ pub(crate) fn toggle_system_title_bar() {
|
||||
pub(crate) fn toggle_ambient_background() {
|
||||
let mode = settings_qt::toggle_ambient_background();
|
||||
log::info!("[qbz-qt] app_background -> {}", if mode == 1 { "ambient" } else { "off" });
|
||||
ui(move |mut b| b.as_mut().set_ambient_mode(mode));
|
||||
shell_bridge::ui(move |mut b| b.as_mut().set_ambient_mode(mode));
|
||||
}
|
||||
|
||||
fn load_library_once() {
|
||||
@@ -1247,7 +1271,7 @@ pub(crate) fn reload_home() {
|
||||
if offline_fwd::engine().status().is_offline() {
|
||||
return;
|
||||
}
|
||||
ui(|mut b| {
|
||||
home_bridge::ui(|mut b| {
|
||||
b.as_mut().set_home_loading(true);
|
||||
b.as_mut().set_home_error(QString::from(""));
|
||||
});
|
||||
@@ -1287,11 +1311,11 @@ pub(crate) fn reload_home() {
|
||||
});
|
||||
}
|
||||
|
||||
ui(|mut b| b.as_mut().set_home_loading(false));
|
||||
home_bridge::ui(|mut b| b.as_mut().set_home_loading(false));
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[qbz-qt] home load failed: {e}");
|
||||
ui(move |mut b| {
|
||||
home_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_home_error(QString::from(e.as_str()));
|
||||
b.as_mut().set_home_loading(false);
|
||||
});
|
||||
@@ -1304,7 +1328,7 @@ fn publish_home_sections(sections: &home_qt::DiscoverSections) {
|
||||
let home_json = serde_json::to_string(§ions.home).unwrap_or_else(|_| "[]".to_string());
|
||||
let editor_json = serde_json::to_string(§ions.editor).unwrap_or_else(|_| "[]".to_string());
|
||||
let for_you_json = serde_json::to_string(§ions.for_you).unwrap_or_else(|_| "[]".to_string());
|
||||
ui(move |mut b| {
|
||||
home_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_home_sections_json(QString::from(home_json.as_str()));
|
||||
b.as_mut()
|
||||
.set_editor_sections_json(QString::from(editor_json.as_str()));
|
||||
|
||||
@@ -72,7 +72,7 @@ fn snapshot(h: &NavHistory) -> (bool, bool, String) {
|
||||
}
|
||||
|
||||
fn publish(can_back: bool, can_forward: bool, current: String) {
|
||||
crate::ui(move |mut b| {
|
||||
crate::shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_can_back(can_back);
|
||||
b.as_mut().set_can_forward(can_forward);
|
||||
b.as_mut().set_current_view(QString::from(current.as_str()));
|
||||
|
||||
@@ -92,7 +92,7 @@ fn mutate(f: impl FnOnce(&mut NowPlayingModel)) {
|
||||
/// Push the full model onto the bridge (Qt thread hop).
|
||||
fn publish(m: &NowPlayingModel) {
|
||||
let m = m.clone();
|
||||
crate::ui(move |mut b| {
|
||||
crate::player_bridge::ui(move |mut b| {
|
||||
let progress = if m.duration_secs > 0 {
|
||||
(m.elapsed_secs as f32 / m.duration_secs as f32).clamp(0.0, 1.0)
|
||||
} else {
|
||||
@@ -253,7 +253,7 @@ pub fn set_position(elapsed_secs: i32, duration_secs: i32, playing: bool, cache:
|
||||
/// mirroring progress.
|
||||
fn publish_with_cache(m: &NowPlayingModel) {
|
||||
let m = m.clone();
|
||||
crate::ui(move |mut b| {
|
||||
crate::player_bridge::ui(move |mut b| {
|
||||
let progress = if m.duration_secs > 0 {
|
||||
(m.elapsed_secs as f32 / m.duration_secs as f32).clamp(0.0, 1.0)
|
||||
} else {
|
||||
|
||||
@@ -63,13 +63,13 @@ pub fn start() {
|
||||
/// login. Spawned once from the boot sequence.
|
||||
pub fn start_ui_forwarder() {
|
||||
let has_previous = UserDataPaths::load_last_user_id().is_some();
|
||||
crate::ui(move |mut b| b.as_mut().set_has_previous_session(has_previous));
|
||||
crate::session_bridge::ui(move |mut b| b.as_mut().set_has_previous_session(has_previous));
|
||||
|
||||
crate::spawn(async move {
|
||||
let mut rx = engine().subscribe();
|
||||
loop {
|
||||
let status = *rx.borrow_and_update();
|
||||
crate::ui(move |b| apply_status(b, status));
|
||||
crate::session_bridge::ui(move |b| apply_status(b, status));
|
||||
if rx.changed().await.is_err() {
|
||||
break;
|
||||
}
|
||||
@@ -79,7 +79,10 @@ pub fn start_ui_forwarder() {
|
||||
|
||||
/// Push one engine status snapshot into the bridge (queued on the Qt
|
||||
/// thread via [`crate::ui`]).
|
||||
fn apply_status(mut b: core::pin::Pin<&mut crate::bridge::qbz_bridge::QbzBridge>, status: OfflineStatus) {
|
||||
fn apply_status(
|
||||
mut b: core::pin::Pin<&mut crate::session_bridge::qbz_session::QbzSession>,
|
||||
status: OfflineStatus,
|
||||
) {
|
||||
b.as_mut().set_offline(status.is_offline());
|
||||
b.as_mut().set_offline_mode(match status.mode {
|
||||
OfflineMode::Online => 0,
|
||||
|
||||
@@ -590,7 +590,7 @@ pub(crate) async fn refresh_now_playing(runtime: &Arc<AppRuntime<LoggingAdapter>
|
||||
let state = runtime.core().get_queue_state().await;
|
||||
let Some(track) = state.current_track else {
|
||||
crate::now_playing::clear_track();
|
||||
crate::ui(move |mut b| {
|
||||
crate::player_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_np_track_id(QString::from(""));
|
||||
});
|
||||
crate::lyrics_qt::publish_idle();
|
||||
@@ -601,7 +601,7 @@ pub(crate) async fn refresh_now_playing(runtime: &Arc<AppRuntime<LoggingAdapter>
|
||||
None => track.title.clone(),
|
||||
};
|
||||
let track_id_str = track.id.to_string();
|
||||
crate::ui(move |mut b| {
|
||||
crate::player_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_np_track_id(QString::from(track_id_str.as_str()));
|
||||
});
|
||||
log::info!(
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
//! QbzPlayer — now-playing/transport domain bridge (phase 23 split of the
|
||||
//! QbzBridge God-object; the pattern is documented in main.rs). Props: the
|
||||
//! NowPlayingState mirror (np_ prefix) + the playing-row indicator ids +
|
||||
//! show-context-icon. Invokables: transport + the play/enqueue entry points
|
||||
//! (album / artist / track / playlist) — one-line forwards into the crate
|
||||
//! handlers.
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_player {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// --- Now playing (Slint NowPlayingState; np_ prefix) ------------
|
||||
// POC: fed by a static NowPlayingModel (src/now_playing.rs) with
|
||||
// empty-state defaults; phase 4 swaps the data source for the real
|
||||
// player poll.
|
||||
#[qproperty(bool, np_has_track)]
|
||||
#[qproperty(QString, np_title)]
|
||||
#[qproperty(QString, np_artist)]
|
||||
#[qproperty(QString, np_artwork_path)]
|
||||
#[qproperty(i32, np_elapsed_secs)]
|
||||
#[qproperty(i32, np_duration_secs)]
|
||||
#[qproperty(f32, np_progress)]
|
||||
#[qproperty(f32, np_cache_progress)]
|
||||
#[qproperty(bool, np_playing)]
|
||||
#[qproperty(bool, np_loading)]
|
||||
#[qproperty(f32, np_volume)]
|
||||
#[qproperty(bool, np_muted)]
|
||||
#[qproperty(bool, np_shuffle)]
|
||||
// 0 off / 1 all / 2 one.
|
||||
#[qproperty(i32, np_repeat_mode)]
|
||||
// "hires" | "mp3" | "lossless" | "cd" (AudioStamp tier mapping).
|
||||
#[qproperty(QString, np_quality_tier)]
|
||||
// e.g. "24-bit / 96 kHz" (AudioStamp detail line).
|
||||
#[qproperty(QString, np_quality_label)]
|
||||
// Now-playing track id (playing-row indicator in track lists).
|
||||
#[qproperty(QString, np_track_id)]
|
||||
#[qproperty(QString, np_album)]
|
||||
#[qproperty(QString, np_album_id)]
|
||||
#[qproperty(QString, np_artist_id)]
|
||||
// "Show track playing context" pref (Playback settings) — feeds the
|
||||
// SongCard layers icon.
|
||||
#[qproperty(bool, show_context_icon)]
|
||||
|
||||
type QbzPlayer = super::QbzPlayerRust;
|
||||
|
||||
/// Registers this object's Qt-thread hop (Main.qml boots EVERY
|
||||
/// domain singleton; only QbzSession.boot also fires crate::on_boot).
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzPlayer>);
|
||||
|
||||
// --- Transport (phase 4 wires the player; POC log-and-noop, except
|
||||
// the pure-UI toggles which mutate the NowPlayingModel) -----------
|
||||
#[qinvokable]
|
||||
fn toggle_play(self: Pin<&mut QbzPlayer>);
|
||||
#[qinvokable]
|
||||
fn next(self: Pin<&mut QbzPlayer>);
|
||||
#[qinvokable]
|
||||
fn previous(self: Pin<&mut QbzPlayer>);
|
||||
#[qinvokable]
|
||||
fn seek(self: Pin<&mut QbzPlayer>, frac: f32);
|
||||
#[qinvokable]
|
||||
fn set_volume(self: Pin<&mut QbzPlayer>, volume: f32);
|
||||
#[qinvokable]
|
||||
fn toggle_mute(self: Pin<&mut QbzPlayer>);
|
||||
#[qinvokable]
|
||||
fn toggle_shuffle(self: Pin<&mut QbzPlayer>);
|
||||
#[qinvokable]
|
||||
fn cycle_repeat(self: Pin<&mut QbzPlayer>);
|
||||
|
||||
/// Album-card click on Home: resolve the album's tracks, enqueue,
|
||||
/// and play through the core's resolved path.
|
||||
#[qinvokable]
|
||||
fn play_album(self: Pin<&mut QbzPlayer>, album_id: QString);
|
||||
|
||||
/// AlbumView header Shuffle.
|
||||
#[qinvokable]
|
||||
fn play_album_shuffled(self: Pin<&mut QbzPlayer>, album_id: QString);
|
||||
/// AlbumView row play: the album starting AT this track.
|
||||
#[qinvokable]
|
||||
fn play_album_from(self: Pin<&mut QbzPlayer>, album_id: QString, track_id: QString);
|
||||
/// AlbumView row "Play next" ("next") / "Add to queue" ("later").
|
||||
#[qinvokable]
|
||||
fn enqueue_album_track(self: Pin<&mut QbzPlayer>, album_id: QString, track_id: QString, mode: QString);
|
||||
/// AlbumCard ⋯ menu: Play next ("next") / Add to queue ("later").
|
||||
#[qinvokable]
|
||||
fn enqueue_album(self: Pin<&mut QbzPlayer>, album_id: QString, mode: QString);
|
||||
/// ArtistView Popular Tracks row play (whole list as the queue).
|
||||
#[qinvokable]
|
||||
fn play_artist_track(self: Pin<&mut QbzPlayer>, track_id: QString);
|
||||
/// ArtistView "Play all" (shuffle=false) / "Shuffle all" (true).
|
||||
#[qinvokable]
|
||||
fn play_artist_top(self: Pin<&mut QbzPlayer>, shuffle: bool);
|
||||
/// ArtistView ⋯ "Add all to queue" — appends the top-tracks queue.
|
||||
#[qinvokable]
|
||||
fn enqueue_artist_top(self: Pin<&mut QbzPlayer>);
|
||||
/// Artist-card overlay play (ArtistGridCard): Popular tracks,
|
||||
/// falling back to the studio discography (playback.rs play_artist).
|
||||
#[qinvokable]
|
||||
fn play_artist_card(self: Pin<&mut QbzPlayer>, artist_id: QString);
|
||||
|
||||
/// Track-row click (Library): play the track as a 1-element queue.
|
||||
#[qinvokable]
|
||||
fn play_track(self: Pin<&mut QbzPlayer>, track_id: QString);
|
||||
/// Library track context menus: Play next ("next") / Play later
|
||||
/// ("later") / Add to queue ("queue") on a single feed track.
|
||||
#[qinvokable]
|
||||
fn enqueue_track(self: Pin<&mut QbzPlayer>, track_id: QString, mode: QString);
|
||||
|
||||
/// Card-level playlist actions (LibPlaylistCard overlay + menu).
|
||||
#[qinvokable]
|
||||
fn play_playlist_by_id(self: Pin<&mut QbzPlayer>, playlist_id: QString);
|
||||
#[qinvokable]
|
||||
fn enqueue_playlist_by_id(self: Pin<&mut QbzPlayer>, playlist_id: QString, mode: QString);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzPlayer {}
|
||||
}
|
||||
|
||||
use qbz_player::QbzPlayer;
|
||||
|
||||
/// Rust side of the player bridge (plain storage, phase-1 pattern).
|
||||
pub struct QbzPlayerRust {
|
||||
np_has_track: bool,
|
||||
np_title: QString,
|
||||
np_artist: QString,
|
||||
np_artwork_path: QString,
|
||||
np_elapsed_secs: i32,
|
||||
np_duration_secs: i32,
|
||||
np_progress: f32,
|
||||
np_cache_progress: f32,
|
||||
np_playing: bool,
|
||||
np_loading: bool,
|
||||
np_volume: f32,
|
||||
np_muted: bool,
|
||||
np_shuffle: bool,
|
||||
np_repeat_mode: i32,
|
||||
np_quality_tier: QString,
|
||||
np_quality_label: QString,
|
||||
np_track_id: QString,
|
||||
np_album: QString,
|
||||
np_album_id: QString,
|
||||
np_artist_id: QString,
|
||||
show_context_icon: bool,
|
||||
}
|
||||
|
||||
impl Default for QbzPlayerRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// A derive would zero these — the model's sane defaults instead.
|
||||
np_volume: 1.0,
|
||||
np_quality_tier: QString::from("cd"),
|
||||
np_has_track: false,
|
||||
np_title: QString::default(),
|
||||
np_artist: QString::default(),
|
||||
np_artwork_path: QString::default(),
|
||||
np_elapsed_secs: 0,
|
||||
np_duration_secs: 0,
|
||||
np_progress: 0.0,
|
||||
np_cache_progress: 0.0,
|
||||
np_playing: false,
|
||||
np_loading: false,
|
||||
np_muted: false,
|
||||
np_shuffle: false,
|
||||
np_repeat_mode: 0,
|
||||
np_quality_label: QString::default(),
|
||||
np_track_id: QString::default(),
|
||||
np_album: QString::default(),
|
||||
np_album_id: QString::default(),
|
||||
np_artist_id: QString::default(),
|
||||
show_context_icon: crate::settings_qt::show_context_icon(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The UI hop (bridges/mod.rs pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzPlayer>> = OnceLock::new();
|
||||
|
||||
/// Queue a player-bridge mutation onto the Qt event loop (no-op before
|
||||
/// boot registers the thread).
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzPlayer>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_player::QbzPlayer {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] player Qt thread already registered");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_play(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_play();
|
||||
}
|
||||
|
||||
pub fn next(self: Pin<&mut Self>) {
|
||||
crate::transport_next();
|
||||
}
|
||||
|
||||
pub fn previous(self: Pin<&mut Self>) {
|
||||
crate::transport_previous();
|
||||
}
|
||||
|
||||
pub fn seek(self: Pin<&mut Self>, frac: f32) {
|
||||
crate::transport_seek(frac);
|
||||
}
|
||||
|
||||
pub fn set_volume(self: Pin<&mut Self>, volume: f32) {
|
||||
crate::transport_set_volume(volume);
|
||||
}
|
||||
|
||||
pub fn toggle_mute(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_mute();
|
||||
}
|
||||
|
||||
pub fn toggle_shuffle(self: Pin<&mut Self>) {
|
||||
crate::transport_toggle_shuffle();
|
||||
}
|
||||
|
||||
pub fn cycle_repeat(self: Pin<&mut Self>) {
|
||||
crate::transport_cycle_repeat();
|
||||
}
|
||||
|
||||
pub fn play_album(self: Pin<&mut Self>, album_id: QString) {
|
||||
crate::play_album(album_id.to_string());
|
||||
}
|
||||
|
||||
pub fn play_album_shuffled(self: Pin<&mut Self>, album_id: QString) {
|
||||
crate::play_album_shuffled(album_id.to_string());
|
||||
}
|
||||
|
||||
pub fn play_album_from(self: Pin<&mut Self>, album_id: QString, track_id: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_album_from_track(album_id.to_string(), tid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_album_track(self: Pin<&mut Self>, album_id: QString, track_id: QString, mode: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_album_track(album_id.to_string(), tid, mode.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_album(self: Pin<&mut Self>, album_id: QString, mode: QString) {
|
||||
crate::enqueue_album(album_id.to_string(), mode.to_string());
|
||||
}
|
||||
|
||||
pub fn play_artist_track(self: Pin<&mut Self>, track_id: QString) {
|
||||
if let Ok(tid) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_artist_track(tid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_artist_top(self: Pin<&mut Self>, shuffle: bool) {
|
||||
crate::play_artist_top(shuffle);
|
||||
}
|
||||
|
||||
pub fn enqueue_artist_top(self: Pin<&mut Self>) {
|
||||
crate::enqueue_artist_top();
|
||||
}
|
||||
|
||||
pub fn play_artist_card(self: Pin<&mut Self>, artist_id: QString) {
|
||||
crate::play_artist_card(artist_id.to_string());
|
||||
}
|
||||
|
||||
pub fn play_track(self: Pin<&mut Self>, track_id: QString) {
|
||||
if let Ok(id) = track_id.to_string().parse::<u64>() {
|
||||
crate::play_track(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_track(self: Pin<&mut Self>, track_id: QString, mode: QString) {
|
||||
if let Ok(id) = track_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_track(id, mode.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_playlist_by_id(self: Pin<&mut Self>, playlist_id: QString) {
|
||||
if let Ok(pid) = playlist_id.to_string().parse::<u64>() {
|
||||
crate::play_playlist_by_id(pid);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enqueue_playlist_by_id(self: Pin<&mut Self>, playlist_id: QString, mode: QString) {
|
||||
if let Ok(pid) = playlist_id.to_string().parse::<u64>() {
|
||||
crate::enqueue_playlist_by_id(pid, mode.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//! QbzQueue — queue-panel domain bridge (phase 23 split of the QbzBridge
|
||||
//! God-object; the pattern is documented in main.rs). Props: the legacy
|
||||
//! queueModel list + the queueJson document. Invokables: the panel's
|
||||
//! tabs/pagination/search and row actions — one-line forwards into the
|
||||
//! crate handlers.
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_queue {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
|
||||
include!("cxx-qt-lib/qvariant.h");
|
||||
type QVariant = cxx_qt_lib::QVariant;
|
||||
|
||||
include!("cxx-qt-lib/qlist.h");
|
||||
type QList_QVariant = cxx_qt_lib::QList<cxx_qt_lib::QVariant>;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// --- Queue panel -------------------------------------------------
|
||||
// POC: empty until phase 4 (QML shows the empty states).
|
||||
#[qproperty(QList_QVariant, queue_model)]
|
||||
|
||||
// --- Queue panel (phase 9) -----------------------------------------
|
||||
// One JSON document (queue_qt.rs QueueDoc: current/upcoming/history
|
||||
// + pagination + #442 section markers). Supersedes `queueModel`.
|
||||
#[qproperty(QString, queue_json)]
|
||||
|
||||
type QbzQueue = super::QbzQueueRust;
|
||||
|
||||
/// Registers this object's Qt-thread hop (Main.qml boots EVERY
|
||||
/// domain singleton; only QbzSession.boot also fires crate::on_boot).
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzQueue>);
|
||||
|
||||
/// Queue panel: tabs/pagination/search.
|
||||
#[qinvokable]
|
||||
fn queue_set_page(self: Pin<&mut QbzQueue>, page: i32);
|
||||
#[qinvokable]
|
||||
fn queue_set_search(self: Pin<&mut QbzQueue>, query: QString);
|
||||
/// Row actions.
|
||||
#[qinvokable]
|
||||
fn queue_play_upcoming(self: Pin<&mut QbzQueue>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_remove_upcoming(self: Pin<&mut QbzQueue>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_remove_all_after(self: Pin<&mut QbzQueue>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_move_track(self: Pin<&mut QbzQueue>, from: i32, to: i32);
|
||||
#[qinvokable]
|
||||
fn queue_play_history(self: Pin<&mut QbzQueue>, index: i32);
|
||||
#[qinvokable]
|
||||
fn queue_toggle_favorite(self: Pin<&mut QbzQueue>, kind: QString, id: QString);
|
||||
/// Footer: Clear queue.
|
||||
#[qinvokable]
|
||||
fn queue_clear(self: Pin<&mut QbzQueue>);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzQueue {}
|
||||
}
|
||||
|
||||
use cxx_qt_lib::{QList, QVariant};
|
||||
use qbz_queue::QbzQueue;
|
||||
|
||||
type QListQVariant = QList<QVariant>;
|
||||
|
||||
/// Rust side of the queue bridge (plain storage, phase-1 pattern).
|
||||
pub struct QbzQueueRust {
|
||||
queue_model: QListQVariant,
|
||||
queue_json: QString,
|
||||
}
|
||||
|
||||
impl Default for QbzQueueRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
queue_model: QListQVariant::default(),
|
||||
queue_json: QString::from("{}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The UI hop (bridges/mod.rs pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzQueue>> = OnceLock::new();
|
||||
|
||||
/// Queue a queue-bridge mutation onto the Qt event loop (no-op before
|
||||
/// boot registers the thread).
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzQueue>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_queue::QbzQueue {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] queue Qt thread already registered");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_set_page(self: Pin<&mut Self>, page: i32) {
|
||||
crate::queue_set_page(page);
|
||||
}
|
||||
|
||||
pub fn queue_set_search(self: Pin<&mut Self>, query: QString) {
|
||||
crate::queue_set_search(query.to_string());
|
||||
}
|
||||
|
||||
pub fn queue_play_upcoming(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_play_upcoming(index);
|
||||
}
|
||||
|
||||
pub fn queue_remove_upcoming(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_remove_upcoming(index);
|
||||
}
|
||||
|
||||
pub fn queue_remove_all_after(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_remove_all_after(index);
|
||||
}
|
||||
|
||||
pub fn queue_move_track(self: Pin<&mut Self>, from: i32, to: i32) {
|
||||
crate::queue_move_track(from, to);
|
||||
}
|
||||
|
||||
pub fn queue_play_history(self: Pin<&mut Self>, index: i32) {
|
||||
crate::queue_play_history(index);
|
||||
}
|
||||
|
||||
pub fn queue_toggle_favorite(self: Pin<&mut Self>, kind: QString, id: QString) {
|
||||
crate::queue_toggle_favorite(kind.to_string(), id.to_string());
|
||||
}
|
||||
|
||||
pub fn queue_clear(self: Pin<&mut Self>) {
|
||||
crate::queue_clear();
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,7 @@ pub async fn publish(runtime: &Arc<AppRuntime<LoggingAdapter>>) {
|
||||
},
|
||||
};
|
||||
let json = serde_json::to_string(&doc).unwrap_or_else(|_| "{}".into());
|
||||
crate::ui(move |mut b| {
|
||||
crate::queue_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_queue_json(QString::from(json.as_str()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
//! QbzSession — session/auth/offline/i18n domain bridge (phase 23 split of
|
||||
//! the QbzBridge God-object; the pattern is documented in main.rs). Props:
|
||||
//! screen/login/offline/connectivity/recovery/session-user + trRev.
|
||||
//! Invokables: boot (THE app boot — registers this object's thread hop AND
|
||||
//! fires crate::on_boot once), the login/logout flow, and `tr` (the one
|
||||
//! i18n lookup every QML string binds to, with the trRev dependency arg).
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_session {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// "splash" | "login" | "shell"
|
||||
#[qproperty(QString, screen)]
|
||||
// Login narration: 0 idle / 1 waiting-for-browser / 2 authenticating.
|
||||
#[qproperty(i32, login_phase)]
|
||||
// Last SIGN-IN failure message ("" = none). Mirrors Slint
|
||||
// `LoginState.error`.
|
||||
#[qproperty(QString, login_error)]
|
||||
// Session-RESTORE failure message ("" = none). Mirrors Slint
|
||||
// `OfflineState.login-error`.
|
||||
#[qproperty(QString, restore_error)]
|
||||
// Offline-MODE engine snapshot (Slint `OfflineState`):
|
||||
#[qproperty(bool, offline)]
|
||||
// 0 online / 1 real offline / 2 induced offline.
|
||||
#[qproperty(i32, offline_mode)]
|
||||
// 0 unknown / 1 up / 2 down.
|
||||
#[qproperty(i32, connectivity)]
|
||||
#[qproperty(bool, captive_portal)]
|
||||
#[qproperty(bool, has_previous_session)]
|
||||
#[qproperty(bool, show_recovery_banner)]
|
||||
#[qproperty(bool, offline_session)]
|
||||
// Shell session header:
|
||||
#[qproperty(QString, session_user_name)]
|
||||
#[qproperty(QString, session_subscription)]
|
||||
// Translation revision (phase 20): bumped on every live language
|
||||
// switch; every `QbzSession.tr(msgid, QbzSession.trRev)` binding
|
||||
// depends on it.
|
||||
#[qproperty(i32, tr_rev)]
|
||||
|
||||
type QbzSession = super::QbzSessionRust;
|
||||
|
||||
/// The first invokable (Main.qml Component.onCompleted): register
|
||||
/// this object's Qt-thread hop, then run the app boot sequence.
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzSession>);
|
||||
/// Start the Qobuz browser-OAuth login flow.
|
||||
#[qinvokable]
|
||||
fn sign_in_via_browser(self: Pin<&mut QbzSession>);
|
||||
/// Abort the in-flight login (back to idle).
|
||||
#[qinvokable]
|
||||
fn cancel_login(self: Pin<&mut QbzSession>);
|
||||
/// Continue in induced-offline mode (no Qobuz services).
|
||||
#[qinvokable]
|
||||
fn start_offline(self: Pin<&mut QbzSession>);
|
||||
/// Retry session restore after a failure.
|
||||
#[qinvokable]
|
||||
fn recovery_login(self: Pin<&mut QbzSession>);
|
||||
/// Open the Qobuz Terms of Service in the browser.
|
||||
#[qinvokable]
|
||||
fn open_tos(self: Pin<&mut QbzSession>);
|
||||
/// Shell logout: back to the login screen.
|
||||
#[qinvokable]
|
||||
fn logout(self: Pin<&mut QbzSession>);
|
||||
|
||||
/// i18n lookup against the shared gettext catalog (qbz-i18n), so the
|
||||
/// QML texts reuse the EXISTING .po translations via the same msgids
|
||||
/// the Slint `@tr()` calls use. `rev` is the trRev binding
|
||||
/// dependency — unused in Rust, it only forces QML re-evaluation
|
||||
/// when the language changes live.
|
||||
#[qinvokable]
|
||||
fn tr(self: &QbzSession, msgid: QString, rev: i32) -> QString;
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzSession {}
|
||||
}
|
||||
|
||||
use qbz_session::QbzSession;
|
||||
|
||||
/// Rust side of the session bridge (plain storage, phase-1 pattern).
|
||||
pub struct QbzSessionRust {
|
||||
screen: QString,
|
||||
login_phase: i32,
|
||||
login_error: QString,
|
||||
restore_error: QString,
|
||||
offline: bool,
|
||||
offline_mode: i32,
|
||||
connectivity: i32,
|
||||
captive_portal: bool,
|
||||
has_previous_session: bool,
|
||||
show_recovery_banner: bool,
|
||||
offline_session: bool,
|
||||
session_user_name: QString,
|
||||
session_subscription: QString,
|
||||
tr_rev: i32,
|
||||
}
|
||||
|
||||
impl Default for QbzSessionRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
screen: QString::from("splash"),
|
||||
login_phase: 0,
|
||||
login_error: QString::default(),
|
||||
restore_error: QString::default(),
|
||||
offline: false,
|
||||
offline_mode: 0,
|
||||
connectivity: 0,
|
||||
captive_portal: false,
|
||||
has_previous_session: false,
|
||||
show_recovery_banner: false,
|
||||
offline_session: false,
|
||||
session_user_name: QString::default(),
|
||||
session_subscription: QString::default(),
|
||||
tr_rev: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The UI hop (bridges/mod.rs pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzSession>> = OnceLock::new();
|
||||
|
||||
/// Queue a session-bridge mutation onto the Qt event loop (no-op before
|
||||
/// boot registers the thread).
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzSession>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_session::QbzSession {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] session Qt thread already registered");
|
||||
}
|
||||
crate::on_boot();
|
||||
}
|
||||
|
||||
pub fn sign_in_via_browser(self: Pin<&mut Self>) {
|
||||
crate::start_login();
|
||||
}
|
||||
|
||||
pub fn cancel_login(self: Pin<&mut Self>) {
|
||||
crate::cancel_login();
|
||||
}
|
||||
|
||||
pub fn start_offline(self: Pin<&mut Self>) {
|
||||
crate::start_offline();
|
||||
}
|
||||
|
||||
pub fn recovery_login(self: Pin<&mut Self>) {
|
||||
crate::start_login();
|
||||
}
|
||||
|
||||
pub fn open_tos(self: Pin<&mut Self>) {
|
||||
// Same URL the Slint shell opens (crates/qbz/src/main.rs
|
||||
// QOBUZ_TOS_URL). `open` spawns xdg-open detached, so this is safe
|
||||
// to run on the Qt thread.
|
||||
if let Err(e) = open::that(crate::QOBUZ_TOS_URL) {
|
||||
log::error!("[qbz-qt] failed to open Terms of Service: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logout(self: Pin<&mut Self>) {
|
||||
crate::do_logout();
|
||||
}
|
||||
|
||||
pub fn tr(&self, msgid: QString, rev: i32) -> QString {
|
||||
let _ = rev; // binding dependency only (see the bridge declaration)
|
||||
QString::from(&qbz_i18n::t(&msgid.to_string()))
|
||||
}
|
||||
}
|
||||
@@ -1141,7 +1141,7 @@ pub async fn settings_bool(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str,
|
||||
// Restart semantics (1:1 Slint): the APPLIED mode is untouched;
|
||||
// only the menu/row state flips live.
|
||||
log::info!("[qbz-qt] use_system_title_bar -> {value} (applies on next launch)");
|
||||
crate::ui(move |mut b| b.as_mut().set_system_title_bar_pref(value));
|
||||
crate::shell_bridge::ui(move |mut b| b.as_mut().set_system_title_bar_pref(value));
|
||||
Ok(Apply::None)
|
||||
}
|
||||
"hide-title-bar" => {
|
||||
@@ -1323,7 +1323,7 @@ pub async fn settings_select(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &st
|
||||
save_pref("app_background", serde_json::json!(mode));
|
||||
// Live (pure QML layering; blurred renders AS ambient in the POC).
|
||||
let ambient = if *mode == "off" { 0 } else { 1 };
|
||||
crate::ui(move |mut b| b.as_mut().set_ambient_mode(ambient));
|
||||
crate::shell_bridge::ui(move |mut b| b.as_mut().set_ambient_mode(ambient));
|
||||
}
|
||||
"language" => {
|
||||
let Some(lang) = LANGUAGE_VALUES.get(index) else {
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
//! QbzShell — shell-chrome domain bridge (phase 23 split of the QbzBridge
|
||||
//! God-object; the pattern is documented in main.rs). Props: sidebar / queue
|
||||
//! / nav-history / now-playing-bar mode / lyrics panel / window chrome /
|
||||
//! ambient background / theme / sidebar playlist tree / drag & drop.
|
||||
//! Invokables: the shell actions (sidebar cycle, nav, npb mode, theme,
|
||||
//! sidebar tree, drag & drop) — one-line forwards into the crate handlers.
|
||||
|
||||
use std::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_shell {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// --- Shell chrome (Slint ShellState) ----------------------------
|
||||
// Three-state sidebar: 0 = open (240px), 1 = mini (64px), 2 = closed.
|
||||
#[qproperty(i32, sidebar_state)]
|
||||
#[qproperty(bool, queue_open)]
|
||||
// Content view id — only "home" exists (phase 3 adds the rest).
|
||||
#[qproperty(QString, current_view)]
|
||||
// Nav history (src/nav_qt.rs):
|
||||
#[qproperty(bool, can_back)]
|
||||
#[qproperty(bool, can_forward)]
|
||||
|
||||
// Now-playing bar mode (phase 18): 0 New / 1 Classic / 2 Small /
|
||||
// 3 Large — the ui_prefs npb_mode key, live-switchable.
|
||||
#[qproperty(i32, npb_mode)]
|
||||
|
||||
// --- Theme (phase 19) --------------------------------------------
|
||||
// ONE JSON token document (theme_qt.rs ThemeTokens: 30 colors + 24
|
||||
// alpha tiers + ambient derivations + isDark) — QbzTheme.qml binds
|
||||
// to it, so a theme switch repaints the whole app live (the Slint
|
||||
// theme::push_colors equivalent).
|
||||
#[qproperty(QString, theme_json)]
|
||||
// The persisted ui_prefs theme slug ("oled", "auto", "custom", ...).
|
||||
#[qproperty(QString, theme_slug)]
|
||||
// The dropdown catalog (36 registry themes + Auto/Custom rows).
|
||||
#[qproperty(QString, theme_list_json)]
|
||||
// Dropdown filter: 0 All / 1 Dark / 2 Light (ui_prefs theme_filter).
|
||||
#[qproperty(i32, theme_filter)]
|
||||
|
||||
// --- Lyrics panel (phase 9) ----------------------------------------
|
||||
#[qproperty(bool, lyrics_open)]
|
||||
// One JSON document (lyrics_qt.rs LyricsDoc: status/lines/synced/
|
||||
// provider/error).
|
||||
#[qproperty(QString, lyrics_json)]
|
||||
|
||||
// --- Sidebar playlist tree ---------------------------------------
|
||||
// One JSON document: the flattened entries (folders + playlists,
|
||||
// expand/sort/search applied Rust-side — sidebar_qt.rs).
|
||||
#[qproperty(QString, sidebar_json)]
|
||||
#[qproperty(QString, sidebar_sort_by)]
|
||||
#[qproperty(bool, sidebar_sort_asc)]
|
||||
|
||||
// --- Window chrome (phase 12) --------------------------------------
|
||||
// The APPLIED titlebar mode (the ui_prefs `use_system_title_bar`
|
||||
// value read at startup — drives the window flags; never mutated at
|
||||
// runtime, matching the Slint restart semantics).
|
||||
#[qproperty(bool, system_title_bar)]
|
||||
// The PERSISTED pref as it stands NOW (the app-menu check state;
|
||||
// flips live when the user toggles, applies on the next launch).
|
||||
#[qproperty(bool, system_title_bar_pref)]
|
||||
|
||||
// --- Ambient background (phase 14) --------------------------------
|
||||
// 0 = off, 1 = on (the ambient look; the owner's store carries
|
||||
// "ambient"). Live — toggling applies immediately (pure QML
|
||||
// layering). Combined in QML with npHasTrack for the D4 no-track
|
||||
// rule.
|
||||
#[qproperty(i32, ambient_mode)]
|
||||
// Album-art triad (ambient_qt.rs), pushed on track change.
|
||||
#[qproperty(QString, ambient_primary)]
|
||||
#[qproperty(QString, ambient_secondary)]
|
||||
#[qproperty(QString, ambient_accent)]
|
||||
// Look knobs (Slint AppearanceState defaults; QBZ_BG_* env seed).
|
||||
#[qproperty(f32, ambient_dim)]
|
||||
#[qproperty(f32, ambient_surface_alpha)]
|
||||
#[qproperty(f32, ambient_bar_alpha)]
|
||||
|
||||
// --- Drag & drop (phase 17) ----------------------------------------
|
||||
// The shared drag state (DragState in state.slint): the ghost reads
|
||||
// count/title/subtitle + the window-coord pointer; sidebar playlist
|
||||
// rows self-detect the drop via dragX/dragY + over-playlist-id.
|
||||
#[qproperty(bool, drag_active)]
|
||||
#[qproperty(i32, drag_count)]
|
||||
#[qproperty(QString, drag_title)]
|
||||
#[qproperty(QString, drag_subtitle)]
|
||||
#[qproperty(f32, drag_x)]
|
||||
#[qproperty(f32, drag_y)]
|
||||
#[qproperty(QString, drag_over_playlist_id)]
|
||||
|
||||
type QbzShell = super::QbzShellRust;
|
||||
|
||||
/// Registers this object's Qt-thread hop (Main.qml boots EVERY
|
||||
/// domain singleton; only QbzSession.boot also fires crate::on_boot).
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzShell>);
|
||||
|
||||
// --- Shell chrome -------------------------------------------------
|
||||
/// Header panel-left button: cycle the sidebar open -> mini ->
|
||||
/// closed -> open (Slint `ShellState.cycle-sidebar()`).
|
||||
#[qinvokable]
|
||||
fn cycle_sidebar(self: Pin<&mut QbzShell>);
|
||||
/// NPB queue button / queue panel close.
|
||||
#[qinvokable]
|
||||
fn toggle_queue(self: Pin<&mut QbzShell>);
|
||||
/// Header history buttons.
|
||||
#[qinvokable]
|
||||
fn navigate_back(self: Pin<&mut QbzShell>);
|
||||
#[qinvokable]
|
||||
fn navigate_forward(self: Pin<&mut QbzShell>);
|
||||
|
||||
/// NPB lyrics button / lyrics panel close X.
|
||||
#[qinvokable]
|
||||
fn toggle_lyrics(self: Pin<&mut QbzShell>);
|
||||
|
||||
/// Sidebar navigation: record a content view ("home" | "library")
|
||||
/// and lazy-load its data on first visit.
|
||||
#[qinvokable]
|
||||
fn navigate_to(self: Pin<&mut QbzShell>, view: QString);
|
||||
|
||||
/// Now-Playing-view flyout: switch the bar mode (0 New / 1 Classic /
|
||||
/// 2 Small / 3 Large) — persists ui_prefs.npb_mode; Large forces the
|
||||
/// sidebar open (the Slint "large" arm).
|
||||
#[qinvokable]
|
||||
fn npb_set_mode(self: Pin<&mut QbzShell>, mode: i32);
|
||||
|
||||
// --- Theme (phase 19) ---------------------------------------------
|
||||
/// Appearance > Theme row: persist the picked slug + republish
|
||||
/// `themeJson` (live switch).
|
||||
#[qinvokable]
|
||||
fn theme_set(self: Pin<&mut QbzShell>, slug: QString);
|
||||
/// Appearance > theme filter cycle button (0 All / 1 Dark / 2 Light).
|
||||
#[qinvokable]
|
||||
fn theme_set_filter(self: Pin<&mut QbzShell>, index: i32);
|
||||
|
||||
/// App-menu chrome toggle: flip the persisted `use_system_title_bar`
|
||||
/// pref (applies on the next launch — the window flags are fixed at
|
||||
/// creation, 1:1 Slint). Updates `systemTitleBarPref` only.
|
||||
#[qinvokable]
|
||||
fn toggle_system_title_bar(self: Pin<&mut QbzShell>);
|
||||
|
||||
/// App-menu ambient toggle: flip the persisted `app_background` pref
|
||||
/// off <-> "ambient" and apply LIVE (pure QML layering — no restart,
|
||||
/// unlike the titlebar).
|
||||
#[qinvokable]
|
||||
fn toggle_ambient_background(self: Pin<&mut QbzShell>);
|
||||
|
||||
/// Sidebar tree: rebuild + republish after load / sort / search /
|
||||
/// folder toggle.
|
||||
#[qinvokable]
|
||||
fn reload_sidebar(self: Pin<&mut QbzShell>);
|
||||
#[qinvokable]
|
||||
fn sidebar_set_sort(self: Pin<&mut QbzShell>, option: QString);
|
||||
#[qinvokable]
|
||||
fn sidebar_search(self: Pin<&mut QbzShell>, query: QString);
|
||||
#[qinvokable]
|
||||
fn sidebar_toggle_folder(self: Pin<&mut QbzShell>, id: QString);
|
||||
/// Sidebar "+" — create an empty playlist (single core call), then
|
||||
/// reload the tree.
|
||||
#[qinvokable]
|
||||
fn create_playlist(self: Pin<&mut QbzShell>);
|
||||
/// Sidebar cover dispatch: JSON array of cover URLS (the tree's
|
||||
/// collage is url-keyed, unlike the feed's artKey).
|
||||
#[qinvokable]
|
||||
fn sidebar_artwork_window(self: Pin<&mut QbzShell>, urls_json: QString);
|
||||
|
||||
// --- Drag & drop (phase 17) ----------------------------------------
|
||||
/// Track-row press-drag start (row id, ghost texts, window coords).
|
||||
#[qinvokable]
|
||||
fn drag_start(self: Pin<&mut QbzShell>, track_id: QString, title: QString, subtitle: QString, x: f32, y: f32);
|
||||
#[qinvokable]
|
||||
fn drag_move(self: Pin<&mut QbzShell>, x: f32, y: f32);
|
||||
/// A sidebar playlist row claims / releases the drop target.
|
||||
#[qinvokable]
|
||||
fn drag_set_over(self: Pin<&mut QbzShell>, playlist_id: QString);
|
||||
/// Release: add the dragged track(s) to the over-playlist target.
|
||||
#[qinvokable]
|
||||
fn drag_end(self: Pin<&mut QbzShell>);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzShell {}
|
||||
}
|
||||
|
||||
use qbz_shell::QbzShell;
|
||||
|
||||
/// Rust side of the shell bridge (plain storage, phase-1 pattern).
|
||||
pub struct QbzShellRust {
|
||||
sidebar_state: i32,
|
||||
queue_open: bool,
|
||||
current_view: QString,
|
||||
can_back: bool,
|
||||
can_forward: bool,
|
||||
npb_mode: i32,
|
||||
theme_json: QString,
|
||||
theme_slug: QString,
|
||||
theme_list_json: QString,
|
||||
theme_filter: i32,
|
||||
lyrics_open: bool,
|
||||
lyrics_json: QString,
|
||||
sidebar_json: QString,
|
||||
sidebar_sort_by: QString,
|
||||
sidebar_sort_asc: bool,
|
||||
system_title_bar: bool,
|
||||
system_title_bar_pref: bool,
|
||||
ambient_mode: i32,
|
||||
ambient_primary: QString,
|
||||
ambient_secondary: QString,
|
||||
ambient_accent: QString,
|
||||
ambient_dim: f32,
|
||||
ambient_surface_alpha: f32,
|
||||
ambient_bar_alpha: f32,
|
||||
drag_active: bool,
|
||||
drag_count: i32,
|
||||
drag_title: QString,
|
||||
drag_subtitle: QString,
|
||||
drag_x: f32,
|
||||
drag_y: f32,
|
||||
drag_over_playlist_id: QString,
|
||||
}
|
||||
|
||||
impl Default for QbzShellRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
sidebar_state: 0,
|
||||
queue_open: false,
|
||||
current_view: QString::from("home"),
|
||||
can_back: false,
|
||||
can_forward: false,
|
||||
npb_mode: crate::settings_qt::npb_mode_index(),
|
||||
theme_json: QString::from(crate::theme_qt::theme_json().as_str()),
|
||||
theme_slug: QString::from(crate::theme_qt::current_slug().as_str()),
|
||||
theme_list_json: QString::from(crate::theme_qt::theme_list_json().as_str()),
|
||||
theme_filter: crate::theme_qt::theme_filter(),
|
||||
lyrics_open: false,
|
||||
lyrics_json: QString::from("{}"),
|
||||
sidebar_json: QString::from("[]"),
|
||||
sidebar_sort_by: QString::from("name"),
|
||||
sidebar_sort_asc: true,
|
||||
system_title_bar: crate::settings_qt::use_system_title_bar(),
|
||||
system_title_bar_pref: crate::settings_qt::use_system_title_bar(),
|
||||
ambient_mode: crate::settings_qt::app_background_mode(),
|
||||
// The Slint ImmersiveState default triad (pre-artwork colors).
|
||||
ambient_primary: QString::from("#00dcc8"),
|
||||
ambient_secondary: QString::from("#9632ff"),
|
||||
ambient_accent: QString::from("#3fd9c8"),
|
||||
ambient_dim: crate::settings_qt::ambient_dim(),
|
||||
ambient_surface_alpha: crate::settings_qt::ambient_surface_alpha(),
|
||||
ambient_bar_alpha: crate::settings_qt::ambient_bar_alpha(),
|
||||
drag_active: false,
|
||||
drag_count: 0,
|
||||
drag_title: QString::default(),
|
||||
drag_subtitle: QString::default(),
|
||||
drag_x: 0.0,
|
||||
drag_y: 0.0,
|
||||
drag_over_playlist_id: QString::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The UI hop (bridges/mod.rs pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzShell>> = OnceLock::new();
|
||||
|
||||
/// Queue a shell-bridge mutation onto the Qt event loop (no-op before
|
||||
/// boot registers the thread).
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzShell>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_shell::QbzShell {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] shell Qt thread already registered");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cycle_sidebar(mut self: Pin<&mut Self>) {
|
||||
let next = (self.sidebar_state() + 1) % 3;
|
||||
self.as_mut().set_sidebar_state(next);
|
||||
}
|
||||
|
||||
pub fn toggle_queue(mut self: Pin<&mut Self>) {
|
||||
let next = !self.queue_open();
|
||||
self.as_mut().set_queue_open(next);
|
||||
}
|
||||
|
||||
pub fn navigate_back(self: Pin<&mut Self>) {
|
||||
crate::nav_qt::back();
|
||||
}
|
||||
|
||||
pub fn navigate_forward(self: Pin<&mut Self>) {
|
||||
crate::nav_qt::forward();
|
||||
}
|
||||
|
||||
pub fn toggle_lyrics(self: Pin<&mut Self>) {
|
||||
crate::toggle_lyrics();
|
||||
}
|
||||
|
||||
pub fn navigate_to(self: Pin<&mut Self>, view: QString) {
|
||||
crate::navigate_to(&view.to_string());
|
||||
}
|
||||
|
||||
pub fn npb_set_mode(self: Pin<&mut Self>, mode: i32) {
|
||||
crate::npb_set_mode(mode);
|
||||
}
|
||||
|
||||
pub fn theme_set(self: Pin<&mut Self>, slug: QString) {
|
||||
crate::theme_set(slug.to_string());
|
||||
}
|
||||
|
||||
pub fn theme_set_filter(self: Pin<&mut Self>, index: i32) {
|
||||
crate::theme_set_filter(index);
|
||||
}
|
||||
|
||||
pub fn toggle_system_title_bar(self: Pin<&mut Self>) {
|
||||
crate::toggle_system_title_bar();
|
||||
}
|
||||
|
||||
pub fn toggle_ambient_background(self: Pin<&mut Self>) {
|
||||
crate::toggle_ambient_background();
|
||||
}
|
||||
|
||||
pub fn reload_sidebar(self: Pin<&mut Self>) {
|
||||
crate::reload_sidebar();
|
||||
}
|
||||
|
||||
pub fn sidebar_set_sort(self: Pin<&mut Self>, option: QString) {
|
||||
crate::sidebar_set_sort(&option.to_string());
|
||||
}
|
||||
|
||||
pub fn sidebar_search(self: Pin<&mut Self>, query: QString) {
|
||||
crate::sidebar_set_search(&query.to_string());
|
||||
}
|
||||
|
||||
pub fn sidebar_toggle_folder(self: Pin<&mut Self>, id: QString) {
|
||||
crate::sidebar_toggle_folder(&id.to_string());
|
||||
}
|
||||
|
||||
pub fn create_playlist(self: Pin<&mut Self>) {
|
||||
crate::create_playlist();
|
||||
}
|
||||
|
||||
pub fn sidebar_artwork_window(self: Pin<&mut Self>, urls_json: QString) {
|
||||
crate::sidebar_artwork_window(urls_json.to_string());
|
||||
}
|
||||
|
||||
pub fn drag_start(self: Pin<&mut Self>, track_id: QString, title: QString, subtitle: QString, x: f32, y: f32) {
|
||||
crate::drag_start(track_id.to_string(), title.to_string(), subtitle.to_string(), x, y);
|
||||
}
|
||||
|
||||
pub fn drag_move(self: Pin<&mut Self>, x: f32, y: f32) {
|
||||
crate::drag_move(x, y);
|
||||
}
|
||||
|
||||
pub fn drag_set_over(self: Pin<&mut Self>, playlist_id: QString) {
|
||||
crate::drag_set_over(playlist_id.to_string());
|
||||
}
|
||||
|
||||
pub fn drag_end(self: Pin<&mut Self>) {
|
||||
crate::drag_end();
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ pub fn theme_json() -> String {
|
||||
pub fn publish_theme() {
|
||||
let json = theme_json();
|
||||
let slug = current_slug();
|
||||
crate::ui(move |mut b| {
|
||||
crate::shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_theme_json(QString::from(json.as_str()));
|
||||
b.as_mut().set_theme_slug(QString::from(slug.as_str()));
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user