From 65eb218d3d7ba52b2869a586a1480eeb3c8f84e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 21 Nov 2019 13:47:20 +0100 Subject: [PATCH] Move widgets from `core` to `native` and `web` Also made fields private and improved `Renderer` traits. --- core/src/align.rs | 26 +++ core/src/lib.rs | 4 +- core/src/widget.rs | 39 ----- core/src/widget/button.rs | 114 ------------ core/src/widget/checkbox.rs | 78 --------- core/src/widget/column.rs | 126 -------------- core/src/widget/container.rs | 88 ---------- core/src/widget/image.rs | 65 ------- core/src/widget/radio.rs | 88 ---------- core/src/widget/row.rs | 121 ------------- core/src/widget/scrollable.rs | 175 ------------------- core/src/widget/slider.rs | 120 ------------- core/src/widget/text.rs | 132 -------------- core/src/widget/text_input.rs | 220 ------------------------ examples/todos.rs | 9 +- examples/tour.rs | 7 +- native/src/element.rs | 57 +----- native/src/lib.rs | 61 ++----- native/src/renderer.rs | 4 + native/src/renderer/null.rs | 143 +++++++++++++++ native/src/user_interface.rs | 11 +- native/src/widget.rs | 2 +- native/src/widget/button.rs | 166 +++++++++++++++--- native/src/widget/checkbox.rs | 128 +++++++++++--- native/src/widget/column.rs | 113 +++++++++++- native/src/widget/container.rs | 92 +++++++++- native/src/widget/image.rs | 81 +++++++-- native/src/widget/radio.rs | 145 +++++++++++++--- native/src/widget/row.rs | 115 ++++++++++++- native/src/widget/scrollable.rs | 217 +++++++++++++++++++++-- native/src/widget/slider.rs | 109 ++++++++++-- native/src/widget/text.rs | 162 +++++++++++++++-- native/src/widget/text_input.rs | 229 ++++++++++++++++++++++++- rustfmt.toml | 1 + src/lib.rs | 4 +- src/native.rs | 51 ++++++ src/winit.rs | 13 -- web/src/lib.rs | 5 +- web/src/widget/button.rs | 100 ++++++++++- web/src/widget/checkbox.rs | 58 ++++++- web/src/widget/column.rs | 106 +++++++++++- web/src/widget/image.rs | 50 +++++- web/src/widget/radio.rs | 67 +++++++- web/src/widget/row.rs | 108 +++++++++++- web/src/widget/slider.rs | 59 ++++++- web/src/widget/text.rs | 108 +++++++++++- wgpu/src/primitive.rs | 9 +- wgpu/src/renderer.rs | 28 +-- wgpu/src/renderer/widget/button.rs | 52 ++---- wgpu/src/renderer/widget/checkbox.rs | 62 ++----- wgpu/src/renderer/widget/column.rs | 7 +- wgpu/src/renderer/widget/image.rs | 25 +-- wgpu/src/renderer/widget/radio.rs | 62 ++----- wgpu/src/renderer/widget/row.rs | 7 +- wgpu/src/renderer/widget/scrollable.rs | 42 ++--- wgpu/src/renderer/widget/slider.rs | 35 ++-- wgpu/src/renderer/widget/text.rs | 55 +++--- wgpu/src/renderer/widget/text_input.rs | 35 ++-- winit/src/lib.rs | 1 + 59 files changed, 2455 insertions(+), 1942 deletions(-) delete mode 100644 core/src/widget.rs delete mode 100644 core/src/widget/button.rs delete mode 100644 core/src/widget/checkbox.rs delete mode 100644 core/src/widget/column.rs delete mode 100644 core/src/widget/container.rs delete mode 100644 core/src/widget/image.rs delete mode 100644 core/src/widget/radio.rs delete mode 100644 core/src/widget/row.rs delete mode 100644 core/src/widget/scrollable.rs delete mode 100644 core/src/widget/slider.rs delete mode 100644 core/src/widget/text.rs delete mode 100644 core/src/widget/text_input.rs create mode 100644 native/src/renderer/null.rs create mode 100644 src/native.rs delete mode 100644 src/winit.rs diff --git a/core/src/align.rs b/core/src/align.rs index d6915086..8b571db4 100644 --- a/core/src/align.rs +++ b/core/src/align.rs @@ -16,3 +16,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/lib.rs b/core/src/lib.rs index 692e40d4..5a608fe8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -12,7 +12,6 @@ #![deny(unused_results)] #![deny(unsafe_code)] #![deny(rust_2018_idioms)] -pub mod widget; mod align; mod background; @@ -23,7 +22,7 @@ mod point; mod rectangle; mod vector; -pub use align::Align; +pub use align::{Align, HorizontalAlignment, VerticalAlignment}; pub use background::Background; pub use color::Color; pub use font::Font; @@ -31,7 +30,6 @@ pub use length::Length; pub use point::Point; pub use rectangle::Rectangle; pub use vector::Vector; -pub use widget::*; #[cfg(feature = "command")] mod command; 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 0ac88671..00000000 --- a/core/src/widget/button.rs +++ /dev/null @@ -1,114 +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. -#[allow(missing_docs)] -#[derive(Debug)] -pub struct Button<'a, Message, Element> { - pub state: &'a mut State, - pub content: Element, - pub on_press: Option, - pub width: Length, - pub min_width: u32, - pub padding: u16, - pub background: Option, - pub border_radius: u16, -} - -impl<'a, Message, Element> Button<'a, Message, Element> { - /// Creates a new [`Button`] with some local [`State`] and the given - /// content. - /// - /// [`Button`]: struct.Button.html - /// [`State`]: struct.State.html - pub fn new(state: &'a mut State, content: E) -> Self - where - E: Into, - { - 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 - } - - /// 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 { - /// Whether the [`Button`] is currently being pressed. - /// - /// [`Button`]: struct.Button.html - pub is_pressed: bool, -} - -impl State { - /// Creates a new [`State`]. - /// - /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() - } -} 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); -/// ``` -/// -/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) -pub struct Checkbox { - /// 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 Message>, - - /// The label of the checkbox - pub label: String, - - /// The color of the label - pub label_color: Option, -} - -impl std::fmt::Debug for Checkbox { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Checkbox") - .field("is_checked", &self.is_checked) - .field("label", &self.label) - .field("label_color", &self.label_color) - .finish() - } -} - -impl Checkbox { - /// Creates a new [`Checkbox`]. - /// - /// It expects: - /// * a boolean describing whether the [`Checkbox`] is checked or not - /// * the label of the [`Checkbox`] - /// * a function that will be called when the [`Checkbox`] is toggled. - /// It will receive the new state of the [`Checkbox`] and must produce - /// a `Message`. - /// - /// [`Checkbox`]: struct.Checkbox.html - pub fn new(is_checked: bool, label: &str, f: F) -> Self - where - F: 'static + Fn(bool) -> Message, - { - Checkbox { - is_checked, - on_toggle: Box::new(f), - label: String::from(label), - label_color: None, - } - } - - /// Sets the color of the label of the [`Checkbox`]. - /// - /// [`Checkbox`]: struct.Checkbox.html - pub fn label_color>(mut self, color: 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 34dbecf7..00000000 --- a/core/src/widget/column.rs +++ /dev/null @@ -1,126 +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 -#[allow(missing_docs)] -pub struct Column { - 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, -} - -impl Column { - /// 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(mut self, child: E) -> Column - where - E: Into, - { - self.children.push(child.into()); - self - } -} - -impl Default for Column { - fn default() -> Self { - Self::new() - } -} - -impl std::fmt::Debug for Column -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 3fe340d1..00000000 --- a/core/src/widget/container.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{Align, Length}; - -use std::u32; - -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. -#[derive(Debug)] -#[allow(missing_docs)] -pub struct Container { - 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 Container { - /// Creates an empty [`Container`]. - /// - /// [`Container`]: struct.Container.html - pub fn new(content: T) -> Self - where - T: Into, - { - 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>, - - /// 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>(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) -> 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); -/// ``` -/// -/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) -pub struct Radio { - /// 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, -} - -impl std::fmt::Debug for Radio -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 Radio { - /// 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(value: V, label: &str, selected: Option, 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>(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 d2b23bdb..00000000 --- a/core/src/widget/row.rs +++ /dev/null @@ -1,121 +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 -#[allow(missing_docs)] -pub struct Row { - 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, -} - -impl Row { - /// 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(mut self, child: E) -> Row - where - E: Into, - { - self.children.push(child.into()); - self - } -} - -impl std::fmt::Debug for Row -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 da03cd93..00000000 --- a/core/src/widget/scrollable.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! Navigate an endless amount of content with a scrollbar. -use crate::{Align, Column, Length, Point, Rectangle}; - -use std::u32; - -/// A widget that can vertically display an infinite amount of content with a -/// scrollbar. -#[allow(missing_docs)] -#[derive(Debug)] -pub struct Scrollable<'a, Element> { - pub state: &'a mut State, - pub height: Length, - pub max_height: u32, - pub content: Column, -} - -impl<'a, Element> Scrollable<'a, Element> { - /// 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(mut self, child: E) -> Scrollable<'a, Element> - where - E: Into, - { - self.content = self.content.push(child); - self - } -} - -/// The local state of a [`Scrollable`]. -/// -/// [`Scrollable`]: struct.Scrollable.html -#[derive(Debug, Clone, Copy, Default)] -pub struct State { - /// The position where the scrollbar was grabbed at, if it's currently - /// grabbed. - pub scrollbar_grabbed_at: Option, - 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() - } -} diff --git a/core/src/widget/slider.rs b/core/src/widget/slider.rs deleted file mode 100644 index d6fe8ade..00000000 --- a/core/src/widget/slider.rs +++ /dev/null @@ -1,120 +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); -/// ``` -/// -/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true) -#[allow(missing_docs)] -pub struct Slider<'a, Message> { - pub state: &'a mut State, - pub range: RangeInclusive, - pub value: f32, - pub on_change: Rc 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) - .field("width", &self.width) - .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( - state: &'a mut State, - range: RangeInclusive, - 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 { - /// Whether the [`Slider`] is currently being dragged or not. - /// - /// [`Slider`]: struct.Slider.html - 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 1746160e..00000000 --- a/core/src/widget/text.rs +++ /dev/null @@ -1,132 +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)] -#[allow(missing_docs)] -pub struct Text { - pub content: String, - pub size: Option, - pub color: Option, - 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 - /// [`Color`]: ../../struct.Color.html - pub fn 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 - } -} - -/// 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 df933380..00000000 --- a/core/src/widget/text_input.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! Display fields that can be filled with text. -//! -//! A [`TextInput`] has some local [`State`]. -//! -//! [`TextInput`]: struct.TextInput.html -//! [`State`]: struct.State.html -use crate::Length; - -/// A widget that can be filled with text by using a keyboard. -#[allow(missing_docs)] -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, - pub on_change: Box Message>, - pub on_submit: Option, -} - -impl<'a, Message> TextInput<'a, Message> { - /// Creates a new [`TextInput`]. - /// - /// [`TextInput`]: struct.TextInput.html - pub fn new( - 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> 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() - } -} - -/// The state of a [`TextInput`]. -/// -/// [`TextInput`]: struct.TextInput.html -#[derive(Debug, Default, Clone)] -pub struct State { - /// Whether the [`TextInput`] is focused or not. - /// - /// [`TextInput`]: struct.TextInput.html - pub 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, - } - } - - /// Moves the cursor of a [`TextInput`] to the right. - /// - /// [`TextInput`]: struct.TextInput.html - pub 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 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 fn move_cursor_to_end(&mut self, value: &Value) { - self.cursor_position = value.len(); - } - - /// 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()) - } -} - -/// The value of a [`TextInput`]. -/// -/// [`TextInput`]: struct.TextInput.html -// TODO: Use `unicode-segmentation` -#[derive(Debug)] -pub struct Value(Vec); - -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/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 6d7a080f..8a805088 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, Application, Background, Button, + Checkbox, Color, Column, Command, Container, Element, HorizontalAlignment, + Image, Length, Radio, Row, Scrollable, Slider, Text, TextInput, }; pub fn main() { diff --git a/native/src/element.rs b/native/src/element.rs index 23f069f1..cb8aaf54 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 + '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, @@ -127,37 +119,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( - /// # &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; @@ -273,12 +235,6 @@ struct Map<'a, A, B, Renderer> { mapper: Box 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( widget: Box + 'a>, @@ -350,17 +306,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/lib.rs b/native/src/lib.rs index bd03ddcd..55331ba5 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -14,8 +14,8 @@ //! [repository]: https://github.com/hecrj/iced //! //! # Usage -//! Inspired by [The Elm Architecture], Iced expects you to split user interfaces -//! into four different concepts: +//! 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 @@ -25,8 +25,8 @@ //! * __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 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: //! @@ -76,49 +76,7 @@ //! # } //! # //! # 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( -//! # &self, -//! # _button: &Button<'_, Message, Self>, -//! # _limits: &layout::Limits, -//! # ) -> layout::Node { -//! # layout::Node::new(Size::ZERO) -//! # } -//! # -//! # fn draw( -//! # &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<'_>, -//! # ) { -//! # } -//! # } +//! # pub use iced_native::renderer::Null as Renderer; //! # } //! use iced_native::{Button, Column, Text}; //! use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own! @@ -183,8 +141,8 @@ //! } //! ``` //! -//! And that's everything! We just wrote a whole user interface. Iced is now able -//! to: +//! 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 @@ -199,7 +157,7 @@ //! [examples]: https://github.com/hecrj/iced/tree/master/examples //! [`UserInterface`]: struct.UserInterface.html //#![deny(missing_docs)] -#![deny(missing_debug_implementations)] +//#![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] #![deny(rust_2018_idioms)] @@ -216,7 +174,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..3e19be33 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -21,9 +21,13 @@ //! [`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}; diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs new file mode 100644 index 00000000..8933b09b --- /dev/null +++ b/native/src/renderer/null.rs @@ -0,0 +1,143 @@ +use crate::{ + button, checkbox, column, radio, row, scrollable, text, text_input, + Background, Color, Element, Font, HorizontalAlignment, Layout, Point, + Rectangle, Renderer, Size, VerticalAlignment, +}; + +pub struct Null; + +impl Renderer for Null { + type Output = (); +} + +impl column::Renderer for Null { + fn draw( + &mut self, + _content: &[Element<'_, Message, Self>], + _layout: Layout<'_>, + _cursor_position: Point, + ) { + } +} + +impl row::Renderer for Null { + fn draw( + &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, + _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, + _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/user_interface.rs b/native/src/user_interface.rs index f031b090..f29cbcc5 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( /// # &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 { @@ -133,8 +132,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}; @@ -152,7 +151,7 @@ where /// # impl iced_native::column::Renderer for Renderer { /// # fn draw( /// # &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 { @@ -252,7 +251,7 @@ where /// # impl iced_native::column::Renderer for Renderer { /// # fn draw( /// # &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 9010b06f..9dd48697 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -65,7 +65,7 @@ use crate::{layout, Event, Hasher, Layout, Length, Point}; /// /// [`Widget`]: trait.Widget.html /// [`Element`]: ../struct.Element.html -pub trait Widget: std::fmt::Debug +pub trait Widget where Renderer: crate::Renderer, { diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 15beaeba..f2cce3d2 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -1,32 +1,144 @@ //! 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, 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, + width: Length, + min_width: u32, + padding: u16, + background: Option, + border_radius: u16, +} -pub type Button<'a, Message, Renderer> = - iced_core::Button<'a, Message, Element<'a, Message, Renderer>>; +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(state: &'a mut State, content: E) -> Self + where + E: Into>, + { + 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 + } + + /// 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 for Button<'a, Message, Renderer> where Renderer: self::Renderer, - Message: Clone + std::fmt::Debug, + Message: Clone, { fn layout( &self, 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( @@ -73,7 +185,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) { @@ -90,24 +215,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( - &self, - button: &Button<'_, Message, Self>, - limits: &layout::Limits, - ) -> layout::Node; - /// Draws a [`Button`]. /// /// [`Button`]: struct.Button.html - fn draw( + fn draw( &mut self, - button: &Button<'_, Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + is_pressed: bool, + background: Option, + border_radius: u16, + content: Self::Output, ) -> Self::Output; } @@ -115,7 +233,7 @@ impl<'a, Message, Renderer> From> 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 3f0f8dda..47512089 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); +/// ``` +/// +/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) +pub struct Checkbox { + is_checked: bool, + on_toggle: Box Message>, + label: String, + label_color: Option, +} + +impl Checkbox { + /// Creates a new [`Checkbox`]. + /// + /// It expects: + /// * a boolean describing whether the [`Checkbox`] is checked or not + /// * the label of the [`Checkbox`] + /// * a function that will be called when the [`Checkbox`] is toggled. It + /// will receive the new state of the [`Checkbox`] and must produce a + /// `Message`. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn new(is_checked: bool, label: &str, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Checkbox { + is_checked, + on_toggle: Box::new(f), + label: String::from(label), + label_color: None, + } + } + + /// Sets the color of the label of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn label_color>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl Widget for Checkbox where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer + row::Renderer, { fn width(&self) -> Length { Length::Fill @@ -19,7 +77,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( @@ -51,7 +120,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) { @@ -67,15 +162,7 @@ where /// [`Checkbox`]: struct.Checkbox.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Checkbox`]. - /// - /// [`Node`]: ../../struct.Node.html - /// [`Checkbox`]: struct.Checkbox.html - fn layout( - &self, - checkbox: &Checkbox, - limits: &layout::Limits, - ) -> layout::Node; + fn default_size(&self) -> u32; /// Draws a [`Checkbox`]. /// @@ -86,18 +173,19 @@ pub trait Renderer: crate::Renderer { /// * whether the [`Checkbox`] is checked or not /// /// [`Checkbox`]: struct.Checkbox.html - fn draw( + fn draw( &mut self, - checkbox: &Checkbox, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + label: Self::Output, ) -> Self::Output; } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: self::Renderer, + Renderer: self::Renderer + text::Renderer + row::Renderer, Message: 'static, { fn from(checkbox: Checkbox) -> Element<'a, Message, Renderer> { diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 7e7156a0..8d98795c 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -1,10 +1,113 @@ 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>; +/// +/// 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>, +} + +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(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message, Renderer> Widget for Column<'a, Message, Renderer> @@ -64,7 +167,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) { @@ -85,7 +188,7 @@ where pub trait Renderer: crate::Renderer + Sized { fn draw( &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 fd7a5ca5..54117eb1 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -1,10 +1,94 @@ 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>; +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(content: T) -> Self + where + T: Into>, + { + 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 for Container<'a, Message, Renderer> diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index b2541b87..8420ca76 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, 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>(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 Widget for Image where @@ -15,7 +63,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( @@ -41,13 +108,7 @@ 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. - /// - /// [`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 b68919e5..a3a091b1 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); +/// ``` +/// +/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) +pub struct Radio { + is_selected: bool, + on_click: Message, + label: String, + label_color: Option, +} + +impl Radio { + /// 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(value: V, label: &str, selected: Option, 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>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl Widget for Radio where - Renderer: self::Renderer, - Message: Clone + std::fmt::Debug, + Renderer: self::Renderer + text::Renderer + row::Renderer, + Message: Clone, { fn width(&self) -> Length { Length::Fill @@ -20,7 +87,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( @@ -50,7 +128,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) { @@ -66,15 +170,7 @@ where /// [`Radio`]: struct.Radio.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Radio`]. - /// - /// [`Node`]: ../../struct.Node.html - /// [`Radio`]: struct.Radio.html - fn layout( - &self, - radio: &Radio, - limits: &layout::Limits, - ) -> layout::Node; + fn default_size(&self) -> u32; /// Draws a [`Radio`] button. /// @@ -85,21 +181,22 @@ pub trait Renderer: crate::Renderer { /// * whether the [`Radio`] is selected or not /// /// [`Radio`]: struct.Radio.html - fn draw( + fn draw( &mut self, - radio: &Radio, - layout: Layout<'_>, - cursor_position: Point, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + label: Self::Output, ) -> Self::Output; } impl<'a, Message, Renderer> From> 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) -> Element<'a, Message, Renderer> { - Element::new(checkbox) + fn from(radio: Radio) -> Element<'a, Message, Renderer> { + Element::new(radio) } } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 132479fd..e63d8d20 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -1,10 +1,115 @@ 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>; +/// +/// 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>, +} + +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(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message, Renderer> Widget for Row<'a, Message, Renderer> @@ -64,7 +169,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,7 +191,7 @@ where pub trait Renderer: crate::Renderer + Sized { fn draw( &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 091dac47..d4568412 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,20 +1,104 @@ 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(mut self, child: E) -> Self + where + E: Into>, + { + self.content = self.content.push(child); + self + } +} impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> @@ -153,13 +237,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, ) } @@ -173,6 +279,80 @@ where } } +/// The local state of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scrollbar_grabbed_at: Option, + 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() + } +} + pub trait Renderer: crate::Renderer + Sized { fn is_mouse_over_scrollbar( &self, @@ -181,12 +361,15 @@ pub trait Renderer: crate::Renderer + Sized { cursor_position: Point, ) -> bool; - fn draw( + 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 3a998c40..f91d3ac5 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -4,12 +4,79 @@ //! //! [`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}; + +pub struct Slider<'a, Message> { + state: &'a mut State, + range: RangeInclusive, + value: f32, + on_change: Box 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( + state: &'a mut State, + range: RangeInclusive, + 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 for Slider<'a, Message> where @@ -24,7 +91,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( @@ -81,7 +154,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) { @@ -97,15 +176,7 @@ where /// [`Slider`]: struct.Slider.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer { - /// Creates a [`Node`] for the provided [`Radio`]. - /// - /// [`Node`]: ../../struct.Node.html - /// [`Radio`]: struct.Radio.html - fn layout( - &self, - slider: &Slider<'_, Message>, - limits: &layout::Limits, - ) -> layout::Node; + fn height(&self) -> u32; /// Draws a [`Slider`]. /// @@ -119,11 +190,13 @@ pub trait Renderer: crate::Renderer { /// [`Slider`]: struct.Slider.html /// [`State`]: struct.State.html /// [`Class`]: enum.Class.html - fn draw( + fn draw( &mut self, - slider: &Slider<'_, Message>, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + range: RangeInclusive, + value: f32, + is_dragging: bool, ) -> Self::Output; } diff --git a/native/src/widget/text.rs b/native/src/widget/text.rs index 10d892a3..f949b607 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, + color: Option, + 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(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 + /// [`Color`]: ../../struct.Color.html + pub fn 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 Widget for Text where @@ -18,7 +122,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( @@ -27,7 +142,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) { @@ -47,17 +170,15 @@ 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. - /// - /// You should probably use [`Node::with_measure`] to allow [`Text`] to - /// adapt to the dimensions of its container. - /// - /// [`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 default_size(&self) -> u16; + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32); /// Draws a [`Text`] fragment. /// @@ -72,7 +193,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, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output; } impl<'a, Message, Renderer> From for Element<'a, Message, Renderer> diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 35e10000..bb5bb523 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -1,10 +1,95 @@ +//! 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 widget that can be filled with text by using a keyboard. +pub struct TextInput<'a, Message> { + state: &'a mut State, + placeholder: String, + value: Value, + width: Length, + max_width: Length, + padding: u16, + size: Option, + on_change: Box Message>, + on_submit: Option, +} + +impl<'a, Message> TextInput<'a, Message> { + /// Creates a new [`TextInput`]. + /// + /// [`TextInput`]: struct.TextInput.html + pub fn new( + 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 for TextInput<'a, Message> where @@ -120,12 +205,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::>().hash(state); @@ -139,12 +231,15 @@ where pub trait Renderer: crate::Renderer + Sized { fn default_size(&self) -> u16; - fn draw( + 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; } @@ -160,3 +255,125 @@ 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, + } + } + + 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 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 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 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); + +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 7678b63c..7e5dded7 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,3 @@ max_width=80 wrap_comments=true +merge_imports=true diff --git a/src/lib.rs b/src/lib.rs index 945af421..e64767c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #[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; pub use platform::*; @@ -34,7 +34,7 @@ impl iced_winit::Application for Instance where A: Application, { - type Renderer = Renderer; + type Renderer = iced_wgpu::Renderer; type Message = A::Message; fn new() -> (Self, Command) { diff --git a/src/native.rs b/src/native.rs new file mode 100644 index 00000000..fcb50d43 --- /dev/null +++ b/src/native.rs @@ -0,0 +1,51 @@ +pub use iced_winit::{ + Align, Background, Color, Command, Font, HorizontalAlignment, Length, + VerticalAlignment, +}; + +pub mod widget { + pub mod button { + pub type Button<'a, Message> = + iced_winit::Button<'a, Message, iced_wgpu::Renderer>; + + pub use iced_winit::button::State; + } + + pub mod scrollable { + pub type Scrollable<'a, Message> = + iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>; + + pub use iced_winit::scrollable::State; + } + + pub mod text_input { + pub use iced_winit::text_input::{State, TextInput}; + } + + pub mod slider { + 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, + }; + + pub type Column<'a, Message> = + iced_winit::Column<'a, Message, iced_wgpu::Renderer>; + + pub type Row<'a, Message> = + iced_winit::Row<'a, Message, iced_wgpu::Renderer>; + + pub type Container<'a, Message> = + iced_winit::Container<'a, Message, iced_wgpu::Renderer>; +} + +#[doc(no_inline)] +pub use widget::*; + +pub type Element<'a, Message> = + iced_winit::Element<'a, Message, iced_wgpu::Renderer>; 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, + width: Length, + min_width: u32, + padding: u16, + background: Option, + border_radius: u16, +} -pub type Button<'a, Message> = - iced_core::Button<'a, Message, Element<'a, Message>>; +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(_state: &'a mut State, content: E) -> Self + where + E: Into>, + { + 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 + } + + /// 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 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); +/// ``` +/// +/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true) +pub struct Checkbox { + is_checked: bool, + on_toggle: Box Message>, + label: String, + label_color: Option, +} + +impl Checkbox { + /// Creates a new [`Checkbox`]. + /// + /// It expects: + /// * a boolean describing whether the [`Checkbox`] is checked or not + /// * the label of the [`Checkbox`] + /// * a function that will be called when the [`Checkbox`] is toggled. It + /// will receive the new state of the [`Checkbox`] and must produce a + /// `Message`. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn new(is_checked: bool, label: &str, f: F) -> Self + where + F: 'static + Fn(bool) -> Message, + { + Checkbox { + is_checked, + on_toggle: Box::new(f), + label: String::from(label), + label_color: None, + } + } + + /// Sets the color of the label of the [`Checkbox`]. + /// + /// [`Checkbox`]: struct.Checkbox.html + pub fn label_color>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl Widget for Checkbox 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>; +/// 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>, +} + +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(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message> Widget 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>(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 Widget 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); +/// ``` +/// +/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true) +pub struct Radio { + is_selected: bool, + on_click: Message, + label: String, + label_color: Option, +} + +impl Radio { + /// 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(value: V, label: &str, selected: Option, 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>(mut self, color: C) -> Self { + self.label_color = Some(color.into()); + self + } +} impl Widget for Radio 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>; +/// 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>, +} + +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(mut self, child: E) -> Self + where + E: Into>, + { + self.children.push(child.into()); + self + } +} impl<'a, Message> Widget 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, + value: f32, + on_change: Rc 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( + state: &'a mut State, + range: RangeInclusive, + 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 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..3740af13 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, + color: Option, + 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(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 + /// [`Color`]: ../../struct.Color.html + pub fn 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 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( - &self, - button: &Button, - 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( + fn draw( &mut self, - button: &Button, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + is_pressed: bool, + background: Option, + 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( - &self, - checkbox: &Checkbox, - 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( + fn draw( &mut self, - checkbox: &Checkbox, - 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( &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( - &self, - radio: &Radio, - 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( + fn draw( &mut self, - radio: &Radio, - 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( &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( + 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( - &self, - slider: &Slider, - 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( + fn draw( &mut self, - slider: &Slider, - layout: Layout<'_>, + bounds: Rectangle, cursor_position: Point, + range: std::ops::RangeInclusive, + 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)); - - layout::Node::new(size) + fn default_size(&self) -> u16 { + DEFAULT_TEXT_SIZE as u16 } - fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output { + 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, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option, + 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( + fn draw( &mut self, - text_input: &TextInput, 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;