1use std::vec::Vec;
4
5use azul_core::{
6 callbacks::{CoreCallbackData, Update},
7 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, NodeType, TabIndex},
8 refany::RefAny,
9 resources::{ImageRef, OptionImageRef},
10};
11#[allow(clippy::wildcard_imports)] use azul_css::{
13 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
14 props::{
15 basic::{
16 color::ColorU,
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#[repr(C)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
36pub enum ButtonType {
37 #[default]
39 Default,
40 Primary,
42 Secondary,
44 Success,
46 Danger,
48 Warning,
50 Info,
52 Link,
54}
55
56impl ButtonType {
57 #[must_use] pub const fn class_name(&self) -> &'static str {
59 match self {
60 Self::Default => "__azul-btn-default",
61 Self::Primary => "__azul-btn-primary",
62 Self::Secondary => "__azul-btn-secondary",
63 Self::Success => "__azul-btn-success",
64 Self::Danger => "__azul-btn-danger",
65 Self::Warning => "__azul-btn-warning",
66 Self::Info => "__azul-btn-info",
67 Self::Link => "__azul-btn-link",
68 }
69 }
70}
71
72#[repr(C)]
73#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
74pub struct Button {
75 pub label: AzString,
77 pub image: OptionImageRef,
79 pub button_type: ButtonType,
81 pub container_style: CssPropertyWithConditionsVec,
83 pub label_style: CssPropertyWithConditionsVec,
85 pub image_style: CssPropertyWithConditionsVec,
87 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
99azul_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 fn get_button_colors(button_type: ButtonType) -> (ColorU, ColorU, ColorU) {
117 match button_type {
119 ButtonType::Default => (
120 ColorU::rgb(248, 249, 250), ColorU::rgb(233, 236, 239), ColorU::rgb(218, 222, 226), ),
124 ButtonType::Primary => (
125 ColorU::bootstrap_primary(),
126 ColorU::bootstrap_primary_hover(),
127 ColorU::bootstrap_primary_active(),
128 ),
129 ButtonType::Secondary => (
130 ColorU::bootstrap_secondary(),
131 ColorU::bootstrap_secondary_hover(),
132 ColorU::bootstrap_secondary_active(),
133 ),
134 ButtonType::Success => (
135 ColorU::bootstrap_success(),
136 ColorU::bootstrap_success_hover(),
137 ColorU::bootstrap_success_active(),
138 ),
139 ButtonType::Danger => (
140 ColorU::bootstrap_danger(),
141 ColorU::bootstrap_danger_hover(),
142 ColorU::bootstrap_danger_active(),
143 ),
144 ButtonType::Warning => (
145 ColorU::bootstrap_warning(),
146 ColorU::bootstrap_warning_hover(),
147 ColorU::bootstrap_warning_active(),
148 ),
149 ButtonType::Info => (
150 ColorU::bootstrap_info(),
151 ColorU::bootstrap_info_hover(),
152 ColorU::bootstrap_info_active(),
153 ),
154 ButtonType::Link => (
155 ColorU::TRANSPARENT,
156 ColorU::TRANSPARENT,
157 ColorU::TRANSPARENT,
158 ),
159 }
160}
161
162const fn get_button_text_color(button_type: ButtonType) -> ColorU {
164 match button_type {
165 ButtonType::Default => ColorU::rgb(33, 37, 41), ButtonType::Warning => ColorU::BLACK, ButtonType::Link => ColorU::bootstrap_link(), _ => ColorU::WHITE, }
170}
171
172fn build_button_container_style(button_type: ButtonType) -> Vec<CssPropertyWithConditions> {
174 return alloc::vec![
184 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
185 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(6))),
186 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(LayoutPaddingBottom::const_px(6))),
187 ];
188 #[allow(unreachable_code)]
189 let (bg_normal, bg_hover, bg_active) = get_button_colors(button_type);
190 let text_color = get_button_text_color(button_type);
191
192 let focus_outline_color = ColorU::bootstrap_primary();
194
195 let mut props = Vec::with_capacity(40);
196
197 props.push(CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)));
199 props.push(CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)));
200 props.push(CssPropertyWithConditions::simple(CssProperty::const_justify_content(LayoutJustifyContent::Center)));
201 props.push(CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)));
202 props.push(CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)));
204 props.push(CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)));
205 props.push(CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))));
206
207 props.push(CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor { inner: text_color })));
209
210 props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(6))));
212 props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(LayoutPaddingBottom::const_px(6))));
213 props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(12))));
214 props.push(CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(12))));
215
216 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(StyleBorderTopLeftRadius::const_px(4))));
218 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(StyleBorderTopRightRadius::const_px(4))));
219 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(StyleBorderBottomLeftRadius::const_px(4))));
220 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(StyleBorderBottomRightRadius::const_px(4))));
221
222 if button_type == ButtonType::Link {
223 props.push(CssPropertyWithConditions::simple(CssProperty::const_background_content(
225 StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(ColorU::TRANSPARENT)]),
226 )));
227
228 props.push(CssPropertyWithConditions::on_hover(CssProperty::TextDecoration(StyleTextDecoration::Underline.into())));
230 } else {
231 props.push(CssPropertyWithConditions::simple(CssProperty::const_background_content(
233 StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_normal)]),
234 )));
235
236 let border_color = if button_type == ButtonType::Default {
238 ColorU::rgb(206, 212, 218)
239 } else {
240 bg_normal
241 };
242 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_width(LayoutBorderTopWidth::const_px(1))));
243 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))));
244 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_width(LayoutBorderLeftWidth::const_px(1))));
245 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_width(LayoutBorderRightWidth::const_px(1))));
246 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle { inner: BorderStyle::Solid })));
247 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })));
248 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle { inner: BorderStyle::Solid })));
249 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_style(StyleBorderRightStyle { inner: BorderStyle::Solid })));
250 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor { inner: border_color })));
251 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: border_color })));
252 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: border_color })));
253 props.push(CssPropertyWithConditions::simple(CssProperty::const_border_right_color(StyleBorderRightColor { inner: border_color })));
254
255 props.push(CssPropertyWithConditions::on_hover(CssProperty::BackgroundContent(
257 StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_hover)]).into(),
258 )));
259 if button_type == ButtonType::Default {
260 let hover_border = ColorU::rgb(173, 181, 189);
261 props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderTopColor(StyleBorderTopColor { inner: hover_border }.into())));
262 props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: hover_border }.into())));
263 props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: hover_border }.into())));
264 props.push(CssPropertyWithConditions::on_hover(CssProperty::BorderRightColor(StyleBorderRightColor { inner: hover_border }.into())));
265 }
266
267 props.push(CssPropertyWithConditions::on_active(CssProperty::BackgroundContent(
269 StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(bg_active)]).into(),
270 )));
271
272 props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderTopColor(StyleBorderTopColor { inner: focus_outline_color }.into())));
275 props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderBottomColor(StyleBorderBottomColor { inner: focus_outline_color }.into())));
276 props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderLeftColor(StyleBorderLeftColor { inner: focus_outline_color }.into())));
277 props.push(CssPropertyWithConditions::on_focus(CssProperty::BorderRightColor(StyleBorderRightColor { inner: focus_outline_color }.into())));
278 }
279
280 props
281}
282
283fn build_button_label_style() -> Vec<CssPropertyWithConditions> {
285 let font_family = StyleFontFamilyVec::from_vec(vec![
287 StyleFontFamily::SystemType(SystemFontType::Ui),
288 ]);
289
290 vec![
291 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
292 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Center)),
293 CssPropertyWithConditions::simple(CssProperty::const_font_family(font_family)),
294 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
295 ]
296}
297
298impl Button {
299 #[inline]
301 #[must_use] pub fn create(label: AzString) -> Self {
302 Self::with_type(label, ButtonType::Default)
303 }
304
305 #[inline]
307 #[must_use] pub fn with_type(label: AzString, button_type: ButtonType) -> Self {
308 let container_style = build_button_container_style(button_type);
309 let label_style = build_button_label_style();
310
311 Self {
312 label,
313 image: None.into(),
314 button_type,
315 on_click: None.into(),
316 container_style: CssPropertyWithConditionsVec::from_vec(container_style),
317 label_style: CssPropertyWithConditionsVec::from_vec(label_style.clone()),
318 image_style: CssPropertyWithConditionsVec::from_vec(label_style),
319 }
320 }
321
322 #[inline]
324 pub fn set_button_type(&mut self, button_type: ButtonType) {
325 self.button_type = button_type;
326 self.container_style = CssPropertyWithConditionsVec::from_vec(build_button_container_style(button_type));
327 }
328
329 #[inline]
331 #[must_use] pub fn with_button_type(mut self, button_type: ButtonType) -> Self {
332 self.set_button_type(button_type);
333 self
334 }
335
336 #[inline]
337 #[must_use]
338 pub fn swap_with_default(&mut self) -> Self {
339 let mut m = Self::create(AzString::from_const_str(""));
340 core::mem::swap(&mut m, self);
341 m
342 }
343
344 #[inline]
345 pub fn set_image(&mut self, image: ImageRef) {
346 self.image = Some(image).into();
347 }
348
349 #[inline]
350 pub fn set_on_click<C: Into<ButtonOnClickCallback>>(&mut self, data: RefAny, on_click: C) {
351 self.on_click = Some(ButtonOnClick {
352 refany: data,
353 callback: on_click.into(),
354 })
355 .into();
356 }
357
358 #[inline]
359 #[must_use]
360 pub fn with_on_click<C: Into<ButtonOnClickCallback>>(
361 mut self,
362 data: RefAny,
363 on_click: C,
364 ) -> Self {
365 self.set_on_click(data, on_click);
366 self
367 }
368
369 #[inline]
370 #[must_use] pub fn dom(self) -> Dom {
371 use azul_core::{
372 callbacks::{CoreCallback, CoreCallbackData},
373 dom::{EventFilter, HoverEventFilter},
374 };
375
376 let callbacks = match self.on_click.into_option() {
377 Some(ButtonOnClick {
378 refany: data,
379 callback,
380 }) => vec![CoreCallbackData {
381 event: EventFilter::Hover(HoverEventFilter::MouseUp),
382 callback: CoreCallback {
383 cb: callback.cb as *const () as usize,
384 ctx: callback.ctx,
385 },
386 refany: data,
387 }],
388 None => Vec::new(),
389 };
390
391 let type_class = self.button_type.class_name();
399 let classes: Vec<IdOrClass> = vec![
400 Class(AzString::from("__azul-native-button")),
401 Class(AzString::from(type_class)),
402 ];
403
404 let label_dom = Dom::create_text(self.label)
411 .with_css_props(self.label_style);
412
413 let mut button = Dom::create_node(NodeType::Button);
414
415 if let Some(image) = self.image.into_option() {
418 button = button.with_child(
419 Dom::create_image(image).with_css_props(self.image_style),
420 );
421 }
422
423 button
424 .with_child(label_dom)
425 .with_css_props(self.container_style)
426 .with_ids_and_classes(IdOrClassVec::from_vec(classes))
427 .with_callbacks(callbacks.into())
428 .with_tab_index(TabIndex::Auto)
429 }
430}