refactor(qt): extract search_bridge.rs from the QbzBridge god-object
QbzBridge carried five domains (Settings, Search, Playlist, Genre filter,
Discover config); its own boot() comment called the split "TEMP (phase 23)".
TRACK-RULES section 2 requires one cxx-qt bridge per domain. This moves the
Search domain out verbatim, ahead of the cortinilla parity work, so the
refactor and the behaviour change stay separately reviewable.
QbzSearch (search_bridge.rs) takes 7 properties and 11 invokables. Two
deliberate changes ride the move:
- selected_index -> cortinilla_selected_index. It is cortinilla-only
state and the bare name on an app-wide singleton is a collision hazard.
- cortinilla_scroll_y f32 -> f64, unifying with the immersive twin in
immersive_bridge.rs, which was already f64.
cortinilla_query is declared here but not yet wired; C2 wires it, which is
what makes Enter carry the text the user actually typed instead of the last
successfully loaded payload's query.
Every Rust writer of a moving property was re-pointed BY HAND, never by
regex over the multi-line ui() closures: the seven crate::ui( sites in
search_qt.rs plus two that live outside it and that the QML-only xref audit
cannot see - settings_qt.rs (the live Settings > Appearance path) and
main.rs toggle_intelligent_search. All 48 QML consumer sites across
Cortinilla.qml, HeaderBar.qml and SearchView.qml were updated, including the
one Connections block, whose onSelectedIndexChanged handler was renamed with
its property.
A stale 6.0 base in flat_index_content_y is left in place and annotated: it
mirrors a Flickable viewport margin rather than content space, so every row
reports 6px low. C4 corrects it with its unit test; this commit is a move.
Contract: qbz-nix-docs/qt-frontend/2026-08-03-cortinilla-parity, commit C0.
Gate: build clean (no new warnings), 256/256 tests, qml_resolution_audit
214 files, qml_singleton_xref 28 bridges with QbzSearch resolving, offscreen
smoke 0 complaints + home published.
cc
This commit is contained in:
11 files changed
+309
-172
No files matched your search
@@ -51,7 +51,7 @@ fn main() {
|
||||
// (myqbz_qt.rs, blacklist_qt.rs, toast_qt.rs, …) are plain
|
||||
// modules and must NOT be listed — only files that declare a
|
||||
// #[cxx_qt::bridge] mod belong in this array.
|
||||
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", "src/viz_bridge.rs", "src/immersive_bridge.rs", "src/suggestions_bridge.rs", "src/hotkeys_bridge.rs", "src/local_bridge.rs", "src/library_bridge.rs", "src/album_bridge.rs", "src/artist_bridge.rs", "src/lyrics_qt.rs", "src/icon_tint_qt.rs", "src/cast_bridge.rs", "src/myqbz_bridge.rs", "src/myqbz_add_bridge.rs", "src/disco_bridge.rs", "src/blacklist_bridge.rs", "src/playlist_picker_bridge.rs", "src/playlist_manager_bridge.rs", "src/playlist_import_bridge.rs", "src/folder_edit_bridge.rs", "src/playlist_edit_bridge.rs", "src/qconnect_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", "src/viz_bridge.rs", "src/immersive_bridge.rs", "src/suggestions_bridge.rs", "src/hotkeys_bridge.rs", "src/search_bridge.rs", "src/local_bridge.rs", "src/library_bridge.rs", "src/album_bridge.rs", "src/artist_bridge.rs", "src/lyrics_qt.rs", "src/icon_tint_qt.rs", "src/cast_bridge.rs", "src/myqbz_bridge.rs", "src/myqbz_add_bridge.rs", "src/disco_bridge.rs", "src/blacklist_bridge.rs", "src/playlist_picker_bridge.rs", "src/playlist_manager_bridge.rs", "src/playlist_import_bridge.rs", "src/folder_edit_bridge.rs", "src/playlist_edit_bridge.rs", "src/qconnect_bridge.rs"],
|
||||
qml_files: &[
|
||||
"qml/LoginScreen.qml",
|
||||
"qml/Main.qml",
|
||||
|
||||
@@ -116,6 +116,14 @@ ApplicationWindow {
|
||||
// after QbzSession like every domain singleton. The B2 dispatcher
|
||||
// (AppShell-root Keys.onPressed) is the only other QML side.
|
||||
QbzHotkeys.boot()
|
||||
// Search (2026-08-03 cortinilla-parity contract, commit C0): the
|
||||
// domain extracted from QbzBridge. Its position among the other
|
||||
// domain singletons is not load-bearing — every boot() here runs
|
||||
// before the first key or click — but the LINE is: boot() is what
|
||||
// registers the Qt-thread hop, and without it `search_bridge::ui()`
|
||||
// is a silent no-op, so the dropdown would never repaint and
|
||||
// nothing would be logged (TRACK-RULES, singleton boot order).
|
||||
QbzSearch.boot()
|
||||
QbzShell.boot()
|
||||
QbzPlayer.boot()
|
||||
QbzQueue.boot()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// translucent panel, white-alpha text) instead of the app theme tokens;
|
||||
// sections ONLY (Artists / Albums / Playlists — NO top-result hero, NO
|
||||
// "View more", NO track rows); reads QbzImmersive.immSearch* (never
|
||||
// QbzBridge.cortinilla*); NO idle auto-close (Slint has no such timer) and
|
||||
// QbzSearch.cortinilla*); NO idle auto-close (Slint has no such timer) and
|
||||
// no view-change dismiss — the immersive host never navigates.
|
||||
//
|
||||
// Data: QbzImmersive.immSearchJson (search_qt.rs CortinillaData: query /
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
// pointer and would kill typing in the header field, the exact reason the
|
||||
// Slint uses a plain Rectangle too).
|
||||
//
|
||||
// Payload: QbzBridge.cortinillaJson (search_qt.rs CortinillaData: query /
|
||||
// Payload: QbzSearch.cortinillaJson (search_qt.rs CortinillaData: query /
|
||||
// top / sections with controller flat indices). Keyboard selection rides
|
||||
// selectedIndex + cortinillaScrollY (content-space top-y of the selected
|
||||
// row — scrolled into view here). Row activation bubbles to the
|
||||
// QbzBridge.cortinilla* invokables; the field clear goes through
|
||||
// QbzSearch.cortinilla* invokables; the field clear goes through
|
||||
// root.header.clearSearch().
|
||||
|
||||
import QtQuick
|
||||
@@ -19,7 +19,7 @@ import "../theme"
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
visible: QbzBridge.cortinillaOpen
|
||||
visible: QbzSearch.cortinillaOpen
|
||||
|
||||
// The host HeaderBar (for clearSearch on row activation).
|
||||
property var headerBar: null
|
||||
@@ -29,14 +29,14 @@ Item {
|
||||
readonly property var doc: parseDoc()
|
||||
function parseDoc() {
|
||||
try {
|
||||
return JSON.parse(QbzBridge.cortinillaJson)
|
||||
return JSON.parse(QbzSearch.cortinillaJson)
|
||||
} catch (e) {
|
||||
return ({})
|
||||
}
|
||||
}
|
||||
readonly property var sections: doc.sections || []
|
||||
readonly property bool hasTop: doc.top !== undefined && doc.top !== null && (doc.top.id || "") !== ""
|
||||
readonly property bool empty: !hasTop && sections.length === 0 && !QbzBridge.cortinillaLoading
|
||||
readonly property bool empty: !hasTop && sections.length === 0 && !QbzSearch.cortinillaLoading
|
||||
|
||||
// The centered header search box width (HeaderBar's responsive rule) —
|
||||
// the click-through gaps below leave it free.
|
||||
@@ -52,17 +52,17 @@ Item {
|
||||
interval: 4500
|
||||
repeat: false
|
||||
running: root.visible && !root.panelHovered
|
||||
onTriggered: QbzBridge.cortinillaDismiss()
|
||||
onTriggered: QbzSearch.cortinillaDismiss()
|
||||
}
|
||||
Connections {
|
||||
target: QbzBridge
|
||||
target: QbzSearch
|
||||
// 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() }
|
||||
function onCortinillaSelectedIndexChanged() { if (root.visible && !root.panelHovered) idleClose.restart() }
|
||||
// Keyboard scroll-into-view: the controller publishes the selected
|
||||
// row's content-space top-y; nudge the Flickable so it is visible.
|
||||
function onCortinillaScrollYChanged() {
|
||||
var y = QbzBridge.cortinillaScrollY
|
||||
var y = QbzSearch.cortinillaScrollY
|
||||
if (y < bodyFlick.contentY) {
|
||||
bodyFlick.contentY = Math.max(0, y)
|
||||
} else if (y + 56 > bodyFlick.contentY + bodyFlick.height) {
|
||||
@@ -74,7 +74,7 @@ Item {
|
||||
Connections {
|
||||
target: QbzShell
|
||||
// A page change dismisses (AppShell's tracked-view close hook).
|
||||
function onCurrentViewChanged() { QbzBridge.cortinillaDismiss() }
|
||||
function onCurrentViewChanged() { QbzSearch.cortinillaDismiss() }
|
||||
}
|
||||
|
||||
// --- Click-outside scrims (any click outside the panel dismisses) ----
|
||||
@@ -85,7 +85,7 @@ Item {
|
||||
y: 42
|
||||
width: root.width
|
||||
height: root.height - 42
|
||||
onClicked: QbzBridge.cortinillaDismiss()
|
||||
onClicked: QbzSearch.cortinillaDismiss()
|
||||
}
|
||||
// 2) header strip LEFT of the search box.
|
||||
MouseArea {
|
||||
@@ -94,7 +94,7 @@ Item {
|
||||
y: 0
|
||||
width: (root.width - root.searchBoxWidth) / 2
|
||||
height: 42
|
||||
onClicked: QbzBridge.cortinillaDismiss()
|
||||
onClicked: QbzSearch.cortinillaDismiss()
|
||||
}
|
||||
// 3) header strip RIGHT of the search box.
|
||||
MouseArea {
|
||||
@@ -103,7 +103,7 @@ Item {
|
||||
y: 0
|
||||
width: root.width - x
|
||||
height: 42
|
||||
onClicked: QbzBridge.cortinillaDismiss()
|
||||
onClicked: QbzSearch.cortinillaDismiss()
|
||||
}
|
||||
|
||||
// --- The panel ----------------------------------------------------------
|
||||
@@ -143,7 +143,7 @@ Item {
|
||||
// --- Loading skeleton (no cached instant-paint — one clean
|
||||
// apply, Cortinilla.slint) -------------------------------
|
||||
Column {
|
||||
visible: QbzBridge.cortinillaLoading
|
||||
visible: QbzSearch.cortinillaLoading
|
||||
width: parent.width
|
||||
topPadding: 4
|
||||
property bool pulse: false
|
||||
@@ -195,7 +195,7 @@ Item {
|
||||
|
||||
// --- Loaded content ----------------------------------------
|
||||
Column {
|
||||
visible: !QbzBridge.cortinillaLoading
|
||||
visible: !QbzSearch.cortinillaLoading
|
||||
width: parent.width
|
||||
|
||||
// No results.
|
||||
@@ -269,7 +269,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.headerBar) root.headerBar.clearSearch()
|
||||
QbzBridge.cortinillaViewMore(modelData.kind)
|
||||
QbzSearch.cortinillaViewMore(modelData.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,7 @@ Item {
|
||||
width: parent ? parent.width : 0
|
||||
height: 56
|
||||
radius: theme.radiusSm
|
||||
readonly property bool active: QbzBridge.selectedIndex === (row.flatIndex ?? -2)
|
||||
readonly property bool active: QbzSearch.cortinillaSelectedIndex === (row.flatIndex ?? -2)
|
||||
color: (active || rowArea.containsMouse) ? theme.surfaceHover : "transparent"
|
||||
|
||||
// Accent bar on the keyboard-active row (distinct from plain hover).
|
||||
@@ -354,7 +354,7 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.headerBar) root.headerBar.clearSearch()
|
||||
QbzBridge.cortinillaRowClicked(row.flatIndex)
|
||||
QbzSearch.cortinillaRowClicked(row.flatIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ Rectangle {
|
||||
// the row / full search, Esc dismisses. The × clears + closes.
|
||||
function clearSearch() {
|
||||
searchInput.text = ""
|
||||
QbzBridge.cortinillaDismiss()
|
||||
QbzSearch.cortinillaDismiss()
|
||||
}
|
||||
|
||||
// nav.search / Ctrl+f (2026-08-03 hotkeys-port contract §4.3, divergence
|
||||
@@ -668,7 +668,7 @@ Rectangle {
|
||||
onTextEdited: {
|
||||
if (text.trim().length < 2) {
|
||||
liveDebounce.stop()
|
||||
QbzBridge.cortinillaDismiss()
|
||||
QbzSearch.cortinillaDismiss()
|
||||
} else {
|
||||
liveDebounce.restart()
|
||||
}
|
||||
@@ -680,7 +680,7 @@ Rectangle {
|
||||
id: liveDebounce
|
||||
interval: 220
|
||||
repeat: false
|
||||
onTriggered: QbzBridge.searchLive(searchInput.text)
|
||||
onTriggered: QbzSearch.searchLive(searchInput.text)
|
||||
}
|
||||
|
||||
// The Enter rule (HeaderBar.slint on-enter): cortinilla open +
|
||||
@@ -688,34 +688,34 @@ Rectangle {
|
||||
// search; closed -> plain submit (also Search > All).
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Down) {
|
||||
if (QbzBridge.cortinillaOpen) {
|
||||
QbzBridge.cortinillaMoveSelection(1)
|
||||
if (QbzSearch.cortinillaOpen) {
|
||||
QbzSearch.cortinillaMoveSelection(1)
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
if (QbzBridge.cortinillaOpen) {
|
||||
QbzBridge.cortinillaMoveSelection(-1)
|
||||
if (QbzSearch.cortinillaOpen) {
|
||||
QbzSearch.cortinillaMoveSelection(-1)
|
||||
event.accepted = true
|
||||
}
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (QbzBridge.cortinillaOpen) {
|
||||
if (QbzBridge.selectedIndex >= 0) {
|
||||
if (QbzSearch.cortinillaOpen) {
|
||||
if (QbzSearch.cortinillaSelectedIndex >= 0) {
|
||||
root.clearSearch()
|
||||
QbzBridge.cortinillaRowClicked(QbzBridge.selectedIndex)
|
||||
QbzSearch.cortinillaRowClicked(QbzSearch.cortinillaSelectedIndex)
|
||||
} else {
|
||||
root.clearSearch()
|
||||
QbzBridge.cortinillaSearchAll()
|
||||
QbzSearch.cortinillaSearchAll()
|
||||
}
|
||||
} else {
|
||||
// Capture BEFORE clearing (clearSearch wipes the input).
|
||||
var q = searchInput.text
|
||||
root.clearSearch()
|
||||
QbzBridge.searchSubmit(q)
|
||||
QbzSearch.searchSubmit(q)
|
||||
}
|
||||
event.accepted = true
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
if (QbzBridge.cortinillaOpen) {
|
||||
QbzBridge.cortinillaDismiss()
|
||||
if (QbzSearch.cortinillaOpen) {
|
||||
QbzSearch.cortinillaDismiss()
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
@@ -740,7 +740,7 @@ Rectangle {
|
||||
// open (Slint: it lives in the box, opposite the magnifier), else
|
||||
// the × clear.
|
||||
Text {
|
||||
visible: QbzBridge.cortinillaOpen
|
||||
visible: QbzSearch.cortinillaOpen
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
height: parent.height
|
||||
@@ -750,7 +750,7 @@ Rectangle {
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
Rectangle {
|
||||
visible: !QbzBridge.cortinillaOpen && searchInput.text !== ""
|
||||
visible: !QbzSearch.cortinillaOpen && searchInput.text !== ""
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
width: 22
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Search results view — the QML port of search/SearchResultsView.slint
|
||||
// (phase 15). One JSON document (QbzBridge.searchJson, search_qt.rs
|
||||
// (phase 15). One JSON document (QbzSearch.searchJson, search_qt.rs
|
||||
// SearchPageDoc: query/tab/loading/filterIndex + the four category lists,
|
||||
// totals, the most-popular hero, the carousel-only artists list).
|
||||
//
|
||||
@@ -65,7 +65,7 @@ Rectangle {
|
||||
readonly property var doc: parseDoc()
|
||||
function parseDoc() {
|
||||
try {
|
||||
return JSON.parse(QbzBridge.searchJson)
|
||||
return JSON.parse(QbzSearch.searchJson)
|
||||
} catch (e) {
|
||||
return ({})
|
||||
}
|
||||
@@ -190,7 +190,7 @@ Rectangle {
|
||||
root.fadeTab = t
|
||||
root.fadeFrom = root.moreFrom
|
||||
moreSettle.restart()
|
||||
QbzBridge.searchLoadMore(t)
|
||||
QbzSearch.searchLoadMore(t)
|
||||
}
|
||||
// `morePending` clears only when the tab's array GROWS — and search_qt::
|
||||
// load_more never republishes on a failed fetch (main.rs just logs), nor
|
||||
@@ -404,11 +404,11 @@ Rectangle {
|
||||
anchors.rightMargin: 32
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 16
|
||||
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) }
|
||||
FilterRadio { label: QbzSession.tr("Main Artist", QbzSession.trRev); selected: root.filterIndex === 1; onPicked: QbzSearch.searchFilterChanged(1) }
|
||||
FilterRadio { label: QbzSession.tr("Performer", QbzSession.trRev); selected: root.filterIndex === 2; onPicked: QbzSearch.searchFilterChanged(2) }
|
||||
FilterRadio { label: QbzSession.tr("Composer", QbzSession.trRev); selected: root.filterIndex === 3; onPicked: QbzSearch.searchFilterChanged(3) }
|
||||
FilterRadio { label: QbzSession.tr("Label", QbzSession.trRev); selected: root.filterIndex === 4; onPicked: QbzSearch.searchFilterChanged(4) }
|
||||
FilterRadio { label: QbzSession.tr("Release Name", QbzSession.trRev); selected: root.filterIndex === 5; onPicked: QbzSearch.searchFilterChanged(5) }
|
||||
Rectangle {
|
||||
visible: root.filterIndex !== 0
|
||||
width: 24
|
||||
@@ -422,7 +422,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.searchFilterChanged(0)
|
||||
onClicked: QbzSearch.searchFilterChanged(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,7 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: QbzBridge.searchTabChanged(modelData.t)
|
||||
onClicked: QbzSearch.searchTabChanged(modelData.t)
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
@@ -583,7 +583,7 @@ Rectangle {
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
onViewAllClicked: QbzBridge.searchTabChanged(3)
|
||||
onViewAllClicked: QbzSearch.searchTabChanged(3)
|
||||
}
|
||||
ListView {
|
||||
width: parent.width
|
||||
@@ -612,7 +612,7 @@ Rectangle {
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
onViewAllClicked: QbzBridge.searchTabChanged(3)
|
||||
onViewAllClicked: QbzSearch.searchTabChanged(3)
|
||||
}
|
||||
ListView {
|
||||
width: parent.width
|
||||
@@ -641,7 +641,7 @@ Rectangle {
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
onViewAllClicked: QbzBridge.searchTabChanged(1)
|
||||
onViewAllClicked: QbzSearch.searchTabChanged(1)
|
||||
}
|
||||
ListView {
|
||||
width: parent.width
|
||||
@@ -772,7 +772,7 @@ Rectangle {
|
||||
showViewAll: root.tab === 0 && (root.doc.tracksTotal || 0) > root.previewCap
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
onViewAllClicked: QbzBridge.searchTabChanged(2)
|
||||
onViewAllClicked: QbzSearch.searchTabChanged(2)
|
||||
}
|
||||
Repeater {
|
||||
model: root.tab === 2 ? root.tracks : root.tracks.slice(0, root.previewCap)
|
||||
@@ -911,7 +911,7 @@ Rectangle {
|
||||
showViewAll: true
|
||||
viewAllAccent: true
|
||||
showChevrons: false
|
||||
onViewAllClicked: QbzBridge.searchTabChanged(4)
|
||||
onViewAllClicked: QbzSearch.searchTabChanged(4)
|
||||
}
|
||||
ListView {
|
||||
width: parent.width
|
||||
|
||||
+10
-107
@@ -46,20 +46,12 @@ pub mod qbz_bridge {
|
||||
// for the same reason (SettingsView.slint:121-200).
|
||||
#[qproperty(i32, settings_section)]
|
||||
|
||||
// --- Search (phase 15) ---------------------------------------------
|
||||
// Cortinilla (live dropdown): open/loading flags, ONE JSON payload
|
||||
// (search_qt.rs CortinillaData: query/top/sections with controller
|
||||
// flat indices), keyboard selection + its content-space scroll y.
|
||||
#[qproperty(bool, cortinilla_open)]
|
||||
#[qproperty(bool, cortinilla_loading)]
|
||||
#[qproperty(QString, cortinilla_json)]
|
||||
#[qproperty(i32, selected_index)]
|
||||
#[qproperty(f32, cortinilla_scroll_y)]
|
||||
// Results page: ONE JSON document (search_qt.rs SearchPageDoc).
|
||||
#[qproperty(QString, search_json)]
|
||||
// The intelligent-search kill switch (ui_prefs pref state — the
|
||||
// app-menu check; live-flippable, no restart).
|
||||
#[qproperty(bool, intelligent_search)]
|
||||
// --- Search: MOVED (cortinilla-parity contract C0) -----------------
|
||||
// cortinilla_* + search_json + intelligent_search -> QbzSearch
|
||||
// (search_bridge.rs). `selected_index` was RENAMED on the way out to
|
||||
// `cortinilla_selected_index` (it is cortinilla-only state, and the
|
||||
// bare name on an app-wide singleton is a collision hazard), and
|
||||
// `cortinilla_scroll_y` went f32 -> f64 to match the immersive twin.
|
||||
|
||||
// --- Playlist view (phase 17) --------------------------------------
|
||||
// ONE JSON document (playlist_qt.rs PlaylistDoc: header + track
|
||||
@@ -124,41 +116,10 @@ pub mod qbz_bridge {
|
||||
#[qinvokable]
|
||||
fn integrations_action(self: Pin<&mut QbzBridge>, action: QString);
|
||||
|
||||
// --- Search (phase 15) ---------------------------------------------
|
||||
/// Header field keystrokes (QML-debounced 220ms, >= 2 chars): drive
|
||||
/// the cortinilla live query.
|
||||
#[qinvokable]
|
||||
fn search_live(self: Pin<&mut QbzBridge>, query: QString);
|
||||
/// Enter with the cortinilla closed: full results page (All tab).
|
||||
#[qinvokable]
|
||||
fn search_submit(self: Pin<&mut QbzBridge>, query: QString);
|
||||
/// Cortinilla: Esc / click-outside / idle-close / page change.
|
||||
#[qinvokable]
|
||||
fn cortinilla_dismiss(self: Pin<&mut QbzBridge>);
|
||||
/// Arrow keys: delta -1 (up) / +1 (down) through the flat list.
|
||||
#[qinvokable]
|
||||
fn cortinilla_move_selection(self: Pin<&mut QbzBridge>, delta: i32);
|
||||
/// Row click or Enter on the keyboard-selected row.
|
||||
#[qinvokable]
|
||||
fn cortinilla_row_clicked(self: Pin<&mut QbzBridge>, index: i32);
|
||||
/// Section "View more" (kind: album | track | artist | playlist).
|
||||
#[qinvokable]
|
||||
fn cortinilla_view_more(self: Pin<&mut QbzBridge>, kind: QString);
|
||||
/// The Enter affordance with no keyboard selection: Search > All.
|
||||
#[qinvokable]
|
||||
fn cortinilla_search_all(self: Pin<&mut QbzBridge>);
|
||||
/// Results page: the five-tab strip.
|
||||
#[qinvokable]
|
||||
fn search_tab_changed(self: Pin<&mut QbzBridge>, tab: i32);
|
||||
/// Per-type tab "Load more".
|
||||
#[qinvokable]
|
||||
fn search_load_more(self: Pin<&mut QbzBridge>, tab: i32);
|
||||
/// searchType filter radios (0 = none, 1..5 = the chips).
|
||||
#[qinvokable]
|
||||
fn search_filter_changed(self: Pin<&mut QbzBridge>, index: i32);
|
||||
/// App-menu intelligent-search toggle (the 2.0.0 opt-out module).
|
||||
#[qinvokable]
|
||||
fn toggle_intelligent_search(self: Pin<&mut QbzBridge>);
|
||||
// --- Search: MOVED (cortinilla-parity contract C0) -----------------
|
||||
// search_live / search_submit / cortinilla_* / search_tab_changed /
|
||||
// search_load_more / search_filter_changed / toggle_intelligent_search
|
||||
// -> QbzSearch (search_bridge.rs).
|
||||
|
||||
// --- Playlist view (phase 17) --------------------------------------
|
||||
/// Sidebar playlist row / playlist card click: open the detail view.
|
||||
@@ -268,13 +229,6 @@ use cxx_qt_lib::QString;
|
||||
pub struct QbzBridgeRust {
|
||||
settings_json: QString,
|
||||
settings_section: i32,
|
||||
cortinilla_open: bool,
|
||||
cortinilla_loading: bool,
|
||||
cortinilla_json: QString,
|
||||
selected_index: i32,
|
||||
cortinilla_scroll_y: f32,
|
||||
search_json: QString,
|
||||
intelligent_search: bool,
|
||||
playlist_json: QString,
|
||||
genre_filter_json: QString,
|
||||
discover_config_json: QString,
|
||||
@@ -286,13 +240,6 @@ impl Default for QbzBridgeRust {
|
||||
settings_json: QString::from("{}"),
|
||||
// 0 = Audio, the section Settings opens on.
|
||||
settings_section: 0,
|
||||
cortinilla_open: false,
|
||||
cortinilla_loading: false,
|
||||
cortinilla_json: QString::from("{}"),
|
||||
selected_index: -1,
|
||||
cortinilla_scroll_y: 0.0,
|
||||
search_json: QString::from("{}"),
|
||||
intelligent_search: crate::search_qt::intelligent_search_pref(),
|
||||
playlist_json: QString::from("{}"),
|
||||
// Seeded from the PERSISTED selection (no network): a remembered
|
||||
// filter colors both genre buttons and narrows the Library feed
|
||||
@@ -348,50 +295,6 @@ impl qbz_bridge::QbzBridge {
|
||||
crate::refresh_devices();
|
||||
}
|
||||
|
||||
pub fn search_live(self: Pin<&mut Self>, query: QString) {
|
||||
crate::search_live(query.to_string());
|
||||
}
|
||||
|
||||
pub fn search_submit(self: Pin<&mut Self>, query: QString) {
|
||||
crate::search_submit(query.to_string());
|
||||
}
|
||||
|
||||
pub fn cortinilla_dismiss(self: Pin<&mut Self>) {
|
||||
crate::search_qt::dismiss();
|
||||
}
|
||||
|
||||
pub fn cortinilla_move_selection(self: Pin<&mut Self>, delta: i32) {
|
||||
crate::search_qt::move_selection(delta);
|
||||
}
|
||||
|
||||
pub fn cortinilla_row_clicked(self: Pin<&mut Self>, index: i32) {
|
||||
crate::search_qt::row_clicked(index);
|
||||
}
|
||||
|
||||
pub fn cortinilla_view_more(self: Pin<&mut Self>, kind: QString) {
|
||||
crate::cortinilla_view_more(kind.to_string());
|
||||
}
|
||||
|
||||
pub fn cortinilla_search_all(self: Pin<&mut Self>) {
|
||||
crate::cortinilla_search_all();
|
||||
}
|
||||
|
||||
pub fn search_tab_changed(self: Pin<&mut Self>, tab: i32) {
|
||||
crate::search_qt::tab_changed(tab);
|
||||
}
|
||||
|
||||
pub fn search_load_more(self: Pin<&mut Self>, tab: i32) {
|
||||
crate::search_load_more(tab);
|
||||
}
|
||||
|
||||
pub fn search_filter_changed(self: Pin<&mut Self>, index: i32) {
|
||||
crate::search_filter_changed(index);
|
||||
}
|
||||
|
||||
pub fn toggle_intelligent_search(self: Pin<&mut Self>) {
|
||||
crate::toggle_intelligent_search();
|
||||
}
|
||||
|
||||
pub fn open_playlist(self: Pin<&mut Self>, playlist_id: QString) {
|
||||
crate::open_playlist(playlist_id.to_string());
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ mod suggestions_bridge;
|
||||
// Hotkeys layer (2026-08-03 hotkeys-port contract, block B1): the
|
||||
// QbzHotkeys singleton — the QML dispatcher's Rust brain (§1.1 pipeline).
|
||||
mod hotkeys_bridge;
|
||||
// Search domain (2026-08-03 cortinilla-parity contract, commit C0): the
|
||||
// QbzSearch singleton, extracted from the QbzBridge god-object.
|
||||
mod search_bridge;
|
||||
mod local_bridge;
|
||||
mod library_bridge;
|
||||
mod album_bridge;
|
||||
@@ -1813,7 +1816,7 @@ pub(crate) fn search_filter_changed(index: i32) {
|
||||
pub(crate) fn toggle_intelligent_search() {
|
||||
let next = search_qt::toggle_intelligent_search();
|
||||
log::info!("[qbz-qt] intelligent_search -> {next}");
|
||||
ui(move |mut b| b.as_mut().set_intelligent_search(next));
|
||||
search_bridge::ui(move |mut b| b.as_mut().set_intelligent_search(next));
|
||||
}
|
||||
|
||||
// ============================ Settings (phase 10) =========================
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
//! QbzSearch — the search domain bridge (cortinilla + results page).
|
||||
//!
|
||||
//! Extracted from the `QbzBridge` god-object per TRACK-RULES §2 (*"un
|
||||
//! `#[cxx_qt::bridge]` por dominio, nunca un God-object"*) and the
|
||||
//! 2026-08-03 cortinilla-parity contract §1.2, commit C0. `QbzBridge`
|
||||
//! carried FIVE domains; this is the Search one, moved verbatim.
|
||||
//!
|
||||
//! Surface:
|
||||
//! - **Cortinilla** (the live as-you-type dropdown): open/loading flags, the
|
||||
//! live query, ONE JSON payload (`search_qt.rs` `CortinillaData`:
|
||||
//! query/top/sections with controller-assigned flat indices), the keyboard
|
||||
//! selection and its content-space scroll y.
|
||||
//! - **Results page**: ONE JSON document (`search_qt.rs` `SearchPageDoc`),
|
||||
//! the five-tab strip, per-type "Load more" and the searchType radios.
|
||||
//! - The **intelligent-search kill switch** (the 2.0.0 opt-out module).
|
||||
//!
|
||||
//! Boot order matters: `ui()` below is a SILENT NO-OP until `boot()` has
|
||||
//! registered this bridge's `CxxQtThread`, so a missing `QbzSearch.boot()`
|
||||
//! in `qml/Main.qml` does not error — the dropdown simply never repaints
|
||||
//! (TRACK-RULES §"Orden de boot de los singletons").
|
||||
//!
|
||||
//! Two renames landed with the move, both deliberate:
|
||||
//! - `selected_index` -> `cortinilla_selected_index` (QML
|
||||
//! `cortinillaSelectedIndex`). It is cortinilla-only state and `selectedIndex`
|
||||
//! on an app-wide singleton is a collision hazard.
|
||||
//! - `cortinilla_scroll_y` `f32` -> `f64`, unifying with the immersive twin
|
||||
//! (`immersive_bridge.rs`), which was already `f64`.
|
||||
|
||||
#[cxx_qt::bridge]
|
||||
pub mod qbz_search {
|
||||
extern "C++" {
|
||||
include!("cxx-qt-lib/qstring.h");
|
||||
type QString = cxx_qt_lib::QString;
|
||||
}
|
||||
|
||||
#[auto_cxx_name]
|
||||
extern "RustQt" {
|
||||
#[qobject]
|
||||
#[qml_element]
|
||||
#[qml_singleton]
|
||||
// --- Cortinilla (the live dropdown) --------------------------------
|
||||
#[qproperty(bool, cortinilla_open)]
|
||||
#[qproperty(bool, cortinilla_loading)]
|
||||
#[qproperty(QString, cortinilla_json)]
|
||||
/// The LIVE header query, published SYNCHRONOUSLY on every keystroke
|
||||
/// that passes the >= 2-char gate — ahead of the debounce and ahead
|
||||
/// of any fetch. Slint's twin is `SearchState.cortinilla-query`
|
||||
/// (`state.slint`), set at `main.rs:9647` before the debounce starts.
|
||||
///
|
||||
/// Declared here by the C0 move so the domain's surface lands in one
|
||||
/// commit; it is WIRED in C2, which is what makes Enter carry the
|
||||
/// text the user actually typed instead of the last successfully
|
||||
/// loaded payload's query (contract §4.1 D-BUG-2).
|
||||
#[qproperty(QString, cortinilla_query)]
|
||||
/// Keyboard selection over the controller-assigned flat indices.
|
||||
/// -1 = nothing selected. Written ONLY by the controller: hover is
|
||||
/// purely visual and must never write it, or the mouse would hijack
|
||||
/// the row that Enter activates (contract row E8).
|
||||
#[qproperty(i32, cortinilla_selected_index)]
|
||||
/// Content-space top-y of the selected row, for scroll-into-view.
|
||||
#[qproperty(f64, cortinilla_scroll_y)]
|
||||
|
||||
// --- Results page --------------------------------------------------
|
||||
#[qproperty(QString, search_json)]
|
||||
|
||||
// --- Intelligent Search kill switch --------------------------------
|
||||
/// `ui_prefs.intelligent_search` mirror. Live-flippable, no restart.
|
||||
/// The LIVE path is the Settings > Appearance row
|
||||
/// (`settings_qt.rs` -> `search_qt::set_enabled`); there is no
|
||||
/// app-menu surface in either frontend.
|
||||
#[qproperty(bool, intelligent_search)]
|
||||
|
||||
type QbzSearch = super::QbzSearchRust;
|
||||
|
||||
/// Registers this bridge's Qt-thread hop. MUST be called from
|
||||
/// `qml/Main.qml`; before it, `ui()` silently drops every update.
|
||||
#[qinvokable]
|
||||
fn boot(self: Pin<&mut QbzSearch>);
|
||||
|
||||
/// Header field keystrokes: drive the cortinilla live query.
|
||||
#[qinvokable]
|
||||
fn search_live(self: Pin<&mut QbzSearch>, query: QString);
|
||||
/// Enter with the cortinilla closed: full results page (All tab).
|
||||
#[qinvokable]
|
||||
fn search_submit(self: Pin<&mut QbzSearch>, query: QString);
|
||||
/// Cortinilla: Esc / click-outside / idle-close / page change.
|
||||
#[qinvokable]
|
||||
fn cortinilla_dismiss(self: Pin<&mut QbzSearch>);
|
||||
/// Arrow keys: delta -1 (up) / +1 (down) through the flat list.
|
||||
#[qinvokable]
|
||||
fn cortinilla_move_selection(self: Pin<&mut QbzSearch>, delta: i32);
|
||||
/// Row click or Enter on the keyboard-selected row.
|
||||
#[qinvokable]
|
||||
fn cortinilla_row_clicked(self: Pin<&mut QbzSearch>, index: i32);
|
||||
/// Section "View more" (kind: album | track | artist | playlist).
|
||||
#[qinvokable]
|
||||
fn cortinilla_view_more(self: Pin<&mut QbzSearch>, kind: QString);
|
||||
/// The Enter affordance with no keyboard selection: Search > All.
|
||||
#[qinvokable]
|
||||
fn cortinilla_search_all(self: Pin<&mut QbzSearch>);
|
||||
/// Results page: the five-tab strip.
|
||||
#[qinvokable]
|
||||
fn search_tab_changed(self: Pin<&mut QbzSearch>, tab: i32);
|
||||
/// Per-type tab "Load more".
|
||||
#[qinvokable]
|
||||
fn search_load_more(self: Pin<&mut QbzSearch>, tab: i32);
|
||||
/// searchType filter radios (0 = none, 1..5 = the chips).
|
||||
#[qinvokable]
|
||||
fn search_filter_changed(self: Pin<&mut QbzSearch>, index: i32);
|
||||
/// Intelligent-search toggle. NOTE: this has zero QML callers — the
|
||||
/// live path is the Settings row. Deleted in C1 (contract DEAD-1);
|
||||
/// moved here first so every intermediate commit compiles.
|
||||
#[qinvokable]
|
||||
fn toggle_intelligent_search(self: Pin<&mut QbzSearch>);
|
||||
}
|
||||
|
||||
impl cxx_qt::Threading for QbzSearch {}
|
||||
}
|
||||
|
||||
use core::pin::Pin;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use cxx_qt::CxxQtThread;
|
||||
use cxx_qt::Threading as _;
|
||||
use cxx_qt_lib::QString;
|
||||
|
||||
use qbz_search::QbzSearch;
|
||||
|
||||
/// Rust side of the bridge. Plain storage; every field is driven through the
|
||||
/// generated `set_*` methods on the Qt thread.
|
||||
pub struct QbzSearchRust {
|
||||
cortinilla_open: bool,
|
||||
cortinilla_loading: bool,
|
||||
cortinilla_json: QString,
|
||||
cortinilla_query: QString,
|
||||
cortinilla_selected_index: i32,
|
||||
cortinilla_scroll_y: f64,
|
||||
search_json: QString,
|
||||
intelligent_search: bool,
|
||||
}
|
||||
|
||||
impl Default for QbzSearchRust {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
cortinilla_open: false,
|
||||
cortinilla_loading: false,
|
||||
cortinilla_json: QString::from("{}"),
|
||||
cortinilla_query: QString::from(""),
|
||||
cortinilla_selected_index: -1,
|
||||
cortinilla_scroll_y: 0.0,
|
||||
search_json: QString::from("{}"),
|
||||
intelligent_search: crate::search_qt::intelligent_search_pref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QT_THREAD: OnceLock<CxxQtThread<QbzSearch>> = OnceLock::new();
|
||||
|
||||
/// Queue a search-bridge mutation onto the Qt event loop. NO-OP before
|
||||
/// `boot()` registers the thread — by design, and the reason a missing
|
||||
/// `QbzSearch.boot()` shows up as "the dropdown never updates" rather than
|
||||
/// as an error.
|
||||
pub(crate) fn ui(f: impl FnOnce(Pin<&mut QbzSearch>) + Send + 'static) {
|
||||
if let Some(thread) = QT_THREAD.get() {
|
||||
let _ = thread.queue(f);
|
||||
}
|
||||
}
|
||||
|
||||
impl qbz_search::QbzSearch {
|
||||
pub fn boot(self: Pin<&mut Self>) {
|
||||
if QT_THREAD.set(self.qt_thread()).is_err() {
|
||||
log::warn!("[qbz-qt] search Qt thread already registered");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search_live(self: Pin<&mut Self>, query: QString) {
|
||||
crate::search_live(query.to_string());
|
||||
}
|
||||
|
||||
pub fn search_submit(self: Pin<&mut Self>, query: QString) {
|
||||
crate::search_submit(query.to_string());
|
||||
}
|
||||
|
||||
pub fn cortinilla_dismiss(self: Pin<&mut Self>) {
|
||||
crate::search_qt::dismiss();
|
||||
}
|
||||
|
||||
pub fn cortinilla_move_selection(self: Pin<&mut Self>, delta: i32) {
|
||||
crate::search_qt::move_selection(delta);
|
||||
}
|
||||
|
||||
pub fn cortinilla_row_clicked(self: Pin<&mut Self>, index: i32) {
|
||||
crate::search_qt::row_clicked(index);
|
||||
}
|
||||
|
||||
pub fn cortinilla_view_more(self: Pin<&mut Self>, kind: QString) {
|
||||
crate::cortinilla_view_more(kind.to_string());
|
||||
}
|
||||
|
||||
pub fn cortinilla_search_all(self: Pin<&mut Self>) {
|
||||
crate::cortinilla_search_all();
|
||||
}
|
||||
|
||||
pub fn search_tab_changed(self: Pin<&mut Self>, tab: i32) {
|
||||
crate::search_qt::tab_changed(tab);
|
||||
}
|
||||
|
||||
pub fn search_load_more(self: Pin<&mut Self>, tab: i32) {
|
||||
crate::search_load_more(tab);
|
||||
}
|
||||
|
||||
pub fn search_filter_changed(self: Pin<&mut Self>, index: i32) {
|
||||
crate::search_filter_changed(index);
|
||||
}
|
||||
|
||||
pub fn toggle_intelligent_search(self: Pin<&mut Self>) {
|
||||
crate::toggle_intelligent_search();
|
||||
}
|
||||
}
|
||||
@@ -690,10 +690,10 @@ fn is_current_cort_version(v: u64) -> bool {
|
||||
|
||||
fn set_cortinilla_open(open: bool) {
|
||||
CORT_OPEN.store(open, Ordering::SeqCst);
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_cortinilla_open(open);
|
||||
if !open {
|
||||
b.as_mut().set_selected_index(-1);
|
||||
b.as_mut().set_cortinilla_selected_index(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -710,10 +710,10 @@ pub fn cortinilla_open() -> bool {
|
||||
CORT_OPEN.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
fn set_selected(index: i32, scroll_y: f32) {
|
||||
fn set_selected(index: i32, scroll_y: f64) {
|
||||
CURRENT_SEL.store(index, Ordering::SeqCst);
|
||||
crate::ui(move |mut b| {
|
||||
b.as_mut().set_selected_index(index);
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_cortinilla_selected_index(index);
|
||||
b.as_mut().set_cortinilla_scroll_y(scroll_y);
|
||||
});
|
||||
}
|
||||
@@ -739,7 +739,7 @@ pub async fn live(runtime: &Arc<AppRuntime<LoggingAdapter>>, query: &str) {
|
||||
let version = next_cort_version();
|
||||
// Query echo + loading flag ride the cortinillaLoading property and a
|
||||
// minimal payload (skeleton rows are pure QML).
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_cortinilla_loading(true);
|
||||
});
|
||||
|
||||
@@ -751,7 +751,7 @@ pub async fn live(runtime: &Arc<AppRuntime<LoggingAdapter>>, query: &str) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
log::error!("[qbz-qt] cortinilla load failed: {e}");
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
if is_current_cort_version(version) {
|
||||
b.as_mut().set_cortinilla_loading(false);
|
||||
}
|
||||
@@ -778,7 +778,7 @@ pub async fn live(runtime: &Arc<AppRuntime<LoggingAdapter>>, query: &str) {
|
||||
}
|
||||
let missing = attach_urls(urls);
|
||||
*LAST_CORT.lock().unwrap() = Some(data.clone());
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
if is_current_cort_version(version) {
|
||||
let json = serde_json::to_string(&data).unwrap_or_else(|_| "{}".into());
|
||||
b.as_mut().set_cortinilla_json(QString::from(json.as_str()));
|
||||
@@ -805,7 +805,7 @@ pub async fn live(runtime: &Arc<AppRuntime<LoggingAdapter>>, query: &str) {
|
||||
let _ = attach_urls(urls);
|
||||
let data = data.clone();
|
||||
*LAST_CORT.lock().unwrap() = Some(data.clone());
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
let json = serde_json::to_string(&data).unwrap_or_else(|_| "{}".into());
|
||||
b.as_mut().set_cortinilla_json(QString::from(json.as_str()));
|
||||
});
|
||||
@@ -859,8 +859,12 @@ pub fn move_selection(delta: i32) {
|
||||
set_selected(new_index, scroll_y);
|
||||
}
|
||||
|
||||
fn flat_index_content_y(data: &CortinillaData, flat_index: i32) -> f32 {
|
||||
let mut y = 6.0f32;
|
||||
fn flat_index_content_y(data: &CortinillaData, flat_index: i32) -> f64 {
|
||||
// NOTE: the `6.0` base is WRONG — it mirrors a 6 px padding that is a
|
||||
// Flickable VIEWPORT margin in the QML, not content space, so every row
|
||||
// is reported 6 px low (contract row E4 / D-BUG-3). C0 is a pure move:
|
||||
// the base is corrected to 0.0 in C4, together with its unit test.
|
||||
let mut y = 6.0f64;
|
||||
if let Some(top) = &data.top {
|
||||
if top.flat_index == flat_index {
|
||||
return y + 4.0 + 22.0;
|
||||
@@ -1818,7 +1822,7 @@ pub(crate) fn apply_favorite_change(kind: &str, id: &str, favorite: bool) {
|
||||
|
||||
fn publish_page(doc: &SearchPageDoc) {
|
||||
let json = serde_json::to_string(doc).unwrap_or_else(|_| "{}".into());
|
||||
crate::ui(move |mut b| {
|
||||
crate::search_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_search_json(QString::from(json.as_str()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1806,7 +1806,7 @@ pub async fn settings_bool(runtime: &Arc<AppRuntime<LoggingAdapter>>, key: &str,
|
||||
// effect immediately — two switches, one setting, different
|
||||
// behaviour.
|
||||
crate::search_qt::set_enabled(value);
|
||||
crate::ui(move |mut b| b.as_mut().set_intelligent_search(value));
|
||||
crate::search_bridge::ui(move |mut b| b.as_mut().set_intelligent_search(value));
|
||||
Ok(Apply::None)
|
||||
}
|
||||
// Section-nav placement (Slint ShellState.nav-in-sidebar /
|
||||
|
||||
Reference in new issue
Block a user