123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- 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<C: Component> {
- root_layout: LayoutNode,
- label_layout: LeafLayoutNode,
- text_cache: TextCache,
- label: String,
- state: ButtonState,
- hook: Option<Box<dyn Fn() -> Option<C::Msg>>>,
- }
- struct RootTag;
- impl<C: Component> Button<C> {
- 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<dyn Fn() -> Option<C::Msg>>) {
- self.hook = Some(to);
- }
- }
- impl<C: Component> LayoutTreeNode<RootTag> for Button<C> {
- fn current_node(&self) -> &LayoutNode {
- &self.root_layout
- }
- fn child_count(&self) -> usize {
- 1
- }
- fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
- if ndx == 0 {
- Some(self.label_layout.access())
- } else {
- None
- }
- }
- }
- impl<C: Component> Widget<C> for Button<C> {
- fn poll(
- &mut self,
- _uih: &mut UIHandle,
- input_state: Option<&crate::input::InputState>,
- ) -> Vec<<C as Component>::Msg> {
- self.text_cache
- .update_node_size_hint(&mut *self.label_layout);
- let mut result: Vec<<C as Component>::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();
- }
- }
|