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 Flat,
18}
19
20#[derive(Clone, PartialEq)]
21pub enum ButtonLayoutVariant {
22 Normal,
23 Compact,
24 Expanded,
25}
26
27#[cfg_attr(feature = "docs",
95 doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
96 doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
97 doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
98 doc = embed_doc_image::embed_image!("flat_button", "images/gallery_flat_button.png"),
99)]
100#[derive(Clone, PartialEq)]
101pub struct Button {
102 pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
103 pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
104 elements: Vec<Element>,
105 on_press: Option<EventHandler<Event<PressEventData>>>,
106 on_secondary_press: Option<EventHandler<Event<PressEventData>>>,
107 key: DiffKey,
108 style_variant: ButtonStyleVariant,
109 layout_variant: ButtonLayoutVariant,
110 enabled: bool,
111 focusable: bool,
112}
113
114impl Default for Button {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120impl ChildrenExt for Button {
121 fn get_children(&mut self) -> &mut Vec<Element> {
122 &mut self.elements
123 }
124}
125
126impl KeyExt for Button {
127 fn write_key(&mut self) -> &mut DiffKey {
128 &mut self.key
129 }
130}
131
132impl Button {
133 pub fn new() -> Self {
134 Self {
135 theme_colors: None,
136 theme_layout: None,
137 style_variant: ButtonStyleVariant::Normal,
138 layout_variant: ButtonLayoutVariant::Normal,
139 on_press: None,
140 on_secondary_press: None,
141 elements: Vec::default(),
142 enabled: true,
143 focusable: true,
144 key: DiffKey::None,
145 }
146 }
147
148 pub fn get_layout_variant(&self) -> &ButtonLayoutVariant {
149 &self.layout_variant
150 }
151
152 pub fn get_theme_layout(&self) -> Option<&ButtonLayoutThemePartial> {
153 self.theme_layout.as_ref()
154 }
155
156 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
157 self.enabled = enabled.into();
158 self
159 }
160
161 pub fn focusable(mut self, focusable: impl Into<bool>) -> Self {
162 self.focusable = focusable.into();
163 self
164 }
165
166 pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
167 self.style_variant = style_variant.into();
168 self
169 }
170
171 pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
172 self.layout_variant = layout_variant.into();
173 self
174 }
175
176 pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
177 self.on_press = Some(on_press.into());
178 self
179 }
180
181 pub fn on_secondary_press(
182 mut self,
183 on_secondary_press: impl Into<EventHandler<Event<PressEventData>>>,
184 ) -> Self {
185 self.on_secondary_press = Some(on_secondary_press.into());
186 self
187 }
188
189 pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
190 self.theme_colors = Some(theme);
191 self
192 }
193
194 pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
195 self.theme_layout = Some(theme);
196 self
197 }
198
199 pub fn compact(self) -> Self {
201 self.layout_variant(ButtonLayoutVariant::Compact)
202 }
203
204 pub fn expanded(self) -> Self {
206 self.layout_variant(ButtonLayoutVariant::Expanded)
207 }
208
209 pub fn filled(self) -> Self {
211 self.style_variant(ButtonStyleVariant::Filled)
212 }
213
214 pub fn outline(self) -> Self {
216 self.style_variant(ButtonStyleVariant::Outline)
217 }
218
219 pub fn flat(self) -> Self {
221 self.style_variant(ButtonStyleVariant::Flat)
222 }
223}
224
225impl CornerRadiusExt for Button {
226 fn with_corner_radius(self, corner_radius: f32) -> Self {
227 self.corner_radius(corner_radius)
228 }
229}
230
231impl Component for Button {
232 fn render(&self) -> impl IntoElement {
233 let mut hovering = use_state(|| false);
234 let focus = use_focus();
235 let focus_status = use_focus_status(focus);
236
237 let enabled = use_reactive(&self.enabled);
238 use_drop(move || {
239 if hovering() && enabled() {
240 Cursor::set(CursorIcon::default());
241 }
242 });
243
244 let theme_colors = match self.style_variant {
245 ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
246 ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
247 ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
248 ButtonStyleVariant::Flat => get_theme!(&self.theme_colors, flat_button),
249 };
250 let theme_layout = match self.layout_variant {
251 ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
252 ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
253 ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
254 };
255
256 let border = if focus_status() == FocusStatus::Keyboard {
257 Border::new()
258 .fill(theme_colors.focus_border_fill)
259 .width(2.)
260 .alignment(BorderAlignment::Inner)
261 } else {
262 Border::new()
263 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
264 .width(1.)
265 .alignment(BorderAlignment::Inner)
266 };
267 let background = if enabled() && hovering() {
268 theme_colors.hover_background
269 } else {
270 theme_colors.background
271 };
272
273 rect()
274 .overflow(Overflow::Clip)
275 .a11y_id(focus.a11y_id())
276 .a11y_focusable(self.enabled && self.focusable)
277 .a11y_role(AccessibilityRole::Button)
278 .background(background.mul_if(!self.enabled, 0.9))
279 .border(border)
280 .padding(theme_layout.padding)
281 .corner_radius(theme_layout.corner_radius)
282 .width(theme_layout.width)
283 .height(theme_layout.height)
284 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
285 .center()
286 .maybe(self.enabled, |rect| {
287 rect.on_pointer_down(|e: Event<PointerEventData>| e.stop_propagation())
288 .on_all_press({
289 let on_press = self.on_press.clone();
290 let on_secondary_press = self.on_secondary_press.clone();
291 move |e: Event<PressEventData>| {
292 focus.request_focus();
293 match e.data() {
294 PressEventData::Mouse(data) => match data.button {
295 Some(MouseButton::Left) => {
296 if let Some(handler) = &on_press {
297 handler.call(e);
298 }
299 }
300 Some(MouseButton::Right) => {
301 if let Some(handler) = &on_secondary_press {
302 handler.call(e);
303 }
304 }
305 _ => {}
306 },
307 PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
308 if let Some(handler) = &on_press {
309 handler.call(e);
310 }
311 }
312 }
313 }
314 })
315 })
316 .on_pointer_enter(move |_| {
317 hovering.set(true);
318 if enabled() {
319 Cursor::set(CursorIcon::Pointer);
320 } else {
321 Cursor::set(CursorIcon::NotAllowed);
322 }
323 })
324 .on_pointer_leave(move |_| {
325 if hovering() {
326 Cursor::set(CursorIcon::default());
327 hovering.set(false);
328 }
329 })
330 .children(self.elements.clone())
331 }
332
333 fn render_key(&self) -> DiffKey {
334 self.key.clone().or(self.default_key())
335 }
336}