aetna_core/widgets/
button.rs1use std::panic::Location;
27
28use crate::anim::Timing;
29use crate::cursor::Cursor;
30use crate::metrics::MetricsRole;
31use crate::style::StyleProfile;
32use crate::tokens;
33use crate::tree::*;
34use crate::{IntoIconSource, icon, text};
35
36#[track_caller]
37pub fn button(label: impl Into<String>) -> El {
38 El::new(Kind::Custom("button"))
39 .at_loc(Location::caller())
40 .style_profile(StyleProfile::Solid)
41 .metrics_role(MetricsRole::Button)
42 .surface_role(SurfaceRole::Raised)
43 .focusable()
44 .paint_overflow(Sides::all(tokens::RING_WIDTH))
45 .hit_overflow(Sides::all(tokens::HIT_OVERFLOW))
46 .cursor(Cursor::Pointer)
47 .text(label)
48 .text_align(TextAlign::Center)
49 .text_role(TextRole::Label)
50 .fill(tokens::SECONDARY)
51 .stroke(tokens::BORDER)
52 .text_color(tokens::SECONDARY_FOREGROUND)
53 .default_radius(tokens::RADIUS_MD)
54 .default_width(Size::Hug)
55 .default_height(Size::Fixed(tokens::CONTROL_HEIGHT))
56 .default_padding(Sides::xy(tokens::SPACE_3, 0.0))
57 .animate(Timing::SPRING_QUICK)
58}
59
60#[track_caller]
61pub fn icon_button(source: impl IntoIconSource) -> El {
62 El::new(Kind::Custom("icon_button"))
63 .at_loc(Location::caller())
64 .style_profile(StyleProfile::Solid)
65 .metrics_role(MetricsRole::IconButton)
66 .surface_role(SurfaceRole::Raised)
67 .focusable()
68 .paint_overflow(Sides::all(tokens::RING_WIDTH))
69 .hit_overflow(Sides::all(tokens::HIT_OVERFLOW))
70 .cursor(Cursor::Pointer)
71 .icon_source(source)
72 .icon_size(tokens::ICON_SM)
73 .icon_stroke_width(2.0)
74 .fill(tokens::SECONDARY)
75 .stroke(tokens::BORDER)
76 .text_color(tokens::SECONDARY_FOREGROUND)
77 .default_radius(tokens::RADIUS_MD)
78 .default_width(Size::Fixed(tokens::CONTROL_HEIGHT))
79 .default_height(Size::Fixed(tokens::CONTROL_HEIGHT))
80 .animate(Timing::SPRING_QUICK)
81}
82
83#[track_caller]
84pub fn button_with_icon(source: impl IntoIconSource, label: impl Into<String>) -> El {
85 El::new(Kind::Custom("button_with_icon"))
86 .at_loc(Location::caller())
87 .style_profile(StyleProfile::Solid)
88 .metrics_role(MetricsRole::Button)
89 .surface_role(SurfaceRole::Raised)
90 .focusable()
91 .paint_overflow(Sides::all(tokens::RING_WIDTH))
92 .hit_overflow(Sides::all(tokens::HIT_OVERFLOW))
93 .cursor(Cursor::Pointer)
94 .axis(Axis::Row)
95 .default_gap(tokens::SPACE_2)
96 .align(Align::Center)
97 .justify(Justify::Center)
98 .child(
99 icon(source)
100 .icon_size(tokens::ICON_SM)
101 .color(tokens::SECONDARY_FOREGROUND),
102 )
103 .child(text(label).label())
104 .fill(tokens::SECONDARY)
105 .stroke(tokens::BORDER)
106 .text_color(tokens::SECONDARY_FOREGROUND)
107 .default_radius(tokens::RADIUS_MD)
108 .default_width(Size::Hug)
109 .default_height(Size::Fixed(tokens::CONTROL_HEIGHT))
110 .default_padding(Sides::xy(tokens::SPACE_3, 0.0))
111 .animate(Timing::SPRING_QUICK)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn buttons_ease_variant_changes() {
120 assert!(button("Save").animate.is_some());
121 assert!(button("Save").primary().animate.is_some());
122 assert!(icon_button("settings").animate.is_some());
123 assert!(button_with_icon("folder", "Open").animate.is_some());
124 }
125
126 #[test]
127 fn buttons_have_conservative_default_hit_overflow() {
128 assert_eq!(
129 button("Save").hit_overflow,
130 Sides::all(tokens::HIT_OVERFLOW)
131 );
132 assert_eq!(
133 icon_button("settings").hit_overflow,
134 Sides::all(tokens::HIT_OVERFLOW)
135 );
136 assert_eq!(
137 button_with_icon("folder", "Open").hit_overflow,
138 Sides::all(tokens::HIT_OVERFLOW)
139 );
140 }
141}