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
71pub trait ElementExt: Any {
72    fn into_element(self) -> Element
73    where
74        Self: Sized + Into<Element>,
75    {
76        self.into()
77    }
78
79    fn changed(&self, _other: &Rc<dyn ElementExt>) -> bool {
80        false
81    }
82
83    fn diff(&self, _other: &Rc<dyn ElementExt>) -> DiffModifies {
84        DiffModifies::empty()
85    }
86
87    fn layout(&'_ self) -> Cow<'_, LayoutData> {
88        Cow::Owned(Default::default())
89    }
90
91    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
92        Cow::Owned(Default::default())
93    }
94
95    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
96        None
97    }
98
99    fn style(&'_ self) -> Cow<'_, StyleState> {
100        Cow::Owned(Default::default())
101    }
102
103    /// Whether the element paints nothing, letting events fall through to
104    /// non-ancestor elements behind it.
105    fn is_transparent(&self) -> bool {
106        self.style().background == Fill::Color(Color::TRANSPARENT)
107    }
108
109    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
110        Cow::Owned(Default::default())
111    }
112
113    fn layer(&self) -> Layer {
114        Layer::default()
115    }
116
117    fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
118        None
119    }
120
121    fn measure(&self, _context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
122        None
123    }
124
125    fn should_hook_measurement(&self) -> bool {
126        false
127    }
128
129    fn should_measure_inner_children(&self) -> bool {
130        true
131    }
132
133    /// Whether this element needs a [ElementExt::post_measure] step after the layout pass.
134    fn needs_post_measure(&self) -> bool {
135        false
136    }
137
138    /// Runs after this node and its children are measured.
139    fn post_measure(&self, _context: PostMeasureContext) -> PostMeasure<NodeId> {
140        PostMeasure::default()
141    }
142
143    fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
144        context
145            .layout_node
146            .visible_area()
147            .contains(context.cursor.to_f32())
148    }
149
150    fn clip(&self, _context: ClipContext) {}
151
152    fn render(&self, _context: RenderContext) {}
153
154    fn render_rect(&self, area: &Area, scale_factor: f32) -> SkRRect {
155        let style = self.style();
156        let corner_radius = style.corner_radius.with_scale(scale_factor);
157        SkRRect::new_rect_radii(
158            SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
159            &[
160                (corner_radius.top_left(), corner_radius.top_left()).into(),
161                (corner_radius.top_right(), corner_radius.top_right()).into(),
162                (corner_radius.bottom_right(), corner_radius.bottom_right()).into(),
163                (corner_radius.bottom_left(), corner_radius.bottom_left()).into(),
164            ],
165        )
166    }
167
168    /// Mutate the accessibility node right before it enters the accessibility tree.
169    fn finish_accessibility(&self, _builder: &mut accesskit::Node) {}
170}
171
172#[allow(dead_code)]
173pub struct LayoutContext<'a> {
174    pub node_id: NodeId,
175    pub torin_node: &'a torin::node::Node,
176    pub area_size: &'a Size2D,
177    pub font_collection: &'a mut FontCollection,
178    pub font_manager: &'a FontMgr,
179    pub text_style_state: &'a TextStyleState,
180    pub fallback_fonts: &'a [Cow<'static, str>],
181    pub scale_factor: f64,
182    pub text_cache: &'a mut TextCache,
183}
184
185#[allow(dead_code)]
186pub struct RenderContext<'a> {
187    pub font_collection: &'a mut FontCollection,
188    pub canvas: &'a Canvas,
189    pub layout_node: &'a LayoutNode,
190    pub text_style_state: &'a TextStyleState,
191    pub tree: &'a Tree,
192    pub scale_factor: f64,
193}
194
195pub struct EventMeasurementContext<'a> {
196    pub cursor: ragnarok::CursorPoint,
197    pub layout_node: &'a LayoutNode,
198    pub scale_factor: f64,
199}
200
201pub struct PostMeasureContext<'a> {
202    pub node_layout: &'a LayoutNode,
203    pub children: &'a [NodeId],
204    pub layout: &'a Torin<NodeId>,
205    pub font_collection: &'a mut FontCollection,
206    pub text_style_state: &'a TextStyleState,
207    pub fallback_fonts: &'a [Cow<'static, str>],
208    pub scale_factor: f64,
209}
210
211pub struct ClipContext<'a> {
212    pub canvas: &'a Canvas,
213    pub visible_area: &'a Area,
214    pub scale_factor: f64,
215}
216
217impl<T: Any + PartialEq> ComponentProps for T {
218    fn changed(&self, other: &dyn ComponentProps) -> bool {
219        (other as &dyn Any)
220            .downcast_ref::<T>()
221            .is_none_or(|other| self != other)
222    }
223}
224
225pub trait ComponentProps: Any {
226    fn changed(&self, other: &dyn ComponentProps) -> bool;
227}
228
229#[derive(Clone)]
230pub enum Element {
231    Component {
232        key: DiffKey,
233        comp: Rc<dyn Fn(Rc<dyn ComponentProps>) -> Element>,
234        props: Rc<dyn ComponentProps>,
235    },
236    Element {
237        key: DiffKey,
238        element: Rc<dyn ElementExt>,
239        elements: Vec<Element>,
240    },
241}
242
243impl Debug for Element {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            Self::Element { key, elements, .. } => {
247                f.write_str(&format!("Element {{ key: {:?} }}", key))?;
248                elements.fmt(f)
249            }
250            Self::Component { key, .. } => f.write_str(&format!("Component {{ key: {:?} }}", key)),
251        }
252    }
253}
254
255pub trait IntoElement {
256    fn into_element(self) -> Element;
257}
258
259impl<T: Into<Element>> IntoElement for T {
260    fn into_element(self) -> Element {
261        self.into()
262    }
263}
264
265/// [App] is a trait for root-level application components.
266/// Types implementing [App] automatically implement [Component] and have a
267/// blanket [PartialEq] implementation that always returns true.
268pub trait App: 'static {
269    fn render(&self) -> impl IntoElement;
270}
271
272/// [AppComponent] is a wrapper for [App] components that returns true in equality checks.
273#[derive(Clone)]
274pub struct AppComponent {
275    render: Rc<dyn Fn() -> Element + 'static>,
276}
277
278impl AppComponent {
279    pub fn new(render: impl App + 'static) -> Self {
280        Self {
281            render: Rc::new(move || render.render().into_element()),
282        }
283    }
284}
285
286impl PartialEq for AppComponent {
287    fn eq(&self, _other: &Self) -> bool {
288        true
289    }
290}
291
292#[cfg(feature = "hotreload")]
293impl<F, E> From<F> for AppComponent
294where
295    F: Fn() -> E + Clone + 'static,
296    E: IntoElement,
297{
298    fn from(render: F) -> Self {
299        AppComponent {
300            render: Rc::new(move || {
301                crate::hotreload::subsecond::HotFn::current(render.clone())
302                    .call(())
303                    .into_element()
304            }),
305        }
306    }
307}
308
309#[cfg(not(feature = "hotreload"))]
310impl<F, E> From<F> for AppComponent
311where
312    F: Fn() -> E + 'static,
313    E: IntoElement,
314{
315    fn from(render: F) -> Self {
316        AppComponent {
317            render: Rc::new(move || render().into_element()),
318        }
319    }
320}
321
322impl Component for AppComponent {
323    fn render(&self) -> impl IntoElement {
324        (self.render)()
325    }
326}
327
328/// Encapsulate reusable pieces of UI by using the [Component] trait.
329/// Every [Component] creates a new layer of state in the app,
330/// meaning that implementors of [Component] can make use of hooks in their [Component::render] method.
331/// ```rust, no_run
332/// # use freya::prelude::*;
333/// #[derive(PartialEq)]
334/// struct ReusableCounter {
335///     pub init_number: u8,
336/// }
337///
338/// impl Component for ReusableCounter {
339///     fn render(&self) -> impl IntoElement {
340///         let mut number = use_state(|| self.init_number);
341///         label()
342///             .on_press(move |_| {
343///                 *number.write() += 1;
344///             })
345///             .text(number.read().to_string())
346///     }
347/// }
348/// ```
349pub trait Component: ComponentKey + PartialEq + 'static {
350    fn render(&self) -> impl IntoElement;
351
352    fn render_key(&self) -> DiffKey {
353        self.default_key()
354    }
355}
356
357pub trait ComponentOwned: ComponentKey + PartialEq + 'static {
358    fn render(self) -> impl IntoElement;
359
360    fn render_key(&self) -> DiffKey {
361        self.default_key()
362    }
363}
364
365pub trait ComponentKey {
366    fn default_key(&self) -> DiffKey;
367}
368
369impl<T> Component for T
370where
371    T: ComponentOwned + Clone + PartialEq,
372{
373    fn render(&self) -> impl IntoElement {
374        <Self as ComponentOwned>::render(self.clone())
375    }
376    fn render_key(&self) -> DiffKey {
377        <Self as ComponentOwned>::render_key(self)
378    }
379}
380
381impl<T> ComponentKey for T
382where
383    T: Component,
384{
385    fn default_key(&self) -> DiffKey {
386        use std::hash::{
387            Hash,
388            Hasher,
389        };
390        let mut hasher = rustc_hash::FxHasher::default();
391        TypeId::of::<T>().hash(&mut hasher);
392        DiffKey::DefaultU64(hasher.finish())
393    }
394}
395
396impl<T> MaybeExt for T where T: Component {}
397
398impl<T: Component> From<T> for Element {
399    fn from(value: T) -> Self {
400        let key = value.render_key();
401        Element::Component {
402            key,
403            #[cfg(feature = "hotreload")]
404            comp: Rc::new(move |props| {
405                let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
406                crate::hotreload::subsecond::HotFn::current(|v: &T| v.render().into_element())
407                    .call((props,))
408            }),
409            #[cfg(not(feature = "hotreload"))]
410            comp: Rc::new(move |props| {
411                let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
412                props.render().into_element()
413            }),
414            props: Rc::new(value),
415        }
416    }
417}
418
419impl PartialEq for Element {
420    fn eq(&self, other: &Self) -> bool {
421        match (self, other) {
422            (
423                Self::Component {
424                    key: key1,
425                    props: props1,
426                    ..
427                },
428                Self::Component {
429                    key: key2,
430                    props: props2,
431                    ..
432                },
433            ) => key1 == key2 && !props1.changed(props2.as_ref()),
434            (
435                Self::Element {
436                    key: key1,
437                    element: element1,
438                    elements: elements1,
439                },
440                Self::Element {
441                    key: key2,
442                    element: element2,
443                    elements: elements2,
444                },
445            ) => key1 == key2 && !element1.changed(element2) && elements1 == elements2,
446            _ => false,
447        }
448    }
449}
450
451pub type EventHandlers = FxHashMap<EventName, EventHandlerType>;
452
453#[derive(Clone, PartialEq)]
454pub enum EventHandlerType {
455    Mouse(EventHandler<Event<MouseEventData>>),
456    Keyboard(EventHandler<Event<KeyboardEventData>>),
457    Sized(EventHandler<Event<SizedEventData>>),
458    Visible(EventHandler<Event<VisibleEventData>>),
459    Styled(EventHandler<Event<StyledEventData>>),
460    Wheel(EventHandler<Event<WheelEventData>>),
461    Touch(EventHandler<Event<TouchEventData>>),
462    Pointer(EventHandler<Event<PointerEventData>>),
463    ImePreedit(EventHandler<Event<ImePreeditEventData>>),
464    File(EventHandler<Event<FileEventData>>),
465}