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