freya_components/
segmented_button.rs1use freya_core::prelude::*;
2use torin::size::Size;
3
4use crate::{
5 get_theme,
6 icons::tick::TickIcon,
7 theming::component_themes::{
8 ButtonSegmentTheme,
9 ButtonSegmentThemePartial,
10 SegmentedButtonTheme,
11 SegmentedButtonThemePartial,
12 },
13};
14
15#[derive(Debug, Default, PartialEq, Clone, Copy)]
17pub enum ButtonSegmentStatus {
18 #[default]
20 Idle,
21 Hovering,
23}
24
25#[derive(Clone, PartialEq)]
51pub struct ButtonSegment {
52 pub(crate) theme: Option<ButtonSegmentThemePartial>,
53 children: Vec<Element>,
54 on_press: Option<EventHandler<Event<PressEventData>>>,
55 selected: bool,
56 enabled: bool,
57 key: DiffKey,
58}
59
60impl Default for ButtonSegment {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl ButtonSegment {
67 pub fn new() -> Self {
68 Self {
69 theme: None,
70 children: Vec::new(),
71 on_press: None,
72 selected: false,
73 enabled: true,
74 key: DiffKey::None,
75 }
76 }
77
78 pub fn get_theme(&self) -> Option<&ButtonSegmentThemePartial> {
80 self.theme.as_ref()
81 }
82
83 pub fn theme(mut self, theme: ButtonSegmentThemePartial) -> Self {
85 self.theme = Some(theme);
86 self
87 }
88
89 pub fn is_selected(&self) -> bool {
91 self.selected
92 }
93
94 pub fn selected(mut self, selected: impl Into<bool>) -> Self {
95 self.selected = selected.into();
96 self
97 }
98
99 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
100 self.enabled = enabled.into();
101 self
102 }
103
104 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
105 self.on_press = Some(on_press.into());
106 self
107 }
108}
109
110impl ChildrenExt for ButtonSegment {
111 fn get_children(&mut self) -> &mut Vec<Element> {
112 &mut self.children
113 }
114}
115
116impl KeyExt for ButtonSegment {
117 fn write_key(&mut self) -> &mut DiffKey {
118 &mut self.key
119 }
120}
121
122impl Component for ButtonSegment {
123 fn render(&self) -> impl IntoElement {
124 let theme = get_theme!(&self.theme, button_segment);
125 let mut status = use_state(|| ButtonSegmentStatus::Idle);
126 let focus = use_focus();
127 let focus_status = use_focus_status(focus);
128
129 let ButtonSegmentTheme {
130 background,
131 hover_background,
132 disabled_background,
133 selected_background,
134 focus_background,
135 padding,
136 selected_padding,
137 width,
138 height,
139 color,
140 selected_icon_fill,
141 } = theme;
142
143 let enabled = use_reactive(&self.enabled);
144 use_drop(move || {
145 if status() == ButtonSegmentStatus::Hovering && enabled() {
146 Cursor::set(CursorIcon::default());
147 }
148 });
149
150 let on_press = self.on_press.clone();
151 let on_press = move |e: Event<PressEventData>| {
152 focus.request_focus();
153 if let Some(on_press) = &on_press {
154 on_press.call(e);
155 }
156 };
157
158 let on_pointer_enter = move |_| {
159 status.set(ButtonSegmentStatus::Hovering);
160 if enabled() {
161 Cursor::set(CursorIcon::Pointer);
162 } else {
163 Cursor::set(CursorIcon::NotAllowed);
164 }
165 };
166
167 let on_pointer_leave = move |_| {
168 if status() == ButtonSegmentStatus::Hovering {
169 Cursor::set(CursorIcon::default());
170 status.set(ButtonSegmentStatus::Idle);
171 }
172 };
173
174 let background = match status() {
175 _ if !self.enabled => disabled_background,
176 _ if self.selected => selected_background,
177 ButtonSegmentStatus::Hovering => hover_background,
178 ButtonSegmentStatus::Idle => background,
179 };
180
181 let padding = if self.selected {
182 selected_padding
183 } else {
184 padding
185 };
186 let background = if *focus_status.read() == FocusStatus::Keyboard {
187 focus_background
188 } else {
189 background
190 };
191
192 rect()
193 .a11y_id(focus.a11y_id())
194 .a11y_focusable(self.enabled)
195 .a11y_role(AccessibilityRole::Button)
196 .maybe(self.enabled, |rect| rect.on_press(on_press))
197 .on_pointer_enter(on_pointer_enter)
198 .on_pointer_leave(on_pointer_leave)
199 .horizontal()
200 .width(width)
201 .height(height)
202 .padding(padding)
203 .overflow(Overflow::Clip)
204 .color(color.mul_if(!self.enabled, 0.9))
205 .background(background.mul_if(!self.enabled, 0.9))
206 .center()
207 .spacing(4.)
208 .maybe_child(self.selected.then(|| {
209 TickIcon::new()
210 .fill(selected_icon_fill)
211 .width(Size::px(12.))
212 .height(Size::px(12.))
213 }))
214 .children(self.children.clone())
215 }
216
217 fn render_key(&self) -> DiffKey {
218 self.key.clone().or(self.default_key())
219 }
220}
221
222#[cfg_attr(feature = "docs",
255 doc = embed_doc_image::embed_image!("segmented_button", "images/gallery_segmented_button.png")
256)]
257#[derive(Clone, PartialEq)]
258pub struct SegmentedButton {
259 pub(crate) theme: Option<SegmentedButtonThemePartial>,
260 children: Vec<Element>,
261 key: DiffKey,
262}
263
264impl Default for SegmentedButton {
265 fn default() -> Self {
266 Self::new()
267 }
268}
269
270impl SegmentedButton {
271 pub fn new() -> Self {
272 Self {
273 theme: None,
274 children: Vec::new(),
275 key: DiffKey::None,
276 }
277 }
278
279 pub fn theme(mut self, theme: SegmentedButtonThemePartial) -> Self {
280 self.theme = Some(theme);
281 self
282 }
283}
284
285impl ChildrenExt for SegmentedButton {
286 fn get_children(&mut self) -> &mut Vec<Element> {
287 &mut self.children
288 }
289}
290
291impl KeyExt for SegmentedButton {
292 fn write_key(&mut self) -> &mut DiffKey {
293 &mut self.key
294 }
295}
296
297impl Component for SegmentedButton {
298 fn render(&self) -> impl IntoElement {
299 let theme = get_theme!(&self.theme, segmented_button);
300
301 let SegmentedButtonTheme {
302 background,
303 border_fill,
304 corner_radius,
305 } = theme;
306
307 rect()
308 .overflow(Overflow::Clip)
309 .background(background)
310 .border(
311 Border::new()
312 .fill(border_fill)
313 .width(1.)
314 .alignment(BorderAlignment::Outer),
315 )
316 .corner_radius(corner_radius)
317 .horizontal()
318 .children(self.children.clone())
319 }
320
321 fn render_key(&self) -> DiffKey {
322 self.key.clone().or(self.default_key())
323 }
324}