use kahlo::math::PixelSideOffsets; use crate::{ input::{MouseButton, MouseCheckStatus}, layout::{ HorizontalAlignment, LayoutNode, LayoutNodeAccess, LayoutTreeNode, LeafLayoutNode, SizePolicy, VerticalAlignment, }, render::{ColourChoice, RenderTarget, TextCache}, ui::UIHandle, Component, }; use super::Widget; #[derive(Clone, Copy, PartialEq)] enum ButtonState { Idle, Hovered, Clicked, } pub struct Button { root_layout: LayoutNode, label_layout: LeafLayoutNode, text_cache: TextCache, label: String, state: ButtonState, hook: Option Option>>, } struct RootTag; 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 { root_layout: layout, label_layout: { let mut node = uih.new_layout_node(); node.set_width_policy(SizePolicy::fixed(0)) .set_height_policy(SizePolicy::fixed(0)) .set_halign(HorizontalAlignment::Centre) .set_valign(VerticalAlignment::Centre); node.into() }, text_cache: TextCache::new_with_font_and_size( uih.theme().ui_font.clone(), uih.theme().ui_font_size, ), label: "".to_string(), state: ButtonState::Idle, hook: None, } } pub fn with_label(mut self, label: &str) -> Self { self.set_label(label); self } pub fn set_label(&mut self, label: &str) { self.label.clear(); self.label += label; self.text_cache.update(&self.label); } pub fn set_hook(&mut self, to: Box Option>) { self.hook = Some(to); } } impl LayoutTreeNode for Button { fn current_node(&self) -> &LayoutNode { &self.root_layout } fn child_count(&self) -> usize { 1 } fn child(&self, ndx: usize) -> Option> { if ndx == 0 { Some(self.label_layout.access()) } else { None } } } impl Widget for Button { fn poll( &mut self, _uih: &mut UIHandle, input_state: Option<&crate::input::InputState>, ) -> Vec<::Msg> { self.text_cache .update_node_size_hint(&mut *self.label_layout); let mut result: Vec<::Msg> = vec![]; if let Some((istate, area)) = input_state.zip(self.root_layout.render_area()) { let saved = self.state; match istate.mouse_status_in(area, MouseButton::Left) { MouseCheckStatus::Idle | MouseCheckStatus::Leave => { self.state = ButtonState::Idle; } MouseCheckStatus::Click | MouseCheckStatus::Hold => { self.state = ButtonState::Clicked; } MouseCheckStatus::Enter | MouseCheckStatus::Hover => { self.state = ButtonState::Hovered; } MouseCheckStatus::Release => { self.state = ButtonState::Hovered; result.extend(self.hook.as_ref().and_then(|v| v())); } } if self.state != saved { self.root_layout.render_needed(); } } result } fn layout_node(&self) -> LayoutNodeAccess { LayoutNodeAccess::new(self) } fn layout_node_mut(&mut self) -> &mut LayoutNode { &mut self.root_layout } fn render<'r, 'data: 'r>(&self, target: &mut RenderTarget<'r, 'data>) { target .with_node(&self.root_layout) .fill(match self.state { ButtonState::Idle => ColourChoice::Background, ButtonState::Hovered => ColourChoice::Panel, ButtonState::Clicked => ColourChoice::Active, }) .border() .finish(); target .with_node(&self.label_layout) .simple_text(&self.text_cache, ColourChoice::Foreground) .finish(); } }