iced_native/widget/
button.rs

1//! Allow your users to perform actions by pressing a button.
2//!
3//! A [`Button`] has some local [`State`].
4use crate::event::{self, Event};
5use crate::layout;
6use crate::mouse;
7use crate::overlay;
8use crate::renderer;
9use crate::touch;
10use crate::widget::tree::{self, Tree};
11use crate::widget::Operation;
12use crate::{
13    Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
14    Rectangle, Shell, Vector, Widget,
15};
16
17pub use iced_style::button::{Appearance, StyleSheet};
18
19/// A generic widget that produces a message when pressed.
20///
21/// ```
22/// # type Button<'a, Message> =
23/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
24/// #
25/// #[derive(Clone)]
26/// enum Message {
27///     ButtonPressed,
28/// }
29///
30/// let button = Button::new("Press me!").on_press(Message::ButtonPressed);
31/// ```
32///
33/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
34/// be disabled:
35///
36/// ```
37/// # type Button<'a, Message> =
38/// #     iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
39/// #
40/// #[derive(Clone)]
41/// enum Message {
42///     ButtonPressed,
43/// }
44///
45/// fn disabled_button<'a>() -> Button<'a, Message> {
46///     Button::new("I'm disabled!")
47/// }
48///
49/// fn enabled_button<'a>() -> Button<'a, Message> {
50///     disabled_button().on_press(Message::ButtonPressed)
51/// }
52/// ```
53#[allow(missing_debug_implementations)]
54pub struct Button<'a, Message, Renderer>
55where
56    Renderer: crate::Renderer,
57    Renderer::Theme: StyleSheet,
58{
59    content: Element<'a, Message, Renderer>,
60    on_press: Option<Message>,
61    width: Length,
62    height: Length,
63    padding: Padding,
64    style: <Renderer::Theme as StyleSheet>::Style,
65}
66
67impl<'a, Message, Renderer> Button<'a, Message, Renderer>
68where
69    Renderer: crate::Renderer,
70    Renderer::Theme: StyleSheet,
71{
72    /// Creates a new [`Button`] with the given content.
73    pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
74        Button {
75            content: content.into(),
76            on_press: None,
77            width: Length::Shrink,
78            height: Length::Shrink,
79            padding: Padding::new(5.0),
80            style: <Renderer::Theme as StyleSheet>::Style::default(),
81        }
82    }
83
84    /// Sets the width of the [`Button`].
85    pub fn width(mut self, width: impl Into<Length>) -> Self {
86        self.width = width.into();
87        self
88    }
89
90    /// Sets the height of the [`Button`].
91    pub fn height(mut self, height: impl Into<Length>) -> Self {
92        self.height = height.into();
93        self
94    }
95
96    /// Sets the [`Padding`] of the [`Button`].
97    pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
98        self.padding = padding.into();
99        self
100    }
101
102    /// Sets the message that will be produced when the [`Button`] is pressed.
103    ///
104    /// Unless `on_press` is called, the [`Button`] will be disabled.
105    pub fn on_press(mut self, msg: Message) -> Self {
106        self.on_press = Some(msg);
107        self
108    }
109
110    /// Sets the style variant of this [`Button`].
111    pub fn style(
112        mut self,
113        style: <Renderer::Theme as StyleSheet>::Style,
114    ) -> Self {
115        self.style = style;
116        self
117    }
118}
119
120impl<'a, Message, Renderer> Widget<Message, Renderer>
121    for Button<'a, Message, Renderer>
122where
123    Message: 'a + Clone,
124    Renderer: 'a + crate::Renderer,
125    Renderer::Theme: StyleSheet,
126{
127    fn tag(&self) -> tree::Tag {
128        tree::Tag::of::<State>()
129    }
130
131    fn state(&self) -> tree::State {
132        tree::State::new(State::new())
133    }
134
135    fn children(&self) -> Vec<Tree> {
136        vec![Tree::new(&self.content)]
137    }
138
139    fn diff(&self, tree: &mut Tree) {
140        tree.diff_children(std::slice::from_ref(&self.content))
141    }
142
143    fn width(&self) -> Length {
144        self.width
145    }
146
147    fn height(&self) -> Length {
148        self.height
149    }
150
151    fn layout(
152        &self,
153        renderer: &Renderer,
154        limits: &layout::Limits,
155    ) -> layout::Node {
156        layout(
157            renderer,
158            limits,
159            self.width,
160            self.height,
161            self.padding,
162            |renderer, limits| {
163                self.content.as_widget().layout(renderer, limits)
164            },
165        )
166    }
167
168    fn operate(
169        &self,
170        tree: &mut Tree,
171        layout: Layout<'_>,
172        renderer: &Renderer,
173        operation: &mut dyn Operation<Message>,
174    ) {
175        operation.container(None, &mut |operation| {
176            self.content.as_widget().operate(
177                &mut tree.children[0],
178                layout.children().next().unwrap(),
179                renderer,
180                operation,
181            );
182        });
183    }
184
185    fn on_event(
186        &mut self,
187        tree: &mut Tree,
188        event: Event,
189        layout: Layout<'_>,
190        cursor_position: Point,
191        renderer: &Renderer,
192        clipboard: &mut dyn Clipboard,
193        shell: &mut Shell<'_, Message>,
194    ) -> event::Status {
195        if let event::Status::Captured = self.content.as_widget_mut().on_event(
196            &mut tree.children[0],
197            event.clone(),
198            layout.children().next().unwrap(),
199            cursor_position,
200            renderer,
201            clipboard,
202            shell,
203        ) {
204            return event::Status::Captured;
205        }
206
207        update(
208            event,
209            layout,
210            cursor_position,
211            shell,
212            &self.on_press,
213            || tree.state.downcast_mut::<State>(),
214        )
215    }
216
217    fn draw(
218        &self,
219        tree: &Tree,
220        renderer: &mut Renderer,
221        theme: &Renderer::Theme,
222        _style: &renderer::Style,
223        layout: Layout<'_>,
224        cursor_position: Point,
225        _viewport: &Rectangle,
226    ) {
227        let bounds = layout.bounds();
228        let content_layout = layout.children().next().unwrap();
229
230        let styling = draw(
231            renderer,
232            bounds,
233            cursor_position,
234            self.on_press.is_some(),
235            theme,
236            &self.style,
237            || tree.state.downcast_ref::<State>(),
238        );
239
240        self.content.as_widget().draw(
241            &tree.children[0],
242            renderer,
243            theme,
244            &renderer::Style {
245                text_color: styling.text_color,
246            },
247            content_layout,
248            cursor_position,
249            &bounds,
250        );
251    }
252
253    fn mouse_interaction(
254        &self,
255        _tree: &Tree,
256        layout: Layout<'_>,
257        cursor_position: Point,
258        _viewport: &Rectangle,
259        _renderer: &Renderer,
260    ) -> mouse::Interaction {
261        mouse_interaction(layout, cursor_position, self.on_press.is_some())
262    }
263
264    fn overlay<'b>(
265        &'b mut self,
266        tree: &'b mut Tree,
267        layout: Layout<'_>,
268        renderer: &Renderer,
269    ) -> Option<overlay::Element<'b, Message, Renderer>> {
270        self.content.as_widget_mut().overlay(
271            &mut tree.children[0],
272            layout.children().next().unwrap(),
273            renderer,
274        )
275    }
276}
277
278impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
279    for Element<'a, Message, Renderer>
280where
281    Message: Clone + 'a,
282    Renderer: crate::Renderer + 'a,
283    Renderer::Theme: StyleSheet,
284{
285    fn from(button: Button<'a, Message, Renderer>) -> Self {
286        Self::new(button)
287    }
288}
289
290/// The local state of a [`Button`].
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
292pub struct State {
293    is_pressed: bool,
294}
295
296impl State {
297    /// Creates a new [`State`].
298    pub fn new() -> State {
299        State::default()
300    }
301}
302
303/// Processes the given [`Event`] and updates the [`State`] of a [`Button`]
304/// accordingly.
305pub fn update<'a, Message: Clone>(
306    event: Event,
307    layout: Layout<'_>,
308    cursor_position: Point,
309    shell: &mut Shell<'_, Message>,
310    on_press: &Option<Message>,
311    state: impl FnOnce() -> &'a mut State,
312) -> event::Status {
313    match event {
314        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
315        | Event::Touch(touch::Event::FingerPressed { .. }) => {
316            if on_press.is_some() {
317                let bounds = layout.bounds();
318
319                if bounds.contains(cursor_position) {
320                    let state = state();
321
322                    state.is_pressed = true;
323
324                    return event::Status::Captured;
325                }
326            }
327        }
328        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
329        | Event::Touch(touch::Event::FingerLifted { .. }) => {
330            if let Some(on_press) = on_press.clone() {
331                let state = state();
332
333                if state.is_pressed {
334                    state.is_pressed = false;
335
336                    let bounds = layout.bounds();
337
338                    if bounds.contains(cursor_position) {
339                        shell.publish(on_press);
340                    }
341
342                    return event::Status::Captured;
343                }
344            }
345        }
346        Event::Touch(touch::Event::FingerLost { .. }) => {
347            let state = state();
348
349            state.is_pressed = false;
350        }
351        _ => {}
352    }
353
354    event::Status::Ignored
355}
356
357/// Draws a [`Button`].
358pub fn draw<'a, Renderer: crate::Renderer>(
359    renderer: &mut Renderer,
360    bounds: Rectangle,
361    cursor_position: Point,
362    is_enabled: bool,
363    style_sheet: &dyn StyleSheet<
364        Style = <Renderer::Theme as StyleSheet>::Style,
365    >,
366    style: &<Renderer::Theme as StyleSheet>::Style,
367    state: impl FnOnce() -> &'a State,
368) -> Appearance
369where
370    Renderer::Theme: StyleSheet,
371{
372    let is_mouse_over = bounds.contains(cursor_position);
373
374    let styling = if !is_enabled {
375        style_sheet.disabled(style)
376    } else if is_mouse_over {
377        let state = state();
378
379        if state.is_pressed {
380            style_sheet.pressed(style)
381        } else {
382            style_sheet.hovered(style)
383        }
384    } else {
385        style_sheet.active(style)
386    };
387
388    if styling.background.is_some() || styling.border_width > 0.0 {
389        if styling.shadow_offset != Vector::default() {
390            // TODO: Implement proper shadow support
391            renderer.fill_quad(
392                renderer::Quad {
393                    bounds: Rectangle {
394                        x: bounds.x + styling.shadow_offset.x,
395                        y: bounds.y + styling.shadow_offset.y,
396                        ..bounds
397                    },
398                    border_radius: styling.border_radius.into(),
399                    border_width: 0.0,
400                    border_color: Color::TRANSPARENT,
401                },
402                Background::Color([0.0, 0.0, 0.0, 0.5].into()),
403            );
404        }
405
406        renderer.fill_quad(
407            renderer::Quad {
408                bounds,
409                border_radius: styling.border_radius.into(),
410                border_width: styling.border_width,
411                border_color: styling.border_color,
412            },
413            styling
414                .background
415                .unwrap_or(Background::Color(Color::TRANSPARENT)),
416        );
417    }
418
419    styling
420}
421
422/// Computes the layout of a [`Button`].
423pub fn layout<Renderer>(
424    renderer: &Renderer,
425    limits: &layout::Limits,
426    width: Length,
427    height: Length,
428    padding: Padding,
429    layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
430) -> layout::Node {
431    let limits = limits.width(width).height(height);
432
433    let mut content = layout_content(renderer, &limits.pad(padding));
434    let padding = padding.fit(content.size(), limits.max());
435    let size = limits.pad(padding).resolve(content.size()).pad(padding);
436
437    content.move_to(Point::new(padding.left, padding.top));
438
439    layout::Node::with_children(size, vec![content])
440}
441
442/// Returns the [`mouse::Interaction`] of a [`Button`].
443pub fn mouse_interaction(
444    layout: Layout<'_>,
445    cursor_position: Point,
446    is_enabled: bool,
447) -> mouse::Interaction {
448    let is_mouse_over = layout.bounds().contains(cursor_position);
449
450    if is_mouse_over && is_enabled {
451        mouse::Interaction::Pointer
452    } else {
453        mouse::Interaction::default()
454    }
455}