Skip to main content

freya_components/
floating_tab.rs

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