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