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