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