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