conrod_core/widget/
toggle.rs

1//! A button that allows for toggling boolean state.
2
3use position::{self, Align};
4use text;
5use widget;
6use {Borderable, Color, Colorable, FontSize, Labelable, Positionable, Scalar, Widget};
7
8/// A pressable widget for toggling the state of a bool.
9///
10/// Like the Button widget, it's reaction is triggered upon release and will return the new bool
11/// state.
12///
13/// Note that the Toggle will not mutate the bool for you, you should do this yourself within the
14/// react function.
15#[derive(Clone, WidgetCommon_)]
16pub struct Toggle<'a> {
17    #[conrod(common_builder)]
18    common: widget::CommonBuilder,
19    value: bool,
20    maybe_label: Option<&'a str>,
21    style: Style,
22    /// If true, will allow user inputs. If false, will disallow user inputs.
23    pub enabled: bool,
24}
25
26/// Styling for the Toggle including coloring, bordering and labelling.
27#[derive(Copy, Clone, Debug, Default, PartialEq, WidgetStyle_)]
28pub struct Style {
29    /// Color of the Toggle's pressable area.
30    #[conrod(default = "theme.shape_color")]
31    pub color: Option<Color>,
32    /// The width of the rectangular border surrounding the Toggle.
33    #[conrod(default = "theme.border_width")]
34    pub border: Option<Scalar>,
35    /// The color of the Toggle's border.
36    #[conrod(default = "theme.border_color")]
37    pub border_color: Option<Color>,
38    /// The color of the Toggle's Text label.
39    #[conrod(default = "theme.label_color")]
40    pub label_color: Option<Color>,
41    /// The font size for the Toggle's Text label.
42    #[conrod(default = "theme.font_size_medium")]
43    pub label_font_size: Option<FontSize>,
44    /// The ID of the font used to display the label.
45    #[conrod(default = "theme.font_id")]
46    pub label_font_id: Option<Option<text::font::Id>>,
47    /// The position of the title bar's `Label` widget over the *x* axis.
48    #[conrod(default = "position::Relative::Align(Align::Middle)")]
49    pub label_x: Option<position::Relative>,
50    /// The position of the title bar's `Label` widget over the *y* axis.
51    #[conrod(default = "position::Relative::Align(Align::Middle)")]
52    pub label_y: Option<position::Relative>,
53}
54
55widget_ids! {
56    struct Ids {
57        rectangle,
58        label,
59    }
60}
61
62/// The state of the Toggle.
63pub struct State {
64    ids: Ids,
65}
66
67/// The `Event` type yielded by the `Toggle` widget.
68///
69/// Implements `Iterator` yielding a `bool` indicating the new state for each time the `Toggle` was
70/// clicked with the left mouse button since the last update.
71#[derive(Clone, Debug)]
72#[allow(missing_copy_implementations)]
73pub struct TimesClicked {
74    state: bool,
75    count: u16,
76}
77
78impl Iterator for TimesClicked {
79    type Item = bool;
80    fn next(&mut self) -> Option<Self::Item> {
81        if self.count > 0 {
82            self.count -= 1;
83            self.state = !self.state;
84            Some(self.state)
85        } else {
86            None
87        }
88    }
89}
90
91impl<'a> Toggle<'a> {
92    /// Construct a new Toggle widget.
93    pub fn new(value: bool) -> Toggle<'a> {
94        Toggle {
95            common: widget::CommonBuilder::default(),
96            style: Style::default(),
97            maybe_label: None,
98            value: value,
99            enabled: true,
100        }
101    }
102
103    /// Specify the font used for displaying the label.
104    pub fn label_font_id(mut self, font_id: text::font::Id) -> Self {
105        self.style.label_font_id = Some(Some(font_id));
106        self
107    }
108
109    /// Specify the label's position relatively to `Toggle` along the *x* axis.
110    pub fn label_x(mut self, x: position::Relative) -> Self {
111        self.style.label_x = Some(x);
112        self
113    }
114
115    /// Specify the label's position relatively to `Toggle` along the *y* axis.
116    pub fn label_y(mut self, y: position::Relative) -> Self {
117        self.style.label_y = Some(y);
118        self
119    }
120
121    builder_methods! {
122        pub enabled { enabled = bool }
123    }
124}
125
126impl<'a> Widget for Toggle<'a> {
127    type State = State;
128    type Style = Style;
129    type Event = TimesClicked;
130
131    fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
132        State {
133            ids: Ids::new(id_gen),
134        }
135    }
136
137    fn style(&self) -> Self::Style {
138        self.style.clone()
139    }
140
141    /// Update the state of the Toggle.
142    fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
143        let widget::UpdateArgs {
144            id,
145            state,
146            style,
147            rect,
148            ui,
149            ..
150        } = args;
151        let Toggle {
152            value,
153            enabled,
154            maybe_label,
155            ..
156        } = self;
157
158        let times_clicked = TimesClicked {
159            state: value,
160            count: if enabled {
161                let input = ui.widget_input(id);
162                (input.clicks().left().count() + input.taps().count()) as u16
163            } else {
164                0
165            },
166        };
167
168        // BorderedRectangle widget.
169        let dim = rect.dim();
170        let border = style.border(ui.theme());
171        let color = {
172            let color = style.color(ui.theme());
173            let new_value = times_clicked.clone().last().unwrap_or(value);
174            let color = if new_value {
175                color
176            } else {
177                color.with_luminance(0.1)
178            };
179            match ui.widget_input(id).mouse() {
180                Some(mouse) => {
181                    if mouse.buttons.left().is_down() {
182                        color.clicked()
183                    } else {
184                        color.highlighted()
185                    }
186                }
187                None => color,
188            }
189        };
190        let border_color = style.border_color(ui.theme());
191        widget::BorderedRectangle::new(dim)
192            .middle_of(id)
193            .graphics_for(id)
194            .color(color)
195            .border(border)
196            .border_color(border_color)
197            .set(state.ids.rectangle, ui);
198
199        // Label widget.
200        if let Some(label) = maybe_label {
201            let color = style.label_color(ui.theme());
202            let font_size = style.label_font_size(ui.theme());
203            let font_id = style.label_font_id(&ui.theme).or(ui.fonts.ids().next());
204            let x = style.label_x(&ui.theme);
205            let y = style.label_y(&ui.theme);
206            widget::Text::new(label)
207                .and_then(font_id, widget::Text::font_id)
208                .x_position_relative_to(id, x)
209                .y_position_relative_to(id, y)
210                .graphics_for(id)
211                .color(color)
212                .font_size(font_size)
213                .set(state.ids.label, ui);
214        }
215
216        times_clicked
217    }
218}
219
220impl<'a> Colorable for Toggle<'a> {
221    builder_method!(color { style.color = Some(Color) });
222}
223
224impl<'a> Borderable for Toggle<'a> {
225    builder_methods! {
226        border { style.border = Some(Scalar) }
227        border_color { style.border_color = Some(Color) }
228    }
229}
230
231impl<'a> Labelable<'a> for Toggle<'a> {
232    builder_methods! {
233        label { maybe_label = Some(&'a str) }
234        label_color { style.label_color = Some(Color) }
235        label_font_size { style.label_font_size = Some(FontSize) }
236    }
237}