Skip to main content

fission_core/
view.rs

1//! Read-only view and selector pattern.
2//!
3//! During widget conversion, the framework provides a scoped [`View`] that gives
4//! read-only access to the current [`GlobalState`], theme, i18n registry,
5//! layout snapshot, and motion values. Widgets use this to decide what
6//! to render without side effects.
7
8use crate::{
9    env::VideoState, Env, GlobalState, LayoutRect, LayoutSize, LayoutSnapshot, MotionPropertyId,
10    MotionValue, RuntimeState, ViewHandle,
11};
12use fission_i18n::I18nRegistry;
13use fission_ir::WidgetId;
14use fission_layout::BoxConstraints;
15use fission_theme::Theme;
16use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
17use std::hash::{BuildHasher, Hash};
18
19/// Read-only access to application state and environment during widget building.
20///
21/// `View` is the primary way widgets read data. It is parameterised over the
22/// concrete [`GlobalState`] type `S`, giving type-safe access to `state` while
23/// also exposing the theme, i18n registry, layout snapshot from the previous
24/// frame, and motion values.
25///
26/// # Example
27///
28/// ```rust,ignore
29/// let name = &view.state().user_name;
30/// let theme = view.theme();
31/// ```
32pub struct View<'a, S: GlobalState> {
33    /// Reference to the current application state.
34    pub state: &'a S,
35    /// Runtime interaction, scroll, text-edit, and motion state.
36    pub runtime: &'a RuntimeState,
37    /// Environment (theme, i18n, viewport size, locale).
38    pub env: &'a Env,
39    /// Layout snapshot from the previous frame, if available.
40    pub layout: Option<&'a LayoutSnapshot>,
41}
42
43impl<'a, S: GlobalState> View<'a, S> {
44    pub fn new(
45        state: &'a S,
46        runtime: &'a RuntimeState,
47        env: &'a Env,
48        layout: Option<&'a LayoutSnapshot>,
49    ) -> Self {
50        Self {
51            state,
52            runtime,
53            env,
54            layout,
55        }
56    }
57
58    pub fn state(&self) -> &S {
59        self.state
60    }
61
62    pub fn runtime(&self) -> &RuntimeState {
63        self.runtime
64    }
65
66    pub fn env(&self) -> &Env {
67        self.env
68    }
69
70    pub fn layout(&self) -> Option<&LayoutSnapshot> {
71        self.layout
72    }
73
74    pub fn theme(&self) -> &Theme {
75        &self.env.theme
76    }
77    pub fn i18n(&self) -> &I18nRegistry {
78        &self.env.i18n
79    }
80
81    pub fn get_rect(&self, id: WidgetId) -> Option<LayoutRect> {
82        let node_id: WidgetId = id.into();
83        self.layout.and_then(|l| l.get_node_rect(node_id))
84    }
85
86    pub fn get_constraints(&self, id: WidgetId) -> Option<BoxConstraints> {
87        let node_id: WidgetId = id.into();
88        self.layout.and_then(|l| l.get_node_constraints(node_id))
89    }
90
91    pub fn viewport_size(&self) -> LayoutSize {
92        self.env.viewport_size
93    }
94
95    pub fn select<R>(&self, selector: impl FnOnce(&S) -> R) -> R {
96        selector(self.state)
97    }
98
99    pub fn motion_value(&self, widget_id: WidgetId, property: MotionPropertyId) -> MotionValue {
100        self.runtime
101            .motion
102            .values
103            .get(&(widget_id, property.clone()))
104            .cloned()
105            .unwrap_or_else(|| property.default_value())
106    }
107
108    pub fn motion_scalar(&self, widget_id: WidgetId, property: MotionPropertyId) -> f32 {
109        self.runtime.motion.scalar_value(widget_id, property)
110    }
111
112    pub fn video_state(&self, widget_id: WidgetId) -> Option<&VideoState> {
113        self.runtime.video.states.get(&widget_id)
114    }
115}
116
117/// A read-only generated view over a value.
118///
119/// `ValueView` is used by generated global-state views for scalar and
120/// collection fields. It borrows the current state; [`get`](Self::get) clones
121/// only when the caller explicitly asks for an owned value.
122#[derive(Clone, Copy, Debug)]
123pub struct ValueView<'a, T> {
124    value: &'a T,
125}
126
127impl<'a, T> ValueView<'a, T> {
128    pub fn new(value: &'a T) -> Self {
129        Self { value }
130    }
131
132    pub fn borrow(&self) -> &'a T {
133        self.value
134    }
135
136    pub fn map<R>(&self, selector: impl FnOnce(&T) -> R) -> ComputedView<R> {
137        ComputedView::new(selector(self.value))
138    }
139}
140
141impl<T: Clone> ValueView<'_, T> {
142    pub fn get(&self) -> T {
143        self.value.clone()
144    }
145}
146
147impl<'a, T> ValueView<'a, Vec<T>> {
148    pub fn len(&self) -> usize {
149        self.value.len()
150    }
151
152    pub fn is_empty(&self) -> bool {
153        self.value.is_empty()
154    }
155
156    pub fn iter(&self) -> std::slice::Iter<'a, T> {
157        self.value.iter()
158    }
159}
160
161impl<'a, T> IntoIterator for ValueView<'a, Vec<T>> {
162    type Item = &'a T;
163    type IntoIter = std::slice::Iter<'a, T>;
164
165    fn into_iter(self) -> Self::IntoIter {
166        self.value.iter()
167    }
168}
169
170/// A computed read-only value produced from a generated state view.
171#[derive(Clone, Debug)]
172pub struct ComputedView<T> {
173    value: T,
174}
175
176impl<T> ComputedView<T> {
177    pub fn new(value: T) -> Self {
178        Self { value }
179    }
180
181    pub fn borrow(&self) -> &T {
182        &self.value
183    }
184
185    pub fn get(self) -> T {
186        self.value
187    }
188
189    pub fn map<R>(self, selector: impl FnOnce(&T) -> R) -> ComputedView<R> {
190        ComputedView::new(selector(&self.value))
191    }
192}
193
194/// Maps a state field type to the view returned by generated view accessors.
195///
196/// `#[derive(FissionStateView)]` implements this trait for nested state
197/// structs. Built-in scalar and collection types map to [`ValueView`].
198pub trait FissionViewField {
199    type View<'a>
200    where
201        Self: 'a;
202
203    fn view_field<'a>(value: &'a Self) -> Self::View<'a>;
204}
205
206macro_rules! scalar_view_field {
207    ($($ty:ty),* $(,)?) => {
208        $(
209            impl FissionViewField for $ty {
210                type View<'a> = ValueView<'a, Self> where Self: 'a;
211
212                fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
213                    ValueView::new(value)
214                }
215            }
216        )*
217    };
218}
219
220scalar_view_field!(
221    bool, char, String, usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64
222);
223
224impl<T> FissionViewField for Vec<T> {
225    type View<'a>
226        = ValueView<'a, Self>
227    where
228        Self: 'a;
229
230    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
231        ValueView::new(value)
232    }
233}
234
235impl<T> FissionViewField for Option<T> {
236    type View<'a>
237        = ValueView<'a, Self>
238    where
239        Self: 'a;
240
241    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
242        ValueView::new(value)
243    }
244}
245
246impl<T, const N: usize> FissionViewField for [T; N] {
247    type View<'a>
248        = ValueView<'a, Self>
249    where
250        Self: 'a;
251
252    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
253        ValueView::new(value)
254    }
255}
256
257impl<T: Ord> FissionViewField for BTreeSet<T> {
258    type View<'a>
259        = ValueView<'a, Self>
260    where
261        Self: 'a;
262
263    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
264        ValueView::new(value)
265    }
266}
267
268impl<K: Ord, V> FissionViewField for BTreeMap<K, V> {
269    type View<'a>
270        = ValueView<'a, Self>
271    where
272        Self: 'a;
273
274    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
275        ValueView::new(value)
276    }
277}
278
279impl<T: Eq + Hash, S: BuildHasher> FissionViewField for HashSet<T, S> {
280    type View<'a>
281        = ValueView<'a, Self>
282    where
283        Self: 'a;
284
285    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
286        ValueView::new(value)
287    }
288}
289
290impl<K: Eq + Hash, V, S: BuildHasher> FissionViewField for HashMap<K, V, S> {
291    type View<'a>
292        = ValueView<'a, Self>
293    where
294        Self: 'a;
295
296    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
297        ValueView::new(value)
298    }
299}
300
301/// A selector that derives a value from a [`ViewHandle`].
302///
303/// Selectors extract and transform data from state so widgets can depend on
304/// derived values without coupling to the full state shape.
305///
306/// # Example
307///
308/// ```rust,ignore
309/// struct ItemCount;
310/// impl Selector<MyState> for ItemCount {
311///     type Output = usize;
312///     fn select(view: ViewHandle<MyState>) -> usize {
313///         view.state().items.len()
314///     }
315/// }
316///
317/// // In a widget:
318/// let count: usize = view.select_with::<ItemCount>();
319/// ```
320pub trait Selector<S: GlobalState> {
321    /// The type produced by the selector.
322    type Output;
323    /// Extract the value from the given view handle.
324    fn select(view: ViewHandle<S>) -> Self::Output;
325}