1use crate::ext::{WidgetChildExt, WidgetLayoutExt};
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{LayoutNode, LayoutStyle, LengthPercentage, StyleNode};
6use maycoon_core::state::{State, Val};
7use maycoon_core::vg::kurbo::{Affine, Rect, RoundedRect, RoundedRectRadii, Vec2};
8use maycoon_core::vg::peniko::{Brush, Fill};
9use maycoon_core::vg::Scene;
10use maycoon_core::widget::Widget;
11use maycoon_core::window::{ElementState, MouseButton};
12use maycoon_theme::id::WidgetId;
13use maycoon_theme::theme::Theme;
14
15pub struct Button<S: State, W: Widget<S> + 'static> {
25 child: Val<S, W>,
26 state: ButtonState,
27 on_pressed: Box<dyn FnMut(&mut S) -> Update>,
28 layout_style: Val<S, LayoutStyle>,
29}
30
31impl<S: State, W: Widget<S> + 'static> Button<S, W> {
32 pub fn new(child: impl Into<Val<S, W>>) -> Self {
34 Self {
35 child: child.into(),
36 state: ButtonState::Idle,
37 on_pressed: Box::new(|_| Update::empty()),
38 layout_style: LayoutStyle {
39 padding: layout::Rect::<LengthPercentage> {
40 left: LengthPercentage::Length(12.0),
41 right: LengthPercentage::Length(12.0),
42 top: LengthPercentage::Length(2.0),
43 bottom: LengthPercentage::Length(10.0),
44 },
45 ..Default::default()
46 }
47 .into(),
48 }
49 }
50
51 pub fn with_on_pressed(mut self, on_pressed: impl FnMut(&mut S) -> Update + 'static) -> Self {
53 self.on_pressed = Box::new(on_pressed);
54 self
55 }
56}
57
58impl<S: State, W: Widget<S>> WidgetChildExt<S, W> for Button<S, W> {
59 fn set_child(&mut self, child: impl Into<Val<S, W>>) {
60 self.child = child.into();
61 }
62}
63
64impl<S: State, W: Widget<S>> WidgetLayoutExt<S> for Button<S, W> {
65 fn set_layout_style(&mut self, layout_style: impl Into<Val<S, LayoutStyle>>) {
66 self.layout_style = layout_style.into();
67 }
68}
69
70impl<S: State, W: Widget<S>> Widget<S> for Button<S, W> {
71 fn render(
72 &mut self,
73 scene: &mut Scene,
74 theme: &mut dyn Theme,
75 info: &AppInfo,
76 layout_node: &LayoutNode,
77 state: &S,
78 ) {
79 let brush = if let Some(style) = theme.of(self.widget_id()) {
80 match self.state {
81 ButtonState::Idle => Brush::Solid(style.get_color("color_idle").unwrap()),
82 ButtonState::Hovered => Brush::Solid(style.get_color("color_hovered").unwrap()),
83 ButtonState::Pressed => Brush::Solid(style.get_color("color_pressed").unwrap()),
84 ButtonState::Released => Brush::Solid(style.get_color("color_hovered").unwrap()),
85 }
86 } else {
87 Brush::Solid(match self.state {
88 ButtonState::Idle => theme.defaults().interactive().inactive(),
89 ButtonState::Hovered => theme.defaults().interactive().hover(),
90 ButtonState::Pressed => theme.defaults().interactive().active(),
91 ButtonState::Released => theme.defaults().interactive().hover(),
92 })
93 };
94
95 scene.fill(
96 Fill::NonZero,
97 Affine::default(),
98 &brush,
99 None,
100 &RoundedRect::from_rect(
101 Rect::new(
102 layout_node.layout.location.x as f64,
103 layout_node.layout.location.y as f64,
104 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
105 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
106 ),
107 RoundedRectRadii::from_single_radius(10.0),
108 ),
109 );
110
111 {
112 theme.globals_mut().invert_text_color = true;
113
114 let mut child_scene = Scene::new();
115
116 self.child.get_mut(state).render(
117 &mut child_scene,
118 theme,
119 info,
120 &layout_node.children[0],
121 state,
122 );
123
124 scene.append(
125 &child_scene,
126 Some(Affine::translate(Vec2::new(
127 layout_node.layout.location.x as f64,
128 layout_node.layout.location.y as f64,
129 ))),
130 );
131
132 theme.globals_mut().invert_text_color = false;
133 }
134 }
135
136 fn layout_style(&mut self, state: &S) -> StyleNode {
137 StyleNode {
138 style: self.layout_style.get_ref(state).clone(),
139 children: vec![self.child.get_mut(state).layout_style(state)],
140 }
141 }
142
143 fn update(&mut self, layout: &LayoutNode, state: &mut S, info: &AppInfo) -> Update {
144 self.layout_style.invalidate();
145 self.child.invalidate();
146
147 let mut update = Update::empty();
148 let old_state = self.state;
149
150 if let Some(cursor) = info.cursor_pos {
152 if cursor.x as f32 >= layout.layout.location.x
153 && cursor.x as f32 <= layout.layout.location.x + layout.layout.size.width
154 && cursor.y as f32 >= layout.layout.location.y
155 && cursor.y as f32 <= layout.layout.location.y + layout.layout.size.height
156 {
157 if self.state != ButtonState::Pressed {
159 self.state = ButtonState::Hovered;
160 }
161
162 for (_, btn, el) in &info.buttons {
164 if *btn == MouseButton::Left {
165 match el {
166 ElementState::Pressed => {
167 self.state = ButtonState::Pressed;
168 },
169
170 ElementState::Released => {
172 self.state = ButtonState::Released;
173 update |= (self.on_pressed)(state);
174 },
175 }
176 }
177 }
178 } else {
179 self.state = ButtonState::Idle;
181 }
182 } else {
183 self.state = ButtonState::Idle;
185 }
186
187 if old_state != self.state {
189 update |= Update::DRAW;
190 }
191
192 update
193 }
194
195 fn widget_id(&self) -> WidgetId {
196 WidgetId::new("maycoon-widgets", "Button")
197 }
198}
199
200#[derive(Copy, Clone, Eq, PartialEq, Debug)]
202pub enum ButtonState {
203 Idle,
205 Hovered,
207 Pressed,
209 Released,
212}