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