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