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