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