freya_components/
sidebar.rs

1use freya_core::prelude::*;
2use torin::size::Size;
3
4use crate::{
5    activable_route_context::use_activable_route,
6    get_theme,
7    scrollviews::ScrollView,
8    theming::component_themes::{
9        SideBarItemTheme,
10        SideBarItemThemePartial,
11        SideBarTheme,
12        SideBarThemePartial,
13    },
14};
15
16#[derive(PartialEq)]
17pub struct SideBar {
18    /// Theme override.
19    pub(crate) theme: Option<SideBarThemePartial>,
20    /// This is what is rendered next to the sidebar.
21    content: Option<Element>,
22    /// This is what is rendered in the sidebar.
23    bar: Option<Element>,
24    /// Width of the sidebar.
25    width: Size,
26}
27
28impl Default for SideBar {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl SideBar {
35    pub fn new() -> Self {
36        Self {
37            theme: None,
38            content: None,
39            bar: None,
40            width: Size::px(180.),
41        }
42    }
43
44    pub fn content(mut self, content: impl Into<Element>) -> Self {
45        self.content = Some(content.into());
46        self
47    }
48
49    pub fn bar(mut self, bar: impl Into<Element>) -> Self {
50        self.bar = Some(bar.into());
51        self
52    }
53
54    pub fn width(mut self, width: impl Into<Size>) -> Self {
55        self.width = width.into();
56        self
57    }
58}
59
60impl Render for SideBar {
61    fn render(&self) -> impl IntoElement {
62        let SideBarTheme {
63            spacing,
64            padding,
65            background,
66            color,
67        } = get_theme!(&self.theme, sidebar);
68
69        rect()
70            .horizontal()
71            .width(Size::fill())
72            .height(Size::fill())
73            .color(color)
74            .child(
75                rect()
76                    .overflow(Overflow::Clip)
77                    .width(self.width.clone())
78                    .height(Size::fill())
79                    .background(background)
80                    .child(
81                        ScrollView::new()
82                            .width(self.width.clone())
83                            .spacing(spacing)
84                            .child(rect().padding(padding).maybe_child(self.bar.clone())),
85                    ),
86            )
87            .child(
88                rect()
89                    .overflow(Overflow::Clip)
90                    .expanded()
91                    .maybe_child(self.content.clone()),
92            )
93    }
94}
95
96#[derive(Debug, Default, PartialEq, Clone, Copy)]
97pub enum ButtonStatus {
98    /// Default state.
99    #[default]
100    Idle,
101    /// Mouse is hovering the button.
102    Hovering,
103}
104#[derive(PartialEq)]
105pub struct SideBarItem {
106    /// Theme override.
107    pub(crate) theme: Option<SideBarItemThemePartial>,
108    /// Inner child for the [SideBarItem].
109    children: Vec<Element>,
110    /// Optionally handle the `on_press` event in the [SideBarItem].
111    on_press: Option<EventHandler<Event<PressEventData>>>,
112    /// Optionally specify a custom `overflow` attribute for this component. Defaults to [OverflowMode::Clip].
113    overflow: Overflow,
114    key: DiffKey,
115}
116
117impl KeyExt for SideBarItem {
118    fn write_key(&mut self) -> &mut DiffKey {
119        &mut self.key
120    }
121}
122
123impl Default for SideBarItem {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl ChildrenExt for SideBarItem {
130    fn get_children(&mut self) -> &mut Vec<Element> {
131        &mut self.children
132    }
133}
134
135impl SideBarItem {
136    pub fn new() -> Self {
137        Self {
138            theme: None,
139            children: Vec::new(),
140            on_press: None,
141            overflow: Overflow::Clip,
142            key: DiffKey::None,
143        }
144    }
145
146    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
147        self.on_press = Some(on_press.into());
148        self
149    }
150
151    pub fn overflow(mut self, overflow: impl Into<Overflow>) -> Self {
152        self.overflow = overflow.into();
153        self
154    }
155}
156
157impl Render for SideBarItem {
158    fn render(&self) -> impl IntoElement {
159        let SideBarItemTheme {
160            margin,
161            hover_background,
162            background,
163            corner_radius,
164            padding,
165            color,
166        } = get_theme!(&self.theme, sidebar_item);
167        let mut status = use_state(ButtonStatus::default);
168        let is_active = use_activable_route();
169
170        use_drop(move || {
171            if status() == ButtonStatus::Hovering {
172                Cursor::set(CursorIcon::default());
173            }
174        });
175
176        let on_pointer_enter = move |_| {
177            status.set(ButtonStatus::Hovering);
178            Cursor::set(CursorIcon::Pointer);
179        };
180
181        let on_pointer_leave = move |_| {
182            status.set(ButtonStatus::default());
183            Cursor::set(CursorIcon::default());
184        };
185
186        let background = match *status.read() {
187            _ if is_active => hover_background,
188            ButtonStatus::Hovering => hover_background,
189            ButtonStatus::Idle => background,
190        };
191
192        rect()
193            .a11y_focusable(true)
194            .a11y_role(AccessibilityRole::Button)
195            .map(self.on_press.clone(), |rect, on_press| {
196                rect.on_press(on_press)
197            })
198            .on_pointer_enter(on_pointer_enter)
199            .on_pointer_leave(on_pointer_leave)
200            .overflow(self.overflow)
201            .width(Size::fill())
202            .margin(margin)
203            .padding(padding)
204            .color(color)
205            .background(background)
206            .corner_radius(corner_radius)
207            .children(self.children.clone())
208    }
209
210    fn render_key(&self) -> DiffKey {
211        self.key.clone().or(self.default_key())
212    }
213}