quadraui 0.6.0

Cross-platform Rust UI toolkit: write one AppLogic and run it unmodified on TUI, GTK4 (Linux), macOS, and Windows backends
Documentation
//! `cargo run --example win_platform_services --features win` (Windows only)
//!
//! Smoke test for `WinPlatformServices`: clipboard round-trip, native file
//! open/save dialogs, a `Shell_NotifyIconW` balloon notification, `open_url`,
//! and `TaskDialogIndirect`-backed message dialogs.
//!
//! Draws nothing — every `WinBackend::draw_*` rasteriser is still a `todo!()`
//! stub (no `draw_status_bar` to report results in-window), so this exercises
//! `backend.services()` directly from `AppLogic::handle` and reports results to
//! stderr instead of an in-window status bar.
//!
//! Controls:
//! - `c` — write a fixed string to the clipboard, then read it back and
//!   report whether it round-tripped
//! - `o` — native file-open dialog (filtered to `*.rs`)
//! - `s` — native file-save dialog (initial name `untitled.txt`)
//! - `n` — fire a balloon notification (watch the notification area)
//! - `m` — native message dialog (`TaskDialogIndirect`) with
//!   Save/Don't Save/Cancel buttons; reports which one was chosen
//! - `u` — `open_url` a fixed address (the default browser should launch)
//! - `t` — query the OS light/dark/accent/high-contrast preference
//!   (`system_theme`) and report it; flip Windows' Settings ->
//!   Personalization -> Colors "Choose your mode" between runs to see the
//!   answer change
//! - `f` — `reveal_in_file_manager` a freshly-written temp file —
//!   an Explorer window should open with it selected
//! - `p` — `open_path` that same temp file with its default handler
//! - `x` — write a second temp file and `move_to_trash` it —
//!   check the Recycle Bin afterward
//! - `b` — `beep`
//! - `d` — `displays`/`cursor_screen_point`: prints every connected monitor
//!   (bounds, work area, scale, primary) and the current cursor position to
//!   stderr — move the window to a second monitor and press `d` again to see
//!   the list/cursor position change
//! - `Esc` / `q` — quit
//!
//! `quadraui::win::run` only exists when compiled for `target_os = "windows"`
//! — this example is Windows-only.

#[cfg(target_os = "windows")]
use quadraui::{
    AppLogic, Backend, DialogSeverity, FileDialogOptions, Key, MessageDialogButton,
    MessageDialogOptions, NamedKey, Notification, Reaction, UiEvent, WidgetId,
};

// `pub` for test coverage: `tests/win_example_driver.rs` constructs this
// directly. This is the one example whose `AppLogic` lives in the example
// file itself rather than `examples/common/` (see module docs above), so the
// test hook is this visibility rather than a `pub mod` in `common/`.
#[cfg(target_os = "windows")]
pub struct PlatformServicesDemo;

/// Write a throwaway temp file for `f`/`p`/`x` to act on, so this demo
/// never touches anything the user actually cares about.
#[cfg(target_os = "windows")]
fn scratch_file(label: &str) -> std::path::PathBuf {
    let path = std::env::temp_dir().join(format!(
        "quadraui-956-win-platform-services-demo-{label}-{}.txt",
        std::process::id()
    ));
    let _ = std::fs::write(&path, b"quadraui#956 demo scratch file");
    path
}

#[cfg(target_os = "windows")]
impl AppLogic for PlatformServicesDemo {
    type AreaId = ();

    fn render(&self, _backend: &mut dyn Backend, _area: ()) {
        // Nothing to paint yet (see module docs above) — `begin_frame`'s
        // `Clear` is the entire visible content of this demo, same as
        // `win_demo`.
    }

    fn handle(&mut self, event: UiEvent, backend: &mut dyn Backend) -> Reaction {
        match event {
            UiEvent::WindowClose => Reaction::Exit,
            UiEvent::KeyPressed {
                key: Key::Named(NamedKey::Escape) | Key::Char('q'),
                ..
            } => Reaction::Exit,
            UiEvent::KeyPressed {
                key: Key::Char('c'),
                ..
            } => {
                const PAYLOAD: &str = "quadraui #23 clipboard round-trip";
                let clipboard = backend.services().clipboard();
                clipboard.write_text(PAYLOAD);
                match clipboard.read_text() {
                    Some(text) if text == PAYLOAD => {
                        eprintln!("clipboard round-trip OK: {text:?}");
                    }
                    Some(text) => eprintln!("clipboard round-trip MISMATCH: got {text:?}"),
                    None => eprintln!("clipboard round-trip FAILED: nothing read back"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('o'),
                ..
            } => {
                let opts = FileDialogOptions {
                    title: Some("Open File".to_string()),
                    filters: vec![("Rust files".to_string(), vec!["rs".to_string()])],
                    ..Default::default()
                };
                match backend.services().show_file_open_dialog(opts) {
                    Some(path) => eprintln!("opened: {}", path.display()),
                    None => eprintln!("open cancelled"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('s'),
                ..
            } => {
                let opts = FileDialogOptions {
                    title: Some("Save As".to_string()),
                    initial_filename: Some("untitled.txt".to_string()),
                    ..Default::default()
                };
                match backend.services().show_file_save_dialog(opts) {
                    Some(path) => eprintln!("save as: {}", path.display()),
                    None => eprintln!("save cancelled"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('n'),
                ..
            } => {
                backend
                    .services()
                    .send_notification(Notification::new("quadraui", "#23 platform services demo"));
                eprintln!("notification fired — check the notification area");
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('m'),
                ..
            } => {
                let opts = MessageDialogOptions {
                    title: "Save changes?".to_string(),
                    body: "quadraui #744 message dialog demo — pick a button.".to_string(),
                    buttons: vec![
                        MessageDialogButton {
                            id: WidgetId::new("save"),
                            label: "Save".to_string(),
                            is_default: true,
                            is_cancel: false,
                        },
                        MessageDialogButton {
                            id: WidgetId::new("dont_save"),
                            label: "Don't Save".to_string(),
                            is_default: false,
                            is_cancel: false,
                        },
                        MessageDialogButton {
                            id: WidgetId::new("cancel"),
                            label: "Cancel".to_string(),
                            is_default: false,
                            is_cancel: true,
                        },
                    ],
                    severity: Some(DialogSeverity::Warning),
                };
                match backend.services().show_message_dialog(opts) {
                    Some(id) => eprintln!("message dialog: chose {id:?}"),
                    None => eprintln!("message dialog: dismissed with no choice"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('u'),
                ..
            } => {
                backend.services().open_url("https://example.com");
                eprintln!("open_url called — the default browser should launch");
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('t'),
                ..
            } => {
                match backend.services().system_theme() {
                    Ok(theme) => eprintln!("system_theme: {theme:?}"),
                    Err(e) => eprintln!("system_theme: unavailable ({e:?})"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('f'),
                ..
            } => {
                let path = scratch_file("reveal");
                match backend.services().reveal_in_file_manager(&path) {
                    Ok(()) => eprintln!("reveal_in_file_manager: opened Explorer at {path:?}"),
                    Err(e) => eprintln!("reveal_in_file_manager FAILED: {e:?}"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('p'),
                ..
            } => {
                let path = scratch_file("open");
                match backend.services().open_path(&path) {
                    Ok(()) => eprintln!("open_path: launched the default handler for {path:?}"),
                    Err(e) => eprintln!("open_path FAILED: {e:?}"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('x'),
                ..
            } => {
                let path = scratch_file("trash");
                match backend.services().move_to_trash(&path) {
                    Ok(()) => eprintln!("move_to_trash: moved {path:?} to the Recycle Bin"),
                    Err(e) => eprintln!("move_to_trash FAILED: {e:?}"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('b'),
                ..
            } => {
                match backend.services().beep() {
                    Ok(()) => eprintln!("beep: ok"),
                    Err(e) => eprintln!("beep FAILED: {e:?}"),
                }
                Reaction::Continue
            }
            UiEvent::KeyPressed {
                key: Key::Char('d'),
                ..
            } => {
                match backend.services().displays() {
                    Ok(displays) => {
                        for (i, d) in displays.iter().enumerate() {
                            eprintln!("display[{i}]: {d:?}");
                        }
                    }
                    Err(e) => eprintln!("displays FAILED: {e:?}"),
                }
                match backend.services().cursor_screen_point() {
                    Ok(p) => eprintln!("cursor_screen_point: {p:?}"),
                    Err(e) => eprintln!("cursor_screen_point FAILED: {e:?}"),
                }
                Reaction::Continue
            }
            _ => Reaction::Continue,
        }
    }
}

#[cfg(target_os = "windows")]
fn main() -> std::process::ExitCode {
    quadraui::win::run(PlatformServicesDemo)
}

#[cfg(not(target_os = "windows"))]
fn main() {
    eprintln!("win_platform_services only runs on Windows — see this file's module docs.");
}