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