1use alloc::boxed::Box;
2
3use crate::{
4 align::Alignment,
5 block::{Block, Border, BorderRadius},
6 color::UiColor,
7 el::{El, ElId},
8 event::{Capture, CommonEvent, Event, EventResponse, Propagate},
9 layout::{Layout, Viewport},
10 padding::Padding,
11 render::Renderer,
12 size::{Length, Size},
13 state::{self, StateNode, StateTag},
14 style::component_style,
15 ui::UiCtx,
16 widget::Widget,
17};
18
19struct ButtonState {
21 is_pressed: bool,
22}
23
24impl Default for ButtonState {
25 fn default() -> Self {
26 Self { is_pressed: false }
27 }
28}
29
30#[derive(Clone, Copy)]
31pub enum ButtonStatus {
32 Normal,
33 Focused,
34 Pressed,
35 }
38
39component_style! {
42 pub ButtonStyle: ButtonStyler(ButtonStatus) {
43 background: background,
44 border: border,
45 }
46}
47
48pub struct Button<'a, Message, R, E, S>
49where
50 R: Renderer,
51 E: Event,
52 S: ButtonStyler<R::Color>,
53{
54 id: ElId,
55 content: El<'a, Message, R, E, S>,
56 size: Size<Length>,
57 padding: Padding,
58 class: S::Class<'a>,
59 on_press: Option<Message>,
60}
61
62impl<'a, Message, R, E, S> Button<'a, Message, R, E, S>
63where
64 Message: Clone,
65 R: Renderer,
66 E: Event,
67 S: ButtonStyler<R::Color>,
68{
69 pub fn new(content: impl Into<El<'a, Message, R, E, S>>) -> Self {
70 let content = content.into();
71 let padding = Padding::default();
72 Self {
75 id: ElId::unique(),
76 content,
77 size: Size::fill(),
78 padding,
79 class: S::default(),
80 on_press: None,
81 }
82 }
83
84 pub fn width(mut self, width: impl Into<Length>) -> Self {
85 self.size.width = width.into();
86 self
87 }
88
89 pub fn height(mut self, height: impl Into<Length>) -> Self {
90 self.size.height = height.into();
91 self
92 }
93
94 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
95 self.padding = padding.into();
96 self
97 }
98
99 pub fn on_press(mut self, on_press: impl Into<Message>) -> Self {
100 self.on_press = Some(on_press.into());
101 self
102 }
103
104 pub fn store_id(self, id: &mut ElId) -> Self {
105 *id = self.id;
106 self
107 }
108
109 pub fn identify(mut self, id: impl Into<ElId>) -> Self {
110 self.id = id.into();
111 self
112 }
113
114 fn status(&self, ctx: &UiCtx<Message>, state: &mut StateNode) -> ButtonStatus {
115 if state.get::<ButtonState>().is_pressed {
116 ButtonStatus::Pressed
117 } else if ctx.is_focused(self) {
118 ButtonStatus::Focused
119 } else {
120 ButtonStatus::Normal
121 }
122 }
123}
124
125impl<'a, Message, R, E, S> Widget<Message, R, E, S> for Button<'a, Message, R, E, S>
126where
127 Message: Clone,
128 R: Renderer,
129 E: Event,
130 S: ButtonStyler<R::Color>,
131{
132 fn id(&self) -> Option<ElId> {
133 Some(self.id)
134 }
135
136 fn tree_ids(&self) -> alloc::vec::Vec<ElId> {
137 let mut ids = vec![self.id];
138 ids.extend(self.content.tree_ids());
139 ids
140 }
141
142 fn size(&self) -> Size<Length> {
143 self.size
144 }
145
146 fn state_tag(&self) -> StateTag {
147 StateTag::of::<ButtonState>()
148 }
149
150 fn state(&self) -> state::State {
151 state::State::new(ButtonState::default())
152 }
153
154 fn state_children(&self) -> alloc::vec::Vec<state::StateNode> {
155 vec![StateNode::new(&self.content)]
156 }
157
158 fn on_event(
159 &mut self,
160 ctx: &mut UiCtx<Message>,
161 event: E,
162 state: &mut StateNode,
163 ) -> EventResponse<E> {
164 match self.content.on_event(ctx, event.clone(), &mut state.children[0])? {
165 Propagate::Ignored => match event.as_common() {
166 Some(common) => match common {
167 CommonEvent::FocusMove(_) if ctx.is_focused(self) => {
169 Propagate::BubbleUp(self.id, event).into()
170 },
171 CommonEvent::FocusClickDown if ctx.is_focused(self) => {
172 state.get_mut::<ButtonState>().is_pressed = true;
173
174 Capture::Captured.into()
175 },
176 CommonEvent::FocusClickUp if ctx.is_focused(self) => {
177 let pressed = state.get::<ButtonState>().is_pressed;
183
184 state.get_mut::<ButtonState>().is_pressed = false;
185
186 if pressed {
187 if let Some(on_press) = self.on_press.clone() {
188 ctx.publish(on_press);
189 return Capture::Captured.into();
190 }
191 }
192
193 Propagate::Ignored.into()
194 },
195 CommonEvent::FocusClickDown
196 | CommonEvent::FocusClickUp
197 | CommonEvent::FocusMove(_) => {
198 state.get_mut::<ButtonState>().is_pressed = false;
200
201 Propagate::Ignored.into()
202 },
203 },
204 None => Propagate::Ignored.into(),
205 },
206 bubbled @ Propagate::BubbleUp(..) => bubbled.into(),
207 }
208 }
209
210 fn layout(
211 &self,
212 ctx: &mut UiCtx<Message>,
213 state: &mut StateNode,
214 styler: &S,
215 limits: &crate::layout::Limits,
216 viewport: &Viewport,
217 ) -> crate::layout::LayoutNode {
218 let style = styler.style(&self.class, self.status(ctx, state));
219
220 Layout::container(
221 limits,
222 self.size,
223 crate::layout::Position::Relative,
224 viewport,
225 self.padding,
226 style.border.width,
227 Alignment::Start,
228 Alignment::Start,
229 |limits| self.content.layout(ctx, &mut state.children[0], styler, limits, viewport),
230 )
231 }
232
233 fn draw(
234 &self,
235 ctx: &mut UiCtx<Message>,
236 state: &mut StateNode,
237 renderer: &mut R,
238 styler: &S,
239 layout: Layout,
240 ) {
241 let bounds = layout.bounds();
242
243 let style = styler.style(&self.class, self.status(ctx, state));
244
245 renderer.block(Block {
246 border: style.border,
247 rect: bounds.into(),
248 background: style.background,
249 });
250
251 self.content.draw(
252 ctx,
253 &mut state.children[0],
254 renderer,
255 styler,
256 layout.children().next().unwrap(),
257 )
258 }
259}
260
261impl<'a, Message, R, E, S> From<Button<'a, Message, R, E, S>> for El<'a, Message, R, E, S>
262where
263 Message: Clone + 'a,
264 R: Renderer + 'a,
265 E: Event + 'a,
266 S: ButtonStyler<R::Color> + 'a,
267{
268 fn from(value: Button<'a, Message, R, E, S>) -> Self {
269 Self::new(value)
270 }
271}