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 animation values. Widgets use this to decide what
6//! to render without side effects.
7
8use crate::{
9    env::VideoState, registry::AnimationPropertyId, Env, GlobalState, LayoutRect, LayoutSize,
10    LayoutSnapshot, 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 animation 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 animation 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 animation_value(&self, widget_id: WidgetId, property: &AnimationPropertyId) -> f32 {
100        self.runtime
101            .animation
102            .values
103            .get(&(widget_id, property.clone()))
104            .copied()
105            .unwrap_or_else(|| property.default_value())
106    }
107
108    pub fn video_state(&self, widget_id: WidgetId) -> Option<&VideoState> {
109        self.runtime.video.states.get(&widget_id)
110    }
111}
112
113/// A read-only generated view over a value.
114///
115/// `ValueView` is used by generated global-state views for scalar and
116/// collection fields. It borrows the current state; [`get`](Self::get) clones
117/// only when the caller explicitly asks for an owned value.
118#[derive(Clone, Copy, Debug)]
119pub struct ValueView<'a, T> {
120    value: &'a T,
121}
122
123impl<'a, T> ValueView<'a, T> {
124    pub fn new(value: &'a T) -> Self {
125        Self { value }
126    }
127
128    pub fn borrow(&self) -> &'a T {
129        self.value
130    }
131
132    pub fn map<R>(&self, selector: impl FnOnce(&T) -> R) -> ComputedView<R> {
133        ComputedView::new(selector(self.value))
134    }
135}
136
137impl<T: Clone> ValueView<'_, T> {
138    pub fn get(&self) -> T {
139        self.value.clone()
140    }
141}
142
143impl<'a, T> ValueView<'a, Vec<T>> {
144    pub fn len(&self) -> usize {
145        self.value.len()
146    }
147
148    pub fn is_empty(&self) -> bool {
149        self.value.is_empty()
150    }
151
152    pub fn iter(&self) -> std::slice::Iter<'a, T> {
153        self.value.iter()
154    }
155}
156
157impl<'a, T> IntoIterator for ValueView<'a, Vec<T>> {
158    type Item = &'a T;
159    type IntoIter = std::slice::Iter<'a, T>;
160
161    fn into_iter(self) -> Self::IntoIter {
162        self.value.iter()
163    }
164}
165
166/// A computed read-only value produced from a generated state view.
167#[derive(Clone, Debug)]
168pub struct ComputedView<T> {
169    value: T,
170}
171
172impl<T> ComputedView<T> {
173    pub fn new(value: T) -> Self {
174        Self { value }
175    }
176
177    pub fn borrow(&self) -> &T {
178        &self.value
179    }
180
181    pub fn get(self) -> T {
182        self.value
183    }
184
185    pub fn map<R>(self, selector: impl FnOnce(&T) -> R) -> ComputedView<R> {
186        ComputedView::new(selector(&self.value))
187    }
188}
189
190/// Maps a state field type to the view returned by generated view accessors.
191///
192/// `#[derive(FissionStateView)]` implements this trait for nested state
193/// structs. Built-in scalar and collection types map to [`ValueView`].
194pub trait FissionViewField {
195    type View<'a>
196    where
197        Self: 'a;
198
199    fn view_field<'a>(value: &'a Self) -> Self::View<'a>;
200}
201
202macro_rules! scalar_view_field {
203    ($($ty:ty),* $(,)?) => {
204        $(
205            impl FissionViewField for $ty {
206                type View<'a> = ValueView<'a, Self> where Self: 'a;
207
208                fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
209                    ValueView::new(value)
210                }
211            }
212        )*
213    };
214}
215
216scalar_view_field!(
217    bool, char, String, usize, u8, u16, u32, u64, u128, isize, i8, i16, i32, i64, i128, f32, f64
218);
219
220impl<T> FissionViewField for Vec<T> {
221    type View<'a>
222        = ValueView<'a, Self>
223    where
224        Self: 'a;
225
226    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
227        ValueView::new(value)
228    }
229}
230
231impl<T> FissionViewField for Option<T> {
232    type View<'a>
233        = ValueView<'a, Self>
234    where
235        Self: 'a;
236
237    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
238        ValueView::new(value)
239    }
240}
241
242impl<T, const N: usize> FissionViewField for [T; N] {
243    type View<'a>
244        = ValueView<'a, Self>
245    where
246        Self: 'a;
247
248    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
249        ValueView::new(value)
250    }
251}
252
253impl<T: Ord> FissionViewField for BTreeSet<T> {
254    type View<'a>
255        = ValueView<'a, Self>
256    where
257        Self: 'a;
258
259    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
260        ValueView::new(value)
261    }
262}
263
264impl<K: Ord, V> FissionViewField for BTreeMap<K, V> {
265    type View<'a>
266        = ValueView<'a, Self>
267    where
268        Self: 'a;
269
270    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
271        ValueView::new(value)
272    }
273}
274
275impl<T: Eq + Hash, S: BuildHasher> FissionViewField for HashSet<T, S> {
276    type View<'a>
277        = ValueView<'a, Self>
278    where
279        Self: 'a;
280
281    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
282        ValueView::new(value)
283    }
284}
285
286impl<K: Eq + Hash, V, S: BuildHasher> FissionViewField for HashMap<K, V, S> {
287    type View<'a>
288        = ValueView<'a, Self>
289    where
290        Self: 'a;
291
292    fn view_field<'a>(value: &'a Self) -> Self::View<'a> {
293        ValueView::new(value)
294    }
295}
296
297/// A selector that derives a value from a [`ViewHandle`].
298///
299/// Selectors extract and transform data from state so widgets can depend on
300/// derived values without coupling to the full state shape.
301///
302/// # Example
303///
304/// ```rust,ignore
305/// struct ItemCount;
306/// impl Selector<MyState> for ItemCount {
307///     type Output = usize;
308///     fn select(view: ViewHandle<MyState>) -> usize {
309///         view.state().items.len()
310///     }
311/// }
312///
313/// // In a widget:
314/// let count: usize = view.select_with::<ItemCount>();
315/// ```
316pub trait Selector<S: GlobalState> {
317    /// The type produced by the selector.
318    type Output;
319    /// Extract the value from the given view handle.
320    fn select(view: ViewHandle<S>) -> Self::Output;
321}