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