1
//! Button widget with Bootstrap-inspired type-based styling (`ButtonType`)
2
//! and platform-adaptive native appearance (Windows, Linux/GTK, macOS).
3

            
4
use std::vec::Vec;
5

            
6
use azul_core::{
7
    callbacks::{CoreCallbackData, Update},
8
    dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, NodeType, TabIndex},
9
    refany::RefAny,
10
    resources::{ImageRef, OptionImageRef},
11
};
12
use 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

            
28
use 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)]
36
pub 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

            
56
impl 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)]
74
pub 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

            
91
pub type ButtonOnClickCallbackType = extern "C" fn(RefAny, CallbackInfo) -> Update;
92
impl_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.
100
azul_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

            
112
const SANS_SERIF_STR: &str = "system:ui";
113
const SANS_SERIF: AzString = AzString::from_const_str(SANS_SERIF_STR);
114
const SANS_SERIF_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SANS_SERIF)];
115
const SANS_SERIF_FAMILY: StyleFontFamilyVec =
116
    StyleFontFamilyVec::from_const_slice(SANS_SERIF_FAMILIES);
117

            
118
// macOS: Helvetica with sans-serif fallback
119
const HELVETICA_STR: &str = "Helvetica Neue";
120
const HELVETICA: AzString = AzString::from_const_str(HELVETICA_STR);
121
const MAC_FONT_FAMILIES: &[StyleFontFamily] = &[
122
    StyleFontFamily::System(HELVETICA),
123
    StyleFontFamily::System(SANS_SERIF),
124
];
125
const MAC_FONT_FAMILY: StyleFontFamilyVec = StyleFontFamilyVec::from_const_slice(MAC_FONT_FAMILIES);
126

            
127
const RGB_172: ColorU = ColorU {
128
    r: 172,
129
    g: 172,
130
    b: 172,
131
    a: 255,
132
};
133
const RGB_239: ColorU = ColorU {
134
    r: 239,
135
    g: 239,
136
    b: 239,
137
    a: 255,
138
};
139
const RGB_229: ColorU = ColorU {
140
    r: 229,
141
    g: 229,
142
    b: 229,
143
    a: 255,
144
};
145

            
146
const WINDOWS_HOVER_START: ColorU = ColorU {
147
    r: 234,
148
    g: 243,
149
    b: 252,
150
    a: 255,
151
};
152
const WINDOWS_HOVER_END: ColorU = ColorU {
153
    r: 126,
154
    g: 180,
155
    b: 234,
156
    a: 255,
157
};
158
const WINDOWS_HOVER_BORDER: ColorU = ColorU {
159
    r: 126,
160
    g: 180,
161
    b: 234,
162
    a: 255,
163
};
164

            
165
const WINDOWS_ACTIVE_START: ColorU = ColorU {
166
    r: 217,
167
    g: 235,
168
    b: 252,
169
    a: 255,
170
};
171
const WINDOWS_ACTIVE_END: ColorU = ColorU {
172
    r: 86,
173
    g: 157,
174
    b: 229,
175
    a: 255,
176
};
177
const WINDOWS_ACTIVE_BORDER: ColorU = ColorU {
178
    r: 86,
179
    g: 157,
180
    b: 229,
181
    a: 255,
182
};
183

            
184
const WINDOWS_FOCUS_BORDER: ColorU = ColorU {
185
    r: 51,
186
    g: 153,
187
    b: 255,
188
    a: 255,
189
};
190

            
191
const 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
];
201
const BUTTON_NORMAL_BACKGROUND: &[StyleBackgroundContent] =
202
    &[StyleBackgroundContent::Color(RGB_229)];
203

            
204
const 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
];
214
const 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
    })];
225
const 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
];
235
const 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

            
247
static 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
384
const 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
];
404
const 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

            
414
const 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
];
434
const 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

            
444
const 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
];
464
const 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

            
474
const LINUX_BORDER_COLOR: ColorU = ColorU {
475
    r: 183,
476
    g: 183,
477
    b: 183,
478
    a: 255,
479
};
480

            
481
static 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
583
const 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
591
const 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
599
const 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
607
const 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)
615
const 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

            
624
static 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

            
724
static BUTTON_CONTAINER_OTHER: &[CssPropertyWithConditions] = &[];
725

            
726
static 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

            
735
static 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

            
749
static 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

            
763
static BUTTON_LABEL_OTHER: &[CssPropertyWithConditions] = &[];
764

            
765
// ============================================================
766
// ButtonType-specific styling
767
// ============================================================
768

            
769
/// Get the background color for a button type
770
fn 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
817
fn 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
827
fn 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
923
fn 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

            
937
impl 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,
        on_click: C,
    ) -> Self {
        self.set_on_click(data, on_click);
        self
    }
    #[inline]
    pub fn dom(self) -> Dom {
        use azul_core::{
            callbacks::{CoreCallback, CoreCallbackData},
            dom::{EventFilter, HoverEventFilter},
        };
        let callbacks = match self.on_click.into_option() {
            Some(ButtonOnClick {
                refany: data,
                callback,
            }) => vec![CoreCallbackData {
                event: EventFilter::Hover(HoverEventFilter::MouseUp),
                callback: CoreCallback {
                    cb: callback.cb as usize,
                    ctx: callback.ctx,
                },
                refany: data,
            }],
            None => Vec::new(),
        };
        // Add both the base class and the type-specific class
        let type_class = self.button_type.class_name();
        let classes: Vec<IdOrClass> = vec![
            Class(AzString::from_const_str("__azul-native-button")),
            Class(AzString::from_const_str(type_class)),
        ];
        // Create label element with styling
        let label_dom = Dom::create_text(self.label)
            .with_css_props(self.label_style);
        // Create button container with label as child
        Dom::create_node(NodeType::Button)
            .with_child(label_dom)
            .with_ids_and_classes(IdOrClassVec::from_vec(classes))
            .with_css_props(self.container_style)
            .with_callbacks(callbacks.into())
            .with_tab_index(TabIndex::Auto)
    }
}