1use std::{
2 any::{
3 Any,
4 TypeId,
5 },
6 borrow::Cow,
7 fmt::Debug,
8 rc::Rc,
9};
10
11use freya_engine::prelude::{
12 Canvas,
13 FontCollection,
14 FontMgr,
15 SkRRect,
16 SkRect,
17};
18use rustc_hash::FxHashMap;
19use torin::{
20 prelude::{
21 Area,
22 LayoutNode,
23 PostMeasure,
24 Size2D,
25 },
26 scaled::Scaled,
27 torin::Torin,
28};
29
30use crate::{
31 data::{
32 AccessibilityData,
33 EffectData,
34 LayoutData,
35 StyleState,
36 TextStyleData,
37 TextStyleState,
38 },
39 diff_key::DiffKey,
40 event_handler::EventHandler,
41 events::{
42 data::{
43 AccessibilityActionEventData,
44 Event,
45 KeyboardEventData,
46 MouseEventData,
47 PointerEventData,
48 SizedEventData,
49 StyledEventData,
50 TouchEventData,
51 VisibleEventData,
52 WheelEventData,
53 },
54 name::EventName,
55 },
56 layers::Layer,
57 node_id::NodeId,
58 prelude::{
59 Color,
60 FileEventData,
61 ImePreeditEventData,
62 MaybeExt,
63 },
64 style::fill::Fill,
65 text_cache::TextCache,
66 tree::{
67 DiffModifies,
68 Tree,
69 },
70};
71
72pub trait ElementExt: Any {
89 fn into_element(self) -> Element
91 where
92 Self: Sized + Into<Element>,
93 {
94 self.into()
95 }
96
97 fn changed(&self, _other: &Rc<dyn ElementExt>) -> bool {
99 false
100 }
101
102 fn diff(&self, _other: &Rc<dyn ElementExt>) -> DiffModifies {
104 DiffModifies::empty()
105 }
106
107 fn layout(&'_ self) -> Cow<'_, LayoutData> {
109 Cow::Owned(Default::default())
110 }
111
112 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
114 Cow::Owned(Default::default())
115 }
116
117 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
119 None
120 }
121
122 fn style(&'_ self) -> Cow<'_, StyleState> {
124 Cow::Owned(Default::default())
125 }
126
127 fn is_transparent(&self) -> bool {
130 self.style().background == Fill::Color(Color::TRANSPARENT)
131 }
132
133 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
135 Cow::Owned(Default::default())
136 }
137
138 fn layer(&self) -> Layer {
140 Layer::default()
141 }
142
143 fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
145 None
146 }
147
148 fn measure(&self, _context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
150 None
151 }
152
153 fn should_hook_measurement(&self) -> bool {
155 false
156 }
157
158 fn should_measure_inner_children(&self) -> bool {
160 true
161 }
162
163 fn needs_post_measure(&self) -> bool {
165 false
166 }
167
168 fn post_measure(&self, _context: PostMeasureContext) -> PostMeasure<NodeId> {
170 PostMeasure::default()
171 }
172
173 fn is_point_inside(&self, context: EventMeasurementContext) -> bool {
175 context
176 .layout_node
177 .visible_area()
178 .contains(context.cursor.to_f32())
179 }
180
181 fn clip(&self, _context: ClipContext) {}
183
184 fn render(&self, _context: RenderContext) {}
186
187 fn render_rect(&self, area: &Area, scale_factor: f32) -> SkRRect {
189 let style = self.style();
190 let corner_radius = style.corner_radius.with_scale(scale_factor);
191 SkRRect::new_rect_radii(
192 SkRect::new(area.min_x(), area.min_y(), area.max_x(), area.max_y()),
193 &[
194 (corner_radius.top_left(), corner_radius.top_left()).into(),
195 (corner_radius.top_right(), corner_radius.top_right()).into(),
196 (corner_radius.bottom_right(), corner_radius.bottom_right()).into(),
197 (corner_radius.bottom_left(), corner_radius.bottom_left()).into(),
198 ],
199 )
200 }
201
202 fn finish_accessibility(&self, _builder: &mut accesskit::Node) {}
204}
205
206#[allow(dead_code)]
207pub struct LayoutContext<'a> {
208 pub node_id: NodeId,
209 pub torin_node: &'a torin::node::Node,
210 pub area_size: &'a Size2D,
211 pub font_collection: &'a mut FontCollection,
212 pub font_manager: &'a FontMgr,
213 pub text_style_state: &'a TextStyleState,
214 pub fallback_fonts: &'a [Cow<'static, str>],
215 pub scale_factor: f64,
216 pub text_cache: &'a mut TextCache,
217}
218
219#[allow(dead_code)]
220pub struct RenderContext<'a> {
221 pub font_collection: &'a mut FontCollection,
222 pub canvas: &'a Canvas,
223 pub layout_node: &'a LayoutNode,
224 pub text_style_state: &'a TextStyleState,
225 pub tree: &'a Tree,
226 pub scale_factor: f64,
227}
228
229pub struct EventMeasurementContext<'a> {
230 pub cursor: ragnarok::CursorPoint,
231 pub layout_node: &'a LayoutNode,
232 pub scale_factor: f64,
233}
234
235pub struct PostMeasureContext<'a> {
236 pub node_layout: &'a LayoutNode,
237 pub children: &'a [NodeId],
238 pub layout: &'a Torin<NodeId>,
239 pub font_collection: &'a mut FontCollection,
240 pub text_style_state: &'a TextStyleState,
241 pub fallback_fonts: &'a [Cow<'static, str>],
242 pub scale_factor: f64,
243}
244
245pub struct ClipContext<'a> {
246 pub canvas: &'a Canvas,
247 pub visible_area: &'a Area,
248 pub scale_factor: f64,
249}
250
251impl<T: Any + PartialEq> ComponentProps for T {
252 fn changed(&self, other: &dyn ComponentProps) -> bool {
253 (other as &dyn Any)
254 .downcast_ref::<T>()
255 .is_none_or(|other| self != other)
256 }
257}
258
259pub trait ComponentProps: Any {
260 fn changed(&self, other: &dyn ComponentProps) -> bool;
261}
262
263#[derive(Clone)]
264pub enum Element {
265 Component {
266 key: DiffKey,
267 comp: Rc<dyn Fn(Rc<dyn ComponentProps>) -> Element>,
268 props: Rc<dyn ComponentProps>,
269 },
270 Element {
271 key: DiffKey,
272 element: Rc<dyn ElementExt>,
273 elements: Vec<Element>,
274 },
275}
276
277impl Debug for Element {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 match self {
280 Self::Element { key, elements, .. } => {
281 f.write_str(&format!("Element {{ key: {:?} }}", key))?;
282 elements.fmt(f)
283 }
284 Self::Component { key, .. } => f.write_str(&format!("Component {{ key: {:?} }}", key)),
285 }
286 }
287}
288
289pub trait IntoElement {
290 fn into_element(self) -> Element;
291}
292
293impl<T: Into<Element>> IntoElement for T {
294 fn into_element(self) -> Element {
295 self.into()
296 }
297}
298
299pub trait App: 'static {
303 fn render(&self) -> impl IntoElement;
304}
305
306#[derive(Clone)]
308pub struct AppComponent {
309 render: Rc<dyn Fn() -> Element + 'static>,
310}
311
312impl AppComponent {
313 pub fn new(render: impl App + 'static) -> Self {
314 Self {
315 render: Rc::new(move || render.render().into_element()),
316 }
317 }
318}
319
320impl PartialEq for AppComponent {
321 fn eq(&self, _other: &Self) -> bool {
322 true
323 }
324}
325
326#[cfg(feature = "hotreload")]
327impl<F, E> From<F> for AppComponent
328where
329 F: Fn() -> E + Clone + 'static,
330 E: IntoElement,
331{
332 fn from(render: F) -> Self {
333 AppComponent {
334 render: Rc::new(move || {
335 crate::hotreload::subsecond::HotFn::current(render.clone())
336 .call(())
337 .into_element()
338 }),
339 }
340 }
341}
342
343#[cfg(not(feature = "hotreload"))]
344impl<F, E> From<F> for AppComponent
345where
346 F: Fn() -> E + 'static,
347 E: IntoElement,
348{
349 fn from(render: F) -> Self {
350 AppComponent {
351 render: Rc::new(move || render().into_element()),
352 }
353 }
354}
355
356impl Component for AppComponent {
357 fn render(&self) -> impl IntoElement {
358 (self.render)()
359 }
360}
361
362pub trait Component: ComponentKey + PartialEq + 'static {
384 fn render(&self) -> impl IntoElement;
385
386 fn render_key(&self) -> DiffKey {
387 self.default_key()
388 }
389}
390
391pub trait ComponentOwned: ComponentKey + PartialEq + 'static {
392 fn render(self) -> impl IntoElement;
393
394 fn render_key(&self) -> DiffKey {
395 self.default_key()
396 }
397}
398
399pub trait ComponentKey {
400 fn default_key(&self) -> DiffKey;
401}
402
403impl<T> Component for T
404where
405 T: ComponentOwned + Clone + PartialEq,
406{
407 fn render(&self) -> impl IntoElement {
408 <Self as ComponentOwned>::render(self.clone())
409 }
410 fn render_key(&self) -> DiffKey {
411 <Self as ComponentOwned>::render_key(self)
412 }
413}
414
415impl<T> ComponentKey for T
416where
417 T: Component,
418{
419 fn default_key(&self) -> DiffKey {
420 use std::hash::{
421 Hash,
422 Hasher,
423 };
424 let mut hasher = rustc_hash::FxHasher::default();
425 TypeId::of::<T>().hash(&mut hasher);
426 DiffKey::DefaultU64(hasher.finish())
427 }
428}
429
430impl<T> MaybeExt for T where T: Component {}
431
432impl<T: Component> From<T> for Element {
433 fn from(value: T) -> Self {
434 let key = value.render_key();
435 Element::Component {
436 key,
437 #[cfg(feature = "hotreload")]
438 comp: Rc::new(move |props| {
439 let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
440 crate::hotreload::subsecond::HotFn::current(|v: &T| v.render().into_element())
441 .call((props,))
442 }),
443 #[cfg(not(feature = "hotreload"))]
444 comp: Rc::new(move |props| {
445 let props = (&*props as &dyn Any).downcast_ref::<T>().unwrap();
446 props.render().into_element()
447 }),
448 props: Rc::new(value),
449 }
450 }
451}
452
453impl PartialEq for Element {
454 fn eq(&self, other: &Self) -> bool {
455 match (self, other) {
456 (
457 Self::Component {
458 key: key1,
459 props: props1,
460 ..
461 },
462 Self::Component {
463 key: key2,
464 props: props2,
465 ..
466 },
467 ) => key1 == key2 && !props1.changed(props2.as_ref()),
468 (
469 Self::Element {
470 key: key1,
471 element: element1,
472 elements: elements1,
473 },
474 Self::Element {
475 key: key2,
476 element: element2,
477 elements: elements2,
478 },
479 ) => key1 == key2 && !element1.changed(element2) && elements1 == elements2,
480 _ => false,
481 }
482 }
483}
484
485pub type EventHandlers = FxHashMap<EventName, EventHandlerType>;
486
487#[derive(Clone, PartialEq)]
488pub enum EventHandlerType {
489 AccessibilityAction(EventHandler<Event<AccessibilityActionEventData>>),
490 Mouse(EventHandler<Event<MouseEventData>>),
491 Keyboard(EventHandler<Event<KeyboardEventData>>),
492 Sized(EventHandler<Event<SizedEventData>>),
493 Visible(EventHandler<Event<VisibleEventData>>),
494 Styled(EventHandler<Event<StyledEventData>>),
495 Wheel(EventHandler<Event<WheelEventData>>),
496 Touch(EventHandler<Event<TouchEventData>>),
497 Pointer(EventHandler<Event<PointerEventData>>),
498 ImePreedit(EventHandler<Event<ImePreeditEventData>>),
499 File(EventHandler<Event<FileEventData>>),
500}