diff options
74 files changed, 3504 insertions, 2111 deletions
diff --git a/core/src/align.rs b/core/src/align.rs index d6915086..05bd5e63 100644 --- a/core/src/align.rs +++ b/core/src/align.rs @@ -1,10 +1,4 @@ -/// Alignment on the cross axis of a container. -/// -/// * On a [`Column`], it describes __horizontal__ alignment. -/// * On a [`Row`], it describes __vertical__ alignment. -/// -/// [`Column`]: widget/struct.Column.html -/// [`Row`]: widget/struct.Row.html +/// Alignment on an unspecified axis of a container. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Align { /// Align at the start of the cross axis. @@ -16,3 +10,29 @@ pub enum Align { /// Align at the end of the cross axis. End, } + +/// The horizontal alignment of some resource. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HorizontalAlignment { + /// Align left + Left, + + /// Horizontally centered + Center, + + /// Align right + Right, +} + +/// The vertical alignment of some resource. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VerticalAlignment { + /// Align top + Top, + + /// Vertically centered + Center, + + /// Align bottom + Bottom, +} diff --git a/core/src/background.rs b/core/src/background.rs index 59b67a2c..98047172 100644 --- a/core/src/background.rs +++ b/core/src/background.rs @@ -1,7 +1,9 @@ use crate::Color; +/// The background of some element. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Background { + /// A solid color Color(Color), // TODO: Add gradient and image variants } diff --git a/core/src/color.rs b/core/src/color.rs index 150bd05b..ec48c185 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -25,6 +25,9 @@ impl Color { a: 1.0, }; + /// Converts the [`Color`] into its linear values. + /// + /// [`Color`]: struct.Color.html pub fn into_linear(self) -> [f32; 4] { // As described in: // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation diff --git a/core/src/command.rs b/core/src/command.rs index e1c865dd..14b48b5b 100644 --- a/core/src/command.rs +++ b/core/src/command.rs @@ -1,16 +1,30 @@ use futures::future::{BoxFuture, Future, FutureExt}; +/// A collection of async operations. +/// +/// You should be able to turn a future easily into a [`Command`], either by +/// using the `From` trait or [`Command::perform`]. +/// +/// [`Command`]: struct.Command.html pub struct Command<T> { futures: Vec<BoxFuture<'static, T>>, } impl<T> Command<T> { + /// Creates an empty [`Command`]. + /// + /// In other words, a [`Command`] that does nothing. + /// + /// [`Command`]: struct.Command.html pub fn none() -> Self { Self { futures: Vec::new(), } } + /// Creates a [`Command`] that performs the action of the given future. + /// + /// [`Command`]: struct.Command.html pub fn perform<A>( future: impl Future<Output = T> + 'static + Send, f: impl Fn(T) -> A + 'static + Send, @@ -20,12 +34,21 @@ impl<T> Command<T> { } } + /// Creates a [`Command`] that performs the actions of all the givens + /// futures. + /// + /// Once this command is run, all the futures will be exectued at once. + /// + /// [`Command`]: struct.Command.html pub fn batch(commands: impl Iterator<Item = Command<T>>) -> Self { Self { futures: commands.flat_map(|command| command.futures).collect(), } } + /// Converts a [`Command`] into its underlying list of futures. + /// + /// [`Command`]: struct.Command.html pub fn futures(self) -> Vec<BoxFuture<'static, T>> { self.futures } diff --git a/core/src/font.rs b/core/src/font.rs index 75ba6a72..be49c825 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -1,8 +1,18 @@ +/// A font. #[derive(Debug, Clone, Copy)] pub enum Font { + /// The default font. + /// + /// This is normally a font configured in a renderer or loaded from the + /// system. Default, + + /// An external font. External { + /// The name of the external font name: &'static str, + + /// The bytes of the external font bytes: &'static [u8], }, } diff --git a/core/src/length.rs b/core/src/length.rs index 73c227d8..63ba6207 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -1,12 +1,24 @@ /// The strategy used to fill space in a specific dimension. #[derive(Debug, Clone, Copy, PartialEq, Hash)] pub enum Length { + /// Fill all the remaining space Fill, + + /// Fill the least amount of space Shrink, + + /// Fill a fixed amount of space Units(u16), } impl Length { + /// Returns the _fill factor_ of the [`Length`]. + /// + /// The _fill factor_ is a relative unit describing how much of the + /// remaining space should be filled when compared to other elements. It + /// is only meant to be used by layout engines. + /// + /// [`Length`]: enum.Length.html pub fn fill_factor(&self) -> u16 { match self { Length::Fill => 1, diff --git a/core/src/lib.rs b/core/src/lib.rs index 3816f8a2..6c36e683 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,24 +1,40 @@ -pub mod widget; +//! The core library of [Iced]. +//! +//! This library holds basic types that can be reused and re-exported in +//! different runtime implementations. For instance, both [`iced_native`] and +//! [`iced_web`] are built on top of `iced_core`. +//! +//!  +//! +//! [Iced]: https://github.com/hecrj/iced +//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native +//! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![deny(rust_2018_idioms)] mod align; mod background; mod color; -#[cfg(feature = "command")] -mod command; mod font; mod length; mod point; mod rectangle; mod vector; -pub use align::Align; +pub use align::{Align, HorizontalAlignment, VerticalAlignment}; pub use background::Background; pub use color::Color; -#[cfg(feature = "command")] -pub use command::Command; pub use font::Font; pub use length::Length; pub use point::Point; pub use rectangle::Rectangle; pub use vector::Vector; -pub use widget::*; + +#[cfg(feature = "command")] +mod command; + +#[cfg(feature = "command")] +pub use command::Command; diff --git a/core/src/vector.rs b/core/src/vector.rs index 92bf64ff..7d87343a 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -1,7 +1,14 @@ /// A 2D vector. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Vector<T = f32> { + /// The X component of the [`Vector`] + /// + /// [`Vector`]: struct.Vector.html pub x: T, + + /// The Y component of the [`Vector`] + /// + /// [`Vector`]: struct.Vector.html pub y: T, } diff --git a/core/src/widget.rs b/core/src/widget.rs deleted file mode 100644 index 9e629e4f..00000000 --- a/core/src/widget.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Use the essential widgets. -//! -//! # Re-exports -//! For convenience, the contents of this module are available at the root -//! module. Therefore, you can directly type: -//! -//! ``` -//! use iced_core::{button, Button}; -//! ``` -mod checkbox; -mod column; -mod container; -mod image; -mod radio; -mod row; - -pub mod button; -pub mod scrollable; -pub mod slider; -pub mod text; -pub mod text_input; - -#[doc(no_inline)] -pub use button::Button; -#[doc(no_inline)] -pub use scrollable::Scrollable; -#[doc(no_inline)] -pub use slider::Slider; -#[doc(no_inline)] -pub use text::Text; -#[doc(no_inline)] -pub use text_input::TextInput; - -pub use checkbox::Checkbox; -pub use column::Column; -pub use container::Container; -pub use image::Image; -pub use radio::Radio; -pub use row::Row; diff --git a/core/src/widget/button.rs b/core/src/widget/button.rs deleted file mode 100644 index e7961284..00000000 --- a/core/src/widget/button.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Allow your users to perform actions by pressing a button. -//! -//! A [`Button`] has some local [`State`]. -//! -//! [`Button`]: struct.Button.html -//! [`State`]: struct.State.html - -use crate::{Background, Length}; - -/// A generic widget that produces a message when clicked. -pub struct Button<'a, Message, Element> { - /// The current state of the button - pub state: &'a mut State, - - pub content: Element, - - /// The message to produce when the button is pressed - pub on_press: Option<Message>, - - pub width: Length, - - pub min_width: u32, - - pub padding: u16, - - pub background: Option<Background>, - - pub border_radius: u16, -} - -impl<'a, Message, Element> std::fmt::Debug for Button<'a, Message, Element> -where - Message: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Button") - .field("state", &self.state) - .field("on_press", &self.on_press) - .finish() - } -} - -impl<'a, Message, Element> Button<'a, Message, Element> { - /// Creates a new [`Button`] with some local [`State`] and the given label. - /// - /// [`Button`]: struct.Button.html - /// [`State`]: struct.State.html - pub fn new<E>(state: &'a mut State, content: E) -> Self - where - E: Into<Element>, - { - Button { - state, - content: content.into(), - on_press: None, - width: Length::Shrink, - min_width: 0, - padding: 0, - background: None, - border_radius: 0, - } - } - - /// Sets the width of the [`Button`]. - /// - /// [`Button`]: struct.Button.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - pub fn min_width(mut self, min_width: u32) -> Self { - self.min_width = min_width; - self - } - - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; - self - } - - pub fn background(mut self, background: Background) -> Self { - self.background = Some(background); - self - } - - pub fn border_radius(mut self, border_radius: u16) -> Self { - self.border_radius = border_radius; - self - } - - /// Sets the message that will be produced when the [`Button`] is pressed. - /// - /// [`Button`]: struct.Button.html - pub fn on_press(mut self, msg: Message) -> Self { - self.on_press = Some(msg); - self - } -} - -/// The local state of a [`Button`]. -/// -/// [`Button`]: struct.Button.html -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - pub is_pressed: bool, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() - } - - /// Returns whether the associated [`Button`] is currently being pressed or - /// not. - /// - /// [`Button`]: struct.Button.html - pub fn is_pressed(&self) -> bool { - self.is_pressed - } -} diff --git a/core/src/widget/checkbox.rs b/core/src/widget/checkbox.rs deleted file mode 100644 index 1f0a0c04..00000000 --- a/core/src/widget/checkbox.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Show toggle controls using checkboxes. -use crate::Color; - -/// A box that can be checked. -/// -/// # Example -/// -/// ``` -/// use iced_core::Checkbox; -/// -/// pub enum Message { -/// CheckboxToggled(bool), -/// } -/// -/// let is_checked = true; -/// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); -/// ``` -/// -///  -pub struct Checkbox<Message> { - /// Whether the checkbox is checked or not - pub is_checked: bool, - - /// Function to call when checkbox is toggled to produce a __message__. - /// - /// The function should be provided `true` when the checkbox is checked - /// and `false` otherwise. - pub on_toggle: Box<dyn Fn(bool) -> Message>, - - /// The label of the checkbox - pub label: String, - - /// The color of the label - pub label_color: Option<Color>, -} - -impl<Message> std::fmt::Debug for Checkbox<Message> { - 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<Message> Checkbox<Message> { - /// 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<F>(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<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); - self - } -} diff --git a/core/src/widget/column.rs b/core/src/widget/column.rs deleted file mode 100644 index 4a97ad98..00000000 --- a/core/src/widget/column.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::{Align, Length}; - -use std::u32; - -/// A container that distributes its contents vertically. -/// -/// A [`Column`] will try to fill the horizontal space of its container. -/// -/// [`Column`]: struct.Column.html -pub struct Column<Element> { - pub spacing: u16, - pub padding: u16, - pub width: Length, - pub height: Length, - pub max_width: u32, - pub max_height: u32, - pub align_items: Align, - pub children: Vec<Element>, -} - -impl<Element> Column<Element> { - /// Creates an empty [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn new() -> Self { - Column { - spacing: 0, - padding: 0, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the padding of the [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; - self - } - - /// Sets the width of the [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Column`] in pixels. - /// - /// [`Column`]: struct.Column.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Column`] . - /// - /// [`Column`]: struct.Column.html - pub fn align_items(mut self, align: Align) -> Self { - self.align_items = align; - self - } - - /// Adds an element to the [`Column`]. - /// - /// [`Column`]: struct.Column.html - pub fn push<E>(mut self, child: E) -> Column<Element> - where - E: Into<Element>, - { - self.children.push(child.into()); - self - } -} - -impl<Element> Default for Column<Element> { - fn default() -> Self { - Self::new() - } -} - -impl<Element> std::fmt::Debug for Column<Element> -where - Element: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Complete once stabilized - f.debug_struct("Column") - .field("spacing", &self.spacing) - .field("children", &self.children) - .finish() - } -} diff --git a/core/src/widget/container.rs b/core/src/widget/container.rs deleted file mode 100644 index 9bc92fe0..00000000 --- a/core/src/widget/container.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{Align, Length}; - -use std::u32; - -#[derive(Debug)] -pub struct Container<Element> { - pub width: Length, - pub height: Length, - pub max_width: u32, - pub max_height: u32, - pub horizontal_alignment: Align, - pub vertical_alignment: Align, - pub content: Element, -} - -impl<Element> Container<Element> { - /// Creates an empty [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn new<T>(content: T) -> Self - where - T: Into<Element>, - { - Container { - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - horizontal_alignment: Align::Start, - vertical_alignment: Align::Start, - content: content.into(), - } - } - - /// Sets the width of the [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Container`] in pixels. - /// - /// [`Container`]: struct.Container.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Centers the contents in the horizontal axis of the [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn center_x(mut self) -> Self { - self.horizontal_alignment = Align::Center; - - self - } - - /// Centers the contents in the vertical axis of the [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn center_y(mut self) -> Self { - self.vertical_alignment = Align::Center; - - self - } -} diff --git a/core/src/widget/image.rs b/core/src/widget/image.rs deleted file mode 100644 index 996ab5e1..00000000 --- a/core/src/widget/image.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! Display images in your user interface. - -use crate::{Length, Rectangle}; - -/// A frame that displays an image while keeping aspect ratio. -/// -/// # Example -/// -/// ``` -/// use iced_core::Image; -/// -/// let image = Image::new("resources/ferris.png"); -/// ``` -#[derive(Debug)] -pub struct Image { - /// The image path - pub path: String, - - /// The part of the image to show - pub clip: Option<Rectangle<u16>>, - - /// The width of the image - pub width: Length, - - /// The height of the image - pub height: Length, -} - -impl Image { - /// Creates a new [`Image`] with the given path. - /// - /// [`Image`]: struct.Image.html - pub fn new<T: Into<String>>(path: T) -> Self { - Image { - path: path.into(), - clip: None, - width: Length::Shrink, - height: Length::Shrink, - } - } - - /// Sets the portion of the [`Image`] to draw. - /// - /// [`Image`]: struct.Image.html - pub fn clip(mut self, clip: Rectangle<u16>) -> Self { - self.clip = Some(clip); - self - } - - /// Sets the width of the [`Image`] boundaries. - /// - /// [`Image`]: struct.Image.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Image`] boundaries. - /// - /// [`Image`]: struct.Image.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } -} diff --git a/core/src/widget/radio.rs b/core/src/widget/radio.rs deleted file mode 100644 index 9765e928..00000000 --- a/core/src/widget/radio.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Create choices using radio buttons. -use crate::Color; - -/// A circular button representing a choice. -/// -/// # Example -/// ``` -/// use iced_core::Radio; -/// -/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -/// pub enum Choice { -/// A, -/// B, -/// } -/// -/// #[derive(Debug, Clone, Copy)] -/// pub enum Message { -/// RadioSelected(Choice), -/// } -/// -/// let selected_choice = Some(Choice::A); -/// -/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); -/// -/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); -/// ``` -/// -///  -pub struct Radio<Message> { - /// Whether the radio button is selected or not - pub is_selected: bool, - - /// The message to produce when the radio button is clicked - pub on_click: Message, - - /// The label of the radio button - pub label: String, - - /// The color of the label - pub label_color: Option<Color>, -} - -impl<Message> std::fmt::Debug for Radio<Message> -where - Message: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Radio") - .field("is_selected", &self.is_selected) - .field("on_click", &self.on_click) - .field("label", &self.label) - .field("label_color", &self.label_color) - .finish() - } -} - -impl<Message> Radio<Message> { - /// Creates a new [`Radio`] button. - /// - /// It expects: - /// * the value related to the [`Radio`] button - /// * the label of the [`Radio`] button - /// * the current selected value - /// * a function that will be called when the [`Radio`] is selected. It - /// receives the value of the radio and must produce a `Message`. - /// - /// [`Radio`]: struct.Radio.html - pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self - where - V: Eq + Copy, - F: 'static + Fn(V) -> Message, - { - Radio { - is_selected: Some(value) == selected, - on_click: f(value), - label: String::from(label), - label_color: None, - } - } - - /// Sets the `Color` of the label of the [`Radio`]. - /// - /// [`Radio`]: struct.Radio.html - pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { - self.label_color = Some(color.into()); - self - } -} diff --git a/core/src/widget/row.rs b/core/src/widget/row.rs deleted file mode 100644 index 3d882b47..00000000 --- a/core/src/widget/row.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::{Align, Length}; - -use std::u32; - -/// A container that distributes its contents horizontally. -/// -/// A [`Row`] will try to fill the horizontal space of its container. -/// -/// [`Row`]: struct.Row.html -pub struct Row<Element> { - pub spacing: u16, - pub padding: u16, - pub width: Length, - pub height: Length, - pub max_width: u32, - pub max_height: u32, - pub align_items: Align, - pub children: Vec<Element>, -} - -impl<Element> Row<Element> { - /// Creates an empty [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn new() -> Self { - Row { - spacing: 0, - padding: 0, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } - } - - /// Sets the horizontal spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.spacing = units; - self - } - - /// Sets the padding of the [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; - self - } - - /// Sets the width of the [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.max_width = max_width; - self - } - - /// Sets the maximum height of the [`Row`]. - /// - /// [`Row`]: struct.Row.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the vertical alignment of the contents of the [`Row`] . - /// - /// [`Row`]: struct.Row.html - pub fn align_items(mut self, align: Align) -> Self { - self.align_items = align; - self - } - - /// Adds an [`Element`] to the [`Row`]. - /// - /// [`Element`]: ../struct.Element.html - /// [`Row`]: struct.Row.html - pub fn push<E>(mut self, child: E) -> Row<Element> - where - E: Into<Element>, - { - self.children.push(child.into()); - self - } -} - -impl<Element> std::fmt::Debug for Row<Element> -where - Element: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Complete once stabilized - f.debug_struct("Row") - .field("spacing", &self.spacing) - .field("children", &self.children) - .finish() - } -} diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs deleted file mode 100644 index 7f2f0e99..00000000 --- a/core/src/widget/scrollable.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::{Align, Column, Length, Point, Rectangle}; - -use std::u32; - -#[derive(Debug)] -pub struct Scrollable<'a, Element> { - pub state: &'a mut State, - pub height: Length, - pub max_height: u32, - pub content: Column<Element>, -} - -impl<'a, Element> Scrollable<'a, Element> { - pub fn new(state: &'a mut State) -> Self { - Scrollable { - state, - height: Length::Shrink, - max_height: u32::MAX, - content: Column::new(), - } - } - - /// Sets the vertical spacing _between_ elements. - /// - /// Custom margins per element do not exist in Iced. You should use this - /// method instead! While less flexible, it helps you keep spacing between - /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { - self.content = self.content.spacing(units); - self - } - - /// Sets the padding of the [`Scrollable`]. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn padding(mut self, units: u16) -> Self { - self.content = self.content.padding(units); - self - } - - /// Sets the width of the [`Scrollable`]. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn width(mut self, width: Length) -> Self { - self.content = self.content.width(width); - self - } - - /// Sets the height of the [`Scrollable`]. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the maximum width of the [`Scrollable`]. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn max_width(mut self, max_width: u32) -> Self { - self.content = self.content.max_width(max_width); - self - } - - /// Sets the maximum height of the [`Scrollable`] in pixels. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn max_height(mut self, max_height: u32) -> Self { - self.max_height = max_height; - self - } - - /// Sets the horizontal alignment of the contents of the [`Scrollable`] . - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn align_items(mut self, align_items: Align) -> Self { - self.content = self.content.align_items(align_items); - self - } - - /// Adds an element to the [`Scrollable`]. - /// - /// [`Scrollable`]: struct.Scrollable.html - pub fn push<E>(mut self, child: E) -> Scrollable<'a, Element> - where - E: Into<Element>, - { - self.content = self.content.push(child); - self - } -} - -#[derive(Debug, Clone, Copy, Default)] -pub struct State { - pub scrollbar_grabbed_at: Option<Point>, - offset: u32, -} - -impl State { - pub fn new() -> Self { - State::default() - } - - pub fn scroll( - &mut self, - delta_y: f32, - bounds: Rectangle, - content_bounds: Rectangle, - ) { - if bounds.height >= content_bounds.height { - return; - } - - self.offset = (self.offset as i32 - delta_y.round() as i32) - .max(0) - .min((content_bounds.height - bounds.height) as i32) - as u32; - } - - pub fn scroll_to( - &mut self, - percentage: f32, - bounds: Rectangle, - content_bounds: Rectangle, - ) { - self.offset = ((content_bounds.height - bounds.height) * percentage) - .max(0.0) as u32; - } - - pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 { - let hidden_content = - (content_bounds.height - bounds.height).max(0.0).round() as u32; - - self.offset.min(hidden_content) - } - - pub fn is_scrollbar_grabbed(&self) -> bool { - self.scrollbar_grabbed_at.is_some() - } -} diff --git a/core/src/widget/slider.rs b/core/src/widget/slider.rs deleted file mode 100644 index b65e3991..00000000 --- a/core/src/widget/slider.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Display an interactive selector of a single value from a range of values. -//! -//! A [`Slider`] has some local [`State`]. -//! -//! [`Slider`]: struct.Slider.html -//! [`State`]: struct.State.html -use crate::Length; - -use std::ops::RangeInclusive; -use std::rc::Rc; - -/// An horizontal bar and a handle that selects a single value from a range of -/// values. -/// -/// A [`Slider`] will try to fill the horizontal space of its container. -/// -/// [`Slider`]: struct.Slider.html -/// -/// # Example -/// ``` -/// use iced_core::{slider, Slider}; -/// -/// pub enum Message { -/// SliderChanged(f32), -/// } -/// -/// let state = &mut slider::State::new(); -/// let value = 50.0; -/// -/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); -/// ``` -/// -///  -pub struct Slider<'a, Message> { - /// The state of the slider - pub state: &'a mut State, - - /// The range of the slider - pub range: RangeInclusive<f32>, - - /// The current value of the slider - pub value: f32, - - /// The function to produce messages on change - pub on_change: Rc<Box<dyn Fn(f32) -> Message>>, - - pub width: Length, -} - -impl<'a, Message> std::fmt::Debug for Slider<'a, Message> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Slider") - .field("state", &self.state) - .field("range", &self.range) - .field("value", &self.value) - .finish() - } -} - -impl<'a, Message> Slider<'a, Message> { - /// Creates a new [`Slider`]. - /// - /// It expects: - /// * the local [`State`] of the [`Slider`] - /// * an inclusive range of possible values - /// * the current value of the [`Slider`] - /// * a function that will be called when the [`Slider`] is dragged. - /// It receives the new value of the [`Slider`] and must produce a - /// `Message`. - /// - /// [`Slider`]: struct.Slider.html - /// [`State`]: struct.State.html - pub fn new<F>( - state: &'a mut State, - range: RangeInclusive<f32>, - value: f32, - on_change: F, - ) -> Self - where - F: 'static + Fn(f32) -> Message, - { - Slider { - state, - value: value.max(*range.start()).min(*range.end()), - range, - on_change: Rc::new(Box::new(on_change)), - width: Length::Fill, - } - } - - /// Sets the width of the [`Slider`]. - /// - /// [`Slider`]: struct.Slider.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } -} - -/// The local state of a [`Slider`]. -/// -/// [`Slider`]: struct.Slider.html -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct State { - pub is_dragging: bool, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() - } - - /// Returns whether the associated [`Slider`] is currently being dragged or - /// not. - /// - /// [`Slider`]: struct.Slider.html - pub fn is_dragging(&self) -> bool { - self.is_dragging - } -} diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs deleted file mode 100644 index 0996e7ff..00000000 --- a/core/src/widget/text.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Write some text for your users to read. -use crate::{Color, Font, Length}; - -/// A paragraph of text. -/// -/// # Example -/// -/// ``` -/// use iced_core::Text; -/// -/// Text::new("I <3 iced!") -/// .size(40); -/// ``` -#[derive(Debug, Clone)] -pub struct Text { - pub content: String, - pub size: Option<u16>, - pub color: Option<Color>, - pub font: Font, - pub width: Length, - pub height: Length, - pub horizontal_alignment: HorizontalAlignment, - pub vertical_alignment: VerticalAlignment, -} - -impl Text { - /// Create a new fragment of [`Text`] with the given contents. - /// - /// [`Text`]: struct.Text.html - pub fn new(label: &str) -> Self { - Text { - content: String::from(label), - size: None, - color: None, - font: Font::Default, - width: Length::Fill, - height: Length::Shrink, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Top, - } - } - - /// Sets the size of the [`Text`]. - /// - /// [`Text`]: struct.Text.html - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - /// Sets the `Color` of the [`Text`]. - /// - /// [`Text`]: struct.Text.html - pub fn color<C: Into<Color>>(mut self, color: C) -> Self { - self.color = Some(color.into()); - self - } - - pub fn font(mut self, font: Font) -> Self { - self.font = font; - self - } - - /// Sets the width of the [`Text`] boundaries. - /// - /// [`Text`]: struct.Text.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Text`] boundaries. - /// - /// [`Text`]: struct.Text.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } - - /// Sets the [`HorizontalAlignment`] of the [`Text`]. - /// - /// [`Text`]: struct.Text.html - /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html - pub fn horizontal_alignment( - mut self, - alignment: HorizontalAlignment, - ) -> Self { - self.horizontal_alignment = alignment; - self - } - - /// Sets the [`VerticalAlignment`] of the [`Text`]. - /// - /// [`Text`]: struct.Text.html - /// [`VerticalAlignment`]: enum.VerticalAlignment.html - pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self { - self.vertical_alignment = alignment; - self - } -} - -/// The horizontal alignment of some resource. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum HorizontalAlignment { - /// Align left - Left, - - /// Horizontally centered - Center, - - /// Align right - Right, -} - -/// The vertical alignment of some resource. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum VerticalAlignment { - /// Align top - Top, - - /// Vertically centered - Center, - - /// Align bottom - Bottom, -} diff --git a/core/src/widget/text_input.rs b/core/src/widget/text_input.rs deleted file mode 100644 index 16c67954..00000000 --- a/core/src/widget/text_input.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::Length; - -pub struct TextInput<'a, Message> { - pub state: &'a mut State, - pub placeholder: String, - pub value: Value, - pub width: Length, - pub max_width: Length, - pub padding: u16, - pub size: Option<u16>, - pub on_change: Box<dyn Fn(String) -> Message>, - pub on_submit: Option<Message>, -} - -impl<'a, Message> TextInput<'a, Message> { - pub fn new<F>( - state: &'a mut State, - placeholder: &str, - value: &str, - on_change: F, - ) -> Self - where - F: 'static + Fn(String) -> Message, - { - Self { - state, - placeholder: String::from(placeholder), - value: Value::new(value), - width: Length::Fill, - max_width: Length::Shrink, - padding: 0, - size: None, - on_change: Box::new(on_change), - on_submit: None, - } - } - - /// Sets the width of the [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the maximum width of the [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - pub fn max_width(mut self, max_width: Length) -> Self { - self.max_width = max_width; - self - } - - /// Sets the padding of the [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - pub fn padding(mut self, units: u16) -> Self { - self.padding = units; - self - } - - pub fn size(mut self, size: u16) -> Self { - self.size = Some(size); - self - } - - pub fn on_submit(mut self, message: Message) -> Self { - self.on_submit = Some(message); - self - } -} - -impl<'a, Message> std::fmt::Debug for TextInput<'a, Message> -where - Message: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // TODO: Complete once stabilized - f.debug_struct("TextInput").finish() - } -} - -#[derive(Debug, Default, Clone)] -pub struct State { - pub is_focused: bool, - cursor_position: usize, -} - -impl State { - pub fn new() -> Self { - Self::default() - } - - pub fn focused() -> Self { - use std::usize; - - Self { - is_focused: true, - cursor_position: usize::MAX, - } - } - - pub fn move_cursor_right(&mut self, value: &Value) { - let current = self.cursor_position(value); - - if current < value.len() { - self.cursor_position = current + 1; - } - } - - pub fn move_cursor_left(&mut self, value: &Value) { - let current = self.cursor_position(value); - - if current > 0 { - self.cursor_position = current - 1; - } - } - - pub fn move_cursor_to_end(&mut self, value: &Value) { - self.cursor_position = value.len(); - } - - pub fn cursor_position(&self, value: &Value) -> usize { - self.cursor_position.min(value.len()) - } -} - -// TODO: Use `unicode-segmentation` -pub struct Value(Vec<char>); - -impl Value { - pub fn new(string: &str) -> Self { - Self(string.chars().collect()) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn until(&self, index: usize) -> Self { - Self(self.0[..index.min(self.len())].to_vec()) - } - - pub fn to_string(&self) -> String { - use std::iter::FromIterator; - String::from_iter(self.0.iter()) - } - - pub fn insert(&mut self, index: usize, c: char) { - self.0.insert(index, c); - } - - pub fn remove(&mut self, index: usize) { - self.0.remove(index); - } -} diff --git a/examples/todos.rs b/examples/todos.rs index a1dfc5aa..a73c45ce 100644 --- a/examples/todos.rs +++ b/examples/todos.rs @@ -1,7 +1,7 @@ use iced::{ - button, scrollable, text::HorizontalAlignment, text_input, Align, - Application, Background, Button, Checkbox, Color, Column, Command, - Container, Element, Font, Length, Row, Scrollable, Text, TextInput, + button, scrollable, text_input, Align, Application, Background, Button, + Checkbox, Color, Column, Command, Container, Element, Font, + HorizontalAlignment, Length, Row, Scrollable, Text, TextInput, }; use serde::{Deserialize, Serialize}; @@ -549,7 +549,8 @@ impl SavedState { .map_err(|_| SaveError::WriteError)?; // This is a simple way to save at most once every couple seconds - // We will be able to get rid of it once we implement event subscriptions + // We will be able to get rid of it once we implement event + // subscriptions std::thread::sleep(std::time::Duration::from_secs(2)); Ok(()) diff --git a/examples/tour.rs b/examples/tour.rs index f5bb84d1..8dbae2b8 100644 --- a/examples/tour.rs +++ b/examples/tour.rs @@ -1,8 +1,7 @@ use iced::{ - button, scrollable, slider, text::HorizontalAlignment, text_input, - Application, Background, Button, Checkbox, Color, Column, Command, - Container, Element, Image, Length, Radio, Row, Scrollable, Slider, Text, - TextInput, + button, scrollable, slider, text_input, Background, Button, Checkbox, + Color, Column, Container, Element, HorizontalAlignment, Image, Length, + Radio, Row, Sandbox, Scrollable, Slider, Text, TextInput, }; pub fn main() { @@ -19,27 +18,24 @@ pub struct Tour { debug: bool, } -impl Application for Tour { +impl Sandbox for Tour { type Message = Message; - fn new() -> (Tour, Command<Message>) { - ( - Tour { - steps: Steps::new(), - scroll: scrollable::State::new(), - back_button: button::State::new(), - next_button: button::State::new(), - debug: false, - }, - Command::none(), - ) + fn new() -> Tour { + Tour { + steps: Steps::new(), + scroll: scrollable::State::new(), + back_button: button::State::new(), + next_button: button::State::new(), + debug: false, + } } fn title(&self) -> String { format!("{} - Iced", self.steps.title()) } - fn update(&mut self, event: Message) -> Command<Message> { + fn update(&mut self, event: Message) { match event { Message::BackPressed => { self.steps.go_back(); @@ -51,8 +47,6 @@ impl Application for Tour { self.steps.update(step_msg, &mut self.debug); } } - - Command::none() } fn view(&mut self) -> Element<Message> { diff --git a/native/src/element.rs b/native/src/element.rs index 791bf9cf..5335fdc1 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -17,14 +17,6 @@ pub struct Element<'a, Message, Renderer> { pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>, } -impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Element") - .field("widget", &self.widget) - .finish() - } -} - impl<'a, Message, Renderer> Element<'a, Message, Renderer> where Renderer: crate::Renderer, @@ -41,31 +33,6 @@ where } } - pub fn width(&self) -> Length { - self.widget.width() - } - - pub fn height(&self) -> Length { - self.widget.height() - } - - pub fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.widget.layout(renderer, limits) - } - - pub fn draw( - &self, - renderer: &mut Renderer, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - self.widget.draw(renderer, layout, cursor_position) - } - /// Applies a transformation to the produced message of the [`Element`]. /// /// This method is useful when you want to decouple different parts of your @@ -127,37 +94,7 @@ where /// # } /// # /// # mod iced_wgpu { - /// # use iced_native::{ - /// # text, row, layout, Text, Size, Point, Rectangle, Layout, Row - /// # }; - /// # pub struct Renderer; - /// # - /// # impl iced_native::Renderer for Renderer { type Output = (); } - /// # - /// # impl iced_native::row::Renderer for Renderer { - /// # fn draw<Message>( - /// # &mut self, - /// # _column: &Row<'_, Message, Self>, - /// # _layout: Layout<'_>, - /// # _cursor_position: Point, - /// # ) {} - /// # } - /// # - /// # impl text::Renderer for Renderer { - /// # fn layout( - /// # &self, - /// # _text: &Text, - /// # _limits: &layout::Limits, - /// # ) -> layout::Node { - /// # layout::Node::new(Size::ZERO) - /// # } - /// # - /// # fn draw( - /// # &mut self, - /// # _text: &Text, - /// # _layout: Layout<'_>, - /// # ) {} - /// # } + /// # pub use iced_native::renderer::Null as Renderer; /// # } /// # /// # use counter::Counter; @@ -263,6 +200,45 @@ where } } + /// Returns the width of the [`Element`]. + /// + /// [`Element`]: struct.Element.html + pub fn width(&self) -> Length { + self.widget.width() + } + + /// Returns the height of the [`Element`]. + /// + /// [`Element`]: struct.Element.html + pub fn height(&self) -> Length { + self.widget.height() + } + + /// Computes the layout of the [`Element`] in the given [`Limits`]. + /// + /// [`Element`]: struct.Element.html + /// [`Limits`]: layout/struct.Limits.html + pub fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.widget.layout(renderer, limits) + } + + /// Draws the [`Element`] and its children using the given [`Layout`]. + /// + /// [`Element`]: struct.Element.html + /// [`Layout`]: layout/struct.Layout.html + pub fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self.widget.draw(renderer, layout, cursor_position) + } + pub(crate) fn hash_layout(&self, state: &mut Hasher) { self.widget.hash_layout(state); } @@ -273,12 +249,6 @@ struct Map<'a, A, B, Renderer> { mapper: Box<dyn Fn(A) -> B>, } -impl<'a, A, B, Renderer> std::fmt::Debug for Map<'a, A, B, Renderer> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Map").field("widget", &self.widget).finish() - } -} - impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> { pub fn new<F>( widget: Box<dyn Widget<A, Renderer> + 'a>, @@ -358,17 +328,6 @@ struct Explain<'a, Message, Renderer: crate::Renderer> { color: Color, } -impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Explain") - .field("element", &self.element) - .finish() - } -} - impl<'a, Message, Renderer> Explain<'a, Message, Renderer> where Renderer: crate::Renderer, diff --git a/native/src/input/mouse/event.rs b/native/src/input/mouse/event.rs index 478f9b4d..aafc4fe3 100644 --- a/native/src/input/mouse/event.rs +++ b/native/src/input/mouse/event.rs @@ -34,11 +34,16 @@ pub enum Event { }, /// The mouse wheel was scrolled. - WheelScrolled { delta: ScrollDelta }, + WheelScrolled { + /// The scroll movement. + delta: ScrollDelta, + }, } +/// A scroll movement. #[derive(Debug, Clone, Copy, PartialEq)] pub enum ScrollDelta { + /// A line-based scroll movement Lines { /// The number of horizontal lines scrolled x: f32, @@ -46,6 +51,7 @@ pub enum ScrollDelta { /// The number of vertical lines scrolled y: f32, }, + /// A pixel-based scroll movement Pixels { /// The number of horizontal pixels scrolled x: f32, diff --git a/native/src/layout.rs b/native/src/layout.rs index 0a744346..e945706b 100644 --- a/native/src/layout.rs +++ b/native/src/layout.rs @@ -1,3 +1,4 @@ +//! Position your widgets properly. mod limits; mod node; @@ -8,6 +9,9 @@ pub use node::Node; use crate::{Point, Rectangle, Vector}; +/// The bounds of a [`Node`] and its children, using absolute coordinates. +/// +/// [`Node`]: struct.Node.html #[derive(Debug, Clone, Copy)] pub struct Layout<'a> { position: Point, @@ -28,6 +32,14 @@ impl<'a> Layout<'a> { } } + /// Gets the bounds of the [`Layout`]. + /// + /// The returned [`Rectangle`] describes the position and size of a + /// [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Rectangle`]: struct.Rectangle.html + /// [`Node`]: struct.Node.html pub fn bounds(&self) -> Rectangle { let bounds = self.node.bounds(); @@ -39,6 +51,10 @@ impl<'a> Layout<'a> { } } + /// Returns an iterator over the [`Layout`] of the children of a [`Node`]. + /// + /// [`Layout`]: struct.Layout.html + /// [`Node`]: struct.Node.html pub fn children(&'a self) -> impl Iterator<Item = Layout<'a>> { self.node.children().iter().map(move |node| { Layout::with_offset( diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs index 7a2b0d70..bc90553e 100644 --- a/native/src/layout/flex.rs +++ b/native/src/layout/flex.rs @@ -1,3 +1,4 @@ +//! Distribute elements using a flex-based layout. // This code is heavily inspired by the [`druid`] codebase. // // [`druid`]: https://github.com/xi-editor/druid @@ -20,9 +21,13 @@ use crate::{ Align, Element, Size, }; +/// The main axis of a flex layout. #[derive(Debug)] pub enum Axis { + /// The horizontal axis Horizontal, + + /// The vertical axis Vertical, } @@ -49,7 +54,12 @@ impl Axis { } } -// TODO: Remove `Message` type parameter +/// Computes the flex layout with the given axis and limits, applying spacing, +/// padding and alignment to the items as needed. +/// +/// It returns a new layout [`Node`]. +/// +/// [`Node`]: ../struct.Node.html pub fn resolve<Message, Renderer>( axis: Axis, renderer: &Renderer, @@ -57,7 +67,7 @@ pub fn resolve<Message, Renderer>( padding: f32, spacing: f32, align_items: Align, - children: &[Element<'_, Message, Renderer>], + items: &[Element<'_, Message, Renderer>], ) -> Node where Renderer: crate::Renderer, @@ -65,14 +75,14 @@ where let limits = limits.pad(padding); let mut total_non_fill = - spacing as f32 * (children.len() as i32 - 1).max(0) as f32; + spacing as f32 * (items.len() as i32 - 1).max(0) as f32; let mut fill_sum = 0; let mut cross = axis.cross(limits.min()); - let mut nodes: Vec<Node> = Vec::with_capacity(children.len()); - nodes.resize(children.len(), Node::default()); + let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); + nodes.resize(items.len(), Node::default()); - for (i, child) in children.iter().enumerate() { + for (i, child) in items.iter().enumerate() { let fill_factor = match axis { Axis::Horizontal => child.width(), Axis::Vertical => child.height(), @@ -97,7 +107,7 @@ where let available = axis.main(limits.max()); let remaining = (available - total_non_fill).max(0.0); - for (i, child) in children.iter().enumerate() { + for (i, child) in items.iter().enumerate() { let fill_factor = match axis { Axis::Horizontal => child.width(), Axis::Vertical => child.height(), diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index af269acd..2705a47d 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -1,5 +1,6 @@ use crate::{Length, Size}; +/// A set of size constraints for layouting. #[derive(Debug, Clone, Copy)] pub struct Limits { min: Size, @@ -8,12 +9,17 @@ pub struct Limits { } impl Limits { + /// No limits pub const NONE: Limits = Limits { min: Size::ZERO, max: Size::INFINITY, fill: Size::INFINITY, }; + /// Creates new [`Limits`] with the given minimum and maximum [`Size`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html pub fn new(min: Size, max: Size) -> Limits { Limits { min, @@ -22,14 +28,25 @@ impl Limits { } } + /// Returns the minimum [`Size`] of the [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html pub fn min(&self) -> Size { self.min } + /// Returns the maximum [`Size`] of the [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html pub fn max(&self) -> Size { self.max } + /// Applies a width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn width(mut self, width: Length) -> Limits { match width { Length::Shrink => { @@ -51,6 +68,9 @@ impl Limits { self } + /// Applies a height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn height(mut self, height: Length) -> Limits { match height { Length::Shrink => { @@ -72,6 +92,9 @@ impl Limits { self } + /// Applies a minimum width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn min_width(mut self, min_width: u32) -> Limits { self.min.width = self.min.width.max(min_width as f32).min(self.max.width); @@ -79,6 +102,9 @@ impl Limits { self } + /// Applies a maximum width constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn max_width(mut self, max_width: u32) -> Limits { self.max.width = self.max.width.min(max_width as f32).max(self.min.width); @@ -86,6 +112,19 @@ impl Limits { self } + /// Applies a minimum height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html + pub fn min_height(mut self, min_height: u32) -> Limits { + self.min.height = + self.min.height.max(min_height as f32).min(self.max.height); + + self + } + + /// Applies a maximum height constraint to the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn max_height(mut self, max_height: u32) -> Limits { self.max.height = self.max.height.min(max_height as f32).max(self.min.height); @@ -93,10 +132,17 @@ impl Limits { self } + /// Shrinks the current [`Limits`] to account for the given padding. + /// + /// [`Limits`]: struct.Limits.html pub fn pad(&self, padding: f32) -> Limits { self.shrink(Size::new(padding * 2.0, padding * 2.0)) } + /// Shrinks the current [`Limits`] by the given [`Size`]. + /// + /// [`Limits`]: struct.Limits.html + /// [`Size`]: ../struct.Size.html pub fn shrink(&self, size: Size) -> Limits { let min = Size::new( (self.min().width - size.width).max(0.0), @@ -116,6 +162,9 @@ impl Limits { Limits { min, max, fill } } + /// Removes the minimum width constraint for the current [`Limits`]. + /// + /// [`Limits`]: struct.Limits.html pub fn loose(&self) -> Limits { Limits { min: Size::ZERO, @@ -124,6 +173,10 @@ impl Limits { } } + /// Computes the resulting [`Size`] that fits the [`Limits`] given the + /// intrinsic size of some content. + /// + /// [`Limits`]: struct.Limits.html pub fn resolve(&self, intrinsic_size: Size) -> Size { Size::new( intrinsic_size diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index 64ebf2d0..ed1cd3da 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -1,16 +1,25 @@ use crate::{Align, Rectangle, Size}; +/// The bounds of an element and its children. #[derive(Debug, Clone, Default)] pub struct Node { - pub bounds: Rectangle, + pub(crate) bounds: Rectangle, children: Vec<Node>, } impl Node { + /// Creates a new [`Node`] with the given [`Size`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html pub fn new(size: Size) -> Self { Self::with_children(size, Vec::new()) } + /// Creates a new [`Node`] with the given [`Size`] and children. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html pub fn with_children(size: Size, children: Vec<Node>) -> Self { Node { bounds: Rectangle { @@ -23,19 +32,29 @@ impl Node { } } + /// Returns the [`Size`] of the [`Node`]. + /// + /// [`Node`]: struct.Node.html + /// [`Size`]: ../struct.Size.html pub fn size(&self) -> Size { Size::new(self.bounds.width, self.bounds.height) } + /// Returns the bounds of the [`Node`]. + /// + /// [`Node`]: struct.Node.html pub fn bounds(&self) -> Rectangle { self.bounds } + /// Returns the children of the [`Node`]. + /// + /// [`Node`]: struct.Node.html pub fn children(&self) -> &[Node] { &self.children } - pub fn align( + pub(crate) fn align( &mut self, horizontal_alignment: Align, vertical_alignment: Align, diff --git a/native/src/lib.rs b/native/src/lib.rs index bd03ddcd..57d014b6 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -1,205 +1,35 @@ -//! Iced is a renderer-agnostic GUI library focused on simplicity and -//! type-safety. Inspired by [Elm]. +//! A renderer-agnostic native GUI runtime. //! -//! # Features -//! * Simple, easy-to-use, renderer-agnostic API -//! * Responsive, flexbox-based layouting -//! * Type-safe, reactive programming model -//! * Built-in widgets -//! * Custom widget support +//!  //! -//! Check out the [repository] and the [examples] for more details! +//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it, +//! featuring: //! -//! [examples]: https://github.com/hecrj/iced/tree/0.1.0/examples -//! [repository]: https://github.com/hecrj/iced +//! - A custom layout engine, greatly inspired by [`druid`] +//! - Event handling for all the built-in widgets +//! - A renderer-agnostic API //! -//! # Usage -//! Inspired by [The Elm Architecture], Iced expects you to split user interfaces -//! into four different concepts: -//! -//! * __State__ — the state of your application -//! * __Messages__ — user interactions or meaningful events that you care -//! about -//! * __View logic__ — a way to display your __state__ as widgets that -//! may produce __messages__ on user interaction -//! * __Update logic__ — a way to react to __messages__ and update your -//! __state__ -//! -//! We can build something to see how this works! Let's say we want a simple counter -//! that can be incremented and decremented using two buttons. -//! -//! We start by modelling the __state__ of our application: -//! -//! ``` -//! use iced_native::button; -//! -//! struct Counter { -//! // The counter value -//! value: i32, -//! -//! // The local state of the two buttons -//! increment_button: button::State, -//! decrement_button: button::State, -//! } -//! ``` -//! -//! Next, we need to define the possible user interactions of our counter: -//! the button presses. These interactions are our __messages__: -//! -//! ``` -//! #[derive(Debug, Clone, Copy)] -//! pub enum Message { -//! IncrementPressed, -//! DecrementPressed, -//! } -//! ``` +//! To achieve this, it introduces a bunch of reusable interfaces: //! -//! Now, let's show the actual counter by putting it all together in our -//! __view logic__: -//! -//! ``` -//! # use iced_native::button; -//! # -//! # struct Counter { -//! # // The counter value -//! # value: i32, -//! # -//! # // The local state of the two buttons -//! # increment_button: button::State, -//! # decrement_button: button::State, -//! # } -//! # -//! # #[derive(Debug, Clone, Copy)] -//! # pub enum Message { -//! # IncrementPressed, -//! # DecrementPressed, -//! # } -//! # -//! # mod iced_wgpu { -//! # use iced_native::{ -//! # button, text, layout, Button, Text, Point, Rectangle, Color, Layout, Size -//! # }; -//! # -//! # pub struct Renderer {} -//! # -//! # impl iced_native::Renderer for Renderer { -//! # type Output = (); -//! # } -//! # -//! # impl button::Renderer for Renderer { -//! # fn layout<Message>( -//! # &self, -//! # _button: &Button<'_, Message, Self>, -//! # _limits: &layout::Limits, -//! # ) -> layout::Node { -//! # layout::Node::new(Size::ZERO) -//! # } -//! # -//! # fn draw<Message>( -//! # &mut self, -//! # _button: &Button<'_, Message, Self>, -//! # _layout: Layout<'_>, -//! # _cursor_position: Point, -//! # ) {} -//! # } -//! # -//! # impl text::Renderer for Renderer { -//! # fn layout( -//! # &self, -//! # _text: &Text, -//! # _limits: &layout::Limits, -//! # ) -> layout::Node { -//! # layout::Node::new(Size::ZERO) -//! # } -//! # -//! # fn draw( -//! # &mut self, -//! # _text: &Text, -//! # _layout: Layout<'_>, -//! # ) { -//! # } -//! # } -//! # } -//! use iced_native::{Button, Column, Text}; -//! use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own! -//! -//! impl Counter { -//! pub fn view(&mut self) -> Column<Message, Renderer> { -//! // We use a column: a simple vertical layout -//! Column::new() -//! .push( -//! // The increment button. We tell it to produce an -//! // `IncrementPressed` message when pressed -//! Button::new(&mut self.increment_button, Text::new("+")) -//! .on_press(Message::IncrementPressed), -//! ) -//! .push( -//! // We show the value of the counter here -//! Text::new(&self.value.to_string()).size(50), -//! ) -//! .push( -//! // The decrement button. We tell it to produce a -//! // `DecrementPressed` message when pressed -//! Button::new(&mut self.decrement_button, Text::new("-")) -//! .on_press(Message::DecrementPressed), -//! ) -//! } -//! } -//! ``` -//! -//! Finally, we need to be able to react to any produced __messages__ and change -//! our __state__ accordingly in our __update logic__: -//! -//! ``` -//! # use iced_native::button; -//! # -//! # struct Counter { -//! # // The counter value -//! # value: i32, -//! # -//! # // The local state of the two buttons -//! # increment_button: button::State, -//! # decrement_button: button::State, -//! # } -//! # -//! # #[derive(Debug, Clone, Copy)] -//! # pub enum Message { -//! # IncrementPressed, -//! # DecrementPressed, -//! # } -//! impl Counter { -//! // ... -//! -//! pub fn update(&mut self, message: Message) { -//! match message { -//! Message::IncrementPressed => { -//! self.value += 1; -//! } -//! Message::DecrementPressed => { -//! self.value -= 1; -//! } -//! } -//! } -//! } -//! ``` -//! -//! And that's everything! We just wrote a whole user interface. Iced is now able -//! to: -//! -//! 1. Take the result of our __view logic__ and layout its widgets. -//! 1. Process events from our system and produce __messages__ for our -//! __update logic__. -//! 1. Draw the resulting user interface using our chosen __renderer__. +//! - A [`Widget`] trait, which is used to implement new widgets: from layout +//! requirements to event and drawing logic. +//! - A bunch of `Renderer` traits, meant to keep the crate renderer-agnostic. +//! - A [`Windowed`] trait, leveraging [`raw-window-handle`], which can be +//! implemented by graphical renderers that target _windows_. Window-based +//! shells (like [`iced_winit`]) can use this trait to stay renderer-agnostic. //! +//! # Usage //! Check out the [`UserInterface`] type to learn how to wire everything up! //! -//! [Elm]: https://elm-lang.org/ -//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ -//! [documentation]: https://docs.rs/iced -//! [examples]: https://github.com/hecrj/iced/tree/master/examples +//! [`iced_core`]: https://github.com/hecrj/iced/tree/master/core +//! [`iced_winit`]: https://github.com/hecrj/iced/tree/master/winit +//! [`druid`]: https://github.com/xi-editor/druid +//! [`raw-window-handle`]: https://github.com/rust-windowing/raw-window-handle +//! [`Widget`]: widget/trait.Widget.html +//! [`Windowed`]: renderer/trait.Windowed.html //! [`UserInterface`]: struct.UserInterface.html -//#![deny(missing_docs)] -#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +//#![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] #![deny(rust_2018_idioms)] @@ -216,7 +46,8 @@ mod size; mod user_interface; pub use iced_core::{ - Align, Background, Color, Command, Font, Length, Point, Rectangle, Vector, + Align, Background, Color, Command, Font, HorizontalAlignment, Length, + Point, Rectangle, Vector, VerticalAlignment, }; pub use element::Element; diff --git a/native/src/renderer.rs b/native/src/renderer.rs index 833de571..7a68ada4 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -21,16 +21,32 @@ //! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html mod debugger; +#[cfg(debug_assertions)] +mod null; mod windowed; pub use debugger::Debugger; +#[cfg(debug_assertions)] +pub use null::Null; pub use windowed::{Target, Windowed}; use crate::{layout, Element}; +/// A component that can take the state of a user interface and produce an +/// output for its users. pub trait Renderer: Sized { + /// The type of output of the [`Renderer`]. + /// + /// If you are implementing a graphical renderer, your output will most + /// likely be a tree of visual primitives. + /// + /// [`Renderer`]: trait.Renderer.html type Output; + /// Lays out the elements of a user interface. + /// + /// You should override this if you need to perform any operations before or + /// after layouting. For instance, trimming the measurements cache. fn layout<'a, Message>( &mut self, element: &Element<'a, Message, Self>, diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs new file mode 100644 index 00000000..3334e6b6 --- /dev/null +++ b/native/src/renderer/null.rs @@ -0,0 +1,144 @@ +use crate::{ + button, checkbox, column, radio, row, scrollable, text, text_input, + Background, Color, Element, Font, HorizontalAlignment, Layout, Point, + Rectangle, Renderer, Size, VerticalAlignment, +}; + +/// A renderer that does nothing. +pub struct Null; + +impl Renderer for Null { + type Output = (); +} + +impl column::Renderer for Null { + fn draw<Message>( + &mut self, + _content: &[Element<'_, Message, Self>], + _layout: Layout<'_>, + _cursor_position: Point, + ) { + } +} + +impl row::Renderer for Null { + fn draw<Message>( + &mut self, + _content: &[Element<'_, Message, Self>], + _layout: Layout<'_>, + _cursor_position: Point, + ) { + } +} + +impl text::Renderer for Null { + fn default_size(&self) -> u16 { + 20 + } + + fn measure( + &self, + _content: &str, + _size: u16, + _font: Font, + _bounds: Size, + ) -> (f32, f32) { + (0.0, 20.0) + } + + fn draw( + &mut self, + _bounds: Rectangle, + _content: &str, + _size: u16, + _font: Font, + _color: Option<Color>, + _horizontal_alignment: HorizontalAlignment, + _vertical_alignment: VerticalAlignment, + ) { + } +} + +impl scrollable::Renderer for Null { + fn is_mouse_over_scrollbar( + &self, + _bounds: Rectangle, + _content_bounds: Rectangle, + _cursor_position: Point, + ) -> bool { + false + } + + fn draw( + &mut self, + _scrollable: &scrollable::State, + _bounds: Rectangle, + _content_bounds: Rectangle, + _is_mouse_over: bool, + _is_mouse_over_scrollbar: bool, + _offset: u32, + _content: Self::Output, + ) { + } +} + +impl text_input::Renderer for Null { + fn default_size(&self) -> u16 { + 20 + } + + fn draw( + &mut self, + _bounds: Rectangle, + _text_bounds: Rectangle, + _cursor_position: Point, + _size: u16, + _placeholder: &str, + _value: &text_input::Value, + _state: &text_input::State, + ) -> Self::Output { + } +} + +impl button::Renderer for Null { + fn draw( + &mut self, + _bounds: Rectangle, + _cursor_position: Point, + _is_pressed: bool, + _background: Option<Background>, + _border_radius: u16, + _content: Self::Output, + ) -> Self::Output { + } +} + +impl radio::Renderer for Null { + fn default_size(&self) -> u32 { + 20 + } + + fn draw( + &mut self, + _bounds: Rectangle, + _is_selected: bool, + _is_mouse_over: bool, + _label: Self::Output, + ) { + } +} + +impl checkbox::Renderer for Null { + fn default_size(&self) -> u32 { + 20 + } + + fn draw( + &mut self, + _bounds: Rectangle, + _is_checked: bool, + _is_mouse_over: bool, + _label: Self::Output, + ) { + } +} diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index 6d0419d2..813a03f2 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -2,11 +2,21 @@ use crate::MouseCursor; use raw_window_handle::HasRawWindowHandle; +/// A renderer that can target windows. pub trait Windowed: super::Renderer + Sized { + /// The type of target. type Target: Target<Renderer = Self>; + /// Creates a new [`Windowed`] renderer. + /// + /// [`Windowed`]: trait.Windowed.html fn new() -> Self; + /// Performs the drawing operations described in the output on the given + /// target. + /// + /// The overlay can be a bunch of debug text logs. It should be rendered on + /// top of the GUI on most scenarios. fn draw<T: AsRef<str>>( &mut self, output: &Self::Output, @@ -15,9 +25,15 @@ pub trait Windowed: super::Renderer + Sized { ) -> MouseCursor; } +/// A rendering target. pub trait Target { + /// The renderer of this target. type Renderer; + /// Creates a new rendering [`Target`] from the given window handle, width, + /// height and dpi factor. + /// + /// [`Target`]: trait.Target.html fn new<W: HasRawWindowHandle>( window: &W, width: u16, @@ -26,6 +42,9 @@ pub trait Target { renderer: &Self::Renderer, ) -> Self; + /// Resizes the current [`Target`]. + /// + /// [`Target`]: trait.Target.html fn resize( &mut self, width: u16, diff --git a/native/src/size.rs b/native/src/size.rs index bd909292..30e2a57e 100644 --- a/native/src/size.rs +++ b/native/src/size.rs @@ -1,5 +1,6 @@ use std::f32; +/// An amount of space in 2 dimensions. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Size { /// The width. @@ -9,13 +10,26 @@ pub struct Size { } impl Size { + /// A [`Size`] with zero width and height. + /// + /// [`Size`]: struct.Size.html pub const ZERO: Size = Size::new(0., 0.); + + /// A [`Size`] with infinite width and height. + /// + /// [`Size`]: struct.Size.html pub const INFINITY: Size = Size::new(f32::INFINITY, f32::INFINITY); + /// A [`Size`] of infinite width and height. + /// + /// [`Size`]: struct.Size.html pub const fn new(width: f32, height: f32) -> Self { Size { width, height } } + /// Increments the [`Size`] to account for the given padding. + /// + /// [`Size`]: struct.Size.html pub fn pad(&self, padding: f32) -> Self { Size { width: self.width + padding * 2.0, diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 9324fe0b..00ab8ea8 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -10,7 +10,6 @@ use std::hash::Hasher; /// charge of using this type in your system in any way you want. /// /// [`Layout`]: struct.Layout.html -#[derive(Debug)] pub struct UserInterface<'a, Message, Renderer> { hash: u64, root: Element<'a, Message, Renderer>, @@ -52,7 +51,7 @@ where /// # impl iced_native::column::Renderer for Renderer { /// # fn draw<Message>( /// # &mut self, - /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _children: &[iced_native::Element<'_, Message, Self>], /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, /// # ) -> Self::Output { @@ -129,8 +128,8 @@ where /// [`Event`]: enum.Event.html /// /// # Example - /// Let's allow our [counter](index.html#usage) to change state by completing - /// [the previous example](#example): + /// Let's allow our [counter](index.html#usage) to change state by + /// completing [the previous example](#example): /// /// ```no_run /// use iced_native::{UserInterface, Cache}; @@ -148,7 +147,7 @@ where /// # impl iced_native::column::Renderer for Renderer { /// # fn draw<Message>( /// # &mut self, - /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _children: &[iced_native::Element<'_, Message, Self>], /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, /// # ) -> Self::Output { @@ -248,7 +247,7 @@ where /// # impl iced_native::column::Renderer for Renderer { /// # fn draw<Message>( /// # &mut self, - /// # _column: &iced_native::Column<'_, Message, Self>, + /// # _children: &[iced_native::Element<'_, Message, Self>], /// # _layout: iced_native::Layout<'_>, /// # _cursor_position: iced_native::Point, /// # ) -> Self::Output { diff --git a/native/src/widget.rs b/native/src/widget.rs index ff765ee6..48605ee3 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -23,6 +23,7 @@ pub mod button; pub mod checkbox; pub mod column; +pub mod container; pub mod image; pub mod radio; pub mod row; @@ -31,8 +32,6 @@ pub mod slider; pub mod text; pub mod text_input; -mod container; - #[doc(no_inline)] pub use button::Button; #[doc(no_inline)] @@ -65,12 +64,18 @@ use crate::{layout, Event, Hasher, Layout, Length, Point}; /// /// [`Widget`]: trait.Widget.html /// [`Element`]: ../struct.Element.html -pub trait Widget<Message, Renderer>: std::fmt::Debug +pub trait Widget<Message, Renderer> where Renderer: crate::Renderer, { + /// Returns the width of the [`Widget`]. + /// + /// [`Widget`]: trait.Widget.html fn width(&self) -> Length; + /// Returns the height of the [`Widget`]. + /// + /// [`Widget`]: trait.Widget.html fn height(&self) -> Length; /// Returns the [`Node`] of the [`Widget`]. diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 248aaaf9..81cb9310 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -1,25 +1,123 @@ //! Allow your users to perform actions by pressing a button. //! -//! A [`Button`] has some local [`State`] and a [`Class`]. +//! A [`Button`] has some local [`State`]. //! //! [`Button`]: struct.Button.html //! [`State`]: struct.State.html -//! [`Class`]: enum.Class.html -use crate::input::{mouse, ButtonState}; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + input::{mouse, ButtonState}, + layout, Background, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Widget, +}; use std::hash::Hash; -pub use iced_core::button::State; +/// A generic widget that produces a message when clicked. +pub struct Button<'a, Message, Renderer> { + state: &'a mut State, + content: Element<'a, Message, Renderer>, + on_press: Option<Message>, + width: Length, + min_width: u32, + padding: u16, + background: Option<Background>, + border_radius: u16, +} + +impl<'a, Message, Renderer> Button<'a, Message, Renderer> { + /// Creates a new [`Button`] with some local [`State`] and the given + /// content. + /// + /// [`Button`]: struct.Button.html + /// [`State`]: struct.State.html + pub fn new<E>(state: &'a mut State, content: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + Button { + state, + content: content.into(), + on_press: None, + width: Length::Shrink, + min_width: 0, + padding: 0, + background: None, + border_radius: 0, + } + } + + /// Sets the width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the minimum width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn min_width(mut self, min_width: u32) -> Self { + self.min_width = min_width; + self + } + + /// Sets the padding of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + /// Sets the [`Background`] of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + /// [`Background`]: ../../struct.Background.html + pub fn background(mut self, background: Background) -> Self { + self.background = Some(background); + self + } + + /// Sets the border radius of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn border_radius(mut self, border_radius: u16) -> Self { + self.border_radius = border_radius; + self + } -pub type Button<'a, Message, Renderer> = - iced_core::Button<'a, Message, Element<'a, Message, Renderer>>; + /// Sets the message that will be produced when the [`Button`] is pressed. + /// + /// [`Button`]: struct.Button.html + pub fn on_press(mut self, msg: Message) -> Self { + self.on_press = Some(msg); + self + } +} + +/// The local state of a [`Button`]. +/// +/// [`Button`]: struct.Button.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_pressed: bool, +} + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> State { + State::default() + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message, Renderer> where Renderer: self::Renderer, - Message: Clone + std::fmt::Debug, + Message: Clone, { fn width(&self) -> Length { self.width @@ -34,7 +132,21 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let padding = f32::from(self.padding); + let limits = limits + .min_width(self.min_width) + .width(self.width) + .height(Length::Shrink) + .pad(padding); + + let mut content = self.content.layout(renderer, &limits); + + content.bounds.x = padding; + content.bounds.y = padding; + + let size = limits.resolve(content.size()).pad(padding); + + layout::Node::with_children(size, vec![content]) } fn on_event( @@ -81,7 +193,20 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + let content = self.content.draw( + renderer, + layout.children().next().unwrap(), + cursor_position, + ); + + renderer.draw( + layout.bounds(), + cursor_position, + self.state.is_pressed, + self.background, + self.border_radius, + content, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -98,24 +223,17 @@ where /// [`Button`]: struct.Button.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { - /// Creates a [`Node`] for the provided [`Button`]. - /// - /// [`Node`]: ../../struct.Node.html - /// [`Button`]: struct.Button.html - fn layout<Message>( - &self, - button: &Button<'_, Message, Self>, - limits: &layout::Limits, - ) -> layout::Node; - /// Draws a [`Button`]. /// /// [`Button`]: struct.Button.html - fn draw<Message>( + fn draw( &mut self, - button: &Button<'_, Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + is_pressed: bool, + background: Option<Background>, + border_radius: u16, + content: Self::Output, ) -> Self::Output; } @@ -123,7 +241,7 @@ impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>> for Element<'a, Message, Renderer> where Renderer: 'static + self::Renderer, - Message: 'static + Clone + std::fmt::Debug, + Message: 'static + Clone, { fn from( button: Button<'a, Message, Renderer>, diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 4ca44e02..8101e6be 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -1,14 +1,72 @@ //! Show toggle controls using checkboxes. use std::hash::Hash; -use crate::input::{mouse, ButtonState}; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + input::{mouse, ButtonState}, + layout, row, text, Align, Color, Element, Event, Font, Hasher, + HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, + VerticalAlignment, Widget, +}; -pub use iced_core::Checkbox; +/// A box that can be checked. +/// +/// # Example +/// +/// ``` +/// # use iced_native::Checkbox; +/// +/// pub enum Message { +/// CheckboxToggled(bool), +/// } +/// +/// let is_checked = true; +/// +/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); +/// ``` +/// +///  +pub struct Checkbox<Message> { + is_checked: bool, + on_toggle: Box<dyn Fn(bool) -> Message>, + label: String, + label_color: Option<Color>, +} + +impl<Message> Checkbox<Message> { + /// 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<F>(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<C: Into<Color>>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer + row::Renderer, { fn width(&self) -> Length { Length::Fill @@ -23,7 +81,18 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let size = self::Renderer::default_size(renderer); + + Row::<(), Renderer>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Row::new() + .width(Length::Units(size as u16)) + .height(Length::Units(size as u16)), + ) + .push(Text::new(&self.label)) + .layout(renderer, limits) } fn on_event( @@ -55,7 +124,33 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + let bounds = layout.bounds(); + let mut children = layout.children(); + + let checkbox_layout = children.next().unwrap(); + let label_layout = children.next().unwrap(); + let checkbox_bounds = checkbox_layout.bounds(); + + let label = text::Renderer::draw( + renderer, + label_layout.bounds(), + &self.label, + text::Renderer::default_size(renderer), + Font::Default, + self.label_color, + HorizontalAlignment::Left, + VerticalAlignment::Center, + ); + + let is_mouse_over = bounds.contains(cursor_position); + + self::Renderer::draw( + renderer, + checkbox_bounds, + self.is_checked, + is_mouse_over, + label, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -71,37 +166,33 @@ where /// [`Checkbox`]: struct.Checkbox.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Checkbox`]. + /// Returns the default size of a [`Checkbox`]. /// - /// [`Node`]: ../../struct.Node.html /// [`Checkbox`]: struct.Checkbox.html - fn layout<Message>( - &self, - checkbox: &Checkbox<Message>, - limits: &layout::Limits, - ) -> layout::Node; + fn default_size(&self) -> u32; /// 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 + /// * whether the [`Checkbox`] is selected or not + /// * whether the mouse is over the [`Checkbox`] or not + /// * the drawn label of the [`Checkbox`] /// /// [`Checkbox`]: struct.Checkbox.html - fn draw<Message>( + fn draw( &mut self, - checkbox: &Checkbox<Message>, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + label: Self::Output, ) -> Self::Output; } impl<'a, Message, Renderer> From<Checkbox<Message>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer + row::Renderer, Message: 'static, { fn from(checkbox: Checkbox<Message>) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 38dbcdc5..281437fd 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,10 +1,114 @@ +//! Distribute content vertically. use std::hash::Hash; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +}; + +use std::u32; /// A container that distributes its contents vertically. -pub type Column<'a, Message, Renderer> = - iced_core::Column<Element<'a, Message, Renderer>>; +/// +/// A [`Column`] will try to fill the horizontal space of its container. +/// +/// [`Column`]: struct.Column.html +pub struct Column<'a, Message, Renderer> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec<Element<'a, Message, Renderer>>, +} + +impl<'a, Message, Renderer> Column<'a, Message, Renderer> { + /// Creates an empty [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn new() -> Self { + Column { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children: Vec::new(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Column`] in pixels. + /// + /// [`Column`]: struct.Column.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + /// + /// [`Column`]: struct.Column.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an element to the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Column<'a, Message, Renderer> @@ -68,7 +172,7 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + renderer.draw(&self.children, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -86,10 +190,26 @@ where } } +/// The renderer of a [`Column`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Column`] in your user interface. +/// +/// [`Column`]: struct.Column.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// Draws a [`Column`]. + /// + /// It receives: + /// - the children of the [`Column`] + /// - the [`Layout`] of the [`Column`] and its children + /// - the cursor position + /// + /// [`Column`]: struct.Row.html + /// [`Layout`]: ../layout/struct.Layout.html fn draw<Message>( &mut self, - row: &Column<'_, Message, Self>, + content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 5aed3121..64a5f4da 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -1,10 +1,95 @@ +//! Decorate content and apply alignment. use std::hash::Hash; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +}; -/// A container that distributes its contents vertically. -pub type Container<'a, Message, Renderer> = - iced_core::Container<Element<'a, Message, Renderer>>; +use std::u32; + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_docs)] +pub struct Container<'a, Message, Renderer> { + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: Align, + vertical_alignment: Align, + content: Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Container<'a, Message, Renderer> { + /// Creates an empty [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn new<T>(content: T) -> Self + where + T: Into<Element<'a, Message, Renderer>>, + { + Container { + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + horizontal_alignment: Align::Start, + vertical_alignment: Align::Start, + content: content.into(), + } + } + + /// Sets the width of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Container`] in pixels. + /// + /// [`Container`]: struct.Container.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Centers the contents in the horizontal axis of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn center_x(mut self) -> Self { + self.horizontal_alignment = Align::Center; + + self + } + + /// Centers the contents in the vertical axis of the [`Container`]. + /// + /// [`Container`]: struct.Container.html + pub fn center_y(mut self) -> Self { + self.vertical_alignment = Align::Center; + + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Container<'a, Message, Renderer> diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 1e203077..c64f07b1 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -1,10 +1,58 @@ //! Display images in your user interface. -use crate::{layout, Element, Hasher, Layout, Length, Point, Widget}; +use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget}; use std::hash::Hash; -pub use iced_core::Image; +/// A frame that displays an image while keeping aspect ratio. +/// +/// # Example +/// +/// ``` +/// # use iced_native::Image; +/// +/// let image = Image::new("resources/ferris.png"); +/// ``` +#[derive(Debug)] +pub struct Image { + /// The image path + pub path: String, + + /// The width of the image + pub width: Length, + + /// The height of the image + pub height: Length, +} + +impl Image { + /// Creates a new [`Image`] with the given path. + /// + /// [`Image`]: struct.Image.html + pub fn new<T: Into<String>>(path: T) -> Self { + Image { + path: path.into(), + width: Length::Shrink, + height: Length::Shrink, + } + } + + /// Sets the width of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} impl<Message, Renderer> Widget<Message, Renderer> for Image where @@ -23,7 +71,26 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let (width, height) = renderer.dimensions(&self.path); + + let aspect_ratio = width as f32 / height as f32; + + // TODO: Deal with additional cases + let (width, height) = match (self.width, self.height) { + (Length::Units(width), _) => ( + self.width, + Length::Units((width as f32 / aspect_ratio).round() as u16), + ), + (_, _) => { + (Length::Units(width as u16), Length::Units(height as u16)) + } + }; + + let mut size = limits.width(width).height(height).resolve(Size::ZERO); + + size.height = size.width / aspect_ratio; + + layout::Node::new(size) } fn draw( @@ -49,13 +116,10 @@ where /// [`Image`]: struct.Image.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Image`]. - /// - /// You should probably keep the original aspect ratio, if possible. + /// Returns the dimensions of an [`Image`] located on the given path. /// - /// [`Node`]: ../../struct.Node.html /// [`Image`]: struct.Image.html - fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node; + fn dimensions(&self, path: &str) -> (u32, u32); /// Draws an [`Image`]. /// diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index c3405d1f..4e48728f 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -1,15 +1,82 @@ //! Create choices using radio buttons. -use crate::input::{mouse, ButtonState}; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + input::{mouse, ButtonState}, + layout, row, text, Align, Color, Element, Event, Font, Hasher, + HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text, + VerticalAlignment, Widget, +}; use std::hash::Hash; -pub use iced_core::Radio; +/// A circular button representing a choice. +/// +/// # Example +/// ``` +/// # use iced_native::Radio; +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// pub enum Choice { +/// A, +/// B, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// pub enum Message { +/// RadioSelected(Choice), +/// } +/// +/// let selected_choice = Some(Choice::A); +/// +/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); +/// +/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); +/// ``` +/// +///  +pub struct Radio<Message> { + is_selected: bool, + on_click: Message, + label: String, + label_color: Option<Color>, +} + +impl<Message> Radio<Message> { + /// Creates a new [`Radio`] button. + /// + /// It expects: + /// * the value related to the [`Radio`] button + /// * the label of the [`Radio`] button + /// * the current selected value + /// * a function that will be called when the [`Radio`] is selected. It + /// receives the value of the radio and must produce a `Message`. + /// + /// [`Radio`]: struct.Radio.html + pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self + where + V: Eq + Copy, + F: 'static + Fn(V) -> Message, + { + Radio { + is_selected: Some(value) == selected, + on_click: f(value), + label: String::from(label), + label_color: None, + } + } + + /// Sets the `Color` of the label of the [`Radio`]. + /// + /// [`Radio`]: struct.Radio.html + pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message> where - Renderer: self::Renderer, - Message: Clone + std::fmt::Debug, + Renderer: self::Renderer + text::Renderer + row::Renderer, + Message: Clone, { fn width(&self) -> Length { Length::Fill @@ -24,7 +91,18 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let size = self::Renderer::default_size(renderer); + + Row::<(), Renderer>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Row::new() + .width(Length::Units(size as u16)) + .height(Length::Units(size as u16)), + ) + .push(Text::new(&self.label)) + .layout(renderer, limits) } fn on_event( @@ -54,7 +132,33 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + let bounds = layout.bounds(); + let mut children = layout.children(); + + let radio_layout = children.next().unwrap(); + let label_layout = children.next().unwrap(); + let radio_bounds = radio_layout.bounds(); + + let label = text::Renderer::draw( + renderer, + label_layout.bounds(), + &self.label, + text::Renderer::default_size(renderer), + Font::Default, + self.label_color, + HorizontalAlignment::Left, + VerticalAlignment::Center, + ); + + let is_mouse_over = bounds.contains(cursor_position); + + self::Renderer::draw( + renderer, + radio_bounds, + self.is_selected, + is_mouse_over, + label, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -70,40 +174,36 @@ where /// [`Radio`]: struct.Radio.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Radio`]. + /// Returns the default size of a [`Radio`] button. /// - /// [`Node`]: ../../struct.Node.html /// [`Radio`]: struct.Radio.html - fn layout<Message>( - &self, - radio: &Radio<Message>, - limits: &layout::Limits, - ) -> layout::Node; + fn default_size(&self) -> u32; /// Draws a [`Radio`] button. /// /// It receives: - /// * the current cursor position /// * the bounds of the [`Radio`] - /// * the bounds of the label of the [`Radio`] /// * whether the [`Radio`] is selected or not + /// * whether the mouse is over the [`Radio`] or not + /// * the drawn label of the [`Radio`] /// /// [`Radio`]: struct.Radio.html - fn draw<Message>( + fn draw( &mut self, - radio: &Radio<Message>, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + label: Self::Output, ) -> Self::Output; } impl<'a, Message, Renderer> From<Radio<Message>> for Element<'a, Message, Renderer> where - Renderer: self::Renderer, - Message: 'static + Clone + std::fmt::Debug, + Renderer: self::Renderer + row::Renderer + text::Renderer, + Message: 'static + Clone, { - fn from(checkbox: Radio<Message>) -> Element<'a, Message, Renderer> { - Element::new(checkbox) + fn from(radio: Radio<Message>) -> Element<'a, Message, Renderer> { + Element::new(radio) } } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 3827fd9a..34c7ca7c 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,10 +1,116 @@ +//! Distribute content horizontally. use std::hash::Hash; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget, +}; + +use std::u32; /// A container that distributes its contents horizontally. -pub type Row<'a, Message, Renderer> = - iced_core::Row<Element<'a, Message, Renderer>>; +/// +/// A [`Row`] will try to fill the horizontal space of its container. +/// +/// [`Row`]: struct.Row.html +#[allow(missing_docs)] +pub struct Row<'a, Message, Renderer> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec<Element<'a, Message, Renderer>>, +} + +impl<'a, Message, Renderer> Row<'a, Message, Renderer> { + /// Creates an empty [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn new() -> Self { + Row { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children: Vec::new(), + } + } + + /// Sets the horizontal spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the vertical alignment of the contents of the [`Row`] . + /// + /// [`Row`]: struct.Row.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an [`Element`] to the [`Row`]. + /// + /// [`Element`]: ../struct.Element.html + /// [`Row`]: struct.Row.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Row<'a, Message, Renderer> @@ -68,7 +174,7 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + renderer.draw(&self.children, layout, cursor_position) } fn hash_layout(&self, state: &mut Hasher) { @@ -87,10 +193,26 @@ where } } +/// The renderer of a [`Row`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Row`] in your user interface. +/// +/// [`Row`]: struct.Row.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// Draws a [`Row`]. + /// + /// It receives: + /// - the children of the [`Row`] + /// - the [`Layout`] of the [`Row`] and its children + /// - the cursor position + /// + /// [`Row`]: struct.Row.html + /// [`Layout`]: ../layout/struct.Layout.html fn draw<Message>( &mut self, - row: &Row<'_, Message, Self>, + children: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 3877f6fe..7b231b5f 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,20 +1,105 @@ +//! Navigate an endless amount of content with a scrollbar. use crate::{ column, input::{mouse, ButtonState}, - layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, - Widget, + layout, Align, Column, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; -pub use iced_core::scrollable::State; +use std::{f32, hash::Hash, u32}; -use std::f32; -use std::hash::Hash; +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +pub struct Scrollable<'a, Message, Renderer> { + state: &'a mut State, + height: Length, + max_height: u32, + content: Column<'a, Message, Renderer>, +} -/// A scrollable [`Column`]. -/// -/// [`Column`]: ../column/struct.Column.html -pub type Scrollable<'a, Message, Renderer> = - iced_core::Scrollable<'a, Element<'a, Message, Renderer>>; +impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { + /// Creates a new [`Scrollable`] with the given [`State`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn new(state: &'a mut State) -> Self { + Scrollable { + state, + height: Length::Shrink, + max_height: u32::MAX, + content: Column::new(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.content = self.content.spacing(units); + self + } + + /// Sets the padding of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn padding(mut self, units: u16) -> Self { + self.content = self.content.padding(units); + self + } + + /// Sets the width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn width(mut self, width: Length) -> Self { + self.content = self.content.width(width); + self + } + + /// Sets the height of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.content = self.content.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Scrollable`] in pixels. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn align_items(mut self, align_items: Align) -> Self { + self.content = self.content.align_items(align_items); + self + } + + /// Adds an element to the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + self.content = self.content.push(child); + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Scrollable<'a, Message, Renderer> @@ -161,13 +246,35 @@ where ) -> Renderer::Output { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let offset = self.state.offset(bounds, content_bounds); + + let is_mouse_over = bounds.contains(cursor_position); + let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); + + let content = { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { + Point::new(cursor_position.x, cursor_position.y + offset as f32) + } else { + Point::new(cursor_position.x, -1.0) + }; + + self.content.draw(renderer, content_layout, cursor_position) + }; self::Renderer::draw( renderer, - &self, + &self.state, bounds, - content_layout, - cursor_position, + content_layout.bounds(), + is_mouse_over, + is_mouse_over_scrollbar, + offset, + content, ) } @@ -181,7 +288,92 @@ where } } +/// The local state of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scrollbar_grabbed_at: Option<Point>, + offset: u32, +} + +impl State { + /// Creates a new [`State`] with the scrollbar located at the top. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + State::default() + } + + /// Apply a scrolling offset to the current [`State`], given the bounds of + /// the [`Scrollable`] and its contents. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll( + &mut self, + delta_y: f32, + bounds: Rectangle, + content_bounds: Rectangle, + ) { + if bounds.height >= content_bounds.height { + return; + } + + self.offset = (self.offset as i32 - delta_y.round() as i32) + .max(0) + .min((content_bounds.height - bounds.height) as i32) + as u32; + } + + /// Moves the scroll position to a relative amount, given the bounds of + /// the [`Scrollable`] and its contents. + /// + /// `0` represents scrollbar at the top, while `1` represents scrollbar at + /// the bottom. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll_to( + &mut self, + percentage: f32, + bounds: Rectangle, + content_bounds: Rectangle, + ) { + self.offset = ((content_bounds.height - bounds.height) * percentage) + .max(0.0) as u32; + } + + /// Returns the current scrolling offset of the [`State`], given the bounds + /// of the [`Scrollable`] and its contents. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 { + let hidden_content = + (content_bounds.height - bounds.height).max(0.0).round() as u32; + + self.offset.min(hidden_content) + } + + /// Returns whether the scrollbar is currently grabbed or not. + pub fn is_scrollbar_grabbed(&self) -> bool { + self.scrollbar_grabbed_at.is_some() + } +} + +/// The renderer of a [`Scrollable`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`Scrollable`] in your user interface. +/// +/// [`Scrollable`]: struct.Scrollable.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// Returns whether the mouse is over the scrollbar given the bounds of + /// the [`Scrollable`] and its contents. + /// + /// [`Scrollable`]: struct.Scrollable.html fn is_mouse_over_scrollbar( &self, bounds: Rectangle, @@ -189,12 +381,27 @@ pub trait Renderer: crate::Renderer + Sized { cursor_position: Point, ) -> bool; - fn draw<Message>( + /// Draws the [`Scrollable`]. + /// + /// It receives: + /// - the [`State`] of the [`Scrollable`] + /// - the bounds of the [`Scrollable`] + /// - whether the mouse is over the [`Scrollable`] or not + /// - whether the mouse is over the scrollbar or not + /// - the scrolling offset + /// - the drawn content + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + fn draw( &mut self, - scrollable: &Scrollable<'_, Message, Self>, + scrollable: &State, bounds: Rectangle, - content_layout: Layout<'_>, - cursor_position: Point, + content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + offset: u32, + content: Self::Output, ) -> Self::Output; } diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index e4f5c7a6..fe503c34 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,12 +4,101 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html -use std::hash::Hash; -use crate::input::{mouse, ButtonState}; -use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget}; +use crate::{ + input::{mouse, ButtonState}, + layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, + Widget, +}; -pub use iced_core::slider::*; +use std::{hash::Hash, ops::RangeInclusive}; + +/// An horizontal bar and a handle that selects a single value from a range of +/// values. +/// +/// A [`Slider`] will try to fill the horizontal space of its container. +/// +/// [`Slider`]: struct.Slider.html +/// +/// # Example +/// ``` +/// # use iced_native::{slider, Slider}; +/// # +/// pub enum Message { +/// SliderChanged(f32), +/// } +/// +/// let state = &mut slider::State::new(); +/// let value = 50.0; +/// +/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged); +/// ``` +/// +///  +pub struct Slider<'a, Message> { + state: &'a mut State, + range: RangeInclusive<f32>, + value: f32, + on_change: Box<dyn Fn(f32) -> Message>, + width: Length, +} + +impl<'a, Message> Slider<'a, Message> { + /// Creates a new [`Slider`]. + /// + /// It expects: + /// * the local [`State`] of the [`Slider`] + /// * an inclusive range of possible values + /// * the current value of the [`Slider`] + /// * a function that will be called when the [`Slider`] is dragged. + /// It receives the new value of the [`Slider`] and must produce a + /// `Message`. + /// + /// [`Slider`]: struct.Slider.html + /// [`State`]: struct.State.html + pub fn new<F>( + state: &'a mut State, + range: RangeInclusive<f32>, + value: f32, + on_change: F, + ) -> Self + where + F: 'static + Fn(f32) -> Message, + { + Slider { + state, + value: value.max(*range.start()).min(*range.end()), + range, + on_change: Box::new(on_change), + width: Length::Fill, + } + } + + /// Sets the width of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } +} + +/// The local state of a [`Slider`]. +/// +/// [`Slider`]: struct.Slider.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State { + is_dragging: bool, +} + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> State { + State::default() + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message> where @@ -28,7 +117,13 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let limits = limits + .width(self.width) + .height(Length::Units(renderer.height() as u16)); + + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) } fn on_event( @@ -85,7 +180,13 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout, cursor_position) + renderer.draw( + layout.bounds(), + cursor_position, + self.range.clone(), + self.value, + self.state.is_dragging, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -101,15 +202,10 @@ where /// [`Slider`]: struct.Slider.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Radio`]. + /// Returns the height of the [`Slider`]. /// - /// [`Node`]: ../../struct.Node.html - /// [`Radio`]: struct.Radio.html - fn layout<Message>( - &self, - slider: &Slider<'_, Message>, - limits: &layout::Limits, - ) -> layout::Node; + /// [`Slider`]: struct.Slider.html + fn height(&self) -> u32; /// Draws a [`Slider`]. /// @@ -123,11 +219,13 @@ pub trait Renderer: crate::Renderer { /// [`Slider`]: struct.Slider.html /// [`State`]: struct.State.html /// [`Class`]: enum.Class.html - fn draw<Message>( + fn draw( &mut self, - slider: &Slider<'_, Message>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + range: RangeInclusive<f32>, + value: f32, + is_dragging: bool, ) -> Self::Output; } diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 3097b8f3..cf0701b9 100644 --- a/native/src/widget/text.rs +++ b/native/src/widget/text.rs @@ -1,9 +1,113 @@ //! Write some text for your users to read. -use crate::{layout, Element, Hasher, Layout, Length, Point, Widget}; +use crate::{ + layout, Color, Element, Font, Hasher, HorizontalAlignment, Layout, Length, + Point, Rectangle, Size, VerticalAlignment, Widget, +}; use std::hash::Hash; -pub use iced_core::text::*; +/// A paragraph of text. +/// +/// # Example +/// +/// ``` +/// # use iced_native::Text; +/// +/// Text::new("I <3 iced!") +/// .size(40); +/// ``` +#[derive(Debug, Clone)] +pub struct Text { + content: String, + size: Option<u16>, + color: Option<Color>, + font: Font, + width: Length, + height: Length, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, +} + +impl Text { + /// Create a new fragment of [`Text`] with the given contents. + /// + /// [`Text`]: struct.Text.html + pub fn new<T: Into<String>>(label: T) -> Self { + Text { + content: label.into(), + size: None, + color: None, + font: Font::Default, + width: Length::Fill, + height: Length::Shrink, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Top, + } + } + + /// Sets the size of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the [`Color`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Color`]: ../../struct.Color.html + pub fn color<C: Into<Color>>(mut self, color: C) -> Self { + self.color = Some(color.into()); + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Font`]: ../../struct.Font.html + pub fn font(mut self, font: Font) -> Self { + self.font = font; + self + } + + /// Sets the width of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the [`HorizontalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html + pub fn horizontal_alignment( + mut self, + alignment: HorizontalAlignment, + ) -> Self { + self.horizontal_alignment = alignment; + self + } + + /// Sets the [`VerticalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`VerticalAlignment`]: enum.VerticalAlignment.html + pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self { + self.vertical_alignment = alignment; + self + } +} impl<Message, Renderer> Widget<Message, Renderer> for Text where @@ -22,7 +126,18 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - renderer.layout(&self, limits) + let limits = limits.width(self.width).height(self.height); + + let size = self.size.unwrap_or(renderer.default_size()); + + let bounds = limits.max(); + + let (width, height) = + renderer.measure(&self.content, size, self.font, bounds); + + let size = limits.resolve(Size::new(width, height)); + + layout::Node::new(size) } fn draw( @@ -31,7 +146,15 @@ where layout: Layout<'_>, _cursor_position: Point, ) -> Renderer::Output { - renderer.draw(&self, layout) + renderer.draw( + layout.bounds(), + &self.content, + self.size.unwrap_or(renderer.default_size()), + self.font, + self.color, + self.horizontal_alignment, + self.vertical_alignment, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -51,17 +174,22 @@ where /// [renderer]: ../../renderer/index.html /// [`UserInterface`]: ../../struct.UserInterface.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] with the given [`Style`] for the provided [`Text`] - /// contents and size. + /// Returns the default size of the [`Text`]. /// - /// You should probably use [`Node::with_measure`] to allow [`Text`] to - /// adapt to the dimensions of its container. + /// [`Text`]: struct.Text.html + fn default_size(&self) -> u16; + + /// Measures the [`Text`] in the given bounds and returns the minimum + /// boundaries that can fit the contents. /// - /// [`Node`]: ../../struct.Node.html - /// [`Style`]: ../../struct.Style.html /// [`Text`]: struct.Text.html - /// [`Node::with_measure`]: ../../struct.Node.html#method.with_measure - fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node; + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32); /// Draws a [`Text`] fragment. /// @@ -76,7 +204,16 @@ pub trait Renderer: crate::Renderer { /// [`Text`]: struct.Text.html /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html /// [`VerticalAlignment`]: enum.VerticalAlignment.html - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output; + fn draw( + &mut self, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option<Color>, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output; } impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer> diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index d54cf82c..c3876b1d 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -1,10 +1,121 @@ +//! Display fields that can be filled with text. +//! +//! A [`TextInput`] has some local [`State`]. +//! +//! [`TextInput`]: struct.TextInput.html +//! [`State`]: struct.State.html use crate::{ input::{keyboard, mouse, ButtonState}, layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; -pub use iced_core::{text_input::State, TextInput}; +/// A field that can be filled with text. +/// +/// # Example +/// ``` +/// # use iced_native::{text_input, TextInput}; +/// # +/// enum Message { +/// TextInputChanged(String), +/// } +/// +/// let mut state = text_input::State::new(); +/// let value = "Some text"; +/// +/// let input = TextInput::new( +/// &mut state, +/// "This is the placeholder...", +/// value, +/// Message::TextInputChanged, +/// ); +/// ``` +pub struct TextInput<'a, Message> { + state: &'a mut State, + placeholder: String, + value: Value, + width: Length, + max_width: Length, + padding: u16, + size: Option<u16>, + on_change: Box<dyn Fn(String) -> Message>, + on_submit: Option<Message>, +} + +impl<'a, Message> TextInput<'a, Message> { + /// Creates a new [`TextInput`]. + /// + /// It expects: + /// - some [`State`] + /// - a placeholder + /// - the current value + /// - a function that produces a message when the [`TextInput`] changes + /// + /// [`TextInput`]: struct.TextInput.html + /// [`State`]: struct.State.html + pub fn new<F>( + state: &'a mut State, + placeholder: &str, + value: &str, + on_change: F, + ) -> Self + where + F: 'static + Fn(String) -> Message, + { + Self { + state, + placeholder: String::from(placeholder), + value: Value::new(value), + width: Length::Fill, + max_width: Length::Shrink, + padding: 0, + size: None, + on_change: Box::new(on_change), + on_submit: None, + } + } + + /// Sets the width of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the maximum width of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn max_width(mut self, max_width: Length) -> Self { + self.max_width = max_width; + self + } + + /// Sets the padding of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the text size of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the message that should be produced when the [`TextInput`] is + /// focused and the enter key is pressed. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn on_submit(mut self, message: Message) -> Self { + self.on_submit = Some(message); + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for TextInput<'a, Message> where @@ -124,12 +235,19 @@ where let bounds = layout.bounds(); let text_bounds = layout.children().next().unwrap().bounds(); - renderer.draw(&self, bounds, text_bounds, cursor_position) + renderer.draw( + bounds, + text_bounds, + cursor_position, + self.size.unwrap_or(renderer.default_size()), + &self.placeholder, + &self.value, + &self.state, + ) } fn hash_layout(&self, state: &mut Hasher) { - use std::any::TypeId; - use std::hash::Hash; + use std::{any::TypeId, hash::Hash}; TypeId::of::<TextInput<'static, ()>>().hash(state); @@ -140,15 +258,41 @@ where } } +/// The renderer of a [`TextInput`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`TextInput`] in your user interface. +/// +/// [`TextInput`]: struct.TextInput.html +/// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + /// Returns the default size of the text of the [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html fn default_size(&self) -> u16; - fn draw<Message>( + /// Draws a [`TextInput`]. + /// + /// It receives: + /// - its bounds of the [`TextInput`] + /// - the bounds of the text (i.e. the current value) + /// - the cursor position + /// - the placeholder to show when the value is empty + /// - the current [`Value`] + /// - the current [`State`] + /// + /// [`TextInput`]: struct.TextInput.html + /// [`Value`]: struct.Value.html + /// [`State`]: struct.State.html + fn draw( &mut self, - text_input: &TextInput<'_, Message>, bounds: Rectangle, text_bounds: Rectangle, cursor_position: Point, + size: u16, + placeholder: &str, + value: &Value, + state: &State, ) -> Self::Output; } @@ -164,3 +308,128 @@ where Element::new(text_input) } } + +/// The state of a [`TextInput`]. +/// +/// [`TextInput`]: struct.TextInput.html +#[derive(Debug, Default, Clone)] +pub struct State { + is_focused: bool, + cursor_position: usize, +} + +impl State { + /// Creates a new [`State`], representing an unfocused [`TextInput`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + Self::default() + } + + /// Creates a new [`State`], representing a focused [`TextInput`]. + /// + /// [`State`]: struct.State.html + pub fn focused() -> Self { + use std::usize; + + Self { + is_focused: true, + cursor_position: usize::MAX, + } + } + + /// Returns whether the [`TextInput`] is currently focused or not. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn is_focused(&self) -> bool { + self.is_focused + } + + /// Returns the cursor position of a [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn cursor_position(&self, value: &Value) -> usize { + self.cursor_position.min(value.len()) + } + + /// Moves the cursor of a [`TextInput`] to the right. + /// + /// [`TextInput`]: struct.TextInput.html + pub(crate) fn move_cursor_right(&mut self, value: &Value) { + let current = self.cursor_position(value); + + if current < value.len() { + self.cursor_position = current + 1; + } + } + + /// Moves the cursor of a [`TextInput`] to the left. + /// + /// [`TextInput`]: struct.TextInput.html + pub(crate) fn move_cursor_left(&mut self, value: &Value) { + let current = self.cursor_position(value); + + if current > 0 { + self.cursor_position = current - 1; + } + } + + /// Moves the cursor of a [`TextInput`] to the end. + /// + /// [`TextInput`]: struct.TextInput.html + pub(crate) fn move_cursor_to_end(&mut self, value: &Value) { + self.cursor_position = value.len(); + } +} + +/// The value of a [`TextInput`]. +/// +/// [`TextInput`]: struct.TextInput.html +// TODO: Use `unicode-segmentation` +#[derive(Debug)] +pub struct Value(Vec<char>); + +impl Value { + /// Creates a new [`Value`] from a string slice. + /// + /// [`Value`]: struct.Value.html + pub fn new(string: &str) -> Self { + Self(string.chars().collect()) + } + + /// Returns the total amount of `char` in the [`Value`]. + /// + /// [`Value`]: struct.Value.html + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns a new [`Value`] containing the `char` until the given `index`. + /// + /// [`Value`]: struct.Value.html + pub fn until(&self, index: usize) -> Self { + Self(self.0[..index.min(self.len())].to_vec()) + } + + /// Converts the [`Value`] into a `String`. + /// + /// [`Value`]: struct.Value.html + pub fn to_string(&self) -> String { + use std::iter::FromIterator; + String::from_iter(self.0.iter()) + } + + /// Inserts a new `char` at the given `index`. + /// + /// [`Value`]: struct.Value.html + pub fn insert(&mut self, index: usize, c: char) { + self.0.insert(index, c); + } + + /// Removes the `char` at the given `index`. + /// + /// [`Value`]: struct.Value.html + pub fn remove(&mut self, index: usize) { + let _ = self.0.remove(index); + } +} diff --git a/rustfmt.toml b/rustfmt.toml index d979d317..7e5dded7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,3 @@ max_width=80 +wrap_comments=true +merge_imports=true diff --git a/src/application.rs b/src/application.rs new file mode 100644 index 00000000..5ecb901e --- /dev/null +++ b/src/application.rs @@ -0,0 +1,190 @@ +use crate::{Command, Element}; + +/// An interactive cross-platform application. +/// +/// This trait is the main entrypoint of Iced. Once implemented, you can run +/// your GUI application by simply calling [`run`](#method.run). +/// +/// - On native platforms, it will run in its own window. +/// - On the web, it will take control of the `<title>` and the `<body>` of the +/// document. +/// +/// An [`Application`](trait.Application.html) can execute asynchronous actions +/// by returning a [`Command`](struct.Command.html) in some of its methods. If +/// you do not intend to perform any background work in your program, the +/// [`Sandbox`](trait.Sandbox.html) trait offers a simplified interface. +/// +/// # Example +/// Let's say we want to run the [`Counter` example we implemented +/// before](index.html#overview). We just need to fill in the gaps: +/// +/// ```no_run +/// use iced::{button, Application, Button, Column, Command, Element, Text}; +/// +/// pub fn main() { +/// Counter::run() +/// } +/// +/// #[derive(Default)] +/// struct Counter { +/// value: i32, +/// increment_button: button::State, +/// decrement_button: button::State, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// enum Message { +/// IncrementPressed, +/// DecrementPressed, +/// } +/// +/// impl Application for Counter { +/// type Message = Message; +/// +/// fn new() -> (Self, Command<Message>) { +/// (Self::default(), Command::none()) +/// } +/// +/// fn title(&self) -> String { +/// String::from("A simple counter") +/// } +/// +/// fn update(&mut self, message: Message) -> Command<Message> { +/// match message { +/// Message::IncrementPressed => { +/// self.value += 1; +/// } +/// Message::DecrementPressed => { +/// self.value -= 1; +/// } +/// } +/// +/// Command::none() +/// } +/// +/// fn view(&mut self) -> Element<Message> { +/// Column::new() +/// .push( +/// Button::new(&mut self.increment_button, Text::new("Increment")) +/// .on_press(Message::IncrementPressed), +/// ) +/// .push( +/// Text::new(self.value.to_string()).size(50), +/// ) +/// .push( +/// Button::new(&mut self.decrement_button, Text::new("Decrement")) +/// .on_press(Message::DecrementPressed), +/// ) +/// .into() +/// } +/// } +/// ``` +pub trait Application: Sized { + /// The type of __messages__ your [`Application`] will produce. + /// + /// [`Application`]: trait.Application.html + type Message: std::fmt::Debug + Send; + + /// Initializes the [`Application`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// Additionally, you can return a [`Command`](struct.Command.html) if you + /// need to perform some async action in the background on startup. This is + /// useful if you want to load state from a file, perform an initial HTTP + /// request, etc. + /// + /// [`Application`]: trait.Application.html + fn new() -> (Self, Command<Self::Message>); + + /// Returns the current title of the [`Application`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Application`]: trait.Application.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Application`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by either user interactions or commands, will be handled by + /// this method. + /// + /// Any [`Command`] returned will be executed immediately in the background. + /// + /// [`Application`]: trait.Application.html + /// [`Command`]: struct.Command.html + fn update(&mut self, message: Self::Message) -> Command<Self::Message>; + + /// Returns the widgets to display in the [`Application`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Application`]: trait.Application.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Runs the [`Application`]. + /// + /// This method will take control of the current thread and __will NOT + /// return__. + /// + /// It should probably be that last thing you call in your `main` function. + /// + /// [`Application`]: trait.Application.html + fn run() + where + Self: 'static, + { + #[cfg(not(target_arch = "wasm32"))] + <Instance<Self> as iced_winit::Application>::run(); + + #[cfg(target_arch = "wasm32")] + iced_web::Application::run(Instance(self)); + } +} + +struct Instance<A: Application>(A); + +#[cfg(not(target_arch = "wasm32"))] +impl<A> iced_winit::Application for Instance<A> +where + A: Application, +{ + type Renderer = iced_wgpu::Renderer; + type Message = A::Message; + + fn new() -> (Self, Command<A::Message>) { + let (app, command) = A::new(); + + (Instance(app), command) + } + + fn title(&self) -> String { + self.0.title() + } + + fn update(&mut self, message: Self::Message) -> Command<Self::Message> { + self.0.update(message) + } + + fn view(&mut self) -> Element<'_, Self::Message> { + self.0.view() + } +} + +#[cfg(target_arch = "wasm32")] +impl<A> iced_web::Application for Instance<A> +where + A: Application, +{ + type Message = A::Message; + + fn update(&mut self, message: Self::Message) { + self.0.update(message); + } + + fn view(&mut self) -> Element<Self::Message> { + self.0.view() + } +} @@ -1,73 +1,190 @@ +//! Iced is a cross-platform GUI library focused on simplicity and type-safety. +//! Inspired by [Elm]. +//! +//! # Features +//! * Simple, easy-to-use, batteries-included API +//! * Type-safe, reactive programming model +//! * [Cross-platform support] (Windows, macOS, Linux, and the Web) +//! * Responsive layout +//! * Built-in widgets (including [text inputs], [scrollables], and more!) +//! * Custom widget support (create your own!) +//! * [Debug overlay with performance metrics] +//! * First-class support for async actions (use futures!) +//! * [Modular ecosystem] split into reusable parts: +//! * A [renderer-agnostic native runtime] enabling integration with existing +//! systems +//! * A [built-in renderer] supporting Vulkan, Metal, DX11, and DX12 +//! * A [windowing shell] +//! * A [web runtime] leveraging the DOM +//! +//! Check out the [repository] and the [examples] for more details! +//! +//! [Cross-platform support]: https://github.com/hecrj/iced/blob/master/docs/images/todos_desktop.jpg?raw=true +//! [text inputs]: https://gfycat.com/alertcalmcrow-rust-gui +//! [scrollables]: https://gfycat.com/perkybaggybaboon-rust-gui +//! [Debug overlay with performance metrics]: https://gfycat.com/artisticdiligenthorseshoebat-rust-gui +//! [Modular ecosystem]: https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md +//! [renderer-agnostic native runtime]: https://github.com/hecrj/iced/tree/master/native +//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs +//! [built-in renderer]: https://github.com/hecrj/iced/tree/master/wgpu +//! [windowing shell]: https://github.com/hecrj/iced/tree/master/winit +//! [`dodrio`]: https://github.com/fitzgen/dodrio +//! [web runtime]: https://github.com/hecrj/iced/tree/master/web +//! [examples]: https://github.com/hecrj/iced/tree/0.1.0/examples +//! [repository]: https://github.com/hecrj/iced +//! +//! # Overview +//! Inspired by [The Elm Architecture], Iced expects you to split user +//! interfaces into four different concepts: +//! +//! * __State__ — the state of your application +//! * __Messages__ — user interactions or meaningful events that you care +//! about +//! * __View logic__ — a way to display your __state__ as widgets that +//! may produce __messages__ on user interaction +//! * __Update logic__ — a way to react to __messages__ and update your +//! __state__ +//! +//! We can build something to see how this works! Let's say we want a simple +//! counter that can be incremented and decremented using two buttons. +//! +//! We start by modelling the __state__ of our application: +//! +//! ``` +//! use iced::button; +//! +//! struct Counter { +//! // The counter value +//! value: i32, +//! +//! // The local state of the two buttons +//! increment_button: button::State, +//! decrement_button: button::State, +//! } +//! ``` +//! +//! Next, we need to define the possible user interactions of our counter: +//! the button presses. These interactions are our __messages__: +//! +//! ``` +//! #[derive(Debug, Clone, Copy)] +//! pub enum Message { +//! IncrementPressed, +//! DecrementPressed, +//! } +//! ``` +//! +//! Now, let's show the actual counter by putting it all together in our +//! __view logic__: +//! +//! ``` +//! # use iced::button; +//! # +//! # struct Counter { +//! # // The counter value +//! # value: i32, +//! # +//! # // The local state of the two buttons +//! # increment_button: button::State, +//! # decrement_button: button::State, +//! # } +//! # +//! # #[derive(Debug, Clone, Copy)] +//! # pub enum Message { +//! # IncrementPressed, +//! # DecrementPressed, +//! # } +//! # +//! use iced::{Button, Column, Text}; +//! +//! impl Counter { +//! pub fn view(&mut self) -> Column<Message> { +//! // We use a column: a simple vertical layout +//! Column::new() +//! .push( +//! // The increment button. We tell it to produce an +//! // `IncrementPressed` message when pressed +//! Button::new(&mut self.increment_button, Text::new("+")) +//! .on_press(Message::IncrementPressed), +//! ) +//! .push( +//! // We show the value of the counter here +//! Text::new(self.value.to_string()).size(50), +//! ) +//! .push( +//! // The decrement button. We tell it to produce a +//! // `DecrementPressed` message when pressed +//! Button::new(&mut self.decrement_button, Text::new("-")) +//! .on_press(Message::DecrementPressed), +//! ) +//! } +//! } +//! ``` +//! +//! Finally, we need to be able to react to any produced __messages__ and change +//! our __state__ accordingly in our __update logic__: +//! +//! ``` +//! # use iced::button; +//! # +//! # struct Counter { +//! # // The counter value +//! # value: i32, +//! # +//! # // The local state of the two buttons +//! # increment_button: button::State, +//! # decrement_button: button::State, +//! # } +//! # +//! # #[derive(Debug, Clone, Copy)] +//! # pub enum Message { +//! # IncrementPressed, +//! # DecrementPressed, +//! # } +//! impl Counter { +//! // ... +//! +//! pub fn update(&mut self, message: Message) { +//! match message { +//! Message::IncrementPressed => { +//! self.value += 1; +//! } +//! Message::DecrementPressed => { +//! self.value -= 1; +//! } +//! } +//! } +//! } +//! ``` +//! +//! And that's everything! We just wrote a whole user interface. Iced is now +//! able to: +//! +//! 1. Take the result of our __view logic__ and layout its widgets. +//! 1. Process events from our system and produce __messages__ for our +//! __update logic__. +//! 1. Draw the resulting user interface. +//! +//! # Usage +//! Take a look at the [`Application`] trait, which streamlines all the process +//! described above for you! +//! +//! [Elm]: https://elm-lang.org/ +//! [The Elm Architecture]: https://guide.elm-lang.org/architecture/ +//! [documentation]: https://docs.rs/iced +//! [examples]: https://github.com/hecrj/iced/tree/master/examples +//! [`Application`]: trait.Application.html +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(unused_results)] +#![deny(unsafe_code)] +#![deny(rust_2018_idioms)] +mod application; #[cfg_attr(target_arch = "wasm32", path = "web.rs")] -#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")] +#[cfg_attr(not(target_arch = "wasm32"), path = "native.rs")] mod platform; +mod sandbox; +pub use application::Application; pub use platform::*; - -pub trait Application: Sized { - type Message: std::fmt::Debug + Send; - - fn new() -> (Self, Command<Self::Message>); - - fn title(&self) -> String; - - fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - - fn view(&mut self) -> Element<Self::Message>; - - fn run() - where - Self: 'static + Sized, - { - #[cfg(not(target_arch = "wasm32"))] - <Instance<Self> as iced_winit::Application>::run(); - - #[cfg(target_arch = "wasm32")] - iced_web::Application::run(Instance(self)); - } -} - -struct Instance<A: Application>(A); - -#[cfg(not(target_arch = "wasm32"))] -impl<A> iced_winit::Application for Instance<A> -where - A: Application, -{ - type Renderer = Renderer; - type Message = A::Message; - - fn new() -> (Self, Command<A::Message>) { - let (app, command) = A::new(); - - (Instance(app), command) - } - - fn title(&self) -> String { - self.0.title() - } - - fn update(&mut self, message: Self::Message) -> Command<Self::Message> { - self.0.update(message) - } - - fn view(&mut self) -> Element<Self::Message> { - self.0.view() - } -} - -#[cfg(target_arch = "wasm32")] -impl<A> iced_web::Application for Instance<A> -where - A: Application, -{ - type Message = A::Message; - - fn update(&mut self, message: Self::Message) { - self.0.update(message); - } - - fn view(&mut self) -> Element<Self::Message> { - self.0.view() - } -} +pub use sandbox::Sandbox; diff --git a/src/native.rs b/src/native.rs new file mode 100644 index 00000000..926b2d11 --- /dev/null +++ b/src/native.rs @@ -0,0 +1,113 @@ +pub use iced_winit::{ + Align, Background, Color, Command, Font, HorizontalAlignment, Length, + VerticalAlignment, +}; + +pub mod widget { + //! Display information and interactive controls in your application. + //! + //! # Re-exports + //! For convenience, the contents of this module are available at the root + //! module. Therefore, you can directly type: + //! + //! ``` + //! use iced::{button, Button}; + //! ``` + //! + //! # Stateful widgets + //! Some widgets need to keep track of __local state__. + //! + //! These widgets have their own module with a `State` type. For instance, a + //! [`TextInput`] has some [`text_input::State`]. + //! + //! [`TextInput`]: text_input/struct.TextInput.html + //! [`text_input::State`]: text_input/struct.State.html + pub mod button { + //! Allow your users to perform actions by pressing a button. + //! + //! A [`Button`] has some local [`State`]. + //! + //! [`Button`]: type.Button.html + //! [`State`]: struct.State.html + + /// A widget that produces a message when clicked. + /// + /// This is an alias of an `iced_native` button with a default + /// `Renderer`. + pub type Button<'a, Message> = + iced_winit::Button<'a, Message, iced_wgpu::Renderer>; + + pub use iced_winit::button::State; + } + + pub mod scrollable { + //! Navigate an endless amount of content with a scrollbar. + + /// A widget that can vertically display an infinite amount of content + /// with a scrollbar. + /// + /// This is an alias of an `iced_native` scrollable with a default + /// `Renderer`. + pub type Scrollable<'a, Message> = + iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>; + + pub use iced_winit::scrollable::State; + } + + pub mod text_input { + //! Ask for information using text fields. + //! + //! A [`TextInput`] has some local [`State`]. + //! + //! [`TextInput`]: struct.TextInput.html + //! [`State`]: struct.State.html + pub use iced_winit::text_input::{State, TextInput}; + } + + pub mod slider { + //! Display an interactive selector of a single value from a range of + //! values. + //! + //! A [`Slider`] has some local [`State`]. + //! + //! [`Slider`]: struct.Slider.html + //! [`State`]: struct.State.html + pub use iced_winit::slider::{Slider, State}; + } + + pub use iced_winit::{Checkbox, Image, Radio, Text}; + + #[doc(no_inline)] + pub use { + button::Button, scrollable::Scrollable, slider::Slider, + text_input::TextInput, + }; + + /// A container that distributes its contents vertically. + /// + /// This is an alias of an `iced_native` column with a default `Renderer`. + pub type Column<'a, Message> = + iced_winit::Column<'a, Message, iced_wgpu::Renderer>; + + /// A container that distributes its contents horizontally. + /// + /// This is an alias of an `iced_native` row with a default `Renderer`. + pub type Row<'a, Message> = + iced_winit::Row<'a, Message, iced_wgpu::Renderer>; + + /// An element decorating some content. + /// + /// This is an alias of an `iced_native` container with a default + /// `Renderer`. + pub type Container<'a, Message> = + iced_winit::Container<'a, Message, iced_wgpu::Renderer>; +} + +#[doc(no_inline)] +pub use widget::*; + +/// A generic widget. +/// +/// This is an alias of an `iced_native` element with a default `Renderer`. +pub type Element<'a, Message> = + iced_winit::Element<'a, Message, iced_wgpu::Renderer>; diff --git a/src/sandbox.rs b/src/sandbox.rs new file mode 100644 index 00000000..698578f4 --- /dev/null +++ b/src/sandbox.rs @@ -0,0 +1,155 @@ +use crate::{Application, Command, Element}; + +/// A sandboxed [`Application`]. +/// +/// A [`Sandbox`] is just an [`Application`] that cannot run any asynchronous +/// actions. +/// +/// If you do not need to leverage a [`Command`], you can use a [`Sandbox`] +/// instead of returning a [`Command::none`] everywhere. +/// +/// [`Application`]: trait.Application.html +/// [`Sandbox`]: trait.Sandbox.html +/// [`Command`]: struct.Command.html +/// [`Command::none`]: struct.Command.html#method.none +/// +/// # Example +/// We can use a [`Sandbox`] to run the [`Counter` example we implemented +/// before](index.html#overview), instead of an [`Application`]. We just need +/// to remove the use of [`Command`]: +/// +/// ```no_run +/// use iced::{button, Button, Column, Element, Sandbox, Text}; +/// +/// pub fn main() { +/// Counter::run() +/// } +/// +/// #[derive(Default)] +/// struct Counter { +/// value: i32, +/// increment_button: button::State, +/// decrement_button: button::State, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// enum Message { +/// IncrementPressed, +/// DecrementPressed, +/// } +/// +/// impl Sandbox for Counter { +/// type Message = Message; +/// +/// fn new() -> Self { +/// Self::default() +/// } +/// +/// fn title(&self) -> String { +/// String::from("A simple counter") +/// } +/// +/// fn update(&mut self, message: Message) { +/// match message { +/// Message::IncrementPressed => { +/// self.value += 1; +/// } +/// Message::DecrementPressed => { +/// self.value -= 1; +/// } +/// } +/// } +/// +/// fn view(&mut self) -> Element<Message> { +/// Column::new() +/// .push( +/// Button::new(&mut self.increment_button, Text::new("Increment")) +/// .on_press(Message::IncrementPressed), +/// ) +/// .push( +/// Text::new(self.value.to_string()).size(50), +/// ) +/// .push( +/// Button::new(&mut self.decrement_button, Text::new("Decrement")) +/// .on_press(Message::DecrementPressed), +/// ) +/// .into() +/// } +/// } +/// ``` +pub trait Sandbox { + /// The type of __messages__ your [`Sandbox`] will produce. + /// + /// [`Sandbox`]: trait.Sandbox.html + type Message: std::fmt::Debug + Send; + + /// Initializes the [`Sandbox`]. + /// + /// Here is where you should return the initial state of your app. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn new() -> Self; + + /// Returns the current title of the [`Sandbox`]. + /// + /// This title can be dynamic! The runtime will automatically update the + /// title of your application when necessary. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn title(&self) -> String; + + /// Handles a __message__ and updates the state of the [`Sandbox`]. + /// + /// This is where you define your __update logic__. All the __messages__, + /// produced by user interactions, will be handled by this method. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn update(&mut self, message: Self::Message); + + /// Returns the widgets to display in the [`Sandbox`]. + /// + /// These widgets can produce __messages__ based on user interaction. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn view(&mut self) -> Element<'_, Self::Message>; + + /// Runs the [`Sandbox`]. + /// + /// This method will take control of the current thread and __will NOT + /// return__. + /// + /// It should probably be that last thing you call in your `main` function. + /// + /// [`Sandbox`]: trait.Sandbox.html + fn run() + where + Self: 'static + Sized, + { + <Self as Application>::run() + } +} + +impl<T> Application for T +where + T: Sandbox, +{ + type Message = T::Message; + + fn new() -> (Self, Command<T::Message>) { + (T::new(), Command::none()) + } + + fn title(&self) -> String { + T::title(self) + } + + fn update(&mut self, message: T::Message) -> Command<T::Message> { + T::update(self, message); + + Command::none() + } + + fn view(&mut self) -> Element<'_, T::Message> { + T::view(self) + } +} diff --git a/src/winit.rs b/src/winit.rs deleted file mode 100644 index c869a269..00000000 --- a/src/winit.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub use iced_wgpu::{Primitive, Renderer}; - -pub use iced_winit::{ - button, scrollable, slider, text, text_input, winit, Align, Background, - Checkbox, Color, Command, Font, Image, Length, Radio, Scrollable, Slider, - Text, TextInput, -}; - -pub type Element<'a, Message> = iced_winit::Element<'a, Message, Renderer>; -pub type Container<'a, Message> = iced_winit::Container<'a, Message, Renderer>; -pub type Row<'a, Message> = iced_winit::Row<'a, Message, Renderer>; -pub type Column<'a, Message> = iced_winit::Column<'a, Message, Renderer>; -pub type Button<'a, Message> = iced_winit::Button<'a, Message, Renderer>; diff --git a/web/src/lib.rs b/web/src/lib.rs index 6252f2be..00a85cf5 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -7,7 +7,10 @@ pub mod widget; pub use bus::Bus; pub use element::Element; -pub use iced_core::{Align, Background, Color, Length}; +pub use iced_core::{ + Align, Background, Color, Font, HorizontalAlignment, Length, + VerticalAlignment, +}; pub use widget::*; pub trait Application { diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 257034a7..ddf67743 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -1,11 +1,103 @@ -use crate::{Bus, Element, Widget}; +use crate::{Background, Bus, Element, Length, Widget}; use dodrio::bumpalo; -pub use iced_core::button::State; +/// A generic widget that produces a message when clicked. +pub struct Button<'a, Message> { + content: Element<'a, Message>, + on_press: Option<Message>, + width: Length, + min_width: u32, + padding: u16, + background: Option<Background>, + border_radius: u16, +} + +impl<'a, Message> Button<'a, Message> { + /// Creates a new [`Button`] with some local [`State`] and the given + /// content. + /// + /// [`Button`]: struct.Button.html + /// [`State`]: struct.State.html + pub fn new<E>(_state: &'a mut State, content: E) -> Self + where + E: Into<Element<'a, Message>>, + { + Button { + content: content.into(), + on_press: None, + width: Length::Shrink, + min_width: 0, + padding: 0, + background: None, + border_radius: 0, + } + } + + /// Sets the width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the minimum width of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn min_width(mut self, min_width: u32) -> Self { + self.min_width = min_width; + self + } + + /// Sets the padding of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } -pub type Button<'a, Message> = - iced_core::Button<'a, Message, Element<'a, Message>>; + /// Sets the [`Background`] of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + /// [`Background`]: ../../struct.Background.html + pub fn background(mut self, background: Background) -> Self { + self.background = Some(background); + self + } + + /// Sets the border radius of the [`Button`]. + /// + /// [`Button`]: struct.Button.html + pub fn border_radius(mut self, border_radius: u16) -> Self { + self.border_radius = border_radius; + self + } + + /// Sets the message that will be produced when the [`Button`] is pressed. + /// + /// [`Button`]: struct.Button.html + pub fn on_press(mut self, msg: Message) -> Self { + self.on_press = Some(msg); + self + } +} + +/// The local state of a [`Button`]. +/// +/// [`Button`]: struct.Button.html +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct State; + +impl State { + /// Creates a new [`State`]. + /// + /// [`State`]: struct.State.html + pub fn new() -> State { + State::default() + } +} impl<'a, Message> Widget<Message> for Button<'a, Message> where diff --git a/web/src/widget/checkbox.rs b/web/src/widget/checkbox.rs index 72f0a2aa..8bcef816 100644 --- a/web/src/widget/checkbox.rs +++ b/web/src/widget/checkbox.rs @@ -1,8 +1,62 @@ -use crate::{Bus, Element, Widget}; +use crate::{Bus, Color, Element, Widget}; use dodrio::bumpalo; -pub use iced_core::Checkbox; +/// A box that can be checked. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Checkbox; +/// +/// pub enum Message { +/// CheckboxToggled(bool), +/// } +/// +/// let is_checked = true; +/// +/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); +/// ``` +/// +///  +pub struct Checkbox<Message> { + is_checked: bool, + on_toggle: Box<dyn Fn(bool) -> Message>, + label: String, + label_color: Option<Color>, +} + +impl<Message> Checkbox<Message> { + /// 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<F>(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<C: Into<Color>>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl<Message> Widget<Message> for Checkbox<Message> where diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index becd6bc6..cea50f6d 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -1,8 +1,110 @@ -use crate::{Bus, Element, Widget}; +use crate::{Align, Bus, Element, Length, Widget}; use dodrio::bumpalo; +use std::u32; -pub type Column<'a, Message> = iced_core::Column<Element<'a, Message>>; +/// A container that distributes its contents vertically. +/// +/// A [`Column`] will try to fill the horizontal space of its container. +/// +/// [`Column`]: struct.Column.html +pub struct Column<'a, Message> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec<Element<'a, Message>>, +} + +impl<'a, Message> Column<'a, Message> { + /// Creates an empty [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn new() -> Self { + Column { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children: Vec::new(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Column`] in pixels. + /// + /// [`Column`]: struct.Column.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + /// + /// [`Column`]: struct.Column.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an element to the [`Column`]. + /// + /// [`Column`]: struct.Column.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message>>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message> Widget<Message> for Column<'a, Message> { fn node<'b>( diff --git a/web/src/widget/image.rs b/web/src/widget/image.rs index bd3e5daf..ab510bdb 100644 --- a/web/src/widget/image.rs +++ b/web/src/widget/image.rs @@ -2,7 +2,55 @@ use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; -pub use iced_core::Image; +/// A frame that displays an image while keeping aspect ratio. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Image; +/// +/// let image = Image::new("resources/ferris.png"); +/// ``` +#[derive(Debug)] +pub struct Image { + /// The image path + pub path: String, + + /// The width of the image + pub width: Length, + + /// The height of the image + pub height: Length, +} + +impl Image { + /// Creates a new [`Image`] with the given path. + /// + /// [`Image`]: struct.Image.html + pub fn new<T: Into<String>>(path: T) -> Self { + Image { + path: path.into(), + width: Length::Shrink, + height: Length::Shrink, + } + } + + /// Sets the width of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Image`] boundaries. + /// + /// [`Image`]: struct.Image.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} impl<Message> Widget<Message> for Image { fn node<'b>( diff --git a/web/src/widget/radio.rs b/web/src/widget/radio.rs index d249ad26..a0b8fc43 100644 --- a/web/src/widget/radio.rs +++ b/web/src/widget/radio.rs @@ -1,8 +1,71 @@ -use crate::{Bus, Element, Widget}; +use crate::{Bus, Color, Element, Widget}; use dodrio::bumpalo; -pub use iced_core::Radio; +/// A circular button representing a choice. +/// +/// # Example +/// ``` +/// # use iced_web::Radio; +/// +/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// pub enum Choice { +/// A, +/// B, +/// } +/// +/// #[derive(Debug, Clone, Copy)] +/// pub enum Message { +/// RadioSelected(Choice), +/// } +/// +/// let selected_choice = Some(Choice::A); +/// +/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected); +/// +/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected); +/// ``` +/// +///  +pub struct Radio<Message> { + is_selected: bool, + on_click: Message, + label: String, + label_color: Option<Color>, +} + +impl<Message> Radio<Message> { + /// Creates a new [`Radio`] button. + /// + /// It expects: + /// * the value related to the [`Radio`] button + /// * the label of the [`Radio`] button + /// * the current selected value + /// * a function that will be called when the [`Radio`] is selected. It + /// receives the value of the radio and must produce a `Message`. + /// + /// [`Radio`]: struct.Radio.html + pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self + where + V: Eq + Copy, + F: 'static + Fn(V) -> Message, + { + Radio { + is_selected: Some(value) == selected, + on_click: f(value), + label: String::from(label), + label_color: None, + } + } + + /// Sets the `Color` of the label of the [`Radio`]. + /// + /// [`Radio`]: struct.Radio.html + pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl<Message> Widget<Message> for Radio<Message> where diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index cf6ae594..44cacd50 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -1,8 +1,112 @@ -use crate::{Bus, Element, Widget}; +use crate::{Align, Bus, Element, Length, Widget}; use dodrio::bumpalo; +use std::u32; -pub type Row<'a, Message> = iced_core::Row<Element<'a, Message>>; +/// A container that distributes its contents horizontally. +/// +/// A [`Row`] will try to fill the horizontal space of its container. +/// +/// [`Row`]: struct.Row.html +#[allow(missing_docs)] +pub struct Row<'a, Message> { + spacing: u16, + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + align_items: Align, + children: Vec<Element<'a, Message>>, +} + +impl<'a, Message> Row<'a, Message> { + /// Creates an empty [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn new() -> Self { + Row { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children: Vec::new(), + } + } + + /// Sets the horizontal spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + + /// Sets the padding of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn padding(mut self, units: u16) -> Self { + self.padding = units; + self + } + + /// Sets the width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + /// Sets the maximum height of the [`Row`]. + /// + /// [`Row`]: struct.Row.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the vertical alignment of the contents of the [`Row`] . + /// + /// [`Row`]: struct.Row.html + pub fn align_items(mut self, align: Align) -> Self { + self.align_items = align; + self + } + + /// Adds an [`Element`] to the [`Row`]. + /// + /// [`Element`]: ../struct.Element.html + /// [`Row`]: struct.Row.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message>>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message> Widget<Message> for Row<'a, Message> { fn node<'b>( diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 54b2fdf6..acdef0a1 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -1,8 +1,55 @@ -use crate::{Bus, Element, Widget}; +use crate::{Bus, Element, Length, Widget}; use dodrio::bumpalo; +use std::{ops::RangeInclusive, rc::Rc}; -pub use iced_core::slider::*; +pub struct Slider<'a, Message> { + _state: &'a mut State, + range: RangeInclusive<f32>, + value: f32, + on_change: Rc<Box<dyn Fn(f32) -> Message>>, + width: Length, +} + +impl<'a, Message> Slider<'a, Message> { + /// Creates a new [`Slider`]. + /// + /// It expects: + /// * the local [`State`] of the [`Slider`] + /// * an inclusive range of possible values + /// * the current value of the [`Slider`] + /// * a function that will be called when the [`Slider`] is dragged. + /// It receives the new value of the [`Slider`] and must produce a + /// `Message`. + /// + /// [`Slider`]: struct.Slider.html + /// [`State`]: struct.State.html + pub fn new<F>( + state: &'a mut State, + range: RangeInclusive<f32>, + value: f32, + on_change: F, + ) -> Self + where + F: 'static + Fn(f32) -> Message, + { + Slider { + _state: state, + value: value.max(*range.start()).min(*range.end()), + range, + on_change: Rc::new(Box::new(on_change)), + width: Length::Fill, + } + } + + /// Sets the width of the [`Slider`]. + /// + /// [`Slider`]: struct.Slider.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } +} impl<'a, Message> Widget<Message> for Slider<'a, Message> where @@ -60,3 +107,11 @@ where Element::new(slider) } } + +pub struct State; + +impl State { + pub fn new() -> Self { + Self + } +} diff --git a/web/src/widget/text.rs b/web/src/widget/text.rs index 41ccd6fc..1183a3cd 100644 --- a/web/src/widget/text.rs +++ b/web/src/widget/text.rs @@ -1,7 +1,111 @@ -use crate::{Bus, Element, Widget}; +use crate::{ + Bus, Color, Element, Font, HorizontalAlignment, Length, VerticalAlignment, + Widget, +}; use dodrio::bumpalo; -pub use iced_core::text::*; +/// A paragraph of text. +/// +/// # Example +/// +/// ``` +/// # use iced_web::Text; +/// +/// Text::new("I <3 iced!") +/// .size(40); +/// ``` +#[derive(Debug, Clone)] +pub struct Text { + content: String, + size: Option<u16>, + color: Option<Color>, + font: Font, + width: Length, + height: Length, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, +} + +impl Text { + /// Create a new fragment of [`Text`] with the given contents. + /// + /// [`Text`]: struct.Text.html + pub fn new<T: Into<String>>(label: T) -> Self { + Text { + content: label.into(), + size: None, + color: None, + font: Font::Default, + width: Length::Fill, + height: Length::Shrink, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Top, + } + } + + /// Sets the size of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + pub fn size(mut self, size: u16) -> Self { + self.size = Some(size); + self + } + + /// Sets the [`Color`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Color`]: ../../struct.Color.html + pub fn color<C: Into<Color>>(mut self, color: C) -> Self { + self.color = Some(color.into()); + self + } + + /// Sets the [`Font`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`Font`]: ../../struct.Font.html + pub fn font(mut self, font: Font) -> Self { + self.font = font; + self + } + + /// Sets the width of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Text`] boundaries. + /// + /// [`Text`]: struct.Text.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the [`HorizontalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`HorizontalAlignment`]: enum.HorizontalAlignment.html + pub fn horizontal_alignment( + mut self, + alignment: HorizontalAlignment, + ) -> Self { + self.horizontal_alignment = alignment; + self + } + + /// Sets the [`VerticalAlignment`] of the [`Text`]. + /// + /// [`Text`]: struct.Text.html + /// [`VerticalAlignment`]: enum.VerticalAlignment.html + pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self { + self.vertical_alignment = alignment; + self + } +} impl<'a, Message> Widget<Message> for Text { fn node<'b>( diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index b9f1ca6f..564dbda4 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,4 +1,7 @@ -use iced_native::{text, Background, Color, Font, Rectangle, Vector}; +use iced_native::{ + Background, Color, Font, HorizontalAlignment, Rectangle, Vector, + VerticalAlignment, +}; #[derive(Debug, Clone)] pub enum Primitive { @@ -12,8 +15,8 @@ pub enum Primitive { color: Color, size: f32, font: Font, - horizontal_alignment: text::HorizontalAlignment, - vertical_alignment: text::VerticalAlignment, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, }, Quad { bounds: Rectangle, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 52764248..4199eee5 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,7 +1,7 @@ use crate::{quad, text, Image, Primitive, Quad, Transformation}; use iced_native::{ - renderer::Debugger, renderer::Windowed, Background, Color, Layout, - MouseCursor, Point, Rectangle, Vector, Widget, + renderer::{Debugger, Windowed}, + Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, }; use wgpu::{ @@ -152,21 +152,21 @@ impl Renderer { vertical_alignment, } => { let x = match horizontal_alignment { - iced_native::text::HorizontalAlignment::Left => bounds.x, - iced_native::text::HorizontalAlignment::Center => { + iced_native::HorizontalAlignment::Left => bounds.x, + iced_native::HorizontalAlignment::Center => { bounds.x + bounds.width / 2.0 } - iced_native::text::HorizontalAlignment::Right => { + iced_native::HorizontalAlignment::Right => { bounds.x + bounds.width } }; let y = match vertical_alignment { - iced_native::text::VerticalAlignment::Top => bounds.y, - iced_native::text::VerticalAlignment::Center => { + iced_native::VerticalAlignment::Top => bounds.y, + iced_native::VerticalAlignment::Center => { bounds.y + bounds.height / 2.0 } - iced_native::text::VerticalAlignment::Bottom => { + iced_native::VerticalAlignment::Bottom => { bounds.y + bounds.height } }; @@ -183,24 +183,24 @@ impl Renderer { font_id: self.text_pipeline.find_font(*font), layout: wgpu_glyph::Layout::default() .h_align(match horizontal_alignment { - iced_native::text::HorizontalAlignment::Left => { + iced_native::HorizontalAlignment::Left => { wgpu_glyph::HorizontalAlign::Left } - iced_native::text::HorizontalAlignment::Center => { + iced_native::HorizontalAlignment::Center => { wgpu_glyph::HorizontalAlign::Center } - iced_native::text::HorizontalAlignment::Right => { + iced_native::HorizontalAlignment::Right => { wgpu_glyph::HorizontalAlign::Right } }) .v_align(match vertical_alignment { - iced_native::text::VerticalAlignment::Top => { + iced_native::VerticalAlignment::Top => { wgpu_glyph::VerticalAlign::Top } - iced_native::text::VerticalAlignment::Center => { + iced_native::VerticalAlignment::Center => { wgpu_glyph::VerticalAlign::Center } - iced_native::text::VerticalAlignment::Bottom => { + iced_native::VerticalAlignment::Bottom => { wgpu_glyph::VerticalAlign::Bottom } }), diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index a19c7d86..86963053 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -1,52 +1,22 @@ use crate::{Primitive, Renderer}; -use iced_native::{ - button, layout, Background, Button, Layout, Length, MouseCursor, Point, - Rectangle, -}; +use iced_native::{button, Background, MouseCursor, Point, Rectangle}; impl button::Renderer for Renderer { - fn layout<Message>( - &self, - button: &Button<Message, Self>, - limits: &layout::Limits, - ) -> layout::Node { - let padding = f32::from(button.padding); - let limits = limits - .min_width(button.min_width) - .width(button.width) - .height(Length::Shrink) - .pad(padding); - - let mut content = button.content.layout(self, &limits); - - content.bounds.x = padding; - content.bounds.y = padding; - - let size = limits.resolve(content.size()).pad(padding); - - layout::Node::with_children(size, vec![content]) - } - - fn draw<Message>( + fn draw( &mut self, - button: &Button<Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + is_pressed: bool, + background: Option<Background>, + border_radius: u16, + (content, _): Self::Output, ) -> Self::Output { - let bounds = layout.bounds(); - - let (content, _) = button.content.draw( - self, - layout.children().next().unwrap(), - cursor_position, - ); - let is_mouse_over = bounds.contains(cursor_position); // TODO: Render proper shadows // TODO: Make hovering and pressed styles configurable let shadow_offset = if is_mouse_over { - if button.state.is_pressed { + if is_pressed { 0.0 } else { 2.0 @@ -56,7 +26,7 @@ impl button::Renderer for Renderer { }; ( - match button.background { + match background { None => content, Some(background) => Primitive::Group { primitives: vec![ @@ -69,12 +39,12 @@ impl button::Renderer for Renderer { background: Background::Color( [0.0, 0.0, 0.0, 0.5].into(), ), - border_radius: button.border_radius, + border_radius, }, Primitive::Quad { bounds, background, - border_radius: button.border_radius, + border_radius, }, content, ], diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index aedb821c..54b4b1cc 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -1,63 +1,35 @@ use crate::{Primitive, Renderer}; use iced_native::{ - checkbox, layout, text, text::HorizontalAlignment, text::VerticalAlignment, - Align, Background, Checkbox, Column, Layout, Length, MouseCursor, Point, - Rectangle, Row, Text, Widget, + checkbox, Background, HorizontalAlignment, MouseCursor, Rectangle, + VerticalAlignment, }; const SIZE: f32 = 28.0; impl checkbox::Renderer for Renderer { - fn layout<Message>( - &self, - checkbox: &Checkbox<Message>, - limits: &layout::Limits, - ) -> layout::Node { - Row::<(), Self>::new() - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SIZE as u16)) - .height(Length::Units(SIZE as u16)), - ) - .push(Text::new(&checkbox.label)) - .layout(self, limits) + fn default_size(&self) -> u32 { + SIZE as u32 } - fn draw<Message>( + fn draw( &mut self, - checkbox: &Checkbox<Message>, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + (label, _): Self::Output, ) -> Self::Output { - let bounds = layout.bounds(); - let mut children = layout.children(); - - let checkbox_layout = children.next().unwrap(); - let label_layout = children.next().unwrap(); - let checkbox_bounds = checkbox_layout.bounds(); - - let (label, _) = text::Renderer::draw( - self, - &Text::new(&checkbox.label), - label_layout, - ); - - let is_mouse_over = bounds.contains(cursor_position); - let (checkbox_border, checkbox_box) = ( Primitive::Quad { - bounds: checkbox_bounds, + bounds, background: Background::Color([0.6, 0.6, 0.6].into()), border_radius: 6, }, Primitive::Quad { bounds: Rectangle { - x: checkbox_bounds.x + 1.0, - y: checkbox_bounds.y + 1.0, - width: checkbox_bounds.width - 2.0, - height: checkbox_bounds.height - 2.0, + x: bounds.x + 1.0, + y: bounds.y + 1.0, + width: bounds.width - 2.0, + height: bounds.height - 2.0, }, background: Background::Color( if is_mouse_over { @@ -73,12 +45,12 @@ impl checkbox::Renderer for Renderer { ( Primitive::Group { - primitives: if checkbox.is_checked { + primitives: if is_checked { let check = Primitive::Text { content: crate::text::CHECKMARK_ICON.to_string(), font: crate::text::BUILTIN_ICONS, - size: checkbox_bounds.height * 0.7, - bounds: checkbox_bounds, + size: bounds.height * 0.7, + bounds: bounds, color: [0.3, 0.3, 0.3].into(), horizontal_alignment: HorizontalAlignment::Center, vertical_alignment: VerticalAlignment::Center, diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index cac6da77..6c31af90 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -1,10 +1,10 @@ use crate::{Primitive, Renderer}; -use iced_native::{column, Column, Layout, MouseCursor, Point}; +use iced_native::{column, Element, Layout, MouseCursor, Point}; impl column::Renderer for Renderer { fn draw<Message>( &mut self, - column: &Column<'_, Message, Self>, + content: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -12,8 +12,7 @@ impl column::Renderer for Renderer { ( Primitive::Group { - primitives: column - .children + primitives: content .iter() .zip(layout.children()) .map(|(child, layout)| { diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 0afb11e3..fe594365 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -1,28 +1,9 @@ use crate::{Primitive, Renderer}; -use iced_native::{image, layout, Image, Layout, Length, MouseCursor, Size}; +use iced_native::{image, Image, Layout, MouseCursor}; impl image::Renderer for Renderer { - fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node { - let (width, height) = self.image_pipeline.dimensions(&image.path); - - let aspect_ratio = width as f32 / height as f32; - - // TODO: Deal with additional cases - let (width, height) = match (image.width, image.height) { - (Length::Units(width), _) => ( - image.width, - Length::Units((width as f32 / aspect_ratio).round() as u16), - ), - (_, _) => { - (Length::Units(width as u16), Length::Units(height as u16)) - } - }; - - let mut size = limits.width(width).height(height).resolve(Size::ZERO); - - size.height = size.width / aspect_ratio; - - layout::Node::new(size) + fn dimensions(&self, path: &str) -> (u32, u32) { + self.image_pipeline.dimensions(path) } fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output { diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 1f17ba28..3c00a4c2 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -1,59 +1,33 @@ use crate::{Primitive, Renderer}; -use iced_native::{ - layout, radio, text, Align, Background, Column, Layout, Length, - MouseCursor, Point, Radio, Rectangle, Row, Text, Widget, -}; +use iced_native::{radio, Background, MouseCursor, Rectangle}; const SIZE: f32 = 28.0; const DOT_SIZE: f32 = SIZE / 2.0; impl radio::Renderer for Renderer { - fn layout<Message>( - &self, - radio: &Radio<Message>, - limits: &layout::Limits, - ) -> layout::Node { - Row::<(), Self>::new() - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SIZE as u16)) - .height(Length::Units(SIZE as u16)), - ) - .push(Text::new(&radio.label)) - .layout(self, limits) + fn default_size(&self) -> u32 { + SIZE as u32 } - fn draw<Message>( + fn draw( &mut self, - radio: &Radio<Message>, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + (label, _): Self::Output, ) -> Self::Output { - let bounds = layout.bounds(); - let mut children = layout.children(); - - let radio_bounds = children.next().unwrap().bounds(); - let label_layout = children.next().unwrap(); - - let (label, _) = - text::Renderer::draw(self, &Text::new(&radio.label), label_layout); - - let is_mouse_over = bounds.contains(cursor_position); - let (radio_border, radio_box) = ( Primitive::Quad { - bounds: radio_bounds, + bounds, background: Background::Color([0.6, 0.6, 0.6].into()), border_radius: (SIZE / 2.0) as u16, }, Primitive::Quad { bounds: Rectangle { - x: radio_bounds.x + 1.0, - y: radio_bounds.y + 1.0, - width: radio_bounds.width - 2.0, - height: radio_bounds.height - 2.0, + x: bounds.x + 1.0, + y: bounds.y + 1.0, + width: bounds.width - 2.0, + height: bounds.height - 2.0, }, background: Background::Color( if is_mouse_over { @@ -69,13 +43,13 @@ impl radio::Renderer for Renderer { ( Primitive::Group { - primitives: if radio.is_selected { + primitives: if is_selected { let radio_circle = Primitive::Quad { bounds: Rectangle { - x: radio_bounds.x + DOT_SIZE / 2.0, - y: radio_bounds.y + DOT_SIZE / 2.0, - width: radio_bounds.width - DOT_SIZE, - height: radio_bounds.height - DOT_SIZE, + x: bounds.x + DOT_SIZE / 2.0, + y: bounds.y + DOT_SIZE / 2.0, + width: bounds.width - DOT_SIZE, + height: bounds.height - DOT_SIZE, }, background: Background::Color([0.3, 0.3, 0.3].into()), border_radius: (DOT_SIZE / 2.0) as u16, diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index bbfef9a1..f082dc61 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -1,10 +1,10 @@ use crate::{Primitive, Renderer}; -use iced_native::{row, Layout, MouseCursor, Point, Row}; +use iced_native::{row, Element, Layout, MouseCursor, Point}; impl row::Renderer for Renderer { fn draw<Message>( &mut self, - row: &Row<'_, Message, Self>, + children: &[Element<'_, Message, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -12,8 +12,7 @@ impl row::Renderer for Renderer { ( Primitive::Group { - primitives: row - .children + primitives: children .iter() .zip(layout.children()) .map(|(child, layout)| { diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index dd6ebcc1..58dc3df9 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,7 +1,6 @@ use crate::{Primitive, Renderer}; use iced_native::{ - scrollable, Background, Layout, MouseCursor, Point, Rectangle, Scrollable, - Vector, Widget, + scrollable, Background, MouseCursor, Point, Rectangle, Vector, }; const SCROLLBAR_WIDTH: u16 = 10; @@ -28,33 +27,18 @@ impl scrollable::Renderer for Renderer { && scrollbar_bounds(bounds).contains(cursor_position) } - fn draw<Message>( + fn draw( &mut self, - scrollable: &Scrollable<'_, Message, Self>, + state: &scrollable::State, bounds: Rectangle, - content: Layout<'_>, - cursor_position: Point, + content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + offset: u32, + (content, mouse_cursor): Self::Output, ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - let content_bounds = content.bounds(); - - let offset = scrollable.state.offset(bounds, content_bounds); let is_content_overflowing = content_bounds.height > bounds.height; let scrollbar_bounds = scrollbar_bounds(bounds); - let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar( - bounds, - content_bounds, - cursor_position, - ); - - let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { - Point::new(cursor_position.x, cursor_position.y + offset as f32) - } else { - Point::new(cursor_position.x, -1.0) - }; - - let (content, mouse_cursor) = - scrollable.content.draw(self, content, cursor_position); let clip = Primitive::Clip { bounds, @@ -64,7 +48,7 @@ impl scrollable::Renderer for Renderer { ( if is_content_overflowing - && (is_mouse_over || scrollable.state.is_scrollbar_grabbed()) + && (is_mouse_over || state.is_scrollbar_grabbed()) { let ratio = bounds.height / content_bounds.height; let scrollbar_height = bounds.height * ratio; @@ -82,9 +66,7 @@ impl scrollable::Renderer for Renderer { border_radius: 5, }; - if is_mouse_over_scrollbar - || scrollable.state.is_scrollbar_grabbed() - { + if is_mouse_over_scrollbar || state.is_scrollbar_grabbed() { let scrollbar_background = Primitive::Quad { bounds: Rectangle { x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), @@ -109,9 +91,7 @@ impl scrollable::Renderer for Renderer { } else { clip }, - if is_mouse_over_scrollbar - || scrollable.state.is_scrollbar_grabbed() - { + if is_mouse_over_scrollbar || state.is_scrollbar_grabbed() { MouseCursor::Idle } else { mouse_cursor diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index 98065bc9..f561be0a 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -1,32 +1,22 @@ use crate::{Primitive, Renderer}; -use iced_native::{ - layout, slider, Background, Color, Layout, Length, MouseCursor, Point, - Rectangle, Size, Slider, -}; +use iced_native::{slider, Background, Color, MouseCursor, Point, Rectangle}; const HANDLE_WIDTH: f32 = 8.0; const HANDLE_HEIGHT: f32 = 22.0; impl slider::Renderer for Renderer { - fn layout<Message>( - &self, - slider: &Slider<Message>, - limits: &layout::Limits, - ) -> layout::Node { - let limits = limits.width(slider.width).height(Length::Units(30)); - let size = limits.resolve(Size::ZERO); - - layout::Node::new(size) + fn height(&self) -> u32 { + 30 } - fn draw<Message>( + fn draw( &mut self, - slider: &Slider<Message>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + range: std::ops::RangeInclusive<f32>, + value: f32, + is_dragging: bool, ) -> Self::Output { - let bounds = layout.bounds(); - let is_mouse_over = bounds.contains(cursor_position); let rail_y = bounds.y + (bounds.height / 2.0).round(); @@ -54,11 +44,10 @@ impl slider::Renderer for Renderer { }, ); - let (range_start, range_end) = slider.range.clone().into_inner(); + let (range_start, range_end) = range.into_inner(); let handle_offset = (bounds.width - HANDLE_WIDTH) - * ((slider.value - range_start) - / (range_end - range_start).max(1.0)); + * ((value - range_start) / (range_end - range_start).max(1.0)); let (handle_border, handle) = ( Primitive::Quad { @@ -79,7 +68,7 @@ impl slider::Renderer for Renderer { height: HANDLE_HEIGHT, }, background: Background::Color( - if slider.state.is_dragging() { + if is_dragging { [0.85, 0.85, 0.85] } else if is_mouse_over { [0.90, 0.90, 0.90] @@ -96,7 +85,7 @@ impl slider::Renderer for Renderer { Primitive::Group { primitives: vec![rail_top, rail_bottom, handle_border, handle], }, - if slider.state.is_dragging() { + if is_dragging { MouseCursor::Grabbing } else if is_mouse_over { MouseCursor::Grab diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index a8ead70b..08a162ba 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -1,5 +1,8 @@ use crate::{Primitive, Renderer}; -use iced_native::{layout, text, Color, Layout, MouseCursor, Size, Text}; +use iced_native::{ + text, Color, Font, HorizontalAlignment, MouseCursor, Rectangle, Size, + VerticalAlignment, +}; use std::f32; @@ -7,30 +10,40 @@ use std::f32; const DEFAULT_TEXT_SIZE: f32 = 20.0; impl text::Renderer for Renderer { - fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node { - let limits = limits.width(text.width).height(text.height); - let size = text.size.map(f32::from).unwrap_or(DEFAULT_TEXT_SIZE); - let bounds = limits.max(); - - let (width, height) = - self.text_pipeline - .measure(&text.content, size, text.font, bounds); - - let size = limits.resolve(Size::new(width, height)); + fn default_size(&self) -> u16 { + DEFAULT_TEXT_SIZE as u16 + } - layout::Node::new(size) + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.text_pipeline + .measure(content, f32::from(size), font, bounds) } - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output { + fn draw( + &mut self, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option<Color>, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output { ( Primitive::Text { - content: text.content.clone(), - size: text.size.map(f32::from).unwrap_or(DEFAULT_TEXT_SIZE), - bounds: layout.bounds(), - color: text.color.unwrap_or(Color::BLACK), - font: text.font, - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, + content: content.to_string(), + size: f32::from(size), + bounds, + color: color.unwrap_or(Color::BLACK), + font, + horizontal_alignment, + vertical_alignment, }, MouseCursor::OutOfBounds, ) diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 855e945c..9ed3b415 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -1,8 +1,8 @@ use crate::{Primitive, Renderer}; use iced_native::{ - text::HorizontalAlignment, text::VerticalAlignment, text_input, Background, - Color, Font, MouseCursor, Point, Rectangle, Size, TextInput, Vector, + text_input, Background, Color, Font, HorizontalAlignment, MouseCursor, + Point, Rectangle, Size, Vector, VerticalAlignment, }; use std::f32; @@ -12,19 +12,22 @@ impl text_input::Renderer for Renderer { 20 } - fn draw<Message>( + fn draw( &mut self, - text_input: &TextInput<Message>, bounds: Rectangle, text_bounds: Rectangle, cursor_position: Point, + size: u16, + placeholder: &str, + value: &text_input::Value, + state: &text_input::State, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); let border = Primitive::Quad { bounds, background: Background::Color( - if is_mouse_over || text_input.state.is_focused { + if is_mouse_over || state.is_focused() { [0.5, 0.5, 0.5] } else { [0.7, 0.7, 0.7] @@ -45,12 +48,12 @@ impl text_input::Renderer for Renderer { border_radius: 5, }; - let size = f32::from(text_input.size.unwrap_or(self.default_size())); - let text = text_input.value.to_string(); + let size = f32::from(size); + let text = value.to_string(); - let value = Primitive::Text { + let text_value = Primitive::Text { content: if text.is_empty() { - text_input.placeholder.clone() + placeholder.to_string() } else { text.clone() }, @@ -70,14 +73,12 @@ impl text_input::Renderer for Renderer { vertical_alignment: VerticalAlignment::Center, }; - let (contents_primitive, offset) = if text_input.state.is_focused { - let text_before_cursor = &text_input - .value - .until(text_input.state.cursor_position(&text_input.value)) - .to_string(); + let (contents_primitive, offset) = if state.is_focused() { + let text_before_cursor = + value.until(state.cursor_position(value)).to_string(); let (mut text_value_width, _) = self.text_pipeline.measure( - text_before_cursor, + &text_before_cursor, size, Font::Default, Size::new(f32::INFINITY, text_bounds.height), @@ -104,7 +105,7 @@ impl text_input::Renderer for Renderer { ( Primitive::Group { - primitives: vec![value, cursor], + primitives: vec![text_value, cursor], }, Vector::new( ((text_value_width + 5.0) - text_bounds.width).max(0.0) @@ -113,7 +114,7 @@ impl text_input::Renderer for Renderer { ), ) } else { - (value, Vector::new(0, 0)) + (text_value, Vector::new(0, 0)) }; let contents = Primitive::Clip { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index d9482fe4..a708b1bd 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -1,3 +1,4 @@ +#[doc(no_inline)] pub use iced_native::*; pub use winit; |