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}