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    children: Vec<Element>,
25    pub(crate) theme: Option<FloatingTabThemePartial>,
26    /// Optionally handle the `onclick` event in the SidebarItem.
27    on_press: Option<EventHandler<()>>,
28}
29
30impl Default for FloatingTab {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl ChildrenExt for FloatingTab {
37    fn get_children(&mut self) -> &mut Vec<Element> {
38        &mut self.children
39    }
40}
41
42/// Floating Tab component.
43///
44/// # Example
45///
46/// ```rust
47/// # use freya::prelude::*;
48/// fn app() -> impl IntoElement {
49///     rect()
50///         .spacing(8.)
51///         .child(FloatingTab::new().child("Page 1"))
52///         .child(FloatingTab::new().child("Page 2"))
53/// }
54///
55/// # use freya_testing::prelude::*;
56/// # launch_doc_hook(|| {
57/// #   rect().center().expanded().child(app())
58/// # }, (250., 250.).into(), "./images/gallery_floating_tab.png", |t| {
59/// #   t.move_cursor((125., 115.));
60/// #   t.sync_and_update();
61/// # });
62/// ```
63///
64/// # Preview
65/// ![FloatingTab Preview][floating_tab]
66#[cfg_attr(feature = "docs",
67    doc = embed_doc_image::embed_image!("floating_tab", "images/gallery_floating_tab.png")
68)]
69impl FloatingTab {
70    pub fn new() -> Self {
71        Self {
72            children: vec![],
73            theme: None,
74            on_press: None,
75        }
76    }
77}
78
79impl Render for FloatingTab {
80    fn render(&self) -> impl IntoElement {
81        let focus = use_focus();
82        let focus_status = use_focus_status(focus);
83        let mut status = use_state(TabStatus::default);
84        let is_active = use_activable_route();
85
86        let FloatingTabTheme {
87            background,
88            hover_background,
89            padding,
90            width,
91            height,
92            color,
93        } = get_theme!(&self.theme, floating_tab);
94
95        let on_press = self.on_press.clone();
96
97        let on_press = move |_| {
98            if let Some(onpress) = &on_press {
99                onpress.call(());
100            }
101        };
102
103        let on_pointer_enter = move |_| {
104            Cursor::set(CursorIcon::Pointer);
105            status.set(TabStatus::Hovering);
106        };
107
108        let on_pointer_leave = move |_| {
109            Cursor::set(CursorIcon::default());
110            status.set(TabStatus::default());
111        };
112
113        let background = match *status.read() {
114            _ if focus_status() == FocusStatus::Keyboard || is_active => hover_background,
115            TabStatus::Hovering => hover_background,
116            TabStatus::Idle => background,
117        };
118
119        rect()
120            .on_press(on_press)
121            .on_pointer_enter(on_pointer_enter)
122            .on_pointer_leave(on_pointer_leave)
123            .a11y_id(focus.a11y_id())
124            .a11y_focusable(Focusable::Enabled)
125            .width(width)
126            .height(height)
127            .center()
128            .overflow(Overflow::Clip)
129            .padding(padding)
130            .background(background)
131            .color(color)
132            .corner_radius(99.)
133            .children(self.children.clone())
134    }
135}