oxiui/runner.rs
1//! Pluggable backend runner infrastructure for OxiUI.
2//!
3//! The [`BackendRunner`] trait decouples backend selection from [`crate::App::run`]
4//! dispatch logic. Each backend provides its own [`BackendRunner`] implementation;
5//! the app selects and boxes the appropriate runner at run time.
6//!
7//! # Current state
8//! `EguiRunner` (feature `egui`) and `IcedRunner` (feature `iced`) are wiring stubs.
9//! The live rendering paths remain in `lib.rs`'s `run_egui_or_fallback` / `run_iced`
10//! methods. These stubs exist to provide a stable public trait surface; full delegation
11//! is planned for M6.
12
13use crate::{AppConfig, AppExit};
14use oxiui_core::UiError;
15
16/// Content closure type passed to a backend runner.
17///
18/// A boxed, send-capable, frame-driven closure that receives a mutable reference
19/// to a [`oxiui_core::UiCtx`] on each frame.
20pub type ContentFn = Box<dyn FnMut(&mut dyn oxiui_core::UiCtx) + Send>;
21
22/// Lifecycle callbacks passed to a [`BackendRunner`] at startup.
23///
24/// Backends call these closures when the corresponding window event fires.
25/// Closures that are `None` are silently skipped.
26#[derive(Default)]
27pub struct LifecycleConfig {
28 /// Called when the window close button is pressed or the process is asked to quit.
29 pub on_close: Option<Box<dyn FnMut() + Send>>,
30 /// Called when the window is resized; arguments are the new `(width, height)` in logical pixels.
31 pub on_resize: Option<Box<dyn FnMut(f32, f32) + Send>>,
32 /// Called when the window gains (`true`) or loses (`false`) focus.
33 pub on_focus: Option<Box<dyn FnMut(bool) + Send>>,
34}
35
36/// Trait for pluggable backend runners.
37///
38/// Implement this trait to integrate a new GUI backend with OxiUI. The trait is
39/// object-safe; callers box the implementor and invoke [`BackendRunner::run`].
40pub trait BackendRunner: Send + 'static {
41 /// Launch the backend event loop.
42 ///
43 /// This call blocks until the event loop terminates, then returns the
44 /// exit status or a [`UiError`] describing why the backend failed to start.
45 fn run(
46 self: Box<Self>,
47 config: AppConfig,
48 content: ContentFn,
49 lifecycle: LifecycleConfig,
50 ) -> Result<AppExit, UiError>;
51}
52
53/// [`BackendRunner`] stub for the egui backend.
54///
55/// The live rendering path for egui remains in `App::run_egui_or_fallback`.
56/// This stub is provided as a stable public type for dependency injection
57/// and testing; it returns `Ok(AppExit::RequestedByUser)` immediately.
58#[cfg(feature = "egui")]
59pub struct EguiRunner;
60
61#[cfg(feature = "egui")]
62impl BackendRunner for EguiRunner {
63 fn run(
64 self: Box<Self>,
65 config: AppConfig,
66 content: ContentFn,
67 _lifecycle: LifecycleConfig,
68 ) -> Result<AppExit, UiError> {
69 // Stub: the live egui run path is in lib.rs `run_egui_or_fallback`.
70 // Full delegation to this runner is planned for M6.
71 let _ = (config, content);
72 Ok(AppExit::RequestedByUser)
73 }
74}
75
76/// [`BackendRunner`] stub for the iced backend.
77///
78/// The live rendering path for iced remains in `App::run_iced`.
79/// This stub is provided as a stable public type for dependency injection
80/// and testing; it returns `Ok(AppExit::RequestedByUser)` immediately.
81#[cfg(feature = "iced")]
82pub struct IcedRunner;
83
84#[cfg(feature = "iced")]
85impl BackendRunner for IcedRunner {
86 fn run(
87 self: Box<Self>,
88 config: AppConfig,
89 content: ContentFn,
90 _lifecycle: LifecycleConfig,
91 ) -> Result<AppExit, UiError> {
92 // Stub: the live iced run path is in lib.rs `run_iced`.
93 // Full delegation to this runner is planned for M6.
94 let _ = (config, content);
95 Ok(AppExit::RequestedByUser)
96 }
97}