refactor(slint): extract toast into qbz-slint-common crate
New shared crate qbz-slint-common (will also host the artwork primitives via a dependency-inversion pass). toast.rs is a clean leaf over qbz-ui: only Slint types (AppWindow, ToastKind, ToastState) + slint core (Timer/Weak). Moved verbatim; the only edit is the import (`crate::` -> `qbz_ui::`). Bin re-exports via `pub use qbz_slint_common::toast;` so all 154 call sites across 21 files are unchanged. slint dep is `default-features = false` (adversarial review: a default-features slint here would turn on the default renderer for the whole build via feature unification). `cargo check -p qbz` green. cc
This commit is contained in:
7 files changed
+27
-2
No files matched your search
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "qbz-slint-common"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Shared Slint-coupled frontend helpers for the qbz app (toast; artwork primitives later)."
|
||||
|
||||
[dependencies]
|
||||
qbz-ui = { path = "../qbz-ui" }
|
||||
slint = { version = "=1.16.1", default-features = false, features = ["std", "compat-1-2"] }
|
||||
@@ -0,0 +1,3 @@
|
||||
//! Shared Slint-coupled helpers extracted from the qbz binary. Depends on
|
||||
//! qbz-ui for the generated types (AppWindow, the globals, structs).
|
||||
pub mod toast;
|
||||
@@ -0,0 +1,115 @@
|
||||
//! In-app toast controller — the Rust side of the Slint port of Tauri's
|
||||
//! `toastStore`. One toast at a time (a new one replaces the current),
|
||||
//! auto-hidden after a per-kind delay. `buffering` is persistent.
|
||||
//!
|
||||
//! All functions touch Slint globals and the shared hide `Timer`, so they
|
||||
//! must run on the event-loop thread. From background threads / async
|
||||
//! tasks use [`show_weak`] / [`error_weak`], which hop onto the event loop
|
||||
//! for you (the established `upgrade_in_event_loop` pattern).
|
||||
//!
|
||||
//! This is the shared toast API surface; not every helper has a call site
|
||||
//! yet (error surfacing is being wired in incrementally), so the unused
|
||||
//! ones are intentionally allowed rather than removed.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use slint::{ComponentHandle, Timer, TimerMode, Weak};
|
||||
|
||||
use qbz_ui::{AppWindow, ToastKind, ToastState};
|
||||
|
||||
thread_local! {
|
||||
// Single reusable auto-hide timer. Reused (stop + start) so a fresh
|
||||
// toast cancels the previous toast's pending hide instead of letting
|
||||
// it fire and dismiss the newer one early — mirrors the
|
||||
// `clearTimeout` in Tauri's toastStore.
|
||||
static HIDE_TIMER: Timer = Timer::default();
|
||||
}
|
||||
|
||||
/// Auto-hide delay per kind. `None` = persistent (buffering). Matches
|
||||
/// Tauri's `defaultDurations`.
|
||||
fn auto_hide(kind: ToastKind) -> Option<Duration> {
|
||||
let ms = match kind {
|
||||
ToastKind::Success => 3000,
|
||||
ToastKind::Info => 3000,
|
||||
ToastKind::Warning => 4000,
|
||||
ToastKind::Error => 5000,
|
||||
ToastKind::Buffering => return None,
|
||||
};
|
||||
Some(Duration::from_millis(ms))
|
||||
}
|
||||
|
||||
/// Show a toast. Must be called on the event-loop thread.
|
||||
pub fn show(window: &AppWindow, message: impl Into<String>, kind: ToastKind) {
|
||||
let message: String = message.into();
|
||||
let state = window.global::<ToastState>();
|
||||
|
||||
// Non-error toasts honor the global toggle; errors always show
|
||||
// (parity with Tauri's `showToast`).
|
||||
if !state.get_enabled() && kind != ToastKind::Error {
|
||||
return;
|
||||
}
|
||||
|
||||
state.set_message(message.as_str().into());
|
||||
state.set_kind(kind);
|
||||
state.set_persistent(kind == ToastKind::Buffering);
|
||||
state.set_visible(true);
|
||||
|
||||
HIDE_TIMER.with(|t| t.stop());
|
||||
if let Some(delay) = auto_hide(kind) {
|
||||
let weak = window.as_weak();
|
||||
HIDE_TIMER.with(|t| {
|
||||
t.start(TimerMode::SingleShot, delay, move || {
|
||||
if let Some(w) = weak.upgrade() {
|
||||
w.global::<ToastState>().set_visible(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide the current toast immediately (also stops the pending auto-hide).
|
||||
pub fn hide(window: &AppWindow) {
|
||||
HIDE_TIMER.with(|t| t.stop());
|
||||
window.global::<ToastState>().set_visible(false);
|
||||
}
|
||||
|
||||
/// Show a toast from any thread (async / background error paths). Hops
|
||||
/// onto the event loop, so the message must be owned/`Send`.
|
||||
pub fn show_weak(weak: &Weak<AppWindow>, message: impl Into<String>, kind: ToastKind) {
|
||||
let message: String = message.into();
|
||||
let _ = weak.upgrade_in_event_loop(move |w| show(&w, message, kind));
|
||||
}
|
||||
|
||||
// ---- Convenience wrappers -------------------------------------------------
|
||||
|
||||
pub fn error(window: &AppWindow, message: impl Into<String>) {
|
||||
show(window, message, ToastKind::Error);
|
||||
}
|
||||
|
||||
pub fn success(window: &AppWindow, message: impl Into<String>) {
|
||||
show(window, message, ToastKind::Success);
|
||||
}
|
||||
|
||||
pub fn info(window: &AppWindow, message: impl Into<String>) {
|
||||
show(window, message, ToastKind::Info);
|
||||
}
|
||||
|
||||
pub fn warning(window: &AppWindow, message: impl Into<String>) {
|
||||
show(window, message, ToastKind::Warning);
|
||||
}
|
||||
|
||||
/// Error toast from any thread.
|
||||
pub fn error_weak(weak: &Weak<AppWindow>, message: impl Into<String>) {
|
||||
show_weak(weak, message, ToastKind::Error);
|
||||
}
|
||||
|
||||
/// Success toast from any thread.
|
||||
pub fn success_weak(weak: &Weak<AppWindow>, message: impl Into<String>) {
|
||||
show_weak(weak, message, ToastKind::Success);
|
||||
}
|
||||
|
||||
/// Info toast from any thread.
|
||||
pub fn info_weak(weak: &Weak<AppWindow>, message: impl Into<String>) {
|
||||
show_weak(weak, message, ToastKind::Info);
|
||||
}
|
||||
Reference in new issue
Block a user