Skip to main content

azul_layout/widgets/
button.rs

1//! Button widget with Bootstrap-inspired type-based styling (`ButtonType`)
2//! and platform-adaptive native appearance (Windows, Linux/GTK, macOS).
3
4use std::vec::Vec;
5
6use azul_core::{
7    callbacks::{CoreCallbackData, Update},
8    dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, NodeType, TabIndex},
9    refany::RefAny,
10    resources::{ImageRef, OptionImageRef},
11};
12use azul_css::{
13    dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
14    props::{
15        basic::{
16            color::{ColorU, ColorOrSystem, SystemColorRef},
17            font::{StyleFontFamily, StyleFontFamilyVec},
18            *,
19        },
20        layout::*,
21        property::{CssProperty, *},
22        style::*,
23    },
24    system::SystemFontType,
25    *,
26};
27
28use crate::callbacks::{Callback, CallbackInfo};
29
30/// The semantic type/role of a button.
31/// 
32/// Each type has distinct styling to indicate its purpose to the user.
33/// Colors are based on Bootstrap's button variants for familiarity.
34#[repr(C)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
36pub enum ButtonType {
37    /// Default button style - neutral/gray appearance
38    #[default]
39    Default,
40    /// Primary action button - blue, uses system accent color on macOS
41    Primary,
42    /// Secondary button - gray, less prominent than primary
43    Secondary,
44    /// Success/confirmation button - green with white text
45    Success,
46    /// Danger/destructive button - red with white text
47    Danger,
48    /// Warning button - yellow with BLACK text
49    Warning,
50    /// Informational button - teal/cyan with white text
51    Info,
52    /// Link-style button - appears as a hyperlink, no background
53    Link,
54}
55
56impl ButtonType {
57    /// Get the CSS class name for this button type
58    pub fn class_name(&self) -> &'static str {
59        match self {
60            ButtonType::Default => "__azul-btn-default",
61            ButtonType::Primary => "__azul-btn-primary",
62            ButtonType::Secondary => "__azul-btn-secondary",
63            ButtonType::Success => "__azul-btn-success",
64            ButtonType::Danger => "__azul-btn-danger",
65            ButtonType::Warning => "__azul-btn-warning",
66            ButtonType::Info => "__azul-btn-info",
67            ButtonType::Link => "__azul-btn-link",
68        }
69    }
70}
71
72#[repr(C)]
73#[derive(Debug, Clone, PartialEq, PartialOrd)]
74pub struct Button {
75    /// Content (image or text) of this button, centered by default
76    pub label: AzString,
77    /// Optional image that is displayed next to the label
78    pub image: OptionImageRef,
79    /// The semantic type of this button (Primary, Success, Danger, etc.)
80    pub button_type: ButtonType,
81    /// Style for this button container
82    pub container_style: CssPropertyWithConditionsVec,
83    /// Style of the label
84    pub label_style: CssPropertyWithConditionsVec,
85    /// Style of the image
86    pub image_style: CssPropertyWithConditionsVec,
87    /// Optional: Function to call when the button is clicked
88    pub on_click: OptionButtonOnClick,
89}
90
91pub type ButtonOnClickCallbackType = extern "C" fn(RefAny, CallbackInfo) -> Update;
92impl_widget_callback!(
93    ButtonOnClick,
94    OptionButtonOnClick,
95    ButtonOnClickCallback,
96    ButtonOnClickCallbackType
97);
98
99// Host-invoker plumbing for managed-FFI bindings — see core/src/host_invoker.rs.
100azul_core::impl_managed_callback! {
101    wrapper:        ButtonOnClickCallback,
102    info_ty:        CallbackInfo,
103    return_ty:      Update,
104    default_ret:    Update::DoNothing,
105    invoker_static: BUTTON_ON_CLICK_INVOKER,
106    invoker_ty:     AzButtonOnClickCallbackInvoker,
107    thunk_fn:       az_button_on_click_callback_thunk,
108    setter_fn:      AzApp_setButtonOnClickCallbackInvoker,
109    from_handle_fn: AzButtonOnClickCallback_createFromHostHandle,
110}
111
112const SANS_SERIF_STR: &str = "system:ui";
113const SANS_SERIF: AzString = AzString::from_const_str(SANS_SERIF_STR);
114const SANS_SERIF_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SANS_SERIF)];
115const SANS_SERIF_FAMILY: StyleFontFamilyVec =
116    StyleFontFamilyVec::from_const_slice(SANS_SERIF_FAMILIES);
117
118// macOS: Helvetica with sans-serif fallback
119const HELVETICA_STR: &str = "Helvetica Neue";
120const HELVETICA: AzString = AzString::from_const_str(HELVETICA_STR);
121const MAC_FONT_FAMILIES: &[StyleFontFamily] = &[
122    StyleFontFamily::System(HELVETICA),
123    StyleFontFamily::System(SANS_SERIF),
124];
125const MAC_FONT_FAMILY: StyleFontFamilyVec = StyleFontFamilyVec::from_const_slice(MAC_FONT_FAMILIES);
126
127const RGB_172: ColorU = ColorU {
128    r: 172,
129    g: 172,
130    b: 172,
131    a: 255,
132};
133const RGB_239: ColorU = ColorU {
134    r: 239,
135    g: 239,
136    b: 239,
137    a: 255,
138};
139const RGB_229: ColorU = ColorU {
140    r: 229,
141    g: 229,
142    b: 229,
143    a: 255,
144};
145
146const WINDOWS_HOVER_START: ColorU = ColorU {
147    r: 234,
148    g: 243,
149    b: 252,
150    a: 255,
151};
152const WINDOWS_HOVER_END: ColorU = ColorU {
153    r: 126,
154    g: 180,
155    b: 234,
156    a: 255,
157};
158const WINDOWS_HOVER_BORDER: ColorU = ColorU {
159    r: 126,
160    g: 180,
161    b: 234,
162    a: 255,
163};
164
165const WINDOWS_ACTIVE_START: ColorU = ColorU {
166    r: 217,
167    g: 235,
168    b: 252,
169    a: 255,
170};
171const WINDOWS_ACTIVE_END: ColorU = ColorU {
172    r: 86,
173    g: 157,
174    b: 229,
175    a: 255,
176};
177const WINDOWS_ACTIVE_BORDER: ColorU = ColorU {
178    r: 86,
179    g: 157,
180    b: 229,
181    a: 255,
182};
183
184const WINDOWS_FOCUS_BORDER: ColorU = ColorU {
185    r: 51,
186    g: 153,
187    b: 255,
188    a: 255,
189};
190
191const BUTTON_NORMAL_BACKGROUND_COLOR_STOPS: &[NormalizedLinearColorStop] = &[
192    NormalizedLinearColorStop {
193        offset: PercentageValue::const_new(0),
194        color: ColorOrSystem::color(RGB_239),
195    },
196    NormalizedLinearColorStop {
197        offset: PercentageValue::const_new(100),
198        color: ColorOrSystem::color(RGB_229),
199    },
200];
201const BUTTON_NORMAL_BACKGROUND: &[StyleBackgroundContent] =
202    &[StyleBackgroundContent::Color(RGB_229)];
203
204const BUTTON_HOVER_BACKGROUND_WINDOWS_COLOR_STOPS: &[NormalizedLinearColorStop] = &[
205    NormalizedLinearColorStop {
206        offset: PercentageValue::const_new(0),
207        color: ColorOrSystem::color(WINDOWS_HOVER_START),
208    },
209    NormalizedLinearColorStop {
210        offset: PercentageValue::const_new(100),
211        color: ColorOrSystem::color(WINDOWS_HOVER_END),
212    },
213];
214const BUTTON_HOVER_BACKGROUND_WINDOWS: &[StyleBackgroundContent] =
215    &[StyleBackgroundContent::LinearGradient(LinearGradient {
216        direction: Direction::FromTo(DirectionCorners {
217            dir_from: DirectionCorner::Top,
218            dir_to: DirectionCorner::Bottom,
219        }),
220        extend_mode: ExtendMode::Clamp,
221        stops: NormalizedLinearColorStopVec::from_const_slice(
222            BUTTON_HOVER_BACKGROUND_WINDOWS_COLOR_STOPS,
223        ),
224    })];
225const BUTTON_ACTIVE_BACKGROUND_WINDOWS_COLOR_STOPS: &[NormalizedLinearColorStop] = &[
226    NormalizedLinearColorStop {
227        offset: PercentageValue::const_new(0),
228        color: ColorOrSystem::color(WINDOWS_ACTIVE_START),
229    },
230    NormalizedLinearColorStop {
231        offset: PercentageValue::const_new(100),
232        color: ColorOrSystem::color(WINDOWS_ACTIVE_END),
233    },
234];
235const BUTTON_ACTIVE_BACKGROUND_WINDOWS: &[StyleBackgroundContent] =
236    &[StyleBackgroundContent::LinearGradient(LinearGradient {
237        direction: Direction::FromTo(DirectionCorners {
238            dir_from: DirectionCorner::Top,
239            dir_to: DirectionCorner::Bottom,
240        }),
241        extend_mode: ExtendMode::Clamp,
242        stops: NormalizedLinearColorStopVec::from_const_slice(
243            BUTTON_ACTIVE_BACKGROUND_WINDOWS_COLOR_STOPS,
244        ),
245    })];
246
247static BUTTON_CONTAINER_WINDOWS: &[CssPropertyWithConditions] = &[
248    // Use InlineFlex so flex properties work correctly
249    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
250    CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)),
251    CssPropertyWithConditions::simple(CssProperty::const_background_content(
252        StyleBackgroundContentVec::from_const_slice(BUTTON_NORMAL_BACKGROUND),
253    )),
254    CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
255        LayoutFlexDirection::Row,
256    )),
257    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
258        LayoutJustifyContent::Center,
259    )),
260    CssPropertyWithConditions::simple(CssProperty::const_align_items(
261        LayoutAlignItems::Center,
262    )),
263    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
264    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
265    //     border: 1px solid rgb(172, 172, 172);
266    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
267        LayoutBorderTopWidth::const_px(1),
268    )),
269    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
270        LayoutBorderBottomWidth::const_px(1),
271    )),
272    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
273        LayoutBorderLeftWidth::const_px(1),
274    )),
275    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
276        LayoutBorderRightWidth::const_px(1),
277    )),
278    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
279        inner: BorderStyle::Solid,
280    })),
281    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
282        StyleBorderBottomStyle {
283            inner: BorderStyle::Solid,
284        },
285    )),
286    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
287        inner: BorderStyle::Solid,
288    })),
289    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
290        StyleBorderRightStyle {
291            inner: BorderStyle::Solid,
292        },
293    )),
294    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
295        inner: RGB_172,
296    })),
297    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
298        StyleBorderBottomColor { inner: RGB_172 },
299    )),
300    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
301        inner: RGB_172,
302    })),
303    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
304        StyleBorderRightColor { inner: RGB_172 },
305    )),
306    // padding: 5px
307    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
308        LayoutPaddingLeft::const_px(5),
309    )),
310    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
311        LayoutPaddingRight::const_px(5),
312    )),
313    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
314        3,
315    ))),
316    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
317        LayoutPaddingBottom::const_px(3),
318    )),
319    CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
320        StyleBackgroundContentVec::from_const_slice(BUTTON_HOVER_BACKGROUND_WINDOWS),
321    )),
322    CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor {
323        inner: WINDOWS_HOVER_BORDER,
324    })),
325    CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(
326        StyleBorderBottomColor {
327            inner: WINDOWS_HOVER_BORDER,
328        },
329    )),
330    CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(
331        StyleBorderLeftColor {
332            inner: WINDOWS_HOVER_BORDER,
333        },
334    )),
335    CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(
336        StyleBorderRightColor {
337            inner: WINDOWS_HOVER_BORDER,
338        },
339    )),
340    CssPropertyWithConditions::on_active(CssProperty::const_background_content(
341        StyleBackgroundContentVec::from_const_slice(BUTTON_ACTIVE_BACKGROUND_WINDOWS),
342    )),
343    CssPropertyWithConditions::on_active(CssProperty::const_border_top_color(
344        StyleBorderTopColor {
345            inner: WINDOWS_ACTIVE_BORDER,
346        },
347    )),
348    CssPropertyWithConditions::on_active(CssProperty::const_border_bottom_color(
349        StyleBorderBottomColor {
350            inner: WINDOWS_ACTIVE_BORDER,
351        },
352    )),
353    CssPropertyWithConditions::on_active(CssProperty::const_border_left_color(
354        StyleBorderLeftColor {
355            inner: WINDOWS_ACTIVE_BORDER,
356        },
357    )),
358    CssPropertyWithConditions::on_active(CssProperty::const_border_right_color(
359        StyleBorderRightColor {
360            inner: WINDOWS_ACTIVE_BORDER,
361        },
362    )),
363    CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
364        inner: WINDOWS_FOCUS_BORDER,
365    })),
366    CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
367        StyleBorderBottomColor {
368            inner: WINDOWS_FOCUS_BORDER,
369        },
370    )),
371    CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(
372        StyleBorderLeftColor {
373            inner: WINDOWS_FOCUS_BORDER,
374        },
375    )),
376    CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
377        StyleBorderRightColor {
378            inner: WINDOWS_FOCUS_BORDER,
379        },
380    )),
381];
382
383// Linux button background gradients
384const LINUX_NORMAL_GRADIENT_STOPS: &[NormalizedLinearColorStop] = &[
385    NormalizedLinearColorStop {
386        offset: PercentageValue::const_new(0),
387        color: ColorOrSystem::color(ColorU {
388            r: 252,
389            g: 252,
390            b: 252,
391            a: 255,
392        }),
393    },
394    NormalizedLinearColorStop {
395        offset: PercentageValue::const_new(100),
396        color: ColorOrSystem::color(ColorU {
397            r: 239,
398            g: 239,
399            b: 239,
400            a: 255,
401        }),
402    },
403];
404const LINUX_NORMAL_BACKGROUND: &[StyleBackgroundContent] =
405    &[StyleBackgroundContent::LinearGradient(LinearGradient {
406        direction: Direction::FromTo(DirectionCorners {
407            dir_from: DirectionCorner::Top,
408            dir_to: DirectionCorner::Bottom,
409        }),
410        extend_mode: ExtendMode::Clamp,
411        stops: NormalizedLinearColorStopVec::from_const_slice(LINUX_NORMAL_GRADIENT_STOPS),
412    })];
413
414const LINUX_HOVER_GRADIENT_STOPS: &[NormalizedLinearColorStop] = &[
415    NormalizedLinearColorStop {
416        offset: PercentageValue::const_new(0),
417        color: ColorOrSystem::color(ColorU {
418            r: 255,
419            g: 255,
420            b: 255,
421            a: 255,
422        }),
423    },
424    NormalizedLinearColorStop {
425        offset: PercentageValue::const_new(100),
426        color: ColorOrSystem::color(ColorU {
427            r: 245,
428            g: 245,
429            b: 245,
430            a: 255,
431        }),
432    },
433];
434const LINUX_HOVER_BACKGROUND: &[StyleBackgroundContent] =
435    &[StyleBackgroundContent::LinearGradient(LinearGradient {
436        direction: Direction::FromTo(DirectionCorners {
437            dir_from: DirectionCorner::Top,
438            dir_to: DirectionCorner::Bottom,
439        }),
440        extend_mode: ExtendMode::Clamp,
441        stops: NormalizedLinearColorStopVec::from_const_slice(LINUX_HOVER_GRADIENT_STOPS),
442    })];
443
444const LINUX_ACTIVE_GRADIENT_STOPS: &[NormalizedLinearColorStop] = &[
445    NormalizedLinearColorStop {
446        offset: PercentageValue::const_new(0),
447        color: ColorOrSystem::color(ColorU {
448            r: 220,
449            g: 220,
450            b: 220,
451            a: 255,
452        }),
453    },
454    NormalizedLinearColorStop {
455        offset: PercentageValue::const_new(100),
456        color: ColorOrSystem::color(ColorU {
457            r: 200,
458            g: 200,
459            b: 200,
460            a: 255,
461        }),
462    },
463];
464const LINUX_ACTIVE_BACKGROUND: &[StyleBackgroundContent] =
465    &[StyleBackgroundContent::LinearGradient(LinearGradient {
466        direction: Direction::FromTo(DirectionCorners {
467            dir_from: DirectionCorner::Top,
468            dir_to: DirectionCorner::Bottom,
469        }),
470        extend_mode: ExtendMode::Clamp,
471        stops: NormalizedLinearColorStopVec::from_const_slice(LINUX_ACTIVE_GRADIENT_STOPS),
472    })];
473
474const LINUX_BORDER_COLOR: ColorU = ColorU {
475    r: 183,
476    g: 183,
477    b: 183,
478    a: 255,
479};
480
481static BUTTON_CONTAINER_LINUX: &[CssPropertyWithConditions] = &[
482    // Linux/GTK-style button styling - use InlineFlex so flex properties work
483    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
484    CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)),
485    CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
486        LayoutFlexDirection::Row,
487    )),
488    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
489        LayoutJustifyContent::Center,
490    )),
491    CssPropertyWithConditions::simple(CssProperty::const_align_items(
492        LayoutAlignItems::Center,
493    )),
494    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
495    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
496    // background: linear-gradient(#fcfcfc, #efefef)
497    CssPropertyWithConditions::simple(CssProperty::const_background_content(
498        StyleBackgroundContentVec::from_const_slice(LINUX_NORMAL_BACKGROUND),
499    )),
500    // border: 1px solid #b7b7b7
501    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
502        LayoutBorderTopWidth::const_px(1),
503    )),
504    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
505        LayoutBorderBottomWidth::const_px(1),
506    )),
507    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
508        LayoutBorderLeftWidth::const_px(1),
509    )),
510    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
511        LayoutBorderRightWidth::const_px(1),
512    )),
513    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
514        inner: BorderStyle::Solid,
515    })),
516    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
517        StyleBorderBottomStyle {
518            inner: BorderStyle::Solid,
519        },
520    )),
521    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
522        inner: BorderStyle::Solid,
523    })),
524    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
525        StyleBorderRightStyle {
526            inner: BorderStyle::Solid,
527        },
528    )),
529    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
530        inner: LINUX_BORDER_COLOR,
531    })),
532    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
533        StyleBorderBottomColor {
534            inner: LINUX_BORDER_COLOR,
535        },
536    )),
537    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
538        inner: LINUX_BORDER_COLOR,
539    })),
540    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
541        StyleBorderRightColor {
542            inner: LINUX_BORDER_COLOR,
543        },
544    )),
545    // border-radius: 4px
546    CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
547        StyleBorderTopLeftRadius::const_px(4),
548    )),
549    CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
550        StyleBorderTopRightRadius::const_px(4),
551    )),
552    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
553        StyleBorderBottomLeftRadius::const_px(4),
554    )),
555    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
556        StyleBorderBottomRightRadius::const_px(4),
557    )),
558    // padding: 5px 10px
559    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
560        5,
561    ))),
562    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
563        LayoutPaddingBottom::const_px(5),
564    )),
565    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
566        LayoutPaddingLeft::const_px(10),
567    )),
568    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
569        LayoutPaddingRight::const_px(10),
570    )),
571    // Hover state
572    CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
573        StyleBackgroundContentVec::from_const_slice(LINUX_HOVER_BACKGROUND),
574    )),
575    // Active state
576    CssPropertyWithConditions::on_active(CssProperty::const_background_content(
577        StyleBackgroundContentVec::from_const_slice(LINUX_ACTIVE_BACKGROUND),
578    )),
579];
580
581// macOS Big Sur+ native button styling
582// Normal state: light gray background with subtle shadow
583const MAC_NORMAL_BACKGROUND: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(ColorU {
584    r: 255,
585    g: 255,
586    b: 255,
587    a: 255,
588})];
589
590// Hover state: slightly brighter
591const MAC_HOVER_BACKGROUND: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(ColorU {
592    r: 250,
593    g: 250,
594    b: 250,
595    a: 255,
596})];
597
598// Active/pressed state: darker gray
599const MAC_ACTIVE_BACKGROUND: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(ColorU {
600    r: 220,
601    g: 220,
602    b: 220,
603    a: 255,
604})];
605
606// macOS uses a subtle gray border
607const MAC_BORDER_COLOR: ColorU = ColorU {
608    r: 200,
609    g: 200,
610    b: 200,
611    a: 255,
612};
613
614// macOS box shadow for depth: 0 1px 1px rgba(0,0,0,0.06)
615const MAC_BOX_SHADOW: &[StyleBoxShadow] = &[StyleBoxShadow {
616    offset_x: PixelValueNoPercent { inner: PixelValue::const_px(0) },
617    offset_y: PixelValueNoPercent { inner: PixelValue::const_px(1) },
618    color: ColorU { r: 0, g: 0, b: 0, a: 15 },
619    blur_radius: PixelValueNoPercent { inner: PixelValue::const_px(1) },
620    spread_radius: PixelValueNoPercent { inner: PixelValue::const_px(0) },
621    clip_mode: BoxShadowClipMode::Outset,
622}];
623
624static BUTTON_CONTAINER_MAC: &[CssPropertyWithConditions] = &[
625    // macOS native button styling - use InlineFlex so flex properties work
626    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
627    CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)),
628    CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
629        LayoutFlexDirection::Row,
630    )),
631    CssPropertyWithConditions::simple(CssProperty::const_justify_content(
632        LayoutJustifyContent::Center,
633    )),
634    CssPropertyWithConditions::simple(CssProperty::const_align_items(
635        LayoutAlignItems::Center,
636    )),
637    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
638    CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
639    // background: linear-gradient(#fcfcfc, #efefef)
640    CssPropertyWithConditions::simple(CssProperty::const_background_content(
641        StyleBackgroundContentVec::from_const_slice(MAC_NORMAL_BACKGROUND),
642    )),
643    // border: 1px solid #b7b7b7
644    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
645        LayoutBorderTopWidth::const_px(1),
646    )),
647    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
648        LayoutBorderBottomWidth::const_px(1),
649    )),
650    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
651        LayoutBorderLeftWidth::const_px(1),
652    )),
653    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
654        LayoutBorderRightWidth::const_px(1),
655    )),
656    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
657        inner: BorderStyle::Solid,
658    })),
659    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
660        StyleBorderBottomStyle {
661            inner: BorderStyle::Solid,
662        },
663    )),
664    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
665        inner: BorderStyle::Solid,
666    })),
667    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
668        StyleBorderRightStyle {
669            inner: BorderStyle::Solid,
670        },
671    )),
672    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
673        inner: MAC_BORDER_COLOR,
674    })),
675    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
676        StyleBorderBottomColor {
677            inner: MAC_BORDER_COLOR,
678        },
679    )),
680    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
681        inner: MAC_BORDER_COLOR,
682    })),
683    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
684        StyleBorderRightColor {
685            inner: MAC_BORDER_COLOR,
686        },
687    )),
688    // border-radius: 4px
689    CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
690        StyleBorderTopLeftRadius::const_px(4),
691    )),
692    CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
693        StyleBorderTopRightRadius::const_px(4),
694    )),
695    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
696        StyleBorderBottomLeftRadius::const_px(4),
697    )),
698    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
699        StyleBorderBottomRightRadius::const_px(4),
700    )),
701    // padding: 5px 10px
702    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
703        5,
704    ))),
705    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
706        LayoutPaddingBottom::const_px(5),
707    )),
708    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
709        LayoutPaddingLeft::const_px(10),
710    )),
711    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
712        LayoutPaddingRight::const_px(10),
713    )),
714    // Hover state
715    CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
716        StyleBackgroundContentVec::from_const_slice(MAC_HOVER_BACKGROUND),
717    )),
718    // Active state
719    CssPropertyWithConditions::on_active(CssProperty::const_background_content(
720        StyleBackgroundContentVec::from_const_slice(MAC_ACTIVE_BACKGROUND),
721    )),
722];
723
724static BUTTON_CONTAINER_OTHER: &[CssPropertyWithConditions] = &[];
725
726static BUTTON_LABEL_WINDOWS: &[CssPropertyWithConditions] = &[
727    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(11))),
728    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Center)),
729    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
730        inner: ColorU::BLACK,
731    })),
732    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
733];
734
735static BUTTON_LABEL_LINUX: &[CssPropertyWithConditions] = &[
736    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
737    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Center)),
738    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
739        inner: ColorU {
740            r: 76,
741            g: 76,
742            b: 76,
743            a: 255,
744        },
745    })),
746    CssPropertyWithConditions::simple(CssProperty::const_font_family(SANS_SERIF_FAMILY)),
747];
748
749static BUTTON_LABEL_MAC: &[CssPropertyWithConditions] = &[
750    CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
751    CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Center)),
752    CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
753        inner: ColorU {
754            r: 76,
755            g: 76,
756            b: 76,
757            a: 255,
758        },
759    })),
760    CssPropertyWithConditions::simple(CssProperty::const_font_family(MAC_FONT_FAMILY)),
761];
762
763static BUTTON_LABEL_OTHER: &[CssPropertyWithConditions] = &[];
764
765// ============================================================
766// ButtonType-specific styling
767// ============================================================
768
769/// Get the background color for a button type
770fn get_button_colors(button_type: ButtonType) -> (ColorU, ColorU, ColorU) {
771    // Returns (normal, hover, active) colors
772    match button_type {
773        ButtonType::Default => (
774            ColorU::rgb(248, 249, 250), // Light gray
775            ColorU::rgb(233, 236, 239), // Darker gray on hover
776            ColorU::rgb(218, 222, 226), // Even darker on active
777        ),
778        ButtonType::Primary => (
779            ColorU::bootstrap_primary(),
780            ColorU::bootstrap_primary_hover(),
781            ColorU::bootstrap_primary_active(),
782        ),
783        ButtonType::Secondary => (
784            ColorU::bootstrap_secondary(),
785            ColorU::bootstrap_secondary_hover(),
786            ColorU::bootstrap_secondary_active(),
787        ),
788        ButtonType::Success => (
789            ColorU::bootstrap_success(),
790            ColorU::bootstrap_success_hover(),
791            ColorU::bootstrap_success_active(),
792        ),
793        ButtonType::Danger => (
794            ColorU::bootstrap_danger(),
795            ColorU::bootstrap_danger_hover(),
796            ColorU::bootstrap_danger_active(),
797        ),
798        ButtonType::Warning => (
799            ColorU::bootstrap_warning(),
800            ColorU::bootstrap_warning_hover(),
801            ColorU::bootstrap_warning_active(),
802        ),
803        ButtonType::Info => (
804            ColorU::bootstrap_info(),
805            ColorU::bootstrap_info_hover(),
806            ColorU::bootstrap_info_active(),
807        ),
808        ButtonType::Link => (
809            ColorU::TRANSPARENT,
810            ColorU::TRANSPARENT,
811            ColorU::TRANSPARENT,
812        ),
813    }
814}
815
816/// Get the text color for a button type
817fn get_button_text_color(button_type: ButtonType) -> ColorU {
818    match button_type {
819        ButtonType::Default => ColorU::rgb(33, 37, 41),   // Dark text
820        ButtonType::Warning => ColorU::BLACK,             // Black text on yellow
821        ButtonType::Link => ColorU::bootstrap_link(),     // Blue link color
822        _ => ColorU::WHITE,                               // White text on colored buttons
823    }
824}
825
826/// Build container style properties for a button type
827fn build_button_container_style(button_type: ButtonType) -> Vec<CssPropertyWithConditions> {
828    let (bg_normal, bg_hover, bg_active) = get_button_colors(button_type);
829    let text_color = get_button_text_color(button_type);
830    
831    // Focus outline uses system accent color
832    let focus_outline_color = ColorU::bootstrap_primary();
833    
834    let mut props = Vec::with_capacity(40);
835    
836    // Basic layout - use InlineFlex so flex properties (justify-content, align-items) work
837    props.push(CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)));
838    props.push(CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)));
839    props.push(CssPropertyWithConditions::simple(CssProperty::const_justify_content(LayoutJustifyContent::Center)));
840    props.push(CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)));
841    // Prevent stretching when inside a flex column container
842    props.push(CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)));
843    props.push(CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)));
844    props.push(CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))));
845    
846    // Text color
847    props.push(CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor { inner: text_color })));
848    
849    // Padding (Bootstrap-like)
850    props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(6))));
851    props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(LayoutPaddingBottom::const_px(6))));
852    props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(12))));
853    props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(12))));
854    
855    // Border radius
856    props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(StyleBorderTopLeftRadius::const_px(4))));
857    props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(StyleBorderTopRightRadius::const_px(4))));
858    props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(StyleBorderBottomLeftRadius::const_px(4))));
859    props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(StyleBorderBottomRightRadius::const_px(4))));
860    
861    if button_type == ButtonType::Link {
862        // Link buttons have no background or border
863        props.push(CssPropertyWithConditions::simple(CssProperty::const_background_content(
864            StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(ColorU::TRANSPARENT)]),
865        )));
866        
867        // Underline on hover - use TextDecoration::Underline variant
868        props.push(CssPropertyWithConditions::on_hover(CssProperty::TextDecoration(StyleTextDecoration::Underline.into())));
869    } else {
870        // Normal background
871        props.push(CssPropertyWithConditions::simple(CssProperty::const_background_content(
872            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_normal)]),
873        )));
874        
875        // Border (subtle for Default, transparent for others to maintain size)
876        let border_color = if button_type == ButtonType::Default {
877            ColorU::rgb(206, 212, 218)
878        } else {
879            bg_normal
880        };
881        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_width(LayoutBorderTopWidth::const_px(1))));
882        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))));
883        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_width(LayoutBorderLeftWidth::const_px(1))));
884        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_width(LayoutBorderRightWidth::const_px(1))));
885        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle { inner: BorderStyle::Solid })));
886        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })));
887        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle { inner: BorderStyle::Solid })));
888        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_style(StyleBorderRightStyle { inner: BorderStyle::Solid })));
889        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor { inner: border_color })));
890        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: border_color })));
891        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: border_color })));
892        props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_color(StyleBorderRightColor { inner: border_color })));
893        
894        // Hover state
895        props.push(CssPropertyWithConditions::on_hover(CssProperty::BackgroundContent(
896            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_hover)]).into(),
897        )));
898        if button_type == ButtonType::Default {
899            let hover_border = ColorU::rgb(173, 181, 189);
900            props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderTopColor(StyleBorderTopColor { inner: hover_border }.into())));
901            props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: hover_border }.into())));
902            props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: hover_border }.into())));
903            props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderRightColor(StyleBorderRightColor { inner: hover_border }.into())));
904        }
905        
906        // Active (pressed) state
907        props.push(CssPropertyWithConditions::on_active(CssProperty::BackgroundContent(
908            StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_active)]).into(),
909        )));
910        
911        // Focus state - uses accent color for outline
912        // This makes the button feel "native" as it uses the system accent
913        props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderTopColor(StyleBorderTopColor { inner: focus_outline_color }.into())));
914        props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: focus_outline_color }.into())));
915        props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: focus_outline_color }.into())));
916        props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderRightColor(StyleBorderRightColor { inner: focus_outline_color }.into())));
917    }
918    
919    props
920}
921
922/// Build label style properties
923fn build_button_label_style() -> Vec<CssPropertyWithConditions> {
924    // Use system UI font
925    let font_family = StyleFontFamilyVec::from_vec(vec![
926        StyleFontFamily::SystemType(SystemFontType::Ui),
927    ]);
928    
929    vec![
930        CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
931        CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Center)),
932        CssPropertyWithConditions::simple(CssProperty::const_font_family(font_family)),
933        CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
934    ]
935}
936
937impl Button {
938    /// Create a button with `ButtonType::Default` styling.
939    #[inline]
940    pub fn create(label: AzString) -> Self {
941        Self::with_type(label, ButtonType::Default)
942    }
943    
944    /// Create a button with a specific type (Primary, Success, Danger, etc.)
945    #[inline]
946    pub fn with_type(label: AzString, button_type: ButtonType) -> Self {
947        let container_style = build_button_container_style(button_type);
948        let label_style = build_button_label_style();
949        
950        Self {
951            label,
952            image: None.into(),
953            button_type,
954            on_click: None.into(),
955            container_style: CssPropertyWithConditionsVec::from_vec(container_style),
956            label_style: CssPropertyWithConditionsVec::from_vec(label_style.clone()),
957            image_style: CssPropertyWithConditionsVec::from_vec(label_style),
958        }
959    }
960    
961    /// Set the button type and update styling accordingly
962    #[inline]
963    pub fn set_button_type(&mut self, button_type: ButtonType) {
964        self.button_type = button_type;
965        self.container_style = CssPropertyWithConditionsVec::from_vec(build_button_container_style(button_type));
966    }
967    
968    /// Builder method to set the button type
969    #[inline]
970    pub fn with_button_type(mut self, button_type: ButtonType) -> Self {
971        self.set_button_type(button_type);
972        self
973    }
974
975    #[inline(always)]
976    pub fn swap_with_default(&mut self) -> Self {
977        let mut m = Self::create(AzString::from_const_str(""));
978        core::mem::swap(&mut m, self);
979        m
980    }
981
982    #[inline]
983    pub fn set_image(&mut self, image: ImageRef) {
984        self.image = Some(image).into();
985    }
986
987    #[inline]
988    pub fn set_on_click<C: Into<ButtonOnClickCallback>>(&mut self, data: RefAny, on_click: C) {
989        self.on_click = Some(ButtonOnClick {
990            refany: data,
991            callback: on_click.into(),
992        })
993        .into();
994    }
995
996    #[inline]
997    pub fn with_on_click<C: Into<ButtonOnClickCallback>>(
998        mut self,
999        data: RefAny,
1000        on_click: C,
1001    ) -> Self {
1002        self.set_on_click(data, on_click);
1003        self
1004    }
1005
1006    #[inline]
1007    pub fn dom(self) -> Dom {
1008        use azul_core::{
1009            callbacks::{CoreCallback, CoreCallbackData},
1010            dom::{EventFilter, HoverEventFilter},
1011        };
1012
1013        let callbacks = match self.on_click.into_option() {
1014            Some(ButtonOnClick {
1015                refany: data,
1016                callback,
1017            }) => vec![CoreCallbackData {
1018                event: EventFilter::Hover(HoverEventFilter::MouseUp),
1019                callback: CoreCallback {
1020                    cb: callback.cb as usize,
1021                    ctx: callback.ctx,
1022                },
1023                refany: data,
1024            }],
1025            None => Vec::new(),
1026        };
1027
1028        // Add both the base class and the type-specific class
1029        let type_class = self.button_type.class_name();
1030        let classes: Vec<IdOrClass> = vec![
1031            Class(AzString::from_const_str("__azul-native-button")),
1032            Class(AzString::from_const_str(type_class)),
1033        ];
1034
1035        // Create label element with styling
1036        let label_dom = Dom::create_text(self.label)
1037            .with_css_props(self.label_style);
1038
1039        // Create button container with label as child
1040        Dom::create_node(NodeType::Button)
1041            .with_child(label_dom)
1042            .with_ids_and_classes(IdOrClassVec::from_vec(classes))
1043            .with_css_props(self.container_style)
1044            .with_callbacks(callbacks.into())
1045            .with_tab_index(TabIndex::Auto)
1046    }
1047}