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
287 let theme_colors = match self.style_variant {
288 ButtonStyleVariant::Normal => {
289 get_theme!(&self.theme_colors, ButtonColorsThemePreference, "button")
290 }
291 ButtonStyleVariant::Outline => get_theme!(
292 &self.theme_colors,
293 ButtonColorsThemePreference,
294 "outline_button"
295 ),
296 ButtonStyleVariant::Filled => get_theme!(
297 &self.theme_colors,
298 ButtonColorsThemePreference,
299 "filled_button"
300 ),
301 ButtonStyleVariant::Flat => get_theme!(
302 &self.theme_colors,
303 ButtonColorsThemePreference,
304 "flat_button"
305 ),
306 };
307 let theme_layout = match self.layout_variant {
308 ButtonLayoutVariant::Normal => get_theme!(
309 &self.theme_layout,
310 ButtonLayoutThemePreference,
311 "button_layout"
312 ),
313 ButtonLayoutVariant::Compact => get_theme!(
314 &self.theme_layout,
315 ButtonLayoutThemePreference,
316 "compact_button_layout"
317 ),
318 ButtonLayoutVariant::Expanded => get_theme!(
319 &self.theme_layout,
320 ButtonLayoutThemePreference,
321 "expanded_button_layout"
322 ),
323 };
324
325 let border = if focus() == Focus::Keyboard {
326 Border::new()
327 .fill(theme_colors.focus_border_fill)
328 .width(2.)
329 .alignment(BorderAlignment::Inner)
330 } else {
331 Border::new()
332 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
333 .width(1.)
334 .alignment(BorderAlignment::Inner)
335 };
336 let background = if enabled() && hovering() {
337 theme_colors.hover_background
338 } else {
339 theme_colors.background
340 };
341
342 rect()
343 .overflow(Overflow::Clip)
344 .a11y_id(a11y_id)
345 .a11y_focusable(self.enabled && self.focusable)
346 .a11y_role(AccessibilityRole::Button)
347 .background(background.mul_if(!self.enabled, 0.9))
348 .border(border)
349 .padding(theme_layout.padding)
350 .corner_radius(theme_layout.corner_radius)
351 .width(theme_layout.width)
352 .height(theme_layout.height)
353 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
354 .center()
355 .maybe(self.enabled, |rect| {
356 rect.map(self.on_pointer_down.clone(), |rect, on_pointer_down| {
357 rect.on_pointer_down(move |e: Event<PointerEventData>| {
358 on_pointer_down.call(e);
359 })
360 })
361 .map(self.on_secondary_down.clone(), |el, on_secondary_down| {
362 el.on_secondary_down(move |e: Event<PressEventData>| {
363 on_secondary_down.call(e);
364 })
365 })
366 .on_all_press({
367 let on_press = self.on_press.clone();
368 move |e: Event<PressEventData>| {
369 a11y_id.request_focus();
370 match e.data() {
371 PressEventData::Mouse(data) => {
372 if data.button == Some(MouseButton::Left)
373 && let Some(handler) = &on_press
374 {
375 handler.call(e);
376 }
377 }
378 PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
379 if let Some(handler) = &on_press {
380 handler.call(e);
381 }
382 }
383 }
384 }
385 })
386 .on_pointer_over(move |_| {
387 hovering.set(true);
388 })
389 .on_pointer_out(move |_| hovering.set_if_modified(false))
390 })
391 .cursor(if self.enabled {
392 self.cursor_icon
393 } else {
394 CursorIcon::NotAllowed
395 })
396 .children(self.elements.clone())
397 }
398
399 fn render_key(&self) -> DiffKey {
400 self.key.clone().or(self.default_key())
401 }
402}