slint-ui-system 0.5.0

Neon Design System — Slint UI components for Rust desktop apps. 35+ components, dark/light theme, neon accents.
//! Rust-side theme helpers for Neon's dark/light switch.
//!
//! Slint can't auto-impl a Rust trait on its generated globals, so
//! these helpers are duck-typed via macros — they work with any
//! window+theme pair from a consumer's `slint::include_modules!()`.
//!
//! For a one-shot setup (instantiate window + apply initial theme)
//! see [`crate::neon_ui_setup!`]. The functions here are for
//! *runtime* theme changes (e.g. a Settings toggle).
//!
//! ## Why functions and not a trait
//!
//! A trait `ThemeBridge` would require `impl ThemeBridge for
//! ConsumerWindow` boilerplate in every consuming app. Two
//! one-line macros wrap the call instead — same ergonomics,
//! zero per-app boilerplate.
//!
//! ## Usage
//!
//! ```ignore
//! use slint::ComponentHandle;
//! use slint_ui_system::theme;
//! slint::include_modules!();
//!
//! let win = AppWindow::new()?;
//! theme::set_dark_mode(&win, false);   // light
//! assert!(!theme::is_dark_mode(&win));
//! ```

/// Set Neon's dark/light flag on a window.
///
/// Looks up the `Theme` global on `window` and calls
/// `set_dark_mode(dark)`. The `Theme` global is generated by
/// `slint::include_modules!()` in the caller's crate when their
/// `.slint` imports `@neon`.
///
/// # Panics
///
/// Panics if `window` doesn't have the `Theme` global. That only
/// happens when the consumer forgot to import Neon's components
/// in their `.slint` — a programmer error, not a runtime
/// condition.
///
/// # Example
///
/// ```ignore
/// use slint_ui_system::theme;
/// theme::set_dark_mode(&win, true);
/// ```
pub fn set_dark_mode<W>(window: &W, dark: bool)
where
    W: ThemeAccess,
{
    window.theme_set_dark_mode(dark);
}

/// Read Neon's current dark/light flag.
pub fn is_dark_mode<W>(window: &W) -> bool
where
    W: ThemeAccess,
{
    window.theme_dark_mode()
}

/// Flip the current theme (dark ↔ light).
///
/// Convenience for "Settings → Toggle theme" buttons. Equivalent
/// to `set_dark_mode(window, !is_dark_mode(window))`.
pub fn toggle<W>(window: &W)
where
    W: ThemeAccess,
{
    let cur = window.theme_dark_mode();
    window.theme_set_dark_mode(!cur);
}

// ── ThemeAccess trait ───────────────────────────────────────────────

/// Trait that the consumer's window type implements to wire up
/// Neon's theme helpers. The implementation is one line per method
/// — the [`impl_theme_access!`](crate::impl_theme_access) macro
/// generates it for you.
///
/// ```ignore
/// use slint_ui_system::theme::impl_theme_access;
/// slint::include_modules!();
///
/// impl_theme_access!(AppWindow, Theme);
/// ```
///
/// After that the consumer can call `theme::set_dark_mode`,
/// `theme::is_dark_mode`, and `theme::toggle` on `AppWindow`.
pub trait ThemeAccess {
    /// Apply the dark-mode flag.
    fn theme_set_dark_mode(&self, dark: bool);
    /// Read the current dark-mode flag.
    fn theme_dark_mode(&self) -> bool;
}

/// Implement [`ThemeAccess`] for a consumer's window type, wiring
/// it through to a Slint-generated `Theme` global.
///
/// ## Arguments
///
/// 1. `$window` — the window type generated by
///    `slint::include_modules!()`, e.g. `AppWindow`.
/// 2. `$theme` — the theme global type generated alongside,
///    typically `Theme`.
///
/// ## Example
///
/// ```ignore
/// use slint_ui_system::impl_theme_access;
/// slint::include_modules!();   // gives us `AppWindow` + `Theme`
/// impl_theme_access!(AppWindow, Theme);
/// ```
#[macro_export]
macro_rules! impl_theme_access {
    ($window:ty, $theme:ty) => {
        impl $crate::theme::ThemeAccess for $window {
            fn theme_set_dark_mode(&self, dark: bool) {
                use ::slint::ComponentHandle as _;
                self.global::<$theme>().set_dark_mode(dark);
            }
            fn theme_dark_mode(&self) -> bool {
                use ::slint::ComponentHandle as _;
                self.global::<$theme>().get_dark_mode()
            }
        }
    };
}