freya_components/
button.rs1use freya_core::prelude::*;
2
3use crate::{
4 get_theme,
5 theming::component_themes::{
6 ButtonColorsThemePartial,
7 ButtonLayoutThemePartial,
8 ButtonLayoutThemePartialExt,
9 },
10};
11
12#[derive(Clone, PartialEq)]
13pub enum ButtonStyleVariant {
14 Normal,
15 Filled,
16 Outline,
17}
18
19#[derive(Clone, PartialEq)]
20pub enum ButtonLayoutVariant {
21 Normal,
22 Compact,
23 Expanded,
24}
25
26#[cfg_attr(feature = "docs",
78 doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
79 doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
80 doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
81)]
82#[derive(Clone, PartialEq)]
83pub struct Button {
84 pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
85 pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
86 elements: Vec<Element>,
87 on_press: Option<EventHandler<Event<PressEventData>>>,
88 key: DiffKey,
89 style_variant: ButtonStyleVariant,
90 layout_variant: ButtonLayoutVariant,
91 enabled: bool,
92}
93
94impl Default for Button {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl ChildrenExt for Button {
101 fn get_children(&mut self) -> &mut Vec<Element> {
102 &mut self.elements
103 }
104}
105
106impl KeyExt for Button {
107 fn write_key(&mut self) -> &mut DiffKey {
108 &mut self.key
109 }
110}
111
112impl Button {
113 pub fn new() -> Self {
114 Self {
115 theme_colors: None,
116 theme_layout: None,
117 style_variant: ButtonStyleVariant::Normal,
118 layout_variant: ButtonLayoutVariant::Normal,
119 on_press: None,
120 elements: Vec::default(),
121 enabled: true,
122 key: DiffKey::None,
123 }
124 }
125
126 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
127 self.enabled = enabled.into();
128 self
129 }
130
131 pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
132 self.style_variant = style_variant.into();
133 self
134 }
135
136 pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
137 self.layout_variant = layout_variant.into();
138 self
139 }
140
141 pub fn on_press(mut self, on_press: impl FnMut(Event<PressEventData>) + 'static) -> Self {
142 self.on_press = Some(EventHandler::new(on_press));
143 self
144 }
145
146 pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
147 self.theme_colors = Some(theme);
148 self
149 }
150
151 pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
152 self.theme_layout = Some(theme);
153 self
154 }
155
156 pub fn compact(self) -> Self {
158 self.layout_variant(ButtonLayoutVariant::Compact)
159 }
160
161 pub fn expanded(self) -> Self {
163 self.layout_variant(ButtonLayoutVariant::Expanded)
164 }
165
166 pub fn filled(self) -> Self {
168 self.style_variant(ButtonStyleVariant::Filled)
169 }
170
171 pub fn outline(self) -> Self {
173 self.style_variant(ButtonStyleVariant::Outline)
174 }
175
176 pub fn rounded(self) -> Self {
178 self.corner_radius(99.)
179 }
180}
181
182impl Render for Button {
183 fn render(&self) -> impl IntoElement {
184 let mut hovering = use_state(|| false);
185 let focus = use_focus();
186 let focus_status = use_focus_status(focus);
187
188 let enabled = use_reactive(&self.enabled);
189 use_drop(move || {
190 if hovering() && enabled() {
191 Cursor::set(CursorIcon::default());
192 }
193 });
194
195 let theme_colors = match self.style_variant {
196 ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
197 ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
198 ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
199 };
200 let theme_layout = match self.layout_variant {
201 ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
202 ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
203 ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
204 };
205
206 let border = if focus_status() == FocusStatus::Keyboard {
207 Border::new()
208 .fill(theme_colors.focus_border_fill)
209 .width(2.)
210 .alignment(BorderAlignment::Inner)
211 } else {
212 Border::new()
213 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
214 .width(1.)
215 .alignment(BorderAlignment::Inner)
216 };
217 let background = if enabled() && hovering() {
218 theme_colors.hover_background
219 } else {
220 theme_colors.background
221 };
222
223 rect()
224 .a11y_id(focus.a11y_id())
225 .a11y_focusable(self.enabled)
226 .a11y_role(AccessibilityRole::Button)
227 .background(background.mul_if(!self.enabled, 0.9))
228 .border(border)
229 .padding(theme_layout.padding)
230 .corner_radius(theme_layout.corner_radius)
231 .width(theme_layout.width)
232 .height(theme_layout.height)
233 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
234 .center()
235 .maybe(self.enabled, |rect| {
236 rect.on_press({
237 let on_press = self.on_press.clone();
238 move |e| {
239 focus.request_focus();
240 if let Some(on_press) = &on_press {
241 on_press.call(e)
242 }
243 }
244 })
245 })
246 .on_pointer_enter(move |_| {
247 hovering.set(true);
248 if enabled() {
249 Cursor::set(CursorIcon::Pointer);
250 } else {
251 Cursor::set(CursorIcon::NotAllowed);
252 }
253 })
254 .on_pointer_leave(move |_| {
255 if hovering() {
256 Cursor::set(CursorIcon::default());
257 hovering.set(false);
258 }
259 })
260 .children(self.elements.clone())
261 }
262
263 fn render_key(&self) -> DiffKey {
264 self.key.clone().or(self.default_key())
265 }
266}