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_context::use_is_active,
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 key_mut(&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 children_mut(&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///
78/// See the [interactive components demo](https://freyaui.dev/demo).
79impl FloatingTab {
80    pub fn new() -> Self {
81        Self {
82            children: vec![],
83            theme: None,
84            on_press: None,
85            key: DiffKey::None,
86        }
87    }
88
89    /// Get the theme override for this component.
90    pub fn get_theme(&self) -> Option<&FloatingTabThemePartial> {
91        self.theme.as_ref()
92    }
93
94    /// Set a theme override for this component.
95    pub fn theme(mut self, theme: FloatingTabThemePartial) -> Self {
96        self.theme = Some(theme);
97        self
98    }
99}
100
101impl Component for FloatingTab {
102    fn render(&self) -> impl IntoElement {
103        let a11y_id = use_a11y();
104        let focus = use_focus(a11y_id);
105        let mut status = use_state(TabStatus::default);
106        let is_active = use_is_active();
107
108        let FloatingTabTheme {
109            background,
110            hover_background,
111            padding,
112            width,
113            height,
114            color,
115            corner_radius,
116        } = get_theme!(&self.theme, FloatingTabThemePreference, "floating_tab");
117
118        let on_pointer_enter = move |_| {
119            status.set(TabStatus::Hovering);
120        };
121
122        let on_pointer_leave = move |_| {
123            status.set(TabStatus::default());
124        };
125
126        let background =
127            if focus() == Focus::Keyboard || is_active || *status.read() == TabStatus::Hovering {
128                hover_background
129            } else {
130                background
131            };
132
133        rect()
134            .a11y_id(a11y_id)
135            .a11y_focusable(Focusable::Enabled)
136            .a11y_role(AccessibilityRole::Tab)
137            .on_pointer_enter(on_pointer_enter)
138            .on_pointer_leave(on_pointer_leave)
139            .cursor(CursorIcon::Pointer)
140            .map(self.on_press.clone(), |el, on_press| el.on_press(on_press))
141            .width(width)
142            .height(height)
143            .center()
144            .overflow(Overflow::Clip)
145            .padding(padding)
146            .background(background)
147            .color(color)
148            .corner_radius(corner_radius)
149            .children(self.children.clone())
150    }
151
152    fn render_key(&self) -> DiffKey {
153        self.key.clone().or(self.default_key())
154    }
155}