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