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 on_press = self.on_press.clone();
177 let on_press = move |e: Event<PressEventData>| {
178 a11y_id.request_focus();
179 if let Some(on_press) = &on_press {
180 on_press.call(e);
181 }
182 };
183
184 let on_pointer_enter = move |_| {
185 status.set(ButtonSegmentStatus::Hovering);
186 };
187
188 let on_pointer_leave = move |_| {
189 if status() == ButtonSegmentStatus::Hovering {
190 status.set(ButtonSegmentStatus::Idle);
191 }
192 };
193
194 let background = match status() {
195 _ if !self.enabled => disabled_background,
196 _ if self.selected => selected_background,
197 ButtonSegmentStatus::Hovering => hover_background,
198 ButtonSegmentStatus::Idle => background,
199 };
200
201 let padding = if self.selected {
202 selected_padding
203 } else {
204 padding
205 };
206 let background = if *focus.read() == Focus::Keyboard {
207 focus_background
208 } else {
209 background
210 };
211
212 rect()
213 .a11y_id(a11y_id)
214 .a11y_focusable(self.enabled)
215 .a11y_role(AccessibilityRole::Button)
216 .maybe(self.enabled, |rect| rect.on_press(on_press))
217 .on_pointer_enter(on_pointer_enter)
218 .on_pointer_leave(on_pointer_leave)
219 .cursor(if self.enabled {
220 self.cursor_icon
221 } else {
222 CursorIcon::NotAllowed
223 })
224 .horizontal()
225 .width(width)
226 .height(height)
227 .padding(padding)
228 .overflow(Overflow::Clip)
229 .color(color.mul_if(!self.enabled, 0.9))
230 .background(background.mul_if(!self.enabled, 0.9))
231 .center()
232 .spacing(4.)
233 .maybe_child(self.selected.then(|| {
234 TickIcon::new()
235 .fill(selected_icon_fill)
236 .width(Size::px(12.))
237 .height(Size::px(12.))
238 }))
239 .children(self.children.clone())
240 }
241
242 fn render_key(&self) -> DiffKey {
243 self.key.clone().or(self.default_key())
244 }
245}
246
247#[cfg_attr(feature = "docs",
279 doc = embed_doc_image::embed_image!("segmented_button", "images/gallery_segmented_button.png")
280)]
281#[derive(Clone, PartialEq)]
282pub struct SegmentedButton {
283 pub(crate) theme: Option<SegmentedButtonThemePartial>,
284 children: Vec<Element>,
285 key: DiffKey,
286}
287
288impl Default for SegmentedButton {
289 fn default() -> Self {
290 Self::new()
291 }
292}
293
294impl SegmentedButton {
295 pub fn new() -> Self {
296 Self {
297 theme: None,
298 children: Vec::new(),
299 key: DiffKey::None,
300 }
301 }
302
303 pub fn theme(mut self, theme: SegmentedButtonThemePartial) -> Self {
304 self.theme = Some(theme);
305 self
306 }
307}
308
309impl ChildrenExt for SegmentedButton {
310 fn get_children(&mut self) -> &mut Vec<Element> {
311 &mut self.children
312 }
313}
314
315impl KeyExt for SegmentedButton {
316 fn write_key(&mut self) -> &mut DiffKey {
317 &mut self.key
318 }
319}
320
321impl Component for SegmentedButton {
322 fn render(&self) -> impl IntoElement {
323 let theme = get_theme!(
324 &self.theme,
325 SegmentedButtonThemePreference,
326 "segmented_button"
327 );
328
329 let SegmentedButtonTheme {
330 background,
331 border_fill,
332 corner_radius,
333 } = theme;
334
335 rect()
336 .overflow(Overflow::Clip)
337 .background(background)
338 .border(
339 Border::new()
340 .fill(border_fill)
341 .width(1.)
342 .alignment(BorderAlignment::Outer),
343 )
344 .corner_radius(corner_radius)
345 .horizontal()
346 .children(self.children.clone())
347 }
348
349 fn render_key(&self) -> DiffKey {
350 self.key.clone().or(self.default_key())
351 }
352}