Skip to main content

azul_layout/widgets/
button.rs

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