From a97401aed2a173260a4abfdb65a77975ce6c0f01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Sep 2019 19:16:06 +0200 Subject: Rethink workspace structure --- src/widget/checkbox.rs | 203 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/widget/checkbox.rs (limited to 'src/widget/checkbox.rs') diff --git a/src/widget/checkbox.rs b/src/widget/checkbox.rs new file mode 100644 index 00000000..c60807fd --- /dev/null +++ b/src/widget/checkbox.rs @@ -0,0 +1,203 @@ +//! Show toggle controls using checkboxes. +use std::hash::Hash; + +use crate::input::{mouse, ButtonState}; +use crate::widget::{text, Column, Row, Text}; +use crate::{ + Align, Element, Event, Hasher, Layout, MouseCursor, Node, Point, Rectangle, + Widget, +}; + +/// A box that can be checked, with a generic text `Color`. +/// +/// It implements [`Widget`] when the associated `Renderer` implements the +/// [`checkbox::Renderer`] trait. +/// +/// [`Widget`]: ../trait.Widget.html +/// [`checkbox::Renderer`]: trait.Renderer.html +/// +/// # Example +/// +/// ``` +/// use iced::Checkbox; +/// +/// #[derive(Debug, Clone, Copy)] +/// pub enum Color { +/// Black, +/// } +/// +/// pub enum Message { +/// CheckboxToggled(bool), +/// } +/// +/// let is_checked = true; +/// +/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled) +/// .label_color(Color::Black); +/// ``` +/// +/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) +pub struct Checkbox { + is_checked: bool, + on_toggle: Box Message>, + label: String, + label_color: Option, +} + +impl std::fmt::Debug for Checkbox +where + Color: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Checkbox") + .field("is_checked", &self.is_checked) + .field("label", &self.label) + .field("label_color", &self.label_color) + .finish() + } +} + +impl Checkbox { + /// Creates a new [`Checkbox`]. + /// + /// It expects: + /// * a boolean describing whether the [`Checkbox`] is checked or not + /// * the label of the [`Checkbox`] + /// * a function that will be called when the [`Checkbox`] is toggled. + /// It will receive the new state of the [`Checkbox`] and must produce + /// a `Message`. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn new(is_checked: bool, label: &str, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Checkbox { + is_checked, + on_toggle: Box::new(f), + label: String::from(label), + label_color: None, + } + } + + /// Sets the `Color` of the label of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn label_color(mut self, color: Color) -> Self { + self.label_color = Some(color); + self + } +} + +impl Widget + for Checkbox +where + Color: 'static + Copy + std::fmt::Debug, + Renderer: self::Renderer + text::Renderer, +{ + fn node(&self, renderer: &Renderer) -> Node { + Row::<(), Renderer>::new() + .spacing(15) + .align_items(Align::Center) + .push(Column::new().width(28).height(28)) + .push(Text::new(&self.label)) + .node(renderer) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + ) { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) => { + let mouse_over = layout + .children() + .any(|child| child.bounds().contains(cursor_position)); + + if mouse_over { + messages.push((self.on_toggle)(!self.is_checked)); + } + } + _ => {} + } + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> MouseCursor { + let children: Vec<_> = layout.children().collect(); + + let text_bounds = children[1].bounds(); + + text::Renderer::draw( + renderer, + text_bounds, + &self.label, + None, + self.label_color, + text::HorizontalAlignment::Left, + text::VerticalAlignment::Top, + ); + + self::Renderer::draw( + renderer, + cursor_position, + children[0].bounds(), + text_bounds, + self.is_checked, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.label.hash(state); + } +} + +/// The renderer of a [`Checkbox`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Checkbox`] in your user interface. +/// +/// [`Checkbox`]: struct.Checkbox.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer { + /// Draws a [`Checkbox`]. + /// + /// It receives: + /// * the current cursor position + /// * the bounds of the [`Checkbox`] + /// * the bounds of the label of the [`Checkbox`] + /// * whether the [`Checkbox`] is checked or not + /// + /// [`Checkbox`]: struct.Checkbox.html + fn draw( + &mut self, + cursor_position: Point, + bounds: Rectangle, + label_bounds: Rectangle, + is_checked: bool, + ) -> MouseCursor; +} + +impl<'a, Color, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Color: 'static + Copy + std::fmt::Debug, + Renderer: self::Renderer + text::Renderer, + Message: 'static, +{ + fn from( + checkbox: Checkbox, + ) -> Element<'a, Message, Renderer> { + Element::new(checkbox) + } +} -- cgit