embedded_ui/kit/
button.rs

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
19// TODO: Double-click (needs time source)
20struct 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    // Disabled,
36    // Hovered,
37}
38
39// pub type ButtonStyleFn<'a, C> = Box<dyn Fn(ButtonStatus) -> ButtonStyle<C> + 'a>;
40
41component_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        // let size = content.size();
73
74        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                    // Tell parent that this child is the currently focused so parent can use it as an offset of focus
168                    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                        // Button was clicked only if
178                        // - Focus wasn't moved
179                        // - Focus button was down on it
180                        // - Focus button released on it
181
182                        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                        // Reset pressed state on click on other element
199                        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}