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