Skip to main content

freya_core/
element.rs

1use std::{
2    any::{
3        Any,
4        TypeId,
5    },
6    borrow::Cow,
7    fmt::Debug,
8    rc::Rc,
9};
10
11use freya_engine::prelude::{
12    Canvas,
13    FontCollection,
14    FontMgr,
15    SkRRect,
16    SkRect,
17};
18use rustc_hash::FxHashMap;
19use torin::{
20    prelude::{
21        Area,
22        LayoutNode,
23        PostMeasure,
24        Size2D,
25    },
26    scaled::Scaled,
27    torin::Torin,
28};
29
30use crate::{
31    data::{
32        AccessibilityData,
33        EffectData,
34        LayoutData,
35        StyleState,
36        TextStyleData,
37        TextStyleState,
38    },
39    diff_key::DiffKey,
40    event_handler::EventHandler,
41    events::{
42        data::{
43            Event,
44            KeyboardEventData,
45            MouseEventData,
46            PointerEventData,
47            SizedEventData,
48            StyledEventData,
49            TouchEventData,
50            VisibleEventData,
51            WheelEventData,
52        },
53        name::EventName,
54    },
55    layers::Layer,
56    node_id::NodeId,
57    prelude::{
58        Color,
59        FileEventData,
60        ImePreeditEventData,
61        MaybeExt,
62    },
63    style::fill::Fill,
64    text_cache::TextCache,
65    tree::{
66        DiffModifies,
67        Tree,
68    },
69};
70
71/// Tells Freya how to lay out, handle events for, describe accessibility and draw an element.
72///
73/// To make your own element, implement this trait and override what you need. For example,
74/// [`render`](Self::render) draws it, [`layout`](Self::layout) supplies its layout,
75/// [`measure`](Self::measure) gives it a custom size, and [`is_point_inside`](Self::is_point_inside)
76/// changes how pointer hits are detected. Everything has a default, so you can start with just
77/// the methods your element needs. Override [`changed`](Self::changed) and [`diff`](Self::diff)
78/// when changes to its data need to update the UI.
79///
80/// To put it in the UI tree, implement `From<YourElement> for Element` (or otherwise make it
81/// convertible into [`Element`]). You can also implement builder traits like `LayoutExt` to let
82/// callers set its properties. Freya stores elements as `Rc<dyn ElementExt>`, so they must be
83/// `'static` through the [`Any`].
84///
85/// See [`feature_element.rs`](https://github.com/marc2332/freya/blob/main/examples/feature_element.rs)
86/// for a full example.
87pub trait ElementExt: Any {
88    /// Converts this value into an [`Element`] if it implements `Into<Element>`.
89    fn into_element(self) -> Element
90    where
91        Self: Sized + Into<Element>,
92    {
93        self.into()
94    }
95
96    /// Returns whether this element has changed compared to another one.
97    fn changed(&self, _other: &Rc<dyn ElementExt>) -> bool {
98        false
99    }
100
101    /// Tells Freya what needs updating when this element changes.
102    fn diff(&self, _other: &Rc<dyn ElementExt>) -> DiffModifies {
103        DiffModifies::empty()
104    }
105
106    /// Returns layout properties used to position and size this element.
107    fn layout(&'_ self) -> Cow<'_, LayoutData> {
108        Cow::Owned(Default::default())
109    }
110
111    /// Returns accessibility properties for this element.
112    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
113        Cow::Owned(Default::default())
114    }
115
116    /// Returns optional effect properties for this element.
117    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
118        None
119    }
120
121    /// Returns visual style properties for this element.
122    fn style(&'_ self) -> Cow<'_, StyleState> {
123        Cow::Owned(Default::default())
124    }
125
126    /// Whether the element paints nothing, letting events fall through to
127    /// non-ancestor elements behind it.
128    fn is_transparent(&self) -> bool {
129        self.style().background == Fill::Color(Color::TRANSPARENT)
130    }
131
132    /// Returns text styling inherited by text content in this element.
133    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
134        Cow::Owned(Default::default())
135    }
136
137    /// Returns the drawing layer assigned to this element.
138    fn layer(&self) -> Layer {
139        Layer::default()
140    }
141
142    /// Returns event handlers registered on this element.
143    fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
144        None
145    }
146
147    /// Measures the element and optionally returns measurement data for later layout stages.
148    fn measure(&self, _context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
149        None
150    }
151
152    /// Returns whether this element hooks into layout measurement.
153    fn should_hook_measurement(&self) -> bool {
154        false
155    }
156
157    /// Returns whether to measure this element's children.
158    fn should_measure_inner_children(&self) -> bool {
159        true
160    }
161
162    /// Whether this element needs a [ElementExt::post_measure] step after the layout pass.
163    fn needs_post_measure(&self) -> bool {
164        false
165    }
166
167    /// Runs after this element and its children are measured.
168    fn post_measure(&self, _context: PostMeasureContext) -> PostMeasure<NodeId> {
169        PostMeasure::default()
170    }
171
172    /// Tests whether the pointer position is inside this element's hit area.
173    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
174        context
175            .layout_node
176            .visible_area()
177            .contains(context.cursor.to_f32())
178    }
179
180    /// Applies clipping for this element before it and its children are rendered.
181    fn clip(&self, _context: ClipContext) {}
182
183    /// Renders this element using the supplied canvas and layout context.
184    fn render(&self, _context: RenderContext) {}
185
186    /// Creates a rounded rectangle from an area using this element's corner radius.
187    fn render_rect(&self, area: &Area, scale_factor: f32) -> SkRRect {
188        let style = self.style();
189        let corner_radius = style.corner_radius.with_scale(scale_factor);
190        SkRRect::new_rect_radii(
191            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
192            &[
193                (corner_radius.top_left(), corner_radius.top_left()).into(),
194                (corner_radius.top_right(), corner_radius.top_right()).into(),
195                (corner_radius.bottom_right(), corner_radius.bottom_right()).into(),
196                (corner_radius.bottom_left(), corner_radius.bottom_left()).into(),
197            ],
198        )
199    }
200
201    /// Mutates the accessibility node immediately before it enters the accessibility tree.
202    fn finish_accessibility(&self, _builder: &mut accesskit::Node) {}
203}
204
205#[allow(dead_code)]
206pub struct LayoutContext<'a> {
207    pub node_id: NodeId,
208    pub torin_node: &'a torin::node::Node,
209    pub area_size: &'a Size2D,
210    pub font_collection: &'a mut FontCollection,
211    pub font_manager: &'a FontMgr,
212    pub text_style_state: &'a TextStyleState,
213    pub fallback_fonts: &'a [Cow<'static, str>],
214    pub scale_factor: f64,
215    pub text_cache: &'a mut TextCache,
216}
217
218#[allow(dead_code)]
219pub struct RenderContext<'a> {
220    pub font_collection: &'a mut FontCollection,
221    pub canvas: &'a Canvas,
222    pub layout_node: &'a LayoutNode,
223    pub text_style_state: &'a TextStyleState,
224    pub tree: &'a Tree,
225    pub scale_factor: f64,
226}
227
228pub struct EventMeasurementContext<'a> {
229    pub cursor: ragnarok::CursorPoint,
230    pub layout_node: &'a LayoutNode,
231    pub scale_factor: f64,
232}
233
234pub struct PostMeasureContext<'a> {
235    pub node_layout: &'a LayoutNode,
236    pub children: &'a [NodeId],
237    pub layout: &'a Torin<NodeId>,
238    pub font_collection: &'a mut FontCollection,
239    pub text_style_state: &'a TextStyleState,
240    pub fallback_fonts: &'a [Cow<'static, str>],
241    pub scale_factor: f64,
242}
243
244pub struct ClipContext<'a> {
245    pub canvas: &'a Canvas,
246    pub visible_area: &'a Area,
247    pub scale_factor: f64,
248}
249
250impl<T: Any + PartialEq> ComponentProps for T {
251    fn changed(&self, other: &dyn ComponentProps) -> bool {
252        (other as &dyn Any)
253            .downcast_ref::<T>()
254            .is_none_or(|other| self != other)
255    }
256}
257
258pub trait ComponentProps: Any {
259    fn changed(&self, other: &dyn ComponentProps) -> bool;
260}
261
262#[derive(Clone)]
263pub enum Element {
264    Component {
265        key: DiffKey,
266        comp: Rc<dyn Fn(Rc<dyn ComponentProps>) -> Element>,
267        props: Rc<dyn ComponentProps>,
268    },
269    Element {
270        key: DiffKey,
271        element: Rc<dyn ElementExt>,
272        elements: Vec<Element>,
273    },
274}
275
276impl Debug for Element {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        match self {
279            Self::Element { key, elements, .. } => {
280                f.write_str(&format!("Element {{ key: {:?} }}", key))?;
281                elements.fmt(f)
282            }
283            Self::Component { key, .. } => f.write_str(&format!("Component {{ key: {:?} }}", key)),
284        }
285    }
286}
287
288pub trait IntoElement {
289    fn into_element(self) -> Element;
290}
291
292impl<T: Into<Element>> IntoElement for T {
293    fn into_element(self) -> Element {
294        self.into()
295    }
296}
297
298/// [App] is a trait for root-level application components.
299/// Types implementing [App] automatically implement [Component] and have a
300/// blanket [PartialEq] implementation that always returns true.
301pub trait App: 'static {
302    fn render(&self) -> impl IntoElement;
303}
304
305/// [AppComponent] is a wrapper for [App] components that returns true in equality checks.
306#[derive(Clone)]
307pub struct AppComponent {
308    render: Rc<dyn Fn() -> Element + 'static>,
309}
310
311impl AppComponent {
312    pub fn new(render: impl App + 'static) -> Self {
313        Self {
314            render: Rc::new(move || render.render().into_element()),
315        }
316    }
317}
318
319impl PartialEq for AppComponent {
320    fn eq(&self, _other: &Self) -> bool {
321        true
322    }
323}
324
325#[cfg(feature = "hotreload")]
326impl<F, E> From<F> for AppComponent
327where
328    F: Fn() -> E + Clone + 'static,
329    E: IntoElement,
330{
331    fn from(render: F) -> Self {
332        AppComponent {
333            render: Rc::new(move || {
334                crate::hotreload::subsecond::HotFn::current(render.clone())
335                    .call(())
336                    .into_element()
337            }),
338        }
339    }
340}
341
342#[cfg(not(feature = "hotreload"))]
343impl<F, E> From<F> for AppComponent
344where
345    F: Fn() -> E + 'static,
346    E: IntoElement,
347{
348    fn from(render: F) -> Self {
349        AppComponent {
350            render: Rc::new(move || render().into_element()),
351        }
352    }
353}
354
355impl Component for AppComponent {
356    fn render(&self) -> impl IntoElement {
357        (self.render)()
358    }
359}
360
361/// Encapsulate reusable pieces of UI by using the [Component] trait.
362/// Every [Component] creates a new layer of state in the app,
363/// meaning that implementors of [Component] can make use of hooks in their [Component::render] method.
364/// ```rust, no_run
365/// # use freya::prelude::*;
366/// #[derive(PartialEq)]
367/// struct ReusableCounter {
368///     pub init_number: u8,
369/// }
370///
371/// impl Component for ReusableCounter {
372///     fn render(&self) -> impl IntoElement {
373///         let mut number = use_state(|| self.init_number);
374///         label()
375///             .on_press(move |_| {
376///                 *number.write() += 1;
377///             })
378///             .text(number.read().to_string())
379///     }
380/// }
381/// ```
382pub trait Component: ComponentKey + PartialEq + 'static {
383    fn render(&self) -> impl IntoElement;
384
385    fn render_key(&self) -> DiffKey {
386        self.default_key()
387    }
388}
389
390pub trait ComponentOwned: ComponentKey + PartialEq + 'static {
391    fn render(self) -> impl IntoElement;
392
393    fn render_key(&self) -> DiffKey {
394        self.default_key()
395    }
396}
397
398pub trait ComponentKey {
399    fn default_key(&self) -> DiffKey;
400}
401
402impl<T> Component for T
403where
404    T: ComponentOwned + Clone + PartialEq,
405{
406    fn render(&self) -> impl IntoElement {
407        <Self as ComponentOwned>::render(self.clone())
408    }
409    fn render_key(&self) -> DiffKey {
410        <Self as ComponentOwned>::render_key(self)
411    }
412}
413
414impl<T> ComponentKey for T
415where
416    T: Component,
417{
418    fn default_key(&self) -> DiffKey {
419        use std::hash::{
420            Hash,
421            Hasher,
422        };
423        let mut hasher = rustc_hash::FxHasher::default();
424        TypeId::of::<T>().hash(&mut hasher);
425        DiffKey::DefaultU64(hasher.finish())
426    }
427}
428
429impl<T> MaybeExt for T where T: Component {}
430
431impl<T: Component> From<T> for Element {
432    fn from(value: T) -> Self {
433        let key = value.render_key();
434        Element::Component {
435            key,
436            #[cfg(feature = "hotreload")]
437            comp: Rc::new(move |props| {
438                let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
439                crate::hotreload::subsecond::HotFn::current(|v: &T| v.render().into_element())
440                    .call((props,))
441            }),
442            #[cfg(not(feature = "hotreload"))]
443            comp: Rc::new(move |props| {
444                let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
445                props.render().into_element()
446            }),
447            props: Rc::new(value),
448        }
449    }
450}
451
452impl PartialEq for Element {
453    fn eq(&self, other: &Self) -> bool {
454        match (self, other) {
455            (
456                Self::Component {
457                    key: key1,
458                    props: props1,
459                    ..
460                },
461                Self::Component {
462                    key: key2,
463                    props: props2,
464                    ..
465                },
466            ) => key1 == key2 && !props1.changed(props2.as_ref()),
467            (
468                Self::Element {
469                    key: key1,
470                    element: element1,
471                    elements: elements1,
472                },
473                Self::Element {
474                    key: key2,
475                    element: element2,
476                    elements: elements2,
477                },
478            ) => key1 == key2 && !element1.changed(element2) && elements1 == elements2,
479            _ => false,
480        }
481    }
482}
483
484pub type EventHandlers = FxHashMap<EventName, EventHandlerType>;
485
486#[derive(Clone, PartialEq)]
487pub enum EventHandlerType {
488    Mouse(EventHandler<Event<MouseEventData>>),
489    Keyboard(EventHandler<Event<KeyboardEventData>>),
490    Sized(EventHandler<Event<SizedEventData>>),
491    Visible(EventHandler<Event<VisibleEventData>>),
492    Styled(EventHandler<Event<StyledEventData>>),
493    Wheel(EventHandler<Event<WheelEventData>>),
494    Touch(EventHandler<Event<TouchEventData>>),
495    Pointer(EventHandler<Event<PointerEventData>>),
496    ImePreedit(EventHandler<Event<ImePreeditEventData>>),
497    File(EventHandler<Event<FileEventData>>),
498}