fix(navigation): restore routed view state
Persist view-owned search, sort, filter and tab state alongside scroll positions across back and forward navigation. cx
This commit is contained in:
3 files changed
+182
-49
No files matched your search
@@ -309,7 +309,7 @@ Column {
|
||||
}
|
||||
SettingRow {
|
||||
label: QbzSession.tr("Click on menu item navigates to first tab", QbzSession.trRev)
|
||||
description: QbzSession.tr("Clicking a section in the sidebar or title bar opens it on its first tab — Discover on Home, Library on All — instead of only showing its menu", QbzSession.trRev)
|
||||
description: QbzSession.tr("Clicking a section in the sidebar or title bar opens its first tab — including your chosen Local Library default — instead of only showing its menu", QbzSession.trRev)
|
||||
QbzToggle {
|
||||
checked: root.doc.navClickFirstTab === true
|
||||
onToggled: function (v) { QbzBridge.settingsBool("nav-click-first-tab", v) }
|
||||
|
||||
+139
-22
@@ -78,6 +78,10 @@ struct Entry {
|
||||
/// `viewport-y`; nothing crosses between the two, so this side keeps the
|
||||
/// sign its own Flickables use.)
|
||||
scroll: f32,
|
||||
/// Opaque view-owned state (tab, query, sort, filters, view mode). Like the
|
||||
/// scroll scope, Rust stores and returns it without interpreting it.
|
||||
state_scope: String,
|
||||
state_json: String,
|
||||
}
|
||||
|
||||
struct NavHistory {
|
||||
@@ -98,6 +102,7 @@ static HISTORY: Mutex<Option<NavHistory>> = Mutex::new(None);
|
||||
/// reused in place rather than reassigned, so a scroll frame allocates nothing
|
||||
/// once the page has reported once.
|
||||
static LIVE: Mutex<(String, f32)> = Mutex::new((String::new(), 0.0));
|
||||
static LIVE_STATE: Mutex<(String, String)> = Mutex::new((String::new(), String::new()));
|
||||
|
||||
/// Record the on-screen scroll container's current `contentY` under its scope.
|
||||
pub fn set_live_scroll(scope: &str, y: f32) {
|
||||
@@ -120,6 +125,25 @@ fn reset_live_scroll() {
|
||||
live.1 = 0.0;
|
||||
}
|
||||
|
||||
pub fn set_live_state(scope: &str, json: &str) {
|
||||
let mut live = LIVE_STATE.lock().unwrap();
|
||||
live.0.clear();
|
||||
live.0.push_str(scope);
|
||||
live.1.clear();
|
||||
live.1.push_str(json);
|
||||
}
|
||||
|
||||
fn live_state() -> (String, String) {
|
||||
let live = LIVE_STATE.lock().unwrap();
|
||||
(live.0.clone(), live.1.clone())
|
||||
}
|
||||
|
||||
fn reset_live_state() {
|
||||
let mut live = LIVE_STATE.lock().unwrap();
|
||||
live.0.clear();
|
||||
live.1.clear();
|
||||
}
|
||||
|
||||
fn with_history<R>(f: impl FnOnce(&mut NavHistory) -> R) -> R {
|
||||
let mut guard = HISTORY.lock().unwrap();
|
||||
let history = guard.get_or_insert_with(|| NavHistory {
|
||||
@@ -130,6 +154,8 @@ fn with_history<R>(f: impl FnOnce(&mut NavHistory) -> R) -> R {
|
||||
view: startup_view(),
|
||||
scope: String::new(),
|
||||
scroll: 0.0,
|
||||
state_scope: String::new(),
|
||||
state_json: String::new(),
|
||||
}],
|
||||
index: 0,
|
||||
});
|
||||
@@ -176,7 +202,10 @@ const VIEW_TO_PREF: &[(&str, &str)] = &[
|
||||
];
|
||||
|
||||
fn pref_for_view(view: &str) -> Option<&'static str> {
|
||||
VIEW_TO_PREF.iter().find(|(v, _)| *v == view).map(|(_, p)| *p)
|
||||
VIEW_TO_PREF
|
||||
.iter()
|
||||
.find(|(v, _)| *v == view)
|
||||
.map(|(_, p)| *p)
|
||||
}
|
||||
|
||||
fn view_for_pref(pref: &str) -> Option<&'static str> {
|
||||
@@ -187,7 +216,11 @@ fn view_for_pref(pref: &str) -> Option<&'static str> {
|
||||
// The reference's `discover` has no separate route here: this port's
|
||||
// Discover IS `home`. Accept it on READ so a prefs file written by the
|
||||
// Slint build restores somewhere sensible instead of falling to Home.
|
||||
.or(if pref == "discover" { Some("home") } else { None })
|
||||
.or(if pref == "discover" {
|
||||
Some("home")
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Crash-chain guard for the restore paths.
|
||||
@@ -241,7 +274,9 @@ pub fn mark_startup_healthy() {
|
||||
|
||||
/// This boot's chain level (1 = the previous shutdown was clean).
|
||||
pub fn crash_level() -> u8 {
|
||||
CRASH_LEVEL.load(std::sync::atomic::Ordering::Relaxed).max(1)
|
||||
CRASH_LEVEL
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
.max(1)
|
||||
}
|
||||
|
||||
/// The view to open on: `startup_page` = "home" always Home, "remember" the last
|
||||
@@ -304,11 +339,16 @@ pub fn record(view: &str) {
|
||||
);
|
||||
h.entries[h.index].scope = scope;
|
||||
h.entries[h.index].scroll = y;
|
||||
let (state_scope, state_json) = live_state();
|
||||
h.entries[h.index].state_scope = state_scope;
|
||||
h.entries[h.index].state_json = state_json;
|
||||
h.entries.truncate(h.index + 1);
|
||||
h.entries.push(Entry {
|
||||
view: view.to_string(),
|
||||
scope: String::new(),
|
||||
scroll: 0.0,
|
||||
state_scope: String::new(),
|
||||
state_json: String::new(),
|
||||
});
|
||||
h.index += 1;
|
||||
pushed = true;
|
||||
@@ -320,12 +360,21 @@ pub fn record(view: &str) {
|
||||
// the user moves it.
|
||||
if pushed {
|
||||
reset_live_scroll();
|
||||
reset_live_state();
|
||||
}
|
||||
// A FORWARD navigation never restores — and it must actively DISARM, or a
|
||||
// scope armed by a back() that its destination never consumed (a page
|
||||
// whose list stayed shorter than the viewport, so the guard never passed)
|
||||
// would fire on whatever mounts next.
|
||||
publish(can_back, can_forward, current, String::new(), 0.0);
|
||||
publish(
|
||||
can_back,
|
||||
can_forward,
|
||||
current,
|
||||
String::new(),
|
||||
0.0,
|
||||
String::new(),
|
||||
String::new(),
|
||||
);
|
||||
}
|
||||
|
||||
/// The id of the entry the user is standing on.
|
||||
@@ -355,23 +404,36 @@ pub fn forward() {
|
||||
/// move the cursor, and hand the destination's saved position to the shell so
|
||||
/// its scroll container can pick it up when it lays out.
|
||||
fn step(delta: isize) {
|
||||
let (can_back, can_forward, current, scope, scroll) = with_history(|h| {
|
||||
let next = h.index as isize + delta;
|
||||
if next >= 0 && (next as usize) < h.entries.len() {
|
||||
// Stamp the page we are leaving before stepping away.
|
||||
let (scope, y) = live_scroll();
|
||||
h.entries[h.index].scope = scope;
|
||||
h.entries[h.index].scroll = y;
|
||||
h.index = next as usize;
|
||||
}
|
||||
let (b, f, c) = snapshot(h);
|
||||
let e = &h.entries[h.index];
|
||||
(b, f, c, e.scope.clone(), e.scroll)
|
||||
});
|
||||
let (can_back, can_forward, current, scope, scroll, state_scope, state_json) =
|
||||
with_history(|h| {
|
||||
let next = h.index as isize + delta;
|
||||
if next >= 0 && (next as usize) < h.entries.len() {
|
||||
// Stamp the page we are leaving before stepping away.
|
||||
let (scope, y) = live_scroll();
|
||||
h.entries[h.index].scope = scope;
|
||||
h.entries[h.index].scroll = y;
|
||||
let (state_scope, state_json) = live_state();
|
||||
h.entries[h.index].state_scope = state_scope;
|
||||
h.entries[h.index].state_json = state_json;
|
||||
h.index = next as usize;
|
||||
}
|
||||
let (b, f, c) = snapshot(h);
|
||||
let e = &h.entries[h.index];
|
||||
(
|
||||
b,
|
||||
f,
|
||||
c,
|
||||
e.scope.clone(),
|
||||
e.scroll,
|
||||
e.state_scope.clone(),
|
||||
e.state_json.clone(),
|
||||
)
|
||||
});
|
||||
// The destination is now the live page: seed the live pair with what it is
|
||||
// about to restore to, so leaving it again before it has reported anything
|
||||
// does not stamp the OUTGOING page's offset onto it.
|
||||
set_live_scroll(&scope, scroll);
|
||||
set_live_state(&state_scope, &state_json);
|
||||
// Arm only for a position worth restoring. A page that was at the top
|
||||
// needs no restore, and arming for 0 would leave a scope standing until
|
||||
// some container happened to clear it.
|
||||
@@ -381,7 +443,15 @@ fn step(delta: isize) {
|
||||
String::new()
|
||||
};
|
||||
log::debug!("[qbz-qt] scroll: {current:?} arms scope {armed:?} y={scroll}");
|
||||
publish(can_back, can_forward, current, armed, scroll);
|
||||
publish(
|
||||
can_back,
|
||||
can_forward,
|
||||
current,
|
||||
armed,
|
||||
scroll,
|
||||
state_scope,
|
||||
state_json,
|
||||
);
|
||||
}
|
||||
|
||||
fn snapshot(h: &NavHistory) -> (bool, bool, String) {
|
||||
@@ -403,6 +473,8 @@ fn publish(
|
||||
current: String,
|
||||
restore_scope: String,
|
||||
scroll_restore: f32,
|
||||
restore_state_scope: String,
|
||||
state_restore: String,
|
||||
) {
|
||||
crate::shell_bridge::ui(move |mut b| {
|
||||
b.as_mut().set_can_back(can_back);
|
||||
@@ -410,19 +482,34 @@ fn publish(
|
||||
b.as_mut()
|
||||
.set_restore_scope(QString::from(restore_scope.as_str()));
|
||||
b.as_mut().set_scroll_restore(scroll_restore);
|
||||
b.as_mut()
|
||||
.set_restore_state_scope(QString::from(restore_state_scope.as_str()));
|
||||
b.as_mut()
|
||||
.set_state_restore(QString::from(state_restore.as_str()));
|
||||
b.as_mut().set_current_view(QString::from(current.as_str()));
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// The history logic is exercised through the Mutex global; these tests
|
||||
// run serially within one process, so drive it through the public API
|
||||
// and re-derive expectations from fresh state each time. (The bridge
|
||||
// publish hop is a no-op off the Qt thread — QT_THREAD unset.)
|
||||
use std::sync::Mutex;
|
||||
|
||||
// Rust's test runner is parallel by default. These tests intentionally
|
||||
// exercise the process-global history, so serialize them and clear every
|
||||
// global seam first. (The bridge publish hop is a no-op off the Qt thread
|
||||
// — QT_THREAD unset.)
|
||||
static TEST_LOCK: Mutex<()> = Mutex::new(());
|
||||
|
||||
fn reset() {
|
||||
*super::HISTORY.lock().unwrap() = None;
|
||||
super::reset_live_scroll();
|
||||
super::reset_live_state();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_back_forward_cycle() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
reset();
|
||||
super::record("home");
|
||||
super::record("album");
|
||||
super::record("artist");
|
||||
@@ -443,6 +530,8 @@ mod tests {
|
||||
/// does not inherit it.
|
||||
#[test]
|
||||
fn scroll_is_stamped_on_leave_and_returned_on_back() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
reset();
|
||||
super::record("home");
|
||||
super::set_live_scroll("home:home", 1234.0);
|
||||
super::record("album");
|
||||
@@ -458,4 +547,32 @@ mod tests {
|
||||
assert_eq!(super::current_view(), "album");
|
||||
assert_eq!(super::live_scroll(), ("album".to_string(), 88.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_view_state_is_stamped_and_restored_in_both_directions() {
|
||||
let _guard = TEST_LOCK.lock().unwrap();
|
||||
reset();
|
||||
super::record("local");
|
||||
let local = r#"{"activeTab":"albums","albumsSearch":"beatles","albumsSort":"year-desc"}"#;
|
||||
super::set_live_state("local", local);
|
||||
|
||||
super::record("album");
|
||||
assert_eq!(super::live_state(), (String::new(), String::new()));
|
||||
let detail = r#"{"version":2}"#;
|
||||
super::set_live_state("localalbum", detail);
|
||||
|
||||
super::back();
|
||||
assert_eq!(super::current_view(), "local");
|
||||
assert_eq!(
|
||||
super::live_state(),
|
||||
("local".to_string(), local.to_string())
|
||||
);
|
||||
|
||||
super::forward();
|
||||
assert_eq!(super::current_view(), "album");
|
||||
assert_eq!(
|
||||
super::live_state(),
|
||||
("localalbum".to_string(), detail.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ pub mod qbz_shell {
|
||||
// (1:1 Slint: persist-sidebar-state).
|
||||
#[qproperty(i32, sidebar_state)]
|
||||
#[qproperty(bool, queue_open)]
|
||||
|
||||
// --- Section-nav placement (Slint ShellState) ---------------------
|
||||
// ON = the Discover / Library / Local Library / My QBZ rows live in
|
||||
// the SIDEBAR (with a hairline under them); the header shows the
|
||||
@@ -66,6 +65,10 @@ pub mod qbz_shell {
|
||||
// Slint uses (`NavState.restore-scope = ""` at the apply site).
|
||||
#[qproperty(QString, restore_scope)]
|
||||
#[qproperty(f32, scroll_restore)]
|
||||
// Opaque view-state companion: a routed view reports one JSON
|
||||
// snapshot and consumes it on back/forward before it mounts data.
|
||||
#[qproperty(QString, restore_state_scope)]
|
||||
#[qproperty(QString, state_restore)]
|
||||
// --- Flyout landing tab (NavFlyout rows carry view + tab) ----------
|
||||
// The tab a nav-flyout click must land on: Discover > For You is
|
||||
// nav_tab_view="home" + nav_tab="forYou". Written by
|
||||
@@ -76,11 +79,9 @@ pub mod qbz_shell {
|
||||
#[qproperty(QString, nav_tab)]
|
||||
#[qproperty(QString, nav_tab_view)]
|
||||
#[qproperty(i32, nav_tab_seq)]
|
||||
|
||||
// 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)]
|
||||
|
||||
// One persisted toggle shared by Appearance, the view-mode flyout and
|
||||
// both seekbar mounts.
|
||||
#[qproperty(bool, seekbar_waveform)]
|
||||
@@ -98,7 +99,6 @@ pub mod qbz_shell {
|
||||
// `height - largeDockHeight`. Both consumers read THIS — never a
|
||||
// literal (Sidebar.slint:1145 is the same contract).
|
||||
#[qproperty(f32, large_dock_height)]
|
||||
|
||||
// --- Theme (phase 19) --------------------------------------------
|
||||
// ONE JSON token document (theme_qt.rs ThemeTokens: 30 colors + 24
|
||||
// alpha tiers + ambient derivations + isDark) — QbzTheme.qml binds
|
||||
@@ -136,7 +136,6 @@ pub mod qbz_shell {
|
||||
// which swatch has the picker open is pure view state and lives in
|
||||
// CustomThemeEditor.qml.
|
||||
#[qproperty(QString, custom_theme_json)]
|
||||
|
||||
// --- Log viewer (log_viewer_qt.rs) --------------------------------
|
||||
// ONE document: {open, rows[], total, shown, filterLevel, search,
|
||||
// autoTail, uploading, uploadedUrl}. The ring can hold thousands of
|
||||
@@ -147,13 +146,11 @@ pub mod qbz_shell {
|
||||
/// the pre-publish frame.
|
||||
#[qproperty(QString, diagnostics_json)]
|
||||
#[qproperty(QString, log_viewer_json)]
|
||||
|
||||
// --- 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).
|
||||
@@ -168,7 +165,6 @@ pub mod qbz_shell {
|
||||
// reference does not have. The default is the FULL SHAPE, never "{}":
|
||||
// `JSON.parse("{}").rows.length` throws in the pre-publish frame.
|
||||
#[qproperty(QString, sidebar_folder_popup_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
|
||||
@@ -210,7 +206,6 @@ pub mod qbz_shell {
|
||||
// `cfg!`, and AppearanceSettings.slint:1011-1043 gates the whole
|
||||
// group AND the Preferred-GPU row on it).
|
||||
#[qproperty(bool, is_linux)]
|
||||
|
||||
// --- Main-window geometry -----------------------------------------
|
||||
// The restored LOGICAL size + maximized flag from the shared
|
||||
// ui_prefs.json (settings_qt::window_size / window_maximized; the
|
||||
@@ -227,7 +222,6 @@ pub mod qbz_shell {
|
||||
// save gate that use it in settings_qt.
|
||||
#[qproperty(f32, window_min_width)]
|
||||
#[qproperty(f32, window_min_height)]
|
||||
|
||||
// --- Ambient background (phase 14) --------------------------------
|
||||
// 0 = off, 1 = on (the ambient look; the owner's store carries
|
||||
// "ambient"). Live — toggling applies immediately (pure QML
|
||||
@@ -352,7 +346,6 @@ pub mod qbz_shell {
|
||||
#[qproperty(i32, pulse_ms)]
|
||||
#[qproperty(f32, ambient_surface_alpha)]
|
||||
#[qproperty(f32, ambient_bar_alpha)]
|
||||
|
||||
// --- Rounded-cover render arm (manual override) --------------------
|
||||
// `RoundedImage` masks covers with a MultiEffect layer and falls back
|
||||
// to its CPU Canvas raster automatically when `GraphicsInfo.api`
|
||||
@@ -363,7 +356,6 @@ pub mod qbz_shell {
|
||||
// instantiate lazily and the first access is a `RoundedImage`
|
||||
// `_useCanvas` read, which can precede `QbzShell.boot()`.
|
||||
#[qproperty(bool, force_canvas_art)]
|
||||
|
||||
// --- In-app toasts (shared) ---------------------------------------
|
||||
// ONE JSON document (toast_qt.rs ToastDoc: seq / kind / message /
|
||||
// persistent), rendered by the single `controls/QbzToast.qml` host
|
||||
@@ -377,7 +369,6 @@ pub mod qbz_shell {
|
||||
// overlay. Rust owns no timer — the auto-hide delay is keyed off `seq`
|
||||
// in QML, so a toast never needs a hide round-trip.
|
||||
#[qproperty(QString, toast_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
|
||||
@@ -425,7 +416,6 @@ pub mod qbz_shell {
|
||||
#[qproperty(bool, art_preview_show)]
|
||||
#[qproperty(f32, art_preview_x)]
|
||||
#[qproperty(f32, art_preview_y)]
|
||||
|
||||
// --- Multi-select seam (2026-08-03 hotkeys-port contract §4.6) ----
|
||||
// QML-REPORTED — selection state is in-view QML JS
|
||||
// (`library_bulk.rs:8`: "select-all / clear never reach Rust"), so the
|
||||
@@ -439,7 +429,6 @@ pub mod qbz_shell {
|
||||
// properties.
|
||||
#[qproperty(bool, multi_select_active, READ, WRITE = set_multi_select_active, NOTIFY)]
|
||||
#[qproperty(bool, multi_select_capable, READ, WRITE = set_multi_select_capable, NOTIFY)]
|
||||
|
||||
type QbzShell = super::QbzShellRust;
|
||||
|
||||
/// Registers this object's Qt-thread hop (Main.qml boots EVERY
|
||||
@@ -499,6 +488,8 @@ pub mod qbz_shell {
|
||||
/// from the route here the way Slint derives it from the entry.)
|
||||
#[qinvokable]
|
||||
fn report_scroll(self: Pin<&mut QbzShell>, scope: QString, y: f32);
|
||||
#[qinvokable]
|
||||
fn report_nav_state(self: Pin<&mut QbzShell>, scope: QString, state: QString);
|
||||
|
||||
/// Sidebar navigation: record a content view ("home" | "library")
|
||||
/// and lazy-load its data on first visit.
|
||||
@@ -686,7 +677,14 @@ pub mod qbz_shell {
|
||||
// --- 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);
|
||||
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.
|
||||
@@ -753,6 +751,8 @@ pub struct QbzShellRust {
|
||||
can_forward: bool,
|
||||
restore_scope: QString,
|
||||
scroll_restore: f32,
|
||||
restore_state_scope: QString,
|
||||
state_restore: QString,
|
||||
nav_tab: QString,
|
||||
nav_tab_view: QString,
|
||||
nav_tab_seq: i32,
|
||||
@@ -848,6 +848,8 @@ impl Default for QbzShellRust {
|
||||
can_back: false,
|
||||
restore_scope: QString::default(),
|
||||
scroll_restore: 0.0,
|
||||
restore_state_scope: QString::default(),
|
||||
state_restore: QString::default(),
|
||||
nav_tab: QString::default(),
|
||||
nav_tab_view: QString::default(),
|
||||
nav_tab_seq: 0,
|
||||
@@ -861,13 +863,9 @@ impl Default for QbzShellRust {
|
||||
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(),
|
||||
app_font_family: QString::from(
|
||||
crate::settings_qt::app_font_family().as_str(),
|
||||
),
|
||||
app_font_family: QString::from(crate::settings_qt::app_font_family().as_str()),
|
||||
custom_theme_json: QString::from(crate::custom_theme_qt::state_json().as_str()),
|
||||
diagnostics_json: QString::from(
|
||||
crate::diagnostics_qt::empty_doc_json().as_str(),
|
||||
),
|
||||
diagnostics_json: QString::from(crate::diagnostics_qt::empty_doc_json().as_str()),
|
||||
log_viewer_json: QString::from("{}"),
|
||||
lyrics_open: false,
|
||||
lyrics_json: QString::from("{}"),
|
||||
@@ -1164,6 +1162,10 @@ impl qbz_shell::QbzShell {
|
||||
crate::nav_qt::set_live_scroll(&scope.to_string(), y);
|
||||
}
|
||||
|
||||
pub fn report_nav_state(self: Pin<&mut Self>, scope: QString, state: QString) {
|
||||
crate::nav_qt::set_live_state(&scope.to_string(), &state.to_string());
|
||||
}
|
||||
|
||||
pub fn navigate_to(self: Pin<&mut Self>, view: QString) {
|
||||
crate::navigate_to(&view.to_string());
|
||||
}
|
||||
@@ -1229,7 +1231,9 @@ impl qbz_shell::QbzShell {
|
||||
pub fn report_issue_open(self: Pin<&mut Self>) {
|
||||
// Same URL the reference opens (crates/qbz/src/main.rs:14619-14624):
|
||||
// the repo's bug-report template, no prefilled body.
|
||||
if let Err(e) = open::that("https://github.com/vicrodh/qbz/issues/new?template=bug_report.yml") {
|
||||
if let Err(e) =
|
||||
open::that("https://github.com/vicrodh/qbz/issues/new?template=bug_report.yml")
|
||||
{
|
||||
log::warn!("[qbz-qt] could not open the issue template: {e}");
|
||||
}
|
||||
}
|
||||
@@ -1314,8 +1318,21 @@ impl qbz_shell::QbzShell {
|
||||
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_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) {
|
||||
@@ -1375,5 +1392,4 @@ impl qbz_shell::QbzShell {
|
||||
log::warn!("[qbz-qt] failed to open '{url}': {e}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user