use kahlo::{math::PixelSideOffsets, prelude::*}; use crate::{ component::Component, input::MouseButton, layout::{ HorizontalAlignment, LayoutNode, LayoutNodeAccess, LayoutNodeContainer, SizePolicy, VerticalAlignment, }, ui::UIHandle, window::RenderFormat, }; use super::{Label, Widget}; #[derive(Clone, Copy, PartialEq)] enum ButtonState { Idle, Hovered, Clicked, } pub struct Button { layout: LayoutNode, label: Label, state: ButtonState, hook: Option Option>>, } impl Button { pub fn new(uih: &UIHandle) -> Self { let mut layout = uih.new_layout_node(); layout .set_width_policy(SizePolicy::expanding(1)) .set_height_policy(SizePolicy { minimum: uih.theme().ui_font_size as usize, desired: (uih.theme().ui_font_size * 1.5) as usize, slack_weight: 0, }) .set_valign(VerticalAlignment::Centre) .set_halign(HorizontalAlignment::Centre); *layout.margin_mut() = PixelSideOffsets::new_all_same(uih.theme().border_width as i32); Self { layout, label: Label::new(uih), state: ButtonState::Idle, hook: None, } } pub fn new_with_label(uih: &UIHandle, label: &str) -> Self { let mut r = Self::new(uih); r.set_label(label); r } pub fn set_label(&mut self, label: &str) { self.label.set_text(label); } pub fn set_hook(&mut self, to: Box Option>) { self.hook = Some(to); } } impl LayoutNodeContainer for Button { fn layout_node(&self) -> &LayoutNode { &self.layout } fn layout_child(&self, ndx: usize) -> Option> { if ndx == 0 { Some(self.label.layout_node()) } else { None } } fn layout_child_count(&self) -> usize { 1 } } impl Widget for Button { fn poll( &mut self, uih: &mut UIHandle, input_state: Option<&crate::input::InputState>, ) -> Vec<::Msg> { let mut result: Vec<::Msg> = vec![]; if let (Some(istate), Some(area)) = (input_state, self.layout.render_area()) { let before = self.state; if area.contains_inclusive(istate.mouse.pos) { if istate.mouse.buttons.active(MouseButton::Left) { self.state = ButtonState::Clicked; } else if istate.mouse.released().active(MouseButton::Left) { self.state = ButtonState::Idle; result.extend(self.hook.as_ref().and_then(|v| v())); } else { self.state = ButtonState::Hovered; } } else { self.state = ButtonState::Idle; } if self.state != before { self.layout.render_needed(); } } result.extend(self.label.poll(uih, input_state)); result } fn layout_node(&self) -> LayoutNodeAccess { LayoutNodeAccess::new(self) } fn layout_node_mut(&mut self) -> &mut LayoutNode { &mut self.layout } fn render(&self, theme: &crate::theme::Theme, target: &mut kahlo::BitmapMut) { let colour = match self.state { ButtonState::Idle => theme.background, ButtonState::Hovered => theme.panel, ButtonState::Clicked => theme.active, }; if self.layout.render_check() { target.fill_region(self.layout.render_area().unwrap(), colour); target.rectangle( self.layout.render_area().unwrap(), theme.border_width, theme.border, ); } self.label.render(theme, target); } }