use std::hash::{DefaultHasher, Hash, Hasher};
use crate::{
buffer::Buffer,
geometry::Padding,
prelude::{MouseButton, MouseEvent, Rect, Vec2},
style::Style,
term::backend::MouseEventKind,
widgets::{Element, EventResult, LayoutNode, Spacer, Widget},
};
pub struct Button<M: 'static> {
child: Element<M>,
padding: Padding,
style: Style,
handlers: Vec<(MouseButton, M)>,
}
impl<M> Button<M> {
#[must_use]
pub fn new<T>(child: T) -> Self
where
T: Into<Element<M>>,
{
Self {
child: child.into(),
padding: Default::default(),
style: Default::default(),
handlers: vec![],
}
}
#[must_use]
pub fn padding<P>(mut self, padding: P) -> Self
where
P: Into<Padding>,
{
self.padding = padding.into();
self
}
#[must_use]
pub fn style<S>(mut self, style: S) -> Self
where
S: Into<Style>,
{
self.style = style.into();
self
}
#[must_use]
pub fn on_click(self, response: M) -> Self {
self.on_press(MouseButton::Left, response)
}
#[must_use]
pub fn on_press(mut self, button: MouseButton, response: M) -> Self {
self.handlers.retain(|(b, _)| *b != button);
self.handlers.push((button, response));
self
}
}
impl<M: Clone + 'static> Button<M> {
#[must_use]
pub fn empty() -> Self {
Self::new(Spacer::new())
}
}
impl<M: Clone + 'static> Widget<M> for Button<M> {
fn render(&self, buffer: &mut Buffer, layout: &LayoutNode) {
buffer.set_area_style(self.style, layout.area);
self.child.render(buffer, &layout.children[0]);
}
fn height(&self, size: &Vec2) -> usize {
let size = Vec2::new(
size.x.saturating_sub(self.padding.get_horizontal()),
size.y.saturating_sub(self.padding.get_vertical()),
);
self.child.height(&size) + self.padding.get_vertical()
}
fn width(&self, size: &Vec2) -> usize {
let size = Vec2::new(
size.x.saturating_sub(self.padding.get_horizontal()),
size.y.saturating_sub(self.padding.get_vertical()),
);
self.child.width(&size) + self.padding.get_horizontal()
}
fn children(&self) -> Vec<&Element<M>> {
vec![&self.child]
}
fn layout_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.padding.hash(&mut hasher);
hasher.finish()
}
fn layout(&self, node: &mut LayoutNode, area: Rect) {
node.children[0].layout(&self.child, area.inner(self.padding));
}
fn on_event(&self, node: &LayoutNode, e: &MouseEvent) -> EventResult<M> {
if !node.area.contains_pos(&e.pos) {
return EventResult::None;
}
self.child
.on_event(&node.children[0], e)
.or_else(|| self.handle_click(e))
}
}
impl<M: Clone> Button<M> {
fn handle_click(&self, event: &MouseEvent) -> EventResult<M> {
match &event.kind {
MouseEventKind::Down(button) => self
.handlers
.iter()
.find(|(b, _)| b == button)
.map(|(_, m)| EventResult::Response(m.clone()))
.unwrap_or(EventResult::None),
_ => EventResult::None,
}
}
}
impl<M: Clone + 'static> From<Button<M>> for Box<dyn Widget<M>> {
fn from(value: Button<M>) -> Self {
Box::new(value)
}
}
impl<M: Clone + 'static> From<Button<M>> for Element<M> {
fn from(value: Button<M>) -> Self {
Element::new(value)
}
}