Skip to main content

freya_components/
sidebar.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 SideBarItem {
16        %[fields]
17        color: Color,
18        background: Color,
19        hover_background: Color,
20        active_background: Color,
21        corner_radius: CornerRadius,
22        margin: Gaps,
23        padding: Gaps,
24    }
25}
26
27#[derive(Debug, Default, PartialEq, Clone, Copy)]
28pub enum SideBarItemStatus {
29    /// Default state.
30    #[default]
31    Idle,
32    /// User is hovering the sidebar item.
33    Hovering,
34}
35
36/// Button designed for sidebars.
37///
38/// # Example
39///
40/// ```rust
41/// # use freya::prelude::*;
42/// fn app() -> impl IntoElement {
43///     rect()
44///         .horizontal()
45///         .child(
46///             rect()
47///                 .theme_background()
48///                 .padding(8.)
49///                 .width(Size::px(150.))
50///                 .height(Size::fill())
51///                 .child(SideBarItem::new().child("Home"))
52///                 .child(SideBarItem::new().child("Settings")),
53///         )
54///         .child(rect().expanded().center().child("Main content"))
55/// }
56/// # use freya_testing::prelude::*;
57/// # launch_doc(|| {
58/// #   rect().center().expanded().child(
59/// #       app()
60/// #   )
61/// # }, "./images/gallery_sidebar.png")
62/// # .with_hook(|t| { t.move_cursor((20., 20.)); t.sync_and_update(); })
63/// # .with_scale_factor(0.75)
64/// # .render();
65/// ```
66///
67/// # Preview
68/// ![SideBarItem Preview][SideBarItem]
69
70#[derive(Clone, PartialEq)]
71pub struct SideBarItem {
72    /// Theme override.
73    pub(crate) theme: Option<SideBarItemThemePartial>,
74    /// Inner child for the [SideBarItem].
75    children: Vec<Element>,
76    /// Optionally handle the `on_press` event in the [SideBarItem].
77    on_press: Option<EventHandler<Event<PressEventData>>>,
78    /// Optionally specify a custom `overflow` attribute for this component. Defaults to [OverflowMode::Clip].
79    overflow: Overflow,
80    key: DiffKey,
81}
82
83impl KeyExt for SideBarItem {
84    fn write_key(&mut self) -> &mut DiffKey {
85        &mut self.key
86    }
87}
88
89impl Default for SideBarItem {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl ChildrenExt for SideBarItem {
96    fn get_children(&mut self) -> &mut Vec<Element> {
97        &mut self.children
98    }
99}
100
101impl SideBarItem {
102    pub fn new() -> Self {
103        Self {
104            theme: None,
105            children: Vec::new(),
106            on_press: None,
107            overflow: Overflow::Clip,
108            key: DiffKey::None,
109        }
110    }
111
112    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
113        self.on_press = Some(on_press.into());
114        self
115    }
116
117    pub fn overflow(mut self, overflow: impl Into<Overflow>) -> Self {
118        self.overflow = overflow.into();
119        self
120    }
121
122    /// Get the theme override for this component.
123    pub fn get_theme(&self) -> Option<&SideBarItemThemePartial> {
124        self.theme.as_ref()
125    }
126
127    /// Set a theme override for this component.
128    pub fn theme(mut self, theme: SideBarItemThemePartial) -> Self {
129        self.theme = Some(theme);
130        self
131    }
132}
133
134impl Component for SideBarItem {
135    fn render(&self) -> impl IntoElement {
136        let SideBarItemTheme {
137            margin,
138            hover_background,
139            active_background,
140            background,
141            corner_radius,
142            padding,
143            color,
144        } = get_theme!(&self.theme, SideBarItemThemePreference, "sidebar_item");
145        let mut status = use_state(SideBarItemStatus::default);
146        let is_active = use_activable_route();
147
148        let on_pointer_enter = move |_| {
149            status.set(SideBarItemStatus::Hovering);
150        };
151
152        let on_pointer_leave = move |_| {
153            status.set(SideBarItemStatus::default());
154        };
155
156        let background = match *status.read() {
157            _ if is_active => active_background,
158            SideBarItemStatus::Hovering => hover_background,
159            SideBarItemStatus::Idle => background,
160        };
161
162        rect()
163            .a11y_focusable(true)
164            .a11y_role(AccessibilityRole::Link)
165            .map(self.on_press.clone(), |rect, on_press| {
166                rect.on_press(on_press)
167            })
168            .on_pointer_enter(on_pointer_enter)
169            .on_pointer_leave(on_pointer_leave)
170            .overflow(self.overflow)
171            .width(Size::fill())
172            .margin(margin)
173            .padding(padding)
174            .color(color)
175            .background(background)
176            .corner_radius(corner_radius)
177            .children(self.children.clone())
178    }
179
180    fn render_key(&self) -> DiffKey {
181        self.key.clone().or(self.default_key())
182    }
183}