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