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 instantiate
134/// the component. This is expect to be different, so it is assumed that almost
135/// all components are generic over the property type, so long as the property
136/// type satisfies the requirements of the chosen layout.
137///
138/// All components must implement [`StateMachineChild`] even if they are stateless,
139/// a derive macro is provided to implement an empty version of the trait for you.
140/// In addition, the component must enforce some rather specific constraints due to
141/// limitations of the rust type system to properly capture them. See the example
142/// 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: 'static;
190
191    fn layout(
192        &self,
193        state: &mut StateManager,
194        driver: &graphics::Driver,
195        window: &Arc<SourceID>,
196    ) -> Box<dyn Layout<Self::Props> + 'static>;
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> + 'static>;
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,
218{
219    fn layout(
220        &self,
221        state: &mut StateManager,
222        driver: &graphics::Driver,
223        window: &Arc<SourceID>,
224    ) -> Box<dyn Layout<U> + 'static> {
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: std::marker::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 general right now.
262    pub(crate) children: im::HashMap<Arc<SourceID>, Option<Window>>,
263}
264
265impl Default for Root {
266    fn default() -> Self {
267        Self::new()
268    }
269}
270
271impl Root {
272    pub fn new() -> Self {
273        Self {
274            states: HashMap::new(),
275            children: im::HashMap::new(),
276        }
277    }
278
279    pub fn layout_all(
280        &mut self,
281        manager: &mut StateManager,
282        driver: &mut std::sync::Weak<graphics::Driver>,
283        on_driver: &mut Option<Box<dyn FnOnce(std::sync::Weak<graphics::Driver>) + 'static>>,
284        instance: &wgpu::Instance,
285        event_loop: &winit::event_loop::ActiveEventLoop,
286    ) -> eyre::Result<()> {
287        // Initialize any states that need to be initialized before calling the layout function
288        // TODO: make this actually efficient by performing the initialization when a new component is initialized
289        for (_, window) in self.children.iter() {
290            let window = window.as_ref().unwrap();
291            window.init_custom(manager, driver, instance, event_loop, on_driver)?;
292            let state: &WindowStateMachine = manager.get(&window.id())?;
293            let id = state.state.window.id();
294            self.states
295                .entry(id)
296                .or_insert_with(|| RootState::new(window.id().clone()));
297
298            let root = self
299                .states
300                .get_mut(&id)
301                .ok_or_eyre("Couldn't find window state")?;
302            let driver = state.state.driver.clone();
303            root.layout_tree = Some(crate::component::Component::layout(
304                window,
305                manager,
306                &driver,
307                &window.id(),
308            ));
309        }
310        Ok(())
311    }
312
313    pub fn stage_all(&mut self, states: &mut StateManager) -> eyre::Result<()> {
314        for (_, window) in self.children.iter() {
315            let window = window.as_ref().unwrap();
316            let state: &mut WindowStateMachine = states.get_mut(&window.id())?;
317            let id = state.state.window.id();
318            let root = self
319                .states
320                .get_mut(&id)
321                .ok_or_eyre("Couldn't find window state")?;
322            if let Some(layout) = root.layout_tree.as_ref() {
323                let layout: &dyn Layout<dyn root::Prop> = &layout.as_ref();
324                let staging =
325                    layout.stage(Default::default(), Default::default(), &mut state.state);
326                root.rtree = staging.get_rtree();
327                root.staging = Some(staging);
328                state.state.window.request_redraw();
329            }
330        }
331        Ok(())
332    }
333
334    pub fn validate_ids(&self) -> eyre::Result<()> {
335        struct Validator(std::collections::HashSet<Arc<SourceID>>);
336        impl Validator {
337            fn f(&mut self, x: &dyn StateMachineChild) -> eyre::Result<()> {
338                let id = x.id();
339                if !self.0.insert(id.clone()) {
340                    return Err(eyre::eyre!(
341                        "Duplicate ID found! Did you forget to add a child index to an ID? {}",
342                        x.id()
343                    ));
344                }
345
346                x.apply_children(&mut |x| self.f(x))
347            }
348        }
349        let mut v = Validator(std::collections::HashSet::new());
350        for (_, child) in &self.children {
351            if let Some(window) = child {
352                v.f(window)?;
353            }
354        }
355
356        Ok(())
357    }
358}