freya_components/
segmented_button.rs1use freya_core::prelude::*;
2use torin::{
3 gaps::Gaps,
4 size::Size,
5};
6
7use crate::{
8 define_theme,
9 get_theme,
10 icons::tick::TickIcon,
11};
12
13define_theme! {
14 %[component]
15 pub ButtonSegment {
16 %[fields]
17 background: Color,
18 hover_background: Color,
19 disabled_background: Color,
20 selected_background: Color,
21 focus_background: Color,
22 padding: Gaps,
23 selected_padding: Gaps,
24 width: Size,
25 height: Size,
26 color: Color,
27 selected_icon_fill: Color,
28 }
29}
30
31define_theme! {
32 %[component]
33 pub SegmentedButton {
34 %[fields]
35 background: Color,
36 border_fill: Color,
37 corner_radius: CornerRadius,
38 }
39}
40
41#[derive(Debug, Default, PartialEq, Clone, Copy)]
43pub enum ButtonSegmentStatus {
44 #[default]
46 Idle,
47 Hovering,
49}
50
51#[derive(Clone, PartialEq)]
77pub struct ButtonSegment {
78 pub(crate) theme: Option<ButtonSegmentThemePartial>,
79 children: Vec<Element>,
80 on_press: Option<EventHandler<Event<PressEventData>>>,
81 selected: bool,
82 enabled: bool,
83 cursor_icon: CursorIcon,
84 key: DiffKey,
85}
86
87impl Default for ButtonSegment {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl ButtonSegment {
94 pub fn new() -> Self {
95 Self {
96 theme: None,
97 children: Vec::new(),
98 on_press: None,
99 selected: false,
100 enabled: true,
101 cursor_icon: CursorIcon::default(),
102 key: DiffKey::None,
103 }
104 }
105
106 pub fn get_theme(&self) -> Option<&ButtonSegmentThemePartial> {
108 self.theme.as_ref()
109 }
110
111 pub fn theme(mut self, theme: ButtonSegmentThemePartial) -> Self {
113 self.theme = Some(theme);
114 self
115 }
116
117 pub fn is_selected(&self) -> bool {
119 self.selected
120 }
121
122 pub fn selected(mut self, selected: impl Into<bool>) -> Self {
123 self.selected = selected.into();
124 self
125 }
126
127 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
128 self.enabled = enabled.into();
129 self
130 }
131
132 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
133 self.on_press = Some(on_press.into());
134 self
135 }
136
137 pub fn cursor_icon(mut self, cursor_icon: impl Into<CursorIcon>) -> Self {
139 self.cursor_icon = cursor_icon.into();
140 self
141 }
142}
143
144impl ChildrenExt for ButtonSegment {
145 fn get_children(&mut self) -> &mut Vec<Element> {
146 &mut self.children
147 }
148}
149
150impl KeyExt for ButtonSegment {
151 fn write_key(&mut self) -> &mut DiffKey {
152 &mut self.key
153 }
154}
155
156impl Component for ButtonSegment {
157 fn render(&self) -> impl IntoElement {
158 let theme = get_theme!(&self.theme, ButtonSegmentThemePreference, "button_segment");
159 let mut status = use_state(|| ButtonSegmentStatus::Idle);
160 let a11y_id = use_a11y();
161 let focus = use_focus(a11y_id);
162
163 let ButtonSegmentTheme {
164 background,
165 hover_background,
166 disabled_background,
167 selected_background,
168 focus_background,
169 padding,
170 selected_padding,
171 width,
172 height,
173 color,
174 selected_icon_fill,
175 } = theme;
176
177 let enabled = use_reactive(&self.enabled);
178 let cursor_icon = self.cursor_icon;
179 use_drop(move || {
180 if status() == ButtonSegmentStatus::Hovering && enabled() {
181 Cursor::set(CursorIcon::default());
182 }
183 });
184
185 let on_press = self.on_press.clone();
186 let on_press = move |e: Event<PressEventData>| {
187 a11y_id.request_focus();
188 if let Some(on_press) = &on_press {
189 on_press.call(e);
190 }
191 };
192
193 let on_pointer_enter = move |_| {
194 status.set(ButtonSegmentStatus::Hovering);
195 if enabled() {
196 Cursor::set(cursor_icon);
197 } else {
198 Cursor::set(CursorIcon::NotAllowed);
199 }
200 };
201
202 let on_pointer_leave = move |_| {
203 if status() == ButtonSegmentStatus::Hovering {
204 Cursor::set(CursorIcon::default());
205 status.set(ButtonSegmentStatus::Idle);
206 }
207 };
208
209 let background = match status() {
210 _ if !self.enabled => disabled_background,
211 _ if self.selected => selected_background,
212 ButtonSegmentStatus::Hovering => hover_background,
213 ButtonSegmentStatus::Idle => background,
214 };
215
216 let padding = if self.selected {
217 selected_padding
218 } else {
219 padding
220 };
221 let background = if *focus.read() == Focus::Keyboard {
222 focus_background
223 } else {
224 background
225 };
226
227 rect()
228 .a11y_id(a11y_id)
229 .a11y_focusable(self.enabled)
230 .a11y_role(AccessibilityRole::Button)
231 .maybe(self.enabled, |rect| rect.on_press(on_press))
232 .on_pointer_enter(on_pointer_enter)
233 .on_pointer_leave(on_pointer_leave)
234 .horizontal()
235 .width(width)
236 .height(height)
237 .padding(padding)
238 .overflow(Overflow::Clip)
239 .color(color.mul_if(!self.enabled, 0.9))
240 .background(background.mul_if(!self.enabled, 0.9))
241 .center()
242 .spacing(4.)
243 .maybe_child(self.selected.then(|| {
244 TickIcon::new()
245 .fill(selected_icon_fill)
246 .width(Size::px(12.))
247 .height(Size::px(12.))
248 }))
249 .children(self.children.clone())
250 }
251
252 fn render_key(&self) -> DiffKey {
253 self.key.clone().or(self.default_key())
254 }
255}
256
257#[cfg_attr(feature = "docs",
290 doc = embed_doc_image::embed_image!("segmented_button", "images/gallery_segmented_button.png")
291)]
292#[derive(Clone, PartialEq)]
293pub struct SegmentedButton {
294 pub(crate) theme: Option<SegmentedButtonThemePartial>,
295 children: Vec<Element>,
296 key: DiffKey,
297}
298
299impl Default for SegmentedButton {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305impl SegmentedButton {
306 pub fn new() -> Self {
307 Self {
308 theme: None,
309 children: Vec::new(),
310 key: DiffKey::None,
311 }
312 }
313
314 pub fn theme(mut self, theme: SegmentedButtonThemePartial) -> Self {
315 self.theme = Some(theme);
316 self
317 }
318}
319
320impl ChildrenExt for SegmentedButton {
321 fn get_children(&mut self) -> &mut Vec<Element> {
322 &mut self.children
323 }
324}
325
326impl KeyExt for SegmentedButton {
327 fn write_key(&mut self) -> &mut DiffKey {
328 &mut self.key
329 }
330}
331
332impl Component for SegmentedButton {
333 fn render(&self) -> impl IntoElement {
334 let theme = get_theme!(
335 &self.theme,
336 SegmentedButtonThemePreference,
337 "segmented_button"
338 );
339
340 let SegmentedButtonTheme {
341 background,
342 border_fill,
343 corner_radius,
344 } = theme;
345
346 rect()
347 .overflow(Overflow::Clip)
348 .background(background)
349 .border(
350 Border::new()
351 .fill(border_fill)
352 .width(1.)
353 .alignment(BorderAlignment::Outer),
354 )
355 .corner_radius(corner_radius)
356 .horizontal()
357 .children(self.children.clone())
358 }
359
360 fn render_key(&self) -> DiffKey {
361 self.key.clone().or(self.default_key())
362 }
363}