use freya_core::prelude::*;
use crate::{
get_theme,
theming::component_themes::{
ButtonColorsThemePartial,
ButtonLayoutThemePartial,
ButtonLayoutThemePartialExt,
},
};
#[derive(Clone, PartialEq)]
pub enum ButtonStyleVariant {
Normal,
Filled,
Outline,
}
#[derive(Clone, PartialEq)]
pub enum ButtonLayoutVariant {
Normal,
Compact,
Expanded,
}
#[cfg_attr(feature = "docs",
doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
)]
#[derive(Clone, PartialEq)]
pub struct Button {
pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
elements: Vec<Element>,
on_press: Option<EventHandler<Event<PressEventData>>>,
on_secondary_press: Option<EventHandler<Event<PressEventData>>>,
key: DiffKey,
style_variant: ButtonStyleVariant,
layout_variant: ButtonLayoutVariant,
enabled: bool,
}
impl Default for Button {
fn default() -> Self {
Self::new()
}
}
impl ChildrenExt for Button {
fn get_children(&mut self) -> &mut Vec<Element> {
&mut self.elements
}
}
impl KeyExt for Button {
fn write_key(&mut self) -> &mut DiffKey {
&mut self.key
}
}
impl Button {
pub fn new() -> Self {
Self {
theme_colors: None,
theme_layout: None,
style_variant: ButtonStyleVariant::Normal,
layout_variant: ButtonLayoutVariant::Normal,
on_press: None,
on_secondary_press: None,
elements: Vec::default(),
enabled: true,
key: DiffKey::None,
}
}
pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
self.enabled = enabled.into();
self
}
pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
self.style_variant = style_variant.into();
self
}
pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
self.layout_variant = layout_variant.into();
self
}
pub fn on_press(mut self, on_press: impl FnMut(Event<PressEventData>) + 'static) -> Self {
self.on_press = Some(EventHandler::new(on_press));
self
}
pub fn on_secondary_press(
mut self,
on_secondary_press: impl FnMut(Event<PressEventData>) + 'static,
) -> Self {
self.on_secondary_press = Some(EventHandler::new(on_secondary_press));
self
}
pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
self.theme_colors = Some(theme);
self
}
pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
self.theme_layout = Some(theme);
self
}
pub fn compact(self) -> Self {
self.layout_variant(ButtonLayoutVariant::Compact)
}
pub fn expanded(self) -> Self {
self.layout_variant(ButtonLayoutVariant::Expanded)
}
pub fn filled(self) -> Self {
self.style_variant(ButtonStyleVariant::Filled)
}
pub fn outline(self) -> Self {
self.style_variant(ButtonStyleVariant::Outline)
}
pub fn rounded(self) -> Self {
self.corner_radius(99.)
}
}
impl Render for Button {
fn render(&self) -> impl IntoElement {
let mut hovering = use_state(|| false);
let focus = use_focus();
let focus_status = use_focus_status(focus);
let enabled = use_reactive(&self.enabled);
use_drop(move || {
if hovering() && enabled() {
Cursor::set(CursorIcon::default());
}
});
let theme_colors = match self.style_variant {
ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
};
let theme_layout = match self.layout_variant {
ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
};
let border = if focus_status() == FocusStatus::Keyboard {
Border::new()
.fill(theme_colors.focus_border_fill)
.width(2.)
.alignment(BorderAlignment::Inner)
} else {
Border::new()
.fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
.width(1.)
.alignment(BorderAlignment::Inner)
};
let background = if enabled() && hovering() {
theme_colors.hover_background
} else {
theme_colors.background
};
rect()
.a11y_id(focus.a11y_id())
.a11y_focusable(self.enabled)
.a11y_role(AccessibilityRole::Button)
.background(background.mul_if(!self.enabled, 0.9))
.border(border)
.padding(theme_layout.padding)
.corner_radius(theme_layout.corner_radius)
.width(theme_layout.width)
.height(theme_layout.height)
.color(theme_colors.color.mul_if(!self.enabled, 0.9))
.center()
.maybe(self.enabled, |rect| {
rect.on_all_press({
let on_press = self.on_press.clone();
let on_secondary_press = self.on_secondary_press.clone();
move |e: Event<PressEventData>| {
focus.request_focus();
match e.data() {
PressEventData::Mouse(data) => match data.button {
Some(MouseButton::Left) => {
if let Some(handler) = &on_press {
handler.call(e);
}
}
Some(MouseButton::Right) => {
if let Some(handler) = &on_secondary_press {
handler.call(e);
}
}
_ => {}
},
PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
if let Some(handler) = &on_press {
handler.call(e);
}
}
}
}
})
})
.on_pointer_enter(move |_| {
hovering.set(true);
if enabled() {
Cursor::set(CursorIcon::Pointer);
} else {
Cursor::set(CursorIcon::NotAllowed);
}
})
.on_pointer_leave(move |_| {
if hovering() {
Cursor::set(CursorIcon::default());
hovering.set(false);
}
})
.children(self.elements.clone())
}
fn render_key(&self) -> DiffKey {
self.key.clone().or(self.default_key())
}
}