1use freya_core::prelude::*;
2use torin::{
3 gaps::Gaps,
4 size::Size,
5};
6
7use crate::{
8 define_theme,
9 get_theme,
10};
11
12define_theme! {
13 for = Button;
14 theme_field = theme_layout;
15
16 %[component]
17 pub ButtonLayout {
18 %[fields]
19 margin: Gaps,
20 corner_radius: CornerRadius,
21 width: Size,
22 height: Size,
23 padding: Gaps,
24 }
25}
26
27define_theme! {
28 for = Button;
29 theme_field = theme_colors;
30
31 %[component]
32 pub ButtonColors {
33 %[fields]
34 background: Color,
35 hover_background: Color,
36 border_fill: Color,
37 focus_border_fill: Color,
38 color: Color,
39 }
40}
41
42#[derive(Clone, PartialEq)]
43pub enum ButtonStyleVariant {
44 Normal,
45 Filled,
46 Outline,
47 Flat,
48}
49
50#[derive(Clone, PartialEq)]
51pub enum ButtonLayoutVariant {
52 Normal,
53 Compact,
54 Expanded,
55}
56
57#[cfg_attr(feature = "docs",
125 doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
126 doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
127 doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
128 doc = embed_doc_image::embed_image!("flat_button", "images/gallery_flat_button.png"),
129)]
130#[derive(Clone, PartialEq)]
131pub struct Button {
132 pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
133 pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
134 elements: Vec<Element>,
135 on_press: Option<EventHandler<Event<PressEventData>>>,
136 on_secondary_down: Option<EventHandler<Event<PressEventData>>>,
137 on_pointer_down: Option<EventHandler<Event<PointerEventData>>>,
138 key: DiffKey,
139 style_variant: ButtonStyleVariant,
140 layout_variant: ButtonLayoutVariant,
141 enabled: bool,
142 focusable: bool,
143 cursor_icon: CursorIcon,
144}
145
146impl Default for Button {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl ChildrenExt for Button {
153 fn get_children(&mut self) -> &mut Vec<Element> {
154 &mut self.elements
155 }
156}
157
158impl KeyExt for Button {
159 fn write_key(&mut self) -> &mut DiffKey {
160 &mut self.key
161 }
162}
163
164impl Button {
165 pub fn new() -> Self {
166 Self {
167 theme_colors: None,
168 theme_layout: None,
169 style_variant: ButtonStyleVariant::Normal,
170 layout_variant: ButtonLayoutVariant::Normal,
171 on_press: None,
172 on_secondary_down: None,
173 on_pointer_down: None,
174 elements: Vec::default(),
175 enabled: true,
176 focusable: true,
177 cursor_icon: CursorIcon::default(),
178 key: DiffKey::None,
179 }
180 }
181
182 pub fn get_layout_variant(&self) -> &ButtonLayoutVariant {
183 &self.layout_variant
184 }
185
186 pub fn get_theme_layout(&self) -> Option<&ButtonLayoutThemePartial> {
187 self.theme_layout.as_ref()
188 }
189
190 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
191 self.enabled = enabled.into();
192 self
193 }
194
195 pub fn focusable(mut self, focusable: impl Into<bool>) -> Self {
196 self.focusable = focusable.into();
197 self
198 }
199
200 pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
201 self.style_variant = style_variant.into();
202 self
203 }
204
205 pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
206 self.layout_variant = layout_variant.into();
207 self
208 }
209
210 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
211 self.on_press = Some(on_press.into());
212 self
213 }
214
215 pub fn on_secondary_down(
216 mut self,
217 on_secondary_down: impl Into<EventHandler<Event<PressEventData>>>,
218 ) -> Self {
219 self.on_secondary_down = Some(on_secondary_down.into());
220 self
221 }
222
223 pub fn on_pointer_down(
224 mut self,
225 on_pointer_down: impl Into<EventHandler<Event<PointerEventData>>>,
226 ) -> Self {
227 self.on_pointer_down = Some(on_pointer_down.into());
228 self
229 }
230
231 pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
232 self.theme_colors = Some(theme);
233 self
234 }
235
236 pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
237 self.theme_layout = Some(theme);
238 self
239 }
240
241 pub fn compact(self) -> Self {
243 self.layout_variant(ButtonLayoutVariant::Compact)
244 }
245
246 pub fn expanded(self) -> Self {
248 self.layout_variant(ButtonLayoutVariant::Expanded)
249 }
250
251 pub fn filled(self) -> Self {
253 self.style_variant(ButtonStyleVariant::Filled)
254 }
255
256 pub fn outline(self) -> Self {
258 self.style_variant(ButtonStyleVariant::Outline)
259 }
260
261 pub fn flat(self) -> Self {
263 self.style_variant(ButtonStyleVariant::Flat)
264 }
265
266 pub fn cursor_icon(mut self, cursor_icon: impl Into<CursorIcon>) -> Self {
268 self.cursor_icon = cursor_icon.into();
269 self
270 }
271}
272
273impl CornerRadiusExt for Button {
274 fn with_corner_radius(self, corner_radius: f32) -> Self {
275 self.corner_radius(corner_radius)
276 }
277}
278
279impl Component for Button {
280 fn render(&self) -> impl IntoElement {
281 let mut hovering = use_state(|| false);
282 let a11y_id = use_a11y();
283 let focus = use_focus(a11y_id);
284
285 let enabled = use_reactive(&self.enabled);
286 let cursor_icon = self.cursor_icon;
287 use_drop(move || {
288 if hovering() {
289 Cursor::set(CursorIcon::default());
290 }
291 });
292
293 let theme_colors = match self.style_variant {
294 ButtonStyleVariant::Normal => {
295 get_theme!(&self.theme_colors, ButtonColorsThemePreference, "button")
296 }
297 ButtonStyleVariant::Outline => get_theme!(
298 &self.theme_colors,
299 ButtonColorsThemePreference,
300 "outline_button"
301 ),
302 ButtonStyleVariant::Filled => get_theme!(
303 &self.theme_colors,
304 ButtonColorsThemePreference,
305 "filled_button"
306 ),
307 ButtonStyleVariant::Flat => get_theme!(
308 &self.theme_colors,
309 ButtonColorsThemePreference,
310 "flat_button"
311 ),
312 };
313 let theme_layout = match self.layout_variant {
314 ButtonLayoutVariant::Normal => get_theme!(
315 &self.theme_layout,
316 ButtonLayoutThemePreference,
317 "button_layout"
318 ),
319 ButtonLayoutVariant::Compact => get_theme!(
320 &self.theme_layout,
321 ButtonLayoutThemePreference,
322 "compact_button_layout"
323 ),
324 ButtonLayoutVariant::Expanded => get_theme!(
325 &self.theme_layout,
326 ButtonLayoutThemePreference,
327 "expanded_button_layout"
328 ),
329 };
330
331 let border = if focus() == Focus::Keyboard {
332 Border::new()
333 .fill(theme_colors.focus_border_fill)
334 .width(2.)
335 .alignment(BorderAlignment::Inner)
336 } else {
337 Border::new()
338 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
339 .width(1.)
340 .alignment(BorderAlignment::Inner)
341 };
342 let background = if enabled() && hovering() {
343 theme_colors.hover_background
344 } else {
345 theme_colors.background
346 };
347
348 rect()
349 .overflow(Overflow::Clip)
350 .a11y_id(a11y_id)
351 .a11y_focusable(self.enabled && self.focusable)
352 .a11y_role(AccessibilityRole::Button)
353 .background(background.mul_if(!self.enabled, 0.9))
354 .border(border)
355 .padding(theme_layout.padding)
356 .corner_radius(theme_layout.corner_radius)
357 .width(theme_layout.width)
358 .height(theme_layout.height)
359 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
360 .center()
361 .maybe(self.enabled, |rect| {
362 rect.map(self.on_pointer_down.clone(), |rect, on_pointer_down| {
363 rect.on_pointer_down(move |e: Event<PointerEventData>| {
364 on_pointer_down.call(e);
365 })
366 })
367 .on_all_press({
368 let on_press = self.on_press.clone();
369 let on_secondary_down = self.on_secondary_down.clone();
370 move |e: Event<PressEventData>| {
371 a11y_id.request_focus();
372 match e.data() {
373 PressEventData::Mouse(data) => match data.button {
374 Some(MouseButton::Left) => {
375 if let Some(handler) = &on_press {
376 handler.call(e);
377 }
378 }
379 Some(MouseButton::Right) => {
380 if let Some(handler) = &on_secondary_down {
381 handler.call(e);
382 }
383 }
384 _ => {}
385 },
386 PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
387 if let Some(handler) = &on_press {
388 handler.call(e);
389 }
390 }
391 }
392 }
393 })
394 .on_pointer_over(move |_| {
395 hovering.set(true);
396 })
397 .on_pointer_out(move |_| hovering.set_if_modified(false))
398 })
399 .on_pointer_enter(move |_| {
400 if enabled() {
401 Cursor::set(cursor_icon);
402 } else {
403 Cursor::set(CursorIcon::NotAllowed);
404 }
405 })
406 .on_pointer_leave(move |_| {
407 Cursor::set(CursorIcon::default());
408 })
409 .children(self.elements.clone())
410 }
411
412 fn render_key(&self) -> DiffKey {
413 self.key.clone().or(self.default_key())
414 }
415}