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}
144
145impl Default for Button {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl ChildrenExt for Button {
152 fn get_children(&mut self) -> &mut Vec<Element> {
153 &mut self.elements
154 }
155}
156
157impl KeyExt for Button {
158 fn write_key(&mut self) -> &mut DiffKey {
159 &mut self.key
160 }
161}
162
163impl Button {
164 pub fn new() -> Self {
165 Self {
166 theme_colors: None,
167 theme_layout: None,
168 style_variant: ButtonStyleVariant::Normal,
169 layout_variant: ButtonLayoutVariant::Normal,
170 on_press: None,
171 on_secondary_down: None,
172 on_pointer_down: None,
173 elements: Vec::default(),
174 enabled: true,
175 focusable: true,
176 key: DiffKey::None,
177 }
178 }
179
180 pub fn get_layout_variant(&self) -> &ButtonLayoutVariant {
181 &self.layout_variant
182 }
183
184 pub fn get_theme_layout(&self) -> Option<&ButtonLayoutThemePartial> {
185 self.theme_layout.as_ref()
186 }
187
188 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
189 self.enabled = enabled.into();
190 self
191 }
192
193 pub fn focusable(mut self, focusable: impl Into<bool>) -> Self {
194 self.focusable = focusable.into();
195 self
196 }
197
198 pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
199 self.style_variant = style_variant.into();
200 self
201 }
202
203 pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
204 self.layout_variant = layout_variant.into();
205 self
206 }
207
208 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
209 self.on_press = Some(on_press.into());
210 self
211 }
212
213 pub fn on_secondary_down(
214 mut self,
215 on_secondary_down: impl Into<EventHandler<Event<PressEventData>>>,
216 ) -> Self {
217 self.on_secondary_down = Some(on_secondary_down.into());
218 self
219 }
220
221 pub fn on_pointer_down(
222 mut self,
223 on_pointer_down: impl Into<EventHandler<Event<PointerEventData>>>,
224 ) -> Self {
225 self.on_pointer_down = Some(on_pointer_down.into());
226 self
227 }
228
229 pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
230 self.theme_colors = Some(theme);
231 self
232 }
233
234 pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
235 self.theme_layout = Some(theme);
236 self
237 }
238
239 pub fn compact(self) -> Self {
241 self.layout_variant(ButtonLayoutVariant::Compact)
242 }
243
244 pub fn expanded(self) -> Self {
246 self.layout_variant(ButtonLayoutVariant::Expanded)
247 }
248
249 pub fn filled(self) -> Self {
251 self.style_variant(ButtonStyleVariant::Filled)
252 }
253
254 pub fn outline(self) -> Self {
256 self.style_variant(ButtonStyleVariant::Outline)
257 }
258
259 pub fn flat(self) -> Self {
261 self.style_variant(ButtonStyleVariant::Flat)
262 }
263}
264
265impl CornerRadiusExt for Button {
266 fn with_corner_radius(self, corner_radius: f32) -> Self {
267 self.corner_radius(corner_radius)
268 }
269}
270
271impl Component for Button {
272 fn render(&self) -> impl IntoElement {
273 let mut hovering = use_state(|| false);
274 let focus = use_focus();
275 let focus_status = use_focus_status(focus);
276
277 let enabled = use_reactive(&self.enabled);
278 use_drop(move || {
279 if hovering() {
280 Cursor::set(CursorIcon::default());
281 }
282 });
283
284 let theme_colors = match self.style_variant {
285 ButtonStyleVariant::Normal => {
286 get_theme!(&self.theme_colors, ButtonColorsThemePreference, "button")
287 }
288 ButtonStyleVariant::Outline => get_theme!(
289 &self.theme_colors,
290 ButtonColorsThemePreference,
291 "outline_button"
292 ),
293 ButtonStyleVariant::Filled => get_theme!(
294 &self.theme_colors,
295 ButtonColorsThemePreference,
296 "filled_button"
297 ),
298 ButtonStyleVariant::Flat => get_theme!(
299 &self.theme_colors,
300 ButtonColorsThemePreference,
301 "flat_button"
302 ),
303 };
304 let theme_layout = match self.layout_variant {
305 ButtonLayoutVariant::Normal => get_theme!(
306 &self.theme_layout,
307 ButtonLayoutThemePreference,
308 "button_layout"
309 ),
310 ButtonLayoutVariant::Compact => get_theme!(
311 &self.theme_layout,
312 ButtonLayoutThemePreference,
313 "compact_button_layout"
314 ),
315 ButtonLayoutVariant::Expanded => get_theme!(
316 &self.theme_layout,
317 ButtonLayoutThemePreference,
318 "expanded_button_layout"
319 ),
320 };
321
322 let border = if focus_status() == FocusStatus::Keyboard {
323 Border::new()
324 .fill(theme_colors.focus_border_fill)
325 .width(2.)
326 .alignment(BorderAlignment::Inner)
327 } else {
328 Border::new()
329 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
330 .width(1.)
331 .alignment(BorderAlignment::Inner)
332 };
333 let background = if enabled() && hovering() {
334 theme_colors.hover_background
335 } else {
336 theme_colors.background
337 };
338
339 rect()
340 .overflow(Overflow::Clip)
341 .a11y_id(focus.a11y_id())
342 .a11y_focusable(self.enabled && self.focusable)
343 .a11y_role(AccessibilityRole::Button)
344 .background(background.mul_if(!self.enabled, 0.9))
345 .border(border)
346 .padding(theme_layout.padding)
347 .corner_radius(theme_layout.corner_radius)
348 .width(theme_layout.width)
349 .height(theme_layout.height)
350 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
351 .center()
352 .maybe(self.enabled, |rect| {
353 rect.map(self.on_pointer_down.clone(), |rect, on_pointer_down| {
354 rect.on_pointer_down(move |e: Event<PointerEventData>| {
355 on_pointer_down.call(e);
356 })
357 })
358 .on_all_press({
359 let on_press = self.on_press.clone();
360 let on_secondary_down = self.on_secondary_down.clone();
361 move |e: Event<PressEventData>| {
362 focus.request_focus();
363 match e.data() {
364 PressEventData::Mouse(data) => match data.button {
365 Some(MouseButton::Left) => {
366 if let Some(handler) = &on_press {
367 handler.call(e);
368 }
369 }
370 Some(MouseButton::Right) => {
371 if let Some(handler) = &on_secondary_down {
372 handler.call(e);
373 }
374 }
375 _ => {}
376 },
377 PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
378 if let Some(handler) = &on_press {
379 handler.call(e);
380 }
381 }
382 }
383 }
384 })
385 .on_pointer_over(move |_| {
386 hovering.set(true);
387 })
388 .on_pointer_out(move |_| hovering.set_if_modified(false))
389 })
390 .on_pointer_enter(move |_| {
391 if enabled() {
392 Cursor::set(CursorIcon::Pointer);
393 } else {
394 Cursor::set(CursorIcon::NotAllowed);
395 }
396 })
397 .on_pointer_leave(move |_| {
398 Cursor::set(CursorIcon::default());
399 })
400 .children(self.elements.clone())
401 }
402
403 fn render_key(&self) -> DiffKey {
404 self.key.clone().or(self.default_key())
405 }
406}