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