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