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