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