freya_core/elements/
extensions.rs

1use std::{
2    borrow::Cow,
3    hash::{
4        Hash,
5        Hasher,
6    },
7};
8
9use paste::paste;
10use rustc_hash::{
11    FxHashMap,
12    FxHasher,
13};
14use torin::{
15    content::Content,
16    gaps::Gaps,
17    prelude::{
18        Alignment,
19        Direction,
20        Length,
21        Position,
22        VisibleSize,
23    },
24    size::{
25        Size,
26        SizeFn,
27        SizeFnContext,
28    },
29};
30
31use crate::{
32    data::{
33        AccessibilityData,
34        LayoutData,
35        TextStyleData,
36    },
37    diff_key::DiffKey,
38    element::{
39        Element,
40        EventHandlerType,
41    },
42    elements::image::{
43        AspectRatio,
44        ImageCover,
45        ImageData,
46        SamplingMode,
47    },
48    event_handler::EventHandler,
49    events::{
50        data::{
51            Event,
52            KeyboardEventData,
53            MouseEventData,
54            SizedEventData,
55            WheelEventData,
56        },
57        name::EventName,
58    },
59    prelude::*,
60    style::{
61        font_size::FontSize,
62        font_slant::FontSlant,
63        font_weight::FontWeight,
64        font_width::FontWidth,
65        text_height::TextHeightBehavior,
66        text_overflow::TextOverflow,
67        text_shadow::TextShadow,
68    },
69};
70
71pub trait SizeExt {
72    fn auto() -> Size;
73    fn fill() -> Size;
74    fn fill_minimum() -> Size;
75    fn percent(percent: impl Into<f32>) -> Size;
76    fn px(px: impl Into<f32>) -> Size;
77    fn window_percent(percent: impl Into<f32>) -> Size;
78    fn flex(flex: impl Into<f32>) -> Size;
79    fn func(func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send) -> Size;
80    fn func_data<D: Hash>(
81        func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send,
82        data: &D,
83    ) -> Size;
84}
85
86impl SizeExt for Size {
87    fn auto() -> Size {
88        Size::Inner
89    }
90
91    fn fill() -> Size {
92        Size::Fill
93    }
94
95    fn fill_minimum() -> Size {
96        Size::FillMinimum
97    }
98
99    fn percent(percent: impl Into<f32>) -> Size {
100        Size::Percentage(Length::new(percent.into()))
101    }
102
103    fn px(px: impl Into<f32>) -> Size {
104        Size::Pixels(Length::new(px.into()))
105    }
106
107    fn window_percent(percent: impl Into<f32>) -> Size {
108        Size::RootPercentage(Length::new(percent.into()))
109    }
110
111    fn flex(flex: impl Into<f32>) -> Size {
112        Size::Flex(Length::new(flex.into()))
113    }
114
115    fn func(func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send) -> Size {
116        Self::Fn(Box::new(SizeFn::new(func)))
117    }
118
119    fn func_data<D: Hash>(
120        func: impl Fn(SizeFnContext) -> Option<f32> + 'static + Sync + Send,
121        data: &D,
122    ) -> Size {
123        Self::Fn(Box::new(SizeFn::new_data(func, data)))
124    }
125}
126
127pub trait DirectionExt {
128    fn vertical() -> Direction;
129    fn horizontal() -> Direction;
130}
131
132impl DirectionExt for Direction {
133    fn vertical() -> Direction {
134        Direction::Vertical
135    }
136    fn horizontal() -> Direction {
137        Direction::Horizontal
138    }
139}
140
141pub trait AlignmentExt {
142    fn start() -> Alignment;
143    fn center() -> Alignment;
144    fn end() -> Alignment;
145    fn space_between() -> Alignment;
146    fn space_evenly() -> Alignment;
147    fn space_around() -> Alignment;
148}
149
150impl AlignmentExt for Alignment {
151    fn start() -> Alignment {
152        Alignment::Start
153    }
154
155    fn center() -> Alignment {
156        Alignment::Center
157    }
158
159    fn end() -> Alignment {
160        Alignment::End
161    }
162
163    fn space_between() -> Alignment {
164        Alignment::SpaceBetween
165    }
166
167    fn space_evenly() -> Alignment {
168        Alignment::SpaceEvenly
169    }
170
171    fn space_around() -> Alignment {
172        Alignment::SpaceAround
173    }
174}
175
176pub trait ContentExt {
177    fn normal() -> Content;
178    fn fit() -> Content;
179    fn flex() -> Content;
180}
181
182impl ContentExt for Content {
183    fn normal() -> Content {
184        Content::Normal
185    }
186
187    fn fit() -> Content {
188        Content::Fit
189    }
190
191    fn flex() -> Content {
192        Content::Flex
193    }
194}
195
196pub trait VisibleSizeExt {
197    fn full() -> VisibleSize;
198    fn inner_percent(value: impl Into<f32>) -> VisibleSize;
199}
200
201impl VisibleSizeExt for VisibleSize {
202    fn full() -> VisibleSize {
203        VisibleSize::Full
204    }
205
206    fn inner_percent(value: impl Into<f32>) -> VisibleSize {
207        VisibleSize::InnerPercentage(Length::new(value.into()))
208    }
209}
210
211pub trait ChildrenExt: Sized {
212    fn get_children(&mut self) -> &mut Vec<Element>;
213
214    fn children_iter<I>(mut self, children_iter: I) -> Self
215    where
216        I: Iterator<Item = Element>,
217    {
218        self.get_children().extend(children_iter);
219        self
220    }
221
222    fn children<V: Into<Vec<Element>>>(mut self, children: V) -> Self {
223        self.get_children().extend(children.into());
224        self
225    }
226
227    fn maybe_child<C: IntoElement>(mut self, child: Option<C>) -> Self {
228        if let Some(child) = child {
229            self.get_children().push(child.into_element());
230        }
231        self
232    }
233
234    fn child<C: IntoElement>(mut self, child: C) -> Self {
235        self.get_children().push(child.into_element());
236        self
237    }
238}
239
240pub trait KeyExt: Sized {
241    fn write_key(&mut self) -> &mut DiffKey;
242
243    fn key(mut self, key: impl Hash) -> Self {
244        let mut hasher = FxHasher::default();
245        key.hash(&mut hasher);
246        *self.write_key() = DiffKey::U64(hasher.finish());
247        self
248    }
249}
250
251pub trait ListExt {
252    fn with(self, other: Self) -> Self;
253}
254
255impl<T> ListExt for Vec<T> {
256    fn with(mut self, other: Self) -> Self {
257        self.extend(other);
258        self
259    }
260}
261
262macro_rules! event_handlers {
263    (
264        $handler_variant:ident, $event_data:ty;
265        $(
266            $name:ident => $event_variant:expr ;
267        )*
268    ) => {
269        paste! {
270            $(
271                fn [<on_$name>](mut self, [<on_$name>]: impl Into<EventHandler<Event<$event_data>>>) -> Self {
272                    self.get_event_handlers()
273                        .insert($event_variant, EventHandlerType::$handler_variant([<on_$name>].into()));
274                    self
275                }
276            )*
277        }
278    };
279}
280
281pub trait EventHandlersExt: Sized + LayoutExt {
282    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType>;
283
284    event_handlers! {
285        Mouse,
286        MouseEventData;
287
288        mouse_down => EventName::MouseDown;
289        mouse_up => EventName::MouseUp;
290        mouse_move => EventName::MouseMove;
291
292        global_mouse_up => EventName::GlobalMouseUp;
293        global_mouse_down => EventName::GlobalMouseDown;
294        global_mouse_move => EventName::GlobalMouseMove;
295
296        capture_global_mouse_move => EventName::CaptureGlobalMouseMove;
297        capture_global_mouse_up => EventName::CaptureGlobalMouseUp;
298    }
299
300    event_handlers! {
301        Keyboard,
302        KeyboardEventData;
303
304        key_down => EventName::KeyDown;
305        key_up => EventName::KeyUp;
306
307        global_key_down => EventName::GlobalKeyDown;
308        global_key_up => EventName::GlobalKeyUp;
309    }
310
311    event_handlers! {
312        Wheel,
313        WheelEventData;
314
315        wheel => EventName::Wheel;
316    }
317
318    event_handlers! {
319        Touch,
320        TouchEventData;
321
322        touch_cancel => EventName::TouchCancel;
323        touch_start => EventName::TouchStart;
324        touch_move => EventName::TouchMove;
325        touch_end => EventName::TouchEnd;
326    }
327
328    event_handlers! {
329        Pointer,
330        PointerEventData;
331
332        pointer_press => EventName::PointerPress;
333        pointer_down => EventName::PointerDown;
334        pointer_enter => EventName::PointerEnter;
335        pointer_leave => EventName::PointerLeave;
336    }
337
338    event_handlers! {
339        File,
340        FileEventData;
341
342        file_drop => EventName::FileDrop;
343        global_file_hover => EventName::GlobalFileHover;
344        global_file_hover_cancelled => EventName::GlobalFileHoverCancelled;
345    }
346
347    event_handlers! {
348        ImePreedit,
349        ImePreeditEventData;
350
351        ime_preedit => EventName::ImePreedit;
352    }
353
354    fn on_sized(mut self, on_sized: impl Into<EventHandler<Event<SizedEventData>>>) -> Self {
355        self.get_event_handlers()
356            .insert(EventName::Sized, EventHandlerType::Sized(on_sized.into()));
357        self.get_layout().layout.has_layout_references = true;
358        self
359    }
360
361    /// This is generally the best event in which to run "press" logic, this might be called `onClick`, `onActivate`, or `onConnect` in other platforms.
362    ///
363    /// Gets triggered when:
364    /// - **Click**: There is a `MouseUp` event in the same element that there had been a `MouseDown` just before, or in other words
365    /// - **Touched**: There is a `TouchEnd` event in the same element that there had been a `TouchStart` just before
366    /// - **Activated**: The element is focused and there is a keydown event pressing the OS activation key (e.g Space, Enter)
367    fn on_press(self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
368        let on_press = on_press.into();
369        self.on_pointer_press({
370            let on_press = on_press.clone();
371            move |e: Event<PointerEventData>| {
372                let event = e.try_map(|d| match d {
373                    PointerEventData::Mouse(m) if m.button == Some(MouseButton::Left) => {
374                        Some(PressEventData::Mouse(m))
375                    }
376                    PointerEventData::Touch(t) => Some(PressEventData::Touch(t)),
377                    _ => None,
378                });
379                if let Some(event) = event {
380                    on_press.call(event);
381                }
382            }
383        })
384        .on_key_down({
385            let on_press = on_press.clone();
386            move |e: Event<KeyboardEventData>| {
387                if Focus::is_pressed(&e) {
388                    on_press.call(e.map(PressEventData::Keyboard))
389                }
390            }
391        })
392    }
393}
394
395#[derive(Debug, Clone, PartialEq)]
396pub enum PressEventData {
397    Mouse(MouseEventData),
398    Keyboard(KeyboardEventData),
399    Touch(TouchEventData),
400}
401
402pub trait ContainerWithContentExt
403where
404    Self: LayoutExt,
405{
406    fn direction(mut self, direction: Direction) -> Self {
407        self.get_layout().layout.direction = direction;
408        self
409    }
410    fn main_align(mut self, main_align: Alignment) -> Self {
411        self.get_layout().layout.main_alignment = main_align;
412        self
413    }
414
415    fn cross_align(mut self, cross_align: Alignment) -> Self {
416        self.get_layout().layout.cross_alignment = cross_align;
417        self
418    }
419    fn spacing(mut self, spacing: f32) -> Self {
420        self.get_layout().layout.spacing = torin::geometry::Length::new(spacing);
421        self
422    }
423
424    fn content(mut self, content: Content) -> Self {
425        self.get_layout().layout.content = content;
426        self
427    }
428    fn center(mut self) -> Self {
429        self.get_layout().layout.main_alignment = Alignment::Center;
430        self.get_layout().layout.cross_alignment = Alignment::Center;
431
432        self
433    }
434
435    fn offset_x(mut self, offset_x: impl Into<f32>) -> Self {
436        self.get_layout().layout.offset_x = Length::new(offset_x.into());
437        self
438    }
439
440    fn offset_y(mut self, offset_y: impl Into<f32>) -> Self {
441        self.get_layout().layout.offset_y = Length::new(offset_y.into());
442        self
443    }
444
445    fn vertical(mut self) -> Self {
446        self.get_layout().layout.direction = Direction::vertical();
447        self
448    }
449
450    fn horizontal(mut self) -> Self {
451        self.get_layout().layout.direction = Direction::horizontal();
452        self
453    }
454}
455
456pub trait ContainerExt
457where
458    Self: LayoutExt,
459{
460    fn position(mut self, position: impl Into<Position>) -> Self {
461        self.get_layout().layout.position = position.into();
462        self
463    }
464
465    fn width(mut self, width: impl Into<Size>) -> Self {
466        self.get_layout().layout.width = width.into();
467        self
468    }
469
470    fn height(mut self, height: impl Into<Size>) -> Self {
471        self.get_layout().layout.height = height.into();
472        self
473    }
474
475    fn padding(mut self, padding: impl Into<Gaps>) -> Self {
476        self.get_layout().layout.padding = padding.into();
477        self
478    }
479
480    fn margin(mut self, margin: impl Into<Gaps>) -> Self {
481        self.get_layout().layout.margin = margin.into();
482        self
483    }
484
485    fn min_width(mut self, minimum_width: impl Into<Size>) -> Self {
486        self.get_layout().layout.minimum_width = minimum_width.into();
487        self
488    }
489
490    fn min_height(mut self, minimum_height: impl Into<Size>) -> Self {
491        self.get_layout().layout.minimum_height = minimum_height.into();
492        self
493    }
494
495    fn max_width(mut self, maximum_width: impl Into<Size>) -> Self {
496        self.get_layout().layout.maximum_width = maximum_width.into();
497        self
498    }
499
500    fn max_height(mut self, maximum_height: impl Into<Size>) -> Self {
501        self.get_layout().layout.maximum_height = maximum_height.into();
502        self
503    }
504
505    fn visible_width(mut self, visible_width: impl Into<VisibleSize>) -> Self {
506        self.get_layout().layout.visible_width = visible_width.into();
507        self
508    }
509
510    fn visible_height(mut self, visible_height: impl Into<VisibleSize>) -> Self {
511        self.get_layout().layout.visible_height = visible_height.into();
512        self
513    }
514
515    fn expanded(mut self) -> Self {
516        self.get_layout().layout.width = Size::fill();
517        self.get_layout().layout.height = Size::fill();
518        self
519    }
520}
521
522pub trait LayoutExt
523where
524    Self: Sized,
525{
526    fn get_layout(&mut self) -> &mut LayoutData;
527
528    fn layout(mut self, layout: LayoutData) -> Self {
529        *self.get_layout() = layout;
530        self
531    }
532}
533
534pub trait ImageExt
535where
536    Self: LayoutExt,
537{
538    fn width(mut self, width: Size) -> Self {
539        self.get_layout().layout.width = width;
540        self
541    }
542
543    fn height(mut self, height: Size) -> Self {
544        self.get_layout().layout.height = height;
545        self
546    }
547
548    fn get_image_data(&mut self) -> &mut ImageData;
549
550    fn image_data(mut self, image_data: ImageData) -> Self {
551        *self.get_image_data() = image_data;
552        self
553    }
554
555    fn sampling_mode(mut self, sampling_mode: SamplingMode) -> Self {
556        self.get_image_data().sampling_mode = sampling_mode;
557        self
558    }
559
560    fn aspect_ratio(mut self, aspect_ratio: AspectRatio) -> Self {
561        self.get_image_data().aspect_ratio = aspect_ratio;
562        self
563    }
564
565    fn image_cover(mut self, image_cover: ImageCover) -> Self {
566        self.get_image_data().image_cover = image_cover;
567        self
568    }
569}
570
571pub trait AccessibilityExt: Sized {
572    fn get_accessibility_data(&mut self) -> &mut AccessibilityData;
573
574    fn accessibility(mut self, accessibility: AccessibilityData) -> Self {
575        *self.get_accessibility_data() = accessibility;
576        self
577    }
578
579    fn a11y_id(mut self, a11y_id: impl Into<Option<AccessibilityId>>) -> Self {
580        self.get_accessibility_data().a11y_id = a11y_id.into();
581        self
582    }
583
584    fn a11y_focusable(mut self, a11y_focusable: impl Into<Focusable>) -> Self {
585        self.get_accessibility_data().a11y_focusable = a11y_focusable.into();
586        self
587    }
588
589    fn a11y_auto_focus(mut self, a11y_auto_focus: impl Into<bool>) -> Self {
590        self.get_accessibility_data().a11y_auto_focus = a11y_auto_focus.into();
591        self
592    }
593
594    fn a11y_member_of(mut self, a11y_member_of: impl Into<AccessibilityId>) -> Self {
595        self.get_accessibility_data()
596            .builder
597            .set_member_of(a11y_member_of.into());
598        self
599    }
600
601    fn a11y_role(mut self, a11y_role: impl Into<AccessibilityRole>) -> Self {
602        self.get_accessibility_data()
603            .builder
604            .set_role(a11y_role.into());
605        self
606    }
607
608    fn a11y_alt(mut self, value: impl Into<Box<str>>) -> Self {
609        self.get_accessibility_data().builder.set_label(value);
610        self
611    }
612
613    fn a11y_builder(mut self, with: impl FnOnce(&mut accesskit::Node)) -> Self {
614        with(&mut self.get_accessibility_data().builder);
615        self
616    }
617}
618
619pub trait TextStyleExt
620where
621    Self: Sized,
622{
623    fn get_text_style_data(&mut self) -> &mut TextStyleData;
624
625    fn color(mut self, color: impl Into<Color>) -> Self {
626        self.get_text_style_data().color = Some(color.into());
627        self
628    }
629
630    fn text_align(mut self, text_align: impl Into<TextAlign>) -> Self {
631        self.get_text_style_data().text_align = Some(text_align.into());
632        self
633    }
634
635    fn font_size(mut self, font_size: impl Into<FontSize>) -> Self {
636        self.get_text_style_data().font_size = Some(font_size.into());
637        self
638    }
639
640    fn font_family(mut self, font_family: impl Into<Cow<'static, str>>) -> Self {
641        self.get_text_style_data()
642            .font_families
643            .push(font_family.into());
644        self
645    }
646
647    fn font_slant(mut self, font_slant: impl Into<FontSlant>) -> Self {
648        self.get_text_style_data().font_slant = Some(font_slant.into());
649        self
650    }
651
652    fn font_weight(mut self, font_weight: impl Into<FontWeight>) -> Self {
653        self.get_text_style_data().font_weight = Some(font_weight.into());
654        self
655    }
656
657    fn font_width(mut self, font_width: impl Into<FontWidth>) -> Self {
658        self.get_text_style_data().font_width = Some(font_width.into());
659        self
660    }
661
662    fn text_height(mut self, text_height: impl Into<TextHeightBehavior>) -> Self {
663        self.get_text_style_data().text_height = Some(text_height.into());
664        self
665    }
666
667    fn text_overflow(mut self, text_overflow: impl Into<TextOverflow>) -> Self {
668        self.get_text_style_data().text_overflow = Some(text_overflow.into());
669        self
670    }
671
672    fn text_shadow(mut self, text_shadow: impl Into<TextShadow>) -> Self {
673        self.get_text_style_data()
674            .text_shadows
675            .push(text_shadow.into());
676        self
677    }
678}
679
680pub trait MaybeExt
681where
682    Self: Sized,
683{
684    fn maybe(self, bool: impl Into<bool>, then: impl FnOnce(Self) -> Self) -> Self {
685        if bool.into() { then(self) } else { self }
686    }
687
688    fn map<T>(self, data: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self {
689        if let Some(data) = data {
690            then(self, data)
691        } else {
692            self
693        }
694    }
695}
696
697pub trait LayerExt
698where
699    Self: Sized,
700{
701    fn get_layer(&mut self) -> &mut i16;
702
703    fn layer(mut self, layer: i16) -> Self {
704        *self.get_layer() = layer;
705        self
706    }
707}
708
709pub trait ScrollableExt
710where
711    Self: Sized,
712{
713    fn get_effect(&mut self) -> &mut EffectData;
714
715    fn scrollable(mut self, scrollable: impl Into<bool>) -> Self {
716        self.get_effect().scrollable = scrollable.into();
717        self
718    }
719}