Skip to main content

fret_runtime/
ui_host.rs

1use std::any::{Any, TypeId};
2
3use crate::{ClipboardToken, FrameId, ImageUploadToken, ShareSheetToken, TickId, TimerToken};
4use fret_core::{AppWindowId, Point, PointerId};
5
6use crate::{CommandRegistry, DragKindId, DragSession, Effect, ModelHost, ModelId};
7
8pub trait GlobalsHost {
9    /// Sets a global value, replacing any existing value of the same type.
10    fn set_global<T: Any>(&mut self, value: T);
11    /// Reads a global value by type.
12    fn global<T: Any>(&self) -> Option<&T>;
13
14    /// Returns a monotonically-increasing token for a global type.
15    ///
16    /// This is intended for derived-state memoization (e.g. selector deps signatures). Hosts that
17    /// track global changes should override this to return a value that changes whenever a tracked
18    /// global is updated via `set_global` / `with_global_mut`.
19    ///
20    /// The default implementation returns `None`, meaning the host does not expose global revision
21    /// tokens (callers may fall back to value hashing or manual invalidation).
22    #[inline]
23    fn global_revision(&self, global: TypeId) -> Option<u64> {
24        let _ = global;
25        None
26    }
27
28    #[inline]
29    fn global_revision_of<T: Any>(&self) -> Option<u64> {
30        self.global_revision(TypeId::of::<T>())
31    }
32
33    fn with_global_mut<T: Any, R>(
34        &mut self,
35        init: impl FnOnce() -> T,
36        f: impl FnOnce(&mut T, &mut Self) -> R,
37    ) -> R;
38
39    /// Like [`GlobalsHost::with_global_mut`], but does not participate in the host's "global
40    /// changed" tracking mechanism.
41    ///
42    /// This is intended for frame-local caches/registries that should not schedule redraw or UI
43    /// invalidation by themselves. Hosts can override this to implement an actual untracked path.
44    #[inline]
45    fn with_global_mut_untracked<T: Any, R>(
46        &mut self,
47        init: impl FnOnce() -> T,
48        f: impl FnOnce(&mut T, &mut Self) -> R,
49    ) -> R {
50        self.with_global_mut(init, f)
51    }
52}
53
54pub trait ModelsHost: ModelHost {
55    /// Returns and clears the list of models that were marked changed since the last call.
56    fn take_changed_models(&mut self) -> Vec<ModelId>;
57}
58
59pub trait CommandsHost {
60    /// Returns the command registry used by this host.
61    fn commands(&self) -> &CommandRegistry;
62}
63
64pub trait EffectSink {
65    /// Requests a redraw for the given window.
66    fn request_redraw(&mut self, window: AppWindowId);
67    /// Enqueues a runtime effect to be handled by the runner/backend.
68    fn push_effect(&mut self, effect: Effect);
69}
70
71pub trait TimeHost {
72    /// Current tick id (monotonically increasing).
73    fn tick_id(&self) -> TickId;
74    /// Current frame id (monotonically increasing).
75    fn frame_id(&self) -> FrameId;
76    /// Allocates the next timer token.
77    fn next_timer_token(&mut self) -> TimerToken;
78    /// Allocates the next clipboard token.
79    fn next_clipboard_token(&mut self) -> ClipboardToken;
80    /// Allocates the next share-sheet token.
81    fn next_share_sheet_token(&mut self) -> ShareSheetToken;
82    /// Allocates the next image-upload token.
83    fn next_image_upload_token(&mut self) -> ImageUploadToken;
84}
85
86pub trait DragHost {
87    /// Returns the drag session associated with the pointer, if any.
88    fn drag(&self, pointer_id: PointerId) -> Option<&DragSession>;
89    /// Returns a mutable drag session associated with the pointer, if any.
90    fn drag_mut(&mut self, pointer_id: PointerId) -> Option<&mut DragSession>;
91    /// Cancels a drag session associated with the pointer, if any.
92    fn cancel_drag(&mut self, pointer_id: PointerId);
93
94    /// Returns `true` if any active drag session matches the predicate.
95    fn any_drag_session(&self, predicate: impl FnMut(&DragSession) -> bool) -> bool;
96
97    /// Finds the first pointer id whose drag session matches the predicate.
98    fn find_drag_pointer_id(
99        &self,
100        predicate: impl FnMut(&DragSession) -> bool,
101    ) -> Option<PointerId>;
102
103    /// Cancels all drag sessions matching the predicate and returns their pointer ids.
104    fn cancel_drag_sessions(
105        &mut self,
106        predicate: impl FnMut(&DragSession) -> bool,
107    ) -> Vec<PointerId>;
108
109    /// Begins a drag session with a typed payload.
110    fn begin_drag_with_kind<T: Any>(
111        &mut self,
112        pointer_id: PointerId,
113        kind: DragKindId,
114        source_window: AppWindowId,
115        start: Point,
116        payload: T,
117    );
118
119    /// Begins a cross-window drag session with a typed payload.
120    fn begin_cross_window_drag_with_kind<T: Any>(
121        &mut self,
122        pointer_id: PointerId,
123        kind: DragKindId,
124        source_window: AppWindowId,
125        start: Point,
126        payload: T,
127    );
128}
129
130/// Host services required by the retained UI runtime (`fret-ui`).
131///
132/// This is intentionally portable: it lives in `fret-runtime` so that third-party engines/editors
133/// can embed `fret-ui` without adopting `fret-app`.
134///
135/// Note: the individual service traits are intentionally split so hosts can implement them
136/// independently. `UiHost` remains the single bound used throughout `fret-ui`.
137///
138/// ## Typical driver flow (outline)
139///
140/// Runners/drivers typically perform the following steps on each tick/frame:
141///
142/// - Feed platform input into the UI runtime (event routing / command dispatch).
143/// - Drain incremental changes from the host:
144///   - model changes via [`ModelsHost::take_changed_models`],
145///   - global changes via host-specific tracking (either revision tokens from
146///     [`GlobalsHost::global_revision`] or an explicit changed-list if the host provides one).
147/// - Run layout/paint for the affected window(s).
148/// - Drain effects emitted by UI/services and forward them to the platform (clipboard, file
149///   dialogs, open-url, quit requests, etc.). The exact "drain" API is host-specific; for
150///   app-level hosts, this often looks like a `flush_effects()` method that returns a `Vec<Effect>`.
151///
152/// ```ignore
153/// // Pseudocode (simplified):
154/// let changed_models = host.take_changed_models();
155/// let theme_rev = host.global_revision_of::<fret_ui::ThemeConfig>();
156///
157/// // ...propagate changes into the window's UiTree and render...
158///
159/// for effect in host.flush_effects() {
160///     runner.handle_effect(effect);
161/// }
162/// ```
163pub trait UiHost:
164    GlobalsHost + ModelsHost + CommandsHost + EffectSink + TimeHost + DragHost
165{
166}
167
168impl<T> UiHost for T where
169    T: GlobalsHost + ModelsHost + CommandsHost + EffectSink + TimeHost + DragHost
170{
171}