123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 |
- 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<C: Component> {
- layout: LayoutNode,
- label: Label<C>,
- state: ButtonState,
- hook: Option<Box<dyn Fn() -> Option<C::Msg>>>,
- }
- 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 {
- 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<dyn Fn() -> Option<C::Msg>>) {
- self.hook = Some(to);
- }
- }
- impl<C: Component> LayoutNodeContainer for Button<C> {
- fn layout_node(&self) -> &LayoutNode {
- &self.layout
- }
- fn layout_child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
- if ndx == 0 {
- Some(self.label.layout_node())
- } else {
- None
- }
- }
- fn layout_child_count(&self) -> usize {
- 1
- }
- }
- 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> {
- let mut result: Vec<<C as Component>::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<RenderFormat>) {
- 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);
- }
- }
|