use alloc::boxed::Box;
use crate::{
align::Alignment,
block::{Block, Border, BorderRadius},
color::UiColor,
el::{El, ElId},
event::{Capture, CommonEvent, Event, EventResponse, Propagate},
layout::{Layout, Viewport},
padding::Padding,
render::Renderer,
size::{Length, Size},
state::{self, StateNode, StateTag},
style::component_style,
ui::UiCtx,
widget::Widget,
};
struct ButtonState {
is_pressed: bool,
}
impl Default for ButtonState {
fn default() -> Self {
Self { is_pressed: false }
}
}
#[derive(Clone, Copy)]
pub enum ButtonStatus {
Normal,
Focused,
Pressed,
}
component_style! {
pub ButtonStyle: ButtonStyler(ButtonStatus) {
background: background,
border: border,
}
}
pub struct Button<'a, Message, R, E, S>
where
R: Renderer,
E: Event,
S: ButtonStyler<R::Color>,
{
id: ElId,
content: El<'a, Message, R, E, S>,
size: Size<Length>,
padding: Padding,
class: S::Class<'a>,
on_press: Option<Message>,
}
impl<'a, Message, R, E, S> Button<'a, Message, R, E, S>
where
Message: Clone,
R: Renderer,
E: Event,
S: ButtonStyler<R::Color>,
{
pub fn new(content: impl Into<El<'a, Message, R, E, S>>) -> Self {
let content = content.into();
let padding = Padding::default();
Self {
id: ElId::unique(),
content,
size: Size::fill(),
padding,
class: S::default(),
on_press: None,
}
}
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.size.width = width.into();
self
}
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.size.height = height.into();
self
}
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
pub fn on_press(mut self, on_press: impl Into<Message>) -> Self {
self.on_press = Some(on_press.into());
self
}
pub fn store_id(self, id: &mut ElId) -> Self {
*id = self.id;
self
}
pub fn identify(mut self, id: impl Into<ElId>) -> Self {
self.id = id.into();
self
}
fn status(&self, ctx: &UiCtx<Message>, state: &mut StateNode) -> ButtonStatus {
if state.get::<ButtonState>().is_pressed {
ButtonStatus::Pressed
} else if ctx.is_focused(self) {
ButtonStatus::Focused
} else {
ButtonStatus::Normal
}
}
}
impl<'a, Message, R, E, S> Widget<Message, R, E, S> for Button<'a, Message, R, E, S>
where
Message: Clone,
R: Renderer,
E: Event,
S: ButtonStyler<R::Color>,
{
fn id(&self) -> Option<ElId> {
Some(self.id)
}
fn tree_ids(&self) -> alloc::vec::Vec<ElId> {
let mut ids = vec![self.id];
ids.extend(self.content.tree_ids());
ids
}
fn size(&self) -> Size<Length> {
self.size
}
fn state_tag(&self) -> StateTag {
StateTag::of::<ButtonState>()
}
fn state(&self) -> state::State {
state::State::new(ButtonState::default())
}
fn state_children(&self) -> alloc::vec::Vec<state::StateNode> {
vec![StateNode::new(&self.content)]
}
fn on_event(
&mut self,
ctx: &mut UiCtx<Message>,
event: E,
state: &mut StateNode,
) -> EventResponse<E> {
match self.content.on_event(ctx, event.clone(), &mut state.children[0])? {
Propagate::Ignored => match event.as_common() {
Some(common) => match common {
CommonEvent::FocusMove(_) if ctx.is_focused(self) => {
Propagate::BubbleUp(self.id, event).into()
},
CommonEvent::FocusClickDown if ctx.is_focused(self) => {
state.get_mut::<ButtonState>().is_pressed = true;
Capture::Captured.into()
},
CommonEvent::FocusClickUp if ctx.is_focused(self) => {
let pressed = state.get::<ButtonState>().is_pressed;
state.get_mut::<ButtonState>().is_pressed = false;
if pressed {
if let Some(on_press) = self.on_press.clone() {
ctx.publish(on_press);
return Capture::Captured.into();
}
}
Propagate::Ignored.into()
},
CommonEvent::FocusClickDown
| CommonEvent::FocusClickUp
| CommonEvent::FocusMove(_) => {
state.get_mut::<ButtonState>().is_pressed = false;
Propagate::Ignored.into()
},
},
None => Propagate::Ignored.into(),
},
bubbled @ Propagate::BubbleUp(..) => bubbled.into(),
}
}
fn layout(
&self,
ctx: &mut UiCtx<Message>,
state: &mut StateNode,
styler: &S,
limits: &crate::layout::Limits,
viewport: &Viewport,
) -> crate::layout::LayoutNode {
let style = styler.style(&self.class, self.status(ctx, state));
Layout::container(
limits,
self.size,
crate::layout::Position::Relative,
viewport,
self.padding,
style.border.width,
Alignment::Start,
Alignment::Start,
|limits| self.content.layout(ctx, &mut state.children[0], styler, limits, viewport),
)
}
fn draw(
&self,
ctx: &mut UiCtx<Message>,
state: &mut StateNode,
renderer: &mut R,
styler: &S,
layout: Layout,
) {
let bounds = layout.bounds();
let style = styler.style(&self.class, self.status(ctx, state));
renderer.block(Block {
border: style.border,
rect: bounds.into(),
background: style.background,
});
self.content.draw(
ctx,
&mut state.children[0],
renderer,
styler,
layout.children().next().unwrap(),
)
}
}
impl<'a, Message, R, E, S> From<Button<'a, Message, R, E, S>> for El<'a, Message, R, E, S>
where
Message: Clone + 'a,
R: Renderer + 'a,
E: Event + 'a,
S: ButtonStyler<R::Color> + 'a,
{
fn from(value: Button<'a, Message, R, E, S>) -> Self {
Self::new(value)
}
}