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