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