button.rs 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. use kahlo::math::PixelSideOffsets;
  2. use crate::{
  3. input::{MouseButton, MouseCheckStatus},
  4. layout::{
  5. HorizontalAlignment, LayoutNode, LayoutNodeAccess, LayoutTreeNode, LeafLayoutNode,
  6. SizePolicy, VerticalAlignment,
  7. },
  8. render::{ColourChoice, RenderTarget, TextCache},
  9. ui::UIHandle,
  10. Component,
  11. };
  12. use super::Widget;
  13. #[derive(Clone, Copy, PartialEq)]
  14. enum ButtonState {
  15. Idle,
  16. Hovered,
  17. Clicked,
  18. }
  19. pub struct Button<C: Component> {
  20. root_layout: LayoutNode,
  21. label_layout: LeafLayoutNode,
  22. text_cache: TextCache,
  23. label: String,
  24. state: ButtonState,
  25. hook: Option<Box<dyn Fn() -> Option<C::Msg>>>,
  26. }
  27. struct RootTag;
  28. impl<C: Component> Button<C> {
  29. pub fn new(uih: &UIHandle) -> Self {
  30. let mut layout = uih.new_layout_node();
  31. layout
  32. .set_width_policy(SizePolicy::expanding(1))
  33. .set_height_policy(SizePolicy {
  34. minimum: uih.theme().ui_font_size as usize,
  35. desired: (uih.theme().ui_font_size * 1.5) as usize,
  36. slack_weight: 0,
  37. })
  38. .set_valign(VerticalAlignment::Centre)
  39. .set_halign(HorizontalAlignment::Centre);
  40. *layout.margin_mut() = PixelSideOffsets::new_all_same(uih.theme().border_width as i32);
  41. Self {
  42. root_layout: layout,
  43. label_layout: {
  44. let mut node = uih.new_layout_node();
  45. node.set_width_policy(SizePolicy::fixed(0))
  46. .set_height_policy(SizePolicy::fixed(0))
  47. .set_halign(HorizontalAlignment::Centre)
  48. .set_valign(VerticalAlignment::Centre);
  49. node.into()
  50. },
  51. text_cache: TextCache::new_with_font_and_size(
  52. uih.theme().ui_font.clone(),
  53. uih.theme().ui_font_size,
  54. ),
  55. label: "".to_string(),
  56. state: ButtonState::Idle,
  57. hook: None,
  58. }
  59. }
  60. pub fn with_label(mut self, label: &str) -> Self {
  61. self.set_label(label);
  62. self
  63. }
  64. pub fn set_label(&mut self, label: &str) {
  65. self.label.clear();
  66. self.label += label;
  67. self.text_cache.update(&self.label);
  68. }
  69. pub fn set_hook(&mut self, to: Box<dyn Fn() -> Option<C::Msg>>) {
  70. self.hook = Some(to);
  71. }
  72. }
  73. impl<C: Component> LayoutTreeNode<RootTag> for Button<C> {
  74. fn current_node(&self) -> &LayoutNode {
  75. &self.root_layout
  76. }
  77. fn child_count(&self) -> usize {
  78. 1
  79. }
  80. fn child(&self, ndx: usize) -> Option<LayoutNodeAccess<'_>> {
  81. if ndx == 0 {
  82. Some(self.label_layout.access())
  83. } else {
  84. None
  85. }
  86. }
  87. }
  88. impl<C: Component> Widget<C> for Button<C> {
  89. fn poll(
  90. &mut self,
  91. _uih: &mut UIHandle,
  92. input_state: Option<&crate::input::InputState>,
  93. ) -> Vec<<C as Component>::Msg> {
  94. self.text_cache
  95. .update_node_size_hint(&mut *self.label_layout);
  96. let mut result: Vec<<C as Component>::Msg> = vec![];
  97. if let Some((istate, area)) = input_state.zip(self.root_layout.render_area()) {
  98. let saved = self.state;
  99. match istate.mouse_status_in(area, MouseButton::Left) {
  100. MouseCheckStatus::Idle | MouseCheckStatus::Leave => {
  101. self.state = ButtonState::Idle;
  102. }
  103. MouseCheckStatus::Click | MouseCheckStatus::Hold => {
  104. self.state = ButtonState::Clicked;
  105. }
  106. MouseCheckStatus::Enter | MouseCheckStatus::Hover => {
  107. self.state = ButtonState::Hovered;
  108. }
  109. MouseCheckStatus::Release => {
  110. self.state = ButtonState::Hovered;
  111. result.extend(self.hook.as_ref().and_then(|v| v()));
  112. }
  113. }
  114. if self.state != saved {
  115. self.root_layout.render_needed();
  116. }
  117. }
  118. result
  119. }
  120. fn layout_node(&self) -> LayoutNodeAccess {
  121. LayoutNodeAccess::new(self)
  122. }
  123. fn layout_node_mut(&mut self) -> &mut LayoutNode {
  124. &mut self.root_layout
  125. }
  126. fn render<'r, 'data: 'r>(&self, target: &mut RenderTarget<'r, 'data>) {
  127. target
  128. .with_node(&self.root_layout)
  129. .fill(match self.state {
  130. ButtonState::Idle => ColourChoice::Background,
  131. ButtonState::Hovered => ColourChoice::Panel,
  132. ButtonState::Clicked => ColourChoice::Active,
  133. })
  134. .border()
  135. .finish();
  136. target
  137. .with_node(&self.label_layout)
  138. .simple_text(&self.text_cache, ColourChoice::Foreground)
  139. .finish();
  140. }
  141. }