Skip to main content

freya_components/
floating_tab.rs

1use freya_core::prelude::*;
2use torin::{
3    gaps::Gaps,
4    size::Size,
5};
6
7use crate::{
8    activable_route_context::use_activable_route,
9    define_theme,
10    get_theme,
11};
12
13define_theme! {
14    %[component]
15    pub FloatingTab {
16        %[fields]
17        background: Color,
18        hover_background: Color,
19        width: Size,
20        height: Size,
21        padding: Gaps,
22        color: Color,
23        corner_radius: CornerRadius,
24    }
25}
26
27/// Current status of the Tab.
28#[derive(Debug, Default, PartialEq, Clone, Copy)]
29pub enum TabStatus {
30    /// Default state.
31    #[default]
32    Idle,
33    /// Mouse is hovering the Tab.
34    Hovering,
35}
36
37#[derive(Clone, PartialEq)]
38pub struct FloatingTab {
39    pub(crate) theme: Option<FloatingTabThemePartial>,
40    children: Vec<Element>,
41    /// Optionally handle the `on_press` event in [FloatingTab].
42    on_press: Option<EventHandler<Event<PressEventData>>>,
43    key: DiffKey,
44}
45
46impl KeyExt for FloatingTab {
47    fn write_key(&mut self) -> &mut DiffKey {
48        &mut self.key
49    }
50}
51
52impl Default for FloatingTab {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58impl ChildrenExt for FloatingTab {
59    fn get_children(&mut self) -> &mut Vec<Element> {
60        &mut self.children
61    }
62}
63
64/// Floating Tab component.
65///
66/// # Example
67///
68/// ```rust
69/// # use freya::prelude::*;
70/// fn app() -> impl IntoElement {
71///     rect()
72///         .spacing(8.)
73///         .child(FloatingTab::new().child("Page 1"))
74///         .child(FloatingTab::new().child("Page 2"))
75/// }
76///
77/// # use freya_testing::prelude::*;
78/// # launch_doc(|| {
79/// #   rect().center().expanded().child(app())
80/// # }, "./images/gallery_floating_tab.png").with_hook(|t| { t.move_cursor((125., 115.)); t.sync_and_update(); }).with_scale_factor(1.).render();
81/// ```
82///
83/// # Preview
84/// ![FloatingTab Preview][floating_tab]
85#[cfg_attr(feature = "docs",
86    doc = embed_doc_image::embed_image!("floating_tab", "images/gallery_floating_tab.png")
87)]
88impl FloatingTab {
89    pub fn new() -> Self {
90        Self {
91            children: vec![],
92            theme: None,
93            on_press: None,
94            key: DiffKey::None,
95        }
96    }
97
98    /// Get the theme override for this component.
99    pub fn get_theme(&self) -> Option<&FloatingTabThemePartial> {
100        self.theme.as_ref()
101    }
102
103    /// Set a theme override for this component.
104    pub fn theme(mut self, theme: FloatingTabThemePartial) -> Self {
105        self.theme = Some(theme);
106        self
107    }
108}
109
110impl Component for FloatingTab {
111    fn render(&self) -> impl IntoElement {
112        let focus = use_focus();
113        let focus_status = use_focus_status(focus);
114        let mut status = use_state(TabStatus::default);
115        let is_active = use_activable_route();
116
117        let FloatingTabTheme {
118            background,
119            hover_background,
120            padding,
121            width,
122            height,
123            color,
124            corner_radius,
125        } = get_theme!(&self.theme, FloatingTabThemePreference, "floating_tab");
126
127        let on_pointer_enter = move |_| {
128            Cursor::set(CursorIcon::Pointer);
129            status.set(TabStatus::Hovering);
130        };
131
132        let on_pointer_leave = move |_| {
133            Cursor::set(CursorIcon::default());
134            status.set(TabStatus::default());
135        };
136
137        let background = if focus_status() == FocusStatus::Keyboard
138            || is_active
139            || *status.read() == TabStatus::Hovering
140        {
141            hover_background
142        } else {
143            background
144        };
145
146        rect()
147            .a11y_id(focus.a11y_id())
148            .a11y_focusable(Focusable::Enabled)
149            .a11y_role(AccessibilityRole::Tab)
150            .on_pointer_enter(on_pointer_enter)
151            .on_pointer_leave(on_pointer_leave)
152            .map(self.on_press.clone(), |el, on_press| el.on_press(on_press))
153            .width(width)
154            .height(height)
155            .center()
156            .overflow(Overflow::Clip)
157            .padding(padding)
158            .background(background)
159            .color(color)
160            .corner_radius(corner_radius)
161            .children(self.children.clone())
162    }
163
164    fn render_key(&self) -> DiffKey {
165        self.key.clone().or(self.default_key())
166    }
167}