pixel_widgets/widget/
button.rs

1use std::mem::replace;
2
3use smallvec::smallvec;
4
5use crate::draw::*;
6use crate::event::{Event, Key};
7use crate::layout::{Rectangle, Size};
8use crate::node::{GenericNode, IntoNode, Node};
9use crate::style::{StyleState, Stylesheet};
10use crate::widget::{Context, StateVec, Widget};
11
12/// A clickable button
13pub struct Button<'a, T> {
14    content: Option<Node<'a, T>>,
15    on_clicked: Option<T>,
16}
17
18/// State for [`Button`](struct.Button.html)
19#[allow(missing_docs)]
20pub enum State {
21    Idle,
22    Hover,
23    Pressed,
24    Disabled,
25}
26
27impl<'a, T: 'a> Default for Button<'a, T> {
28    fn default() -> Self {
29        Self {
30            content: None,
31            on_clicked: None,
32        }
33    }
34}
35
36impl<'a, T: 'a> Button<'a, T> {
37    /// Construct a new button
38    pub fn new<C: IntoNode<'a, T> + 'a>(content: C) -> Self {
39        Self {
40            content: Some(content.into_node()),
41            on_clicked: None,
42        }
43    }
44
45    /// Sets the message to be posted when this button is clicked.
46    pub fn on_clicked(mut self, message: T) -> Self {
47        self.on_clicked = Some(message);
48        self
49    }
50
51    /// Sets the content of the button to be a paragraph of text.
52    pub fn text(mut self, text: impl Into<String> + 'a) -> Self {
53        self.content = Some(text.into_node());
54        self
55    }
56
57    /// Sets the content of the button from an iterator.
58    /// Note that only the first element will be taken.
59    pub fn extend<I: IntoIterator<Item = N>, N: IntoNode<'a, T>>(mut self, iter: I) -> Self {
60        if self.content.is_none() {
61            self.content = iter.into_iter().next().map(IntoNode::into_node);
62        }
63        self
64    }
65
66    fn content(&self) -> &Node<'a, T> {
67        self.content.as_ref().expect("content of `Button` must be set")
68    }
69
70    fn content_mut(&mut self) -> &mut Node<'a, T> {
71        self.content.as_mut().expect("content of `Button` must be set")
72    }
73}
74
75impl<'a, T: 'a + Send> Widget<'a, T> for Button<'a, T> {
76    type State = State;
77
78    fn mount(&self) -> State {
79        State::Idle
80    }
81
82    fn widget(&self) -> &'static str {
83        "button"
84    }
85
86    fn state(&self, state: &State) -> StateVec {
87        match state {
88            State::Idle => StateVec::new(),
89            State::Hover => smallvec![StyleState::Hover],
90            State::Pressed => smallvec![StyleState::Pressed],
91            State::Disabled => smallvec![StyleState::Disabled],
92        }
93    }
94
95    fn len(&self) -> usize {
96        1
97    }
98
99    fn visit_children(&mut self, visitor: &mut dyn FnMut(&mut dyn GenericNode<'a, T>)) {
100        visitor(&mut **self.content_mut());
101    }
102
103    fn size(&self, _: &State, style: &Stylesheet) -> (Size, Size) {
104        style
105            .background
106            .resolve_size((style.width, style.height), self.content().size(), style.padding)
107    }
108
109    fn event(
110        &mut self,
111        state: &mut State,
112        layout: Rectangle,
113        clip: Rectangle,
114        _: &Stylesheet,
115        event: Event,
116        context: &mut Context<T>,
117    ) {
118        match event {
119            Event::Cursor(x, y) => {
120                *state = match replace(state, State::Idle) {
121                    State::Idle => {
122                        if layout.point_inside(x, y) && clip.point_inside(x, y) {
123                            context.redraw();
124                            State::Hover
125                        } else {
126                            State::Idle
127                        }
128                    }
129                    State::Hover => {
130                        if layout.point_inside(x, y) && clip.point_inside(x, y) {
131                            State::Hover
132                        } else {
133                            context.redraw();
134                            State::Idle
135                        }
136                    }
137                    State::Pressed => {
138                        if layout.point_inside(x, y) && clip.point_inside(x, y) {
139                            State::Pressed
140                        } else {
141                            context.redraw();
142                            State::Idle
143                        }
144                    }
145                    State::Disabled => State::Disabled,
146                };
147            }
148
149            Event::Press(Key::LeftMouseButton) => {
150                *state = match replace(state, State::Idle) {
151                    State::Hover => {
152                        context.redraw();
153                        State::Pressed
154                    }
155                    other => other,
156                };
157            }
158
159            Event::Release(Key::LeftMouseButton) => {
160                *state = match replace(state, State::Idle) {
161                    State::Pressed => {
162                        context.redraw();
163                        context.extend(self.on_clicked.take());
164                        State::Hover
165                    }
166                    other => other,
167                };
168            }
169
170            _ => (),
171        }
172    }
173
174    fn draw(&mut self, _: &mut State, layout: Rectangle, clip: Rectangle, style: &Stylesheet) -> Vec<Primitive<'a>> {
175        let content_rect = style.background.content_rect(layout, style.padding);
176
177        style
178            .background
179            .render(layout)
180            .into_iter()
181            .chain(self.content_mut().draw(content_rect, clip).into_iter())
182            .collect()
183    }
184}
185
186impl<'a, T: 'a + Send> IntoNode<'a, T> for Button<'a, T> {
187    fn into_node(self) -> Node<'a, T> {
188        Node::from_widget(self)
189    }
190}
191
192impl Default for State {
193    fn default() -> Self {
194        State::Idle
195    }
196}