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