feather_ui/component/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4pub mod button;
5pub mod domain_line;
6pub mod domain_point;
7pub mod flexbox;
8pub mod gridbox;
9pub mod image;
10pub mod line;
11pub mod listbox;
12pub mod mouse_area;
13pub mod paragraph;
14pub mod region;
15pub mod scroll_area;
16pub mod shape;
17pub mod text;
18pub mod textbox;
19pub mod window;
20
21use crate::component::window::Window;
22use crate::event::EventRouter;
23use crate::layout::{Desc, Layout, Staged, root};
24use crate::{
25    DispatchPair, Dispatchable, InputResult, PxRect, Slot, SourceID, StateMachineChild,
26    StateManager, graphics, rtree,
27};
28use dyn_clone::DynClone;
29use eyre::{OptionExt, Result};
30use smallvec::SmallVec;
31use std::any::Any;
32use std::collections::HashMap;
33use std::sync::Arc;
34use window::WindowStateMachine;
35
36pub trait StateMachineWrapper: Any {
37    fn process(
38        &mut self,
39        input: DispatchPair,
40        index: u64,
41        dpi: crate::RelDim,
42        area: PxRect,
43        extent: PxRect,
44        driver: &std::sync::Weak<crate::Driver>,
45    ) -> InputResult<SmallVec<[DispatchPair; 1]>>;
46    fn output_slot(&self, i: usize) -> Result<&Option<Slot>>;
47    fn input_mask(&self) -> u64;
48    fn changed(&self) -> bool;
49    fn set_changed(&mut self, changed: bool);
50}
51
52pub struct StateMachine<State, const OUTPUT_SIZE: usize> {
53    pub state: State,
54    pub output: [Option<Slot>; OUTPUT_SIZE],
55    pub input_mask: u64,
56    pub(crate) changed: bool,
57}
58
59impl<State: EventRouter + PartialEq + 'static, const OUTPUT_SIZE: usize> StateMachineWrapper
60    for StateMachine<State, OUTPUT_SIZE>
61{
62    fn process(
63        &mut self,
64        input: DispatchPair,
65        _index: u64,
66        dpi: crate::RelDim,
67        area: PxRect,
68        extent: PxRect,
69        driver: &std::sync::Weak<crate::Driver>,
70    ) -> InputResult<SmallVec<[DispatchPair; 1]>> {
71        if input.0 & self.input_mask == 0 {
72            return InputResult::Error(crate::Error::UnhandledEvent.into());
73        }
74
75        let s = match State::Input::restore(input) {
76            Ok(s) => s,
77            Err(e) => return InputResult::Error(e.into()),
78        };
79
80        State::process(
81            crate::AccessCell {
82                value: &mut self.state,
83                changed: &mut self.changed,
84            },
85            s,
86            area,
87            extent,
88            dpi,
89            driver,
90        )
91        .map(|x| x.into_iter().map(|x| x.extract()).collect())
92    }
93    fn output_slot(&self, i: usize) -> Result<&Option<Slot>> {
94        self.output.get(i).ok_or(crate::Error::OutOfRange(i).into())
95    }
96    fn input_mask(&self) -> u64 {
97        self.input_mask
98    }
99    fn changed(&self) -> bool {
100        self.changed
101    }
102    fn set_changed(&mut self, changed: bool) {
103        self.changed = changed
104    }
105}
106
107/*pub struct EventRouter<const N: usize> {
108    pub input: (u64, EventWrapper<Output, State>),
109    pub output: [Option<Slot>; N],
110}
111
112impl<const N: usize> StateMachineWrapper for EventRouter<N> {
113    fn process(
114        &mut self,
115        input: DispatchPair,
116        index: u64,
117        dpi: crate::RelDim,
118        area: AbsRect,
119    ) -> InputResult<SmallVec<[DispatchPair; 1]>>{
120        todo!()
121    }
122
123    fn output_slot(&self, i: usize) -> Result<&Option<Slot>> {
124        self.output.get(i).ok_or(crate::Error::OutOfRange(i).into())
125    }
126
127    fn input_masks(&self) -> SmallVec<[u64; 4]> {
128        SmallVec::from_buf([self.input.0])
129    }
130}*/
131
132/// The trait representing an arbitrary UI component. The Props associated type
133/// must be used to expose the concrete property type that was used to
134/// instantiate the component. This is expect to be different, so it is assumed
135/// that almost all components are generic over the property type, so long as
136/// the property type satisfies the requirements of the chosen layout.
137///
138/// All components must implement [`StateMachineChild`] even if they are
139/// stateless, a derive macro is provided to implement an empty version of the
140/// trait for you. In addition, the component must enforce some rather specific
141/// constraints due to limitations of the rust type system to properly capture
142/// them. See the example for what the simplest possible component looks like.
143///
144/// # Examples
145/// ```
146/// use feather_ui::component::{Component};
147/// use feather_ui::layout::base;
148/// use feather_ui::{ StateMachineChild, SourceID, layout, graphics, StateManager };
149/// use std::sync::Arc;
150/// use std::rc::Rc;
151///
152/// // #[derive(feather_macro::StateMachineChild)]
153/// // This derive macro simply implements the following. The implementation would be
154/// // more complex if our component had children, which the derive macro also handles.
155/// impl<T> StateMachineChild for MyComponent<T> {
156///     fn id(&self) -> Arc<SourceID> {
157///         self.id.clone()
158///     }
159/// }
160///
161/// #[derive_where::derive_where(Clone)]
162/// pub struct MyComponent<T> {
163///     pub id: Arc<SourceID>,
164///     pub props: Rc<T>,
165/// }
166/// impl<T: base::Empty + 'static> Component for MyComponent<T>
167/// where
168///     for<'a> &'a T: Into<&'a (dyn base::Empty + 'static)>,
169/// {
170///     type Props = T;
171///
172///     fn layout(
173///         &self,
174///         _: &mut StateManager,
175///         _: &graphics::Driver,
176///         _: &Arc<SourceID>,
177///     ) -> Box<dyn layout::Layout<T>> {
178///         Box::new(layout::Node::<T, dyn base::Empty> {
179///             props: self.props.clone(),
180///             children: Default::default(),
181///             id: Arc::downgrade(&self.id),
182///             renderable: None,
183///             layer: None,
184///         })
185///     }
186/// }
187/// ```
188pub trait Component: crate::StateMachineChild + DynClone {
189    type Props;
190
191    fn layout(
192        &self,
193        state: &mut StateManager,
194        driver: &graphics::Driver,
195        window: &Arc<SourceID>,
196    ) -> Box<dyn Layout<Self::Props>>;
197}
198
199dyn_clone::clone_trait_object!(<Parent> Component<Props = Parent> where Parent:?Sized);
200
201pub type ChildOf<D> = dyn ComponentWrap<<D as Desc>::Child>;
202
203pub trait ComponentWrap<T: ?Sized>: crate::StateMachineChild + DynClone {
204    fn layout(
205        &self,
206        state: &mut StateManager,
207        driver: &graphics::Driver,
208        window: &Arc<SourceID>,
209    ) -> Box<dyn Layout<T>>;
210}
211
212dyn_clone::clone_trait_object!(<T> ComponentWrap<T> where T:?Sized);
213
214impl<U: ?Sized, C: Component> ComponentWrap<U> for C
215where
216    for<'a> &'a U: From<&'a <C as Component>::Props>,
217    <C as Component>::Props: Sized + 'static,
218{
219    fn layout(
220        &self,
221        state: &mut StateManager,
222        driver: &graphics::Driver,
223        window: &Arc<SourceID>,
224    ) -> Box<dyn Layout<U>> {
225        Box::new(Component::layout(self, state, driver, window))
226    }
227}
228
229impl<T: Component + 'static, U> From<Box<T>> for Box<dyn ComponentWrap<U>>
230where
231    for<'a> &'a U: std::convert::From<&'a <T as Component>::Props>,
232    <T as Component>::Props: Sized,
233{
234    fn from(value: Box<T>) -> Self {
235        value
236    }
237}
238
239// Stores the root node for the various trees.
240
241pub struct RootState {
242    pub(crate) id: Arc<SourceID>,
243    layout_tree: Option<Box<dyn crate::layout::Layout<crate::PxDim>>>,
244    pub(crate) staging: Option<Box<dyn Staged>>,
245    rtree: std::rc::Weak<rtree::Node>,
246}
247
248impl RootState {
249    fn new(id: Arc<SourceID>) -> Self {
250        Self {
251            id,
252            layout_tree: None,
253            staging: None,
254            rtree: std::rc::Weak::<rtree::Node>::new(),
255        }
256    }
257}
258
259pub struct Root {
260    pub(crate) states: HashMap<winit::window::WindowId, RootState>,
261    // We currently rely on window-specific functions, so there's no point trying to make this more
262    // general right now.
263    pub(crate) children: im::HashMap<Arc<SourceID>, Option<Window>>,
264}
265
266impl Default for Root {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272impl Root {
273    pub fn new() -> Self {
274        Self {
275            states: HashMap::new(),
276            children: im::HashMap::new(),
277        }
278    }
279
280    pub fn layout_all(
281        &mut self,
282        manager: &mut StateManager,
283        driver: &mut std::sync::Weak<graphics::Driver>,
284        on_driver: &mut Option<Box<dyn FnOnce(std::sync::Weak<graphics::Driver>)>>,
285        instance: &wgpu::Instance,
286        event_loop: &winit::event_loop::ActiveEventLoop,
287    ) -> eyre::Result<()> {
288        // Initialize any states that need to be initialized before calling the layout
289        // function TODO: make this actually efficient by performing the
290        // initialization when a new component is initialized
291        for (_, window) in self.children.iter() {
292            let window = window.as_ref().unwrap();
293            window.init_custom(manager, driver, instance, event_loop, on_driver)?;
294            let state: &WindowStateMachine = manager.get(&window.id())?;
295            let id = state.state.window.id();
296            self.states
297                .entry(id)
298                .or_insert_with(|| RootState::new(window.id().clone()));
299
300            let root = self
301                .states
302                .get_mut(&id)
303                .ok_or_eyre("Couldn't find window state")?;
304            let driver = state.state.driver.clone();
305            root.layout_tree = Some(crate::component::Component::layout(
306                window,
307                manager,
308                &driver,
309                &window.id(),
310            ));
311        }
312        Ok(())
313    }
314
315    pub fn stage_all(&mut self, states: &mut StateManager) -> eyre::Result<()> {
316        for (_, window) in self.children.iter() {
317            let window = window.as_ref().unwrap();
318            let state: &mut WindowStateMachine = states.get_mut(&window.id())?;
319            let id = state.state.window.id();
320            let root = self
321                .states
322                .get_mut(&id)
323                .ok_or_eyre("Couldn't find window state")?;
324            if let Some(layout) = root.layout_tree.as_ref() {
325                let layout: &dyn Layout<dyn root::Prop> = &layout.as_ref();
326                let staging =
327                    layout.stage(Default::default(), Default::default(), &mut state.state);
328                root.rtree = staging.get_rtree();
329                root.staging = Some(staging);
330                state.state.window.request_redraw();
331            }
332        }
333        Ok(())
334    }
335
336    pub fn validate_ids(&self) -> eyre::Result<()> {
337        struct Validator(std::collections::HashSet<Arc<SourceID>>);
338        impl Validator {
339            fn f(&mut self, x: &dyn StateMachineChild) -> eyre::Result<()> {
340                let id = x.id();
341                if !self.0.insert(id.clone()) {
342                    return Err(eyre::eyre!(
343                        "Duplicate ID found! Did you forget to add a child index to an ID? {}",
344                        x.id()
345                    ));
346                }
347
348                x.apply_children(&mut |x| self.f(x))
349            }
350        }
351        let mut v = Validator(std::collections::HashSet::new());
352        for (_, child) in &self.children {
353            if let Some(window) = child {
354                v.f(window)?;
355            }
356        }
357
358        Ok(())
359    }
360}