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 on_secondary_press: Option<EventHandler<Event<PressEventData>>>,
89 key: DiffKey,
90 style_variant: ButtonStyleVariant,
91 layout_variant: ButtonLayoutVariant,
92 enabled: bool,
93}
94
95impl Default for Button {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl ChildrenExt for Button {
102 fn get_children(&mut self) -> &mut Vec<Element> {
103 &mut self.elements
104 }
105}
106
107impl KeyExt for Button {
108 fn write_key(&mut self) -> &mut DiffKey {
109 &mut self.key
110 }
111}
112
113impl Button {
114 pub fn new() -> Self {
115 Self {
116 theme_colors: None,
117 theme_layout: None,
118 style_variant: ButtonStyleVariant::Normal,
119 layout_variant: ButtonLayoutVariant::Normal,
120 on_press: None,
121 on_secondary_press: None,
122 elements: Vec::default(),
123 enabled: true,
124 key: DiffKey::None,
125 }
126 }
127
128 pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
129 self.enabled = enabled.into();
130 self
131 }
132
133 pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
134 self.style_variant = style_variant.into();
135 self
136 }
137
138 pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
139 self.layout_variant = layout_variant.into();
140 self
141 }
142
143 pub fn on_press(mut self, on_press: impl FnMut(Event<PressEventData>) + 'static) -> Self {
144 self.on_press = Some(EventHandler::new(on_press));
145 self
146 }
147
148 pub fn on_secondary_press(
149 mut self,
150 on_secondary_press: impl FnMut(Event<PressEventData>) + 'static,
151 ) -> Self {
152 self.on_secondary_press = Some(EventHandler::new(on_secondary_press));
153 self
154 }
155
156 pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
157 self.theme_colors = Some(theme);
158 self
159 }
160
161 pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
162 self.theme_layout = Some(theme);
163 self
164 }
165
166 pub fn compact(self) -> Self {
168 self.layout_variant(ButtonLayoutVariant::Compact)
169 }
170
171 pub fn expanded(self) -> Self {
173 self.layout_variant(ButtonLayoutVariant::Expanded)
174 }
175
176 pub fn filled(self) -> Self {
178 self.style_variant(ButtonStyleVariant::Filled)
179 }
180
181 pub fn outline(self) -> Self {
183 self.style_variant(ButtonStyleVariant::Outline)
184 }
185
186 pub fn rounded(self) -> Self {
188 self.corner_radius(99.)
189 }
190}
191
192impl Render for Button {
193 fn render(&self) -> impl IntoElement {
194 let mut hovering = use_state(|| false);
195 let focus = use_focus();
196 let focus_status = use_focus_status(focus);
197
198 let enabled = use_reactive(&self.enabled);
199 use_drop(move || {
200 if hovering() && enabled() {
201 Cursor::set(CursorIcon::default());
202 }
203 });
204
205 let theme_colors = match self.style_variant {
206 ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
207 ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
208 ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
209 };
210 let theme_layout = match self.layout_variant {
211 ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
212 ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
213 ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
214 };
215
216 let border = if focus_status() == FocusStatus::Keyboard {
217 Border::new()
218 .fill(theme_colors.focus_border_fill)
219 .width(2.)
220 .alignment(BorderAlignment::Inner)
221 } else {
222 Border::new()
223 .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
224 .width(1.)
225 .alignment(BorderAlignment::Inner)
226 };
227 let background = if enabled() && hovering() {
228 theme_colors.hover_background
229 } else {
230 theme_colors.background
231 };
232
233 rect()
234 .a11y_id(focus.a11y_id())
235 .a11y_focusable(self.enabled)
236 .a11y_role(AccessibilityRole::Button)
237 .background(background.mul_if(!self.enabled, 0.9))
238 .border(border)
239 .padding(theme_layout.padding)
240 .corner_radius(theme_layout.corner_radius)
241 .width(theme_layout.width)
242 .height(theme_layout.height)
243 .color(theme_colors.color.mul_if(!self.enabled, 0.9))
244 .center()
245 .maybe(self.enabled, |rect| {
246 rect.on_all_press({
247 let on_press = self.on_press.clone();
248 let on_secondary_press = self.on_secondary_press.clone();
249 move |e: Event<PressEventData>| {
250 focus.request_focus();
251 if let PressEventData::Mouse(data) = e.data() {
252 match (data.button, &on_press, &on_secondary_press) {
253 (Some(MouseButton::Left), Some(on_press), _) => on_press.call(e),
254 (Some(MouseButton::Right), _, Some(on_secondary_press)) => {
255 on_secondary_press.call(e)
256 }
257 _ => {}
258 }
259 }
260 }
261 })
262 })
263 .on_pointer_enter(move |_| {
264 hovering.set(true);
265 if enabled() {
266 Cursor::set(CursorIcon::Pointer);
267 } else {
268 Cursor::set(CursorIcon::NotAllowed);
269 }
270 })
271 .on_pointer_leave(move |_| {
272 if hovering() {
273 Cursor::set(CursorIcon::default());
274 hovering.set(false);
275 }
276 })
277 .children(self.elements.clone())
278 }
279
280 fn render_key(&self) -> DiffKey {
281 self.key.clone().or(self.default_key())
282 }
283}