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            active_background,
163            background,
164            corner_radius,
165            padding,
166            color,
167        } = get_theme!(&self.theme, sidebar_item);
168        let mut status = use_state(ButtonStatus::default);
169        let is_active = use_activable_route();
170
171        use_drop(move || {
172            if status() == ButtonStatus::Hovering {
173                Cursor::set(CursorIcon::default());
174            }
175        });
176
177        let on_pointer_enter = move |_| {
178            status.set(ButtonStatus::Hovering);
179            Cursor::set(CursorIcon::Pointer);
180        };
181
182        let on_pointer_leave = move |_| {
183            status.set(ButtonStatus::default());
184            Cursor::set(CursorIcon::default());
185        };
186
187        let background = match *status.read() {
188            _ if is_active => active_background,
189            ButtonStatus::Hovering => hover_background,
190            ButtonStatus::Idle => background,
191        };
192
193        rect()
194            .a11y_focusable(true)
195            .a11y_role(AccessibilityRole::Button)
196            .map(self.on_press.clone(), |rect, on_press| {
197                rect.on_press(on_press)
198            })
199            .on_pointer_enter(on_pointer_enter)
200            .on_pointer_leave(on_pointer_leave)
201            .overflow(self.overflow)
202            .width(Size::fill())
203            .margin(margin)
204            .padding(padding)
205            .color(color)
206            .background(background)
207            .corner_radius(corner_radius)
208            .children(self.children.clone())
209    }
210
211    fn render_key(&self) -> DiffKey {
212        self.key.clone().or(self.default_key())
213    }
214}