Skip to main content

freya_core/
element.rs

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