freya_components/
segmented_button.rs1use freya_core::prelude::*;
2use torin::size::Size;
3
4use crate::{
5 get_theme,
6 icons::tick::TickIcon,
7 theming::component_themes::{
8 ButtonSegmentTheme,
9 ButtonSegmentThemePartial,
10 SegmentedButtonTheme,
11 SegmentedButtonThemePartial,
12 },
13};
14
15#[derive(Debug, Default, PartialEq, Clone, Copy)]
17pub enum ButtonSegmentStatus {
18 #[default]
20 Idle,
21 Hovering,
23}
24
25#[derive(Clone, PartialEq)]
51pub struct ButtonSegment {
52 pub(crate) theme: Option<ButtonSegmentThemePartial>,
53 children: Vec<Element>,
54 on_press: Option<EventHandler<Event<PressEventData>>>,
55 selected: bool,
56 enabled: bool,
57 key: DiffKey,
58}
59
60impl Default for ButtonSegment {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl ButtonSegment {
67 pub fn new() -> Self {
68 Self {
69 theme: None,
70 children: Vec::new(),
71 on_press: None,
72 selected: false,
73 enabled: true,
74 key: DiffKey::None,
75 }
76 }
77
78 pub fn selected(mut self, selected: impl Into<bool>) -> Self {
79 self.selected = selected.into();
80 self
81 }
82
83 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
84 self.enabled = enabled.into();
85 self
86 }
87
88 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
89 self.on_press = Some(on_press.into());
90 self
91 }
92}
93
94impl ChildrenExt for ButtonSegment {
95 fn get_children(&mut self) -> &mut Vec<Element> {
96 &mut self.children
97 }
98}
99
100impl KeyExt for ButtonSegment {
101 fn write_key(&mut self) -> &mut DiffKey {
102 &mut self.key
103 }
104}
105
106impl Render for ButtonSegment {
107 fn render(&self) -> impl IntoElement {
108 let theme = get_theme!(&self.theme, button_segment);
109 let mut status = use_state(|| ButtonSegmentStatus::Idle);
110 let focus = use_focus();
111 let focus_status = use_focus_status(focus);
112
113 let ButtonSegmentTheme {
114 background,
115 hover_background,
116 disabled_background,
117 selected_background,
118 focus_background,
119 padding,
120 selected_padding,
121 width,
122 height,
123 color,
124 selected_icon_fill,
125 } = theme;
126
127 let enabled = use_reactive(&self.enabled);
128 use_drop(move || {
129 if status() == ButtonSegmentStatus::Hovering && enabled() {
130 Cursor::set(CursorIcon::default());
131 }
132 });
133
134 let on_press = self.on_press.clone();
135 let on_press = move |e: Event<PressEventData>| {
136 focus.request_focus();
137 if let Some(on_press) = &on_press {
138 on_press.call(e);
139 }
140 };
141
142 let on_pointer_enter = move |_| {
143 status.set(ButtonSegmentStatus::Hovering);
144 if enabled() {
145 Cursor::set(CursorIcon::Pointer);
146 } else {
147 Cursor::set(CursorIcon::NotAllowed);
148 }
149 };
150
151 let on_pointer_leave = move |_| {
152 if status() == ButtonSegmentStatus::Hovering {
153 Cursor::set(CursorIcon::default());
154 status.set(ButtonSegmentStatus::Idle);
155 }
156 };
157
158 let background = match status() {
159 _ if !self.enabled => disabled_background,
160 _ if self.selected => selected_background,
161 ButtonSegmentStatus::Hovering => hover_background,
162 ButtonSegmentStatus::Idle => background,
163 };
164
165 let padding = if self.selected {
166 selected_padding
167 } else {
168 padding
169 };
170 let background = if *focus_status.read() == FocusStatus::Keyboard {
171 focus_background
172 } else {
173 background
174 };
175
176 rect()
177 .a11y_id(focus.a11y_id())
178 .a11y_focusable(self.enabled)
179 .a11y_role(AccessibilityRole::Button)
180 .maybe(self.enabled, |rect| rect.on_press(on_press))
181 .on_pointer_enter(on_pointer_enter)
182 .on_pointer_leave(on_pointer_leave)
183 .horizontal()
184 .width(width)
185 .height(height)
186 .padding(padding)
187 .overflow(Overflow::Clip)
188 .color(color.mul_if(!self.enabled, 0.9))
189 .background(background.mul_if(!self.enabled, 0.9))
190 .center()
191 .spacing(4.)
192 .maybe_child(self.selected.then(|| {
193 TickIcon::new()
194 .fill(selected_icon_fill)
195 .width(Size::px(12.))
196 .height(Size::px(12.))
197 }))
198 .children(self.children.clone())
199 }
200
201 fn render_key(&self) -> DiffKey {
202 self.key.clone().or(self.default_key())
203 }
204}
205
206#[cfg_attr(feature = "docs",
239 doc = embed_doc_image::embed_image!("segmented_button", "images/gallery_segmented_button.png")
240)]
241#[derive(Clone, PartialEq)]
242pub struct SegmentedButton {
243 pub(crate) theme: Option<SegmentedButtonThemePartial>,
244 children: Vec<Element>,
245 key: DiffKey,
246}
247
248impl Default for SegmentedButton {
249 fn default() -> Self {
250 Self::new()
251 }
252}
253
254impl SegmentedButton {
255 pub fn new() -> Self {
256 Self {
257 theme: None,
258 children: Vec::new(),
259 key: DiffKey::None,
260 }
261 }
262
263 pub fn theme(mut self, theme: SegmentedButtonThemePartial) -> Self {
264 self.theme = Some(theme);
265 self
266 }
267}
268
269impl ChildrenExt for SegmentedButton {
270 fn get_children(&mut self) -> &mut Vec<Element> {
271 &mut self.children
272 }
273}
274
275impl KeyExt for SegmentedButton {
276 fn write_key(&mut self) -> &mut DiffKey {
277 &mut self.key
278 }
279}
280
281impl Render for SegmentedButton {
282 fn render(&self) -> impl IntoElement {
283 let theme = get_theme!(&self.theme, segmented_button);
284
285 let SegmentedButtonTheme {
286 background,
287 border_fill,
288 corner_radius,
289 } = theme;
290
291 rect()
292 .overflow(Overflow::Clip)
293 .background(background)
294 .border(
295 Border::new()
296 .fill(border_fill)
297 .width(1.)
298 .alignment(BorderAlignment::Outer),
299 )
300 .corner_radius(corner_radius)
301 .horizontal()
302 .children(self.children.clone())
303 }
304
305 fn render_key(&self) -> DiffKey {
306 self.key.clone().or(self.default_key())
307 }
308}