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