commit
149fd2aa1f
88 changed files with 3950 additions and 2160 deletions
|
|
@ -1,18 +1,38 @@
|
|||
/// 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 axis of a container.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Align {
|
||||
/// Align at the start of the cross axis.
|
||||
/// Align at the start of the axis.
|
||||
Start,
|
||||
|
||||
/// Align at the center of the cross axis.
|
||||
/// Align at the center of the axis.
|
||||
Center,
|
||||
|
||||
/// Align at the end of the cross axis.
|
||||
/// Align at the end of the 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,40 @@
|
|||
pub mod widget;
|
||||
//! The core library of [Iced].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! This library holds basic types that can be reused and re-exported in
|
||||
//! different runtime implementations. For instance, both [`iced_native`] and
|
||||
//! [`iced_web`] are built on top of `iced_core`.
|
||||
//!
|
||||
//! [Iced]: https://github.com/hecrj/iced
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
//! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
mod align;
|
||||
mod background;
|
||||
mod color;
|
||||
#[cfg(feature = "command")]
|
||||
mod command;
|
||||
mod font;
|
||||
mod length;
|
||||
mod point;
|
||||
mod rectangle;
|
||||
mod vector;
|
||||
|
||||
pub use align::Align;
|
||||
pub use align::{Align, HorizontalAlignment, VerticalAlignment};
|
||||
pub use background::Background;
|
||||
pub use color::Color;
|
||||
#[cfg(feature = "command")]
|
||||
pub use command::Command;
|
||||
pub use font::Font;
|
||||
pub use length::Length;
|
||||
pub use point::Point;
|
||||
pub use rectangle::Rectangle;
|
||||
pub use vector::Vector;
|
||||
pub use widget::*;
|
||||
|
||||
#[cfg(feature = "command")]
|
||||
mod command;
|
||||
|
||||
#[cfg(feature = "command")]
|
||||
pub use command::Command;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
//! Show toggle controls using checkboxes.
|
||||
use crate::Color;
|
||||
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use iced_core::Checkbox;
|
||||
///
|
||||
/// pub enum Message {
|
||||
/// CheckboxToggled(bool),
|
||||
/// }
|
||||
///
|
||||
/// let is_checked = true;
|
||||
///
|
||||
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
pub struct Checkbox<Message> {
|
||||
/// Whether the checkbox is checked or not
|
||||
pub is_checked: bool,
|
||||
|
||||
/// Function to call when checkbox is toggled to produce a __message__.
|
||||
///
|
||||
/// The function should be provided `true` when the checkbox is checked
|
||||
/// and `false` otherwise.
|
||||
pub on_toggle: Box<dyn Fn(bool) -> Message>,
|
||||
|
||||
/// The label of the checkbox
|
||||
pub label: String,
|
||||
|
||||
/// The color of the label
|
||||
pub label_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<Message> std::fmt::Debug for Checkbox<Message> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Checkbox")
|
||||
.field("is_checked", &self.is_checked)
|
||||
.field("label", &self.label)
|
||||
.field("label_color", &self.label_color)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> Checkbox<Message> {
|
||||
/// Creates a new [`Checkbox`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * a boolean describing whether the [`Checkbox`] is checked or not
|
||||
/// * the label of the [`Checkbox`]
|
||||
/// * a function that will be called when the [`Checkbox`] is toggled.
|
||||
/// It will receive the new state of the [`Checkbox`] and must produce
|
||||
/// a `Message`.
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn new<F>(is_checked: bool, label: &str, f: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(bool) -> Message,
|
||||
{
|
||||
Checkbox {
|
||||
is_checked,
|
||||
on_toggle: Box::new(f),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the color of the label of the [`Checkbox`].
|
||||
///
|
||||
/// [`Checkbox`]: struct.Checkbox.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
//! Create choices using radio buttons.
|
||||
use crate::Color;
|
||||
|
||||
/// A circular button representing a choice.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use iced_core::Radio;
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// pub enum Choice {
|
||||
/// A,
|
||||
/// B,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
/// pub enum Message {
|
||||
/// RadioSelected(Choice),
|
||||
/// }
|
||||
///
|
||||
/// let selected_choice = Some(Choice::A);
|
||||
///
|
||||
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
|
||||
///
|
||||
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
pub struct Radio<Message> {
|
||||
/// Whether the radio button is selected or not
|
||||
pub is_selected: bool,
|
||||
|
||||
/// The message to produce when the radio button is clicked
|
||||
pub on_click: Message,
|
||||
|
||||
/// The label of the radio button
|
||||
pub label: String,
|
||||
|
||||
/// The color of the label
|
||||
pub label_color: Option<Color>,
|
||||
}
|
||||
|
||||
impl<Message> std::fmt::Debug for Radio<Message>
|
||||
where
|
||||
Message: std::fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Radio")
|
||||
.field("is_selected", &self.is_selected)
|
||||
.field("on_click", &self.on_click)
|
||||
.field("label", &self.label)
|
||||
.field("label_color", &self.label_color)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message> Radio<Message> {
|
||||
/// Creates a new [`Radio`] button.
|
||||
///
|
||||
/// It expects:
|
||||
/// * the value related to the [`Radio`] button
|
||||
/// * the label of the [`Radio`] button
|
||||
/// * the current selected value
|
||||
/// * a function that will be called when the [`Radio`] is selected. It
|
||||
/// receives the value of the radio and must produce a `Message`.
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self
|
||||
where
|
||||
V: Eq + Copy,
|
||||
F: 'static + Fn(V) -> Message,
|
||||
{
|
||||
Radio {
|
||||
is_selected: Some(value) == selected,
|
||||
on_click: f(value),
|
||||
label: String::from(label),
|
||||
label_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the `Color` of the label of the [`Radio`].
|
||||
///
|
||||
/// [`Radio`]: struct.Radio.html
|
||||
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
|
||||
self.label_color = Some(color.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
//! Display an interactive selector of a single value from a range of values.
|
||||
//!
|
||||
//! A [`Slider`] has some local [`State`].
|
||||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
use crate::Length;
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
///
|
||||
/// A [`Slider`] will try to fill the horizontal space of its container.
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use iced_core::{slider, Slider};
|
||||
///
|
||||
/// pub enum Message {
|
||||
/// SliderChanged(f32),
|
||||
/// }
|
||||
///
|
||||
/// let state = &mut slider::State::new();
|
||||
/// let value = 50.0;
|
||||
///
|
||||
/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
pub struct Slider<'a, Message> {
|
||||
/// The state of the slider
|
||||
pub state: &'a mut State,
|
||||
|
||||
/// The range of the slider
|
||||
pub range: RangeInclusive<f32>,
|
||||
|
||||
/// The current value of the slider
|
||||
pub value: f32,
|
||||
|
||||
/// The function to produce messages on change
|
||||
pub on_change: Rc<Box<dyn Fn(f32) -> Message>>,
|
||||
|
||||
pub width: Length,
|
||||
}
|
||||
|
||||
impl<'a, Message> std::fmt::Debug for Slider<'a, Message> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Slider")
|
||||
.field("state", &self.state)
|
||||
.field("range", &self.range)
|
||||
.field("value", &self.value)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> Slider<'a, Message> {
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * the local [`State`] of the [`Slider`]
|
||||
/// * an inclusive range of possible values
|
||||
/// * the current value of the [`Slider`]
|
||||
/// * a function that will be called when the [`Slider`] is dragged.
|
||||
/// It receives the new value of the [`Slider`] and must produce a
|
||||
/// `Message`.
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new<F>(
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
on_change: F,
|
||||
) -> Self
|
||||
where
|
||||
F: 'static + Fn(f32) -> Message,
|
||||
{
|
||||
Slider {
|
||||
state,
|
||||
value: value.max(*range.start()).min(*range.end()),
|
||||
range,
|
||||
on_change: Rc::new(Box::new(on_change)),
|
||||
width: Length::Fill,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct State {
|
||||
pub is_dragging: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> State {
|
||||
State::default()
|
||||
}
|
||||
|
||||
/// Returns whether the associated [`Slider`] is currently being dragged or
|
||||
/// not.
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.is_dragging
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
BIN
docs/images/checkbox.png
Normal file
BIN
docs/images/checkbox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
BIN
docs/images/radio.png
Normal file
BIN
docs/images/radio.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
docs/images/text.png
Normal file
BIN
docs/images/text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/images/text_input.png
Normal file
BIN
docs/images/text_input.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -13,18 +13,11 @@ use crate::{
|
|||
/// [built-in widget]: widget/index.html#built-in-widgets
|
||||
/// [`Widget`]: widget/trait.Widget.html
|
||||
/// [`Element`]: struct.Element.html
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +34,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 +95,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 +201,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 +250,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 +329,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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,204 +1,40 @@
|
|||
//! Iced is a renderer-agnostic GUI library focused on simplicity and
|
||||
//! type-safety. Inspired by [Elm].
|
||||
//! A renderer-agnostic native GUI runtime.
|
||||
//!
|
||||
//! # Features
|
||||
//! * Simple, easy-to-use, renderer-agnostic API
|
||||
//! * Responsive, flexbox-based layouting
|
||||
//! * Type-safe, reactive programming model
|
||||
//! * Built-in widgets
|
||||
//! * Custom widget support
|
||||
//! 
|
||||
//!
|
||||
//! Check out the [repository] and the [examples] for more details!
|
||||
//! `iced_native` takes [`iced_core`] and builds a native runtime on top of it,
|
||||
//! featuring:
|
||||
//!
|
||||
//! [examples]: https://github.com/hecrj/iced/tree/0.1.0/examples
|
||||
//! [repository]: https://github.com/hecrj/iced
|
||||
//! - A custom layout engine, greatly inspired by [`druid`]
|
||||
//! - Event handling for all the built-in widgets
|
||||
//! - A renderer-agnostic API
|
||||
//!
|
||||
//! To achieve this, it introduces a bunch of reusable interfaces:
|
||||
//!
|
||||
//! - 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
|
||||
//! Inspired by [The Elm Architecture], Iced expects you to split user interfaces
|
||||
//! into four different concepts:
|
||||
//! The strategy to use this crate depends on your particular use case. If you
|
||||
//! want to:
|
||||
//! - Implement a custom shell or integrate it in your own system, you should
|
||||
//! check out the [`UserInterface`] type.
|
||||
//! - Build a new renderer, see the [renderer] module.
|
||||
//! - Build a custom widget, start at the [`Widget`] trait.
|
||||
//!
|
||||
//! * __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,
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! 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__.
|
||||
//!
|
||||
//! 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)]
|
||||
//! [renderer]: renderer/index.html
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![deny(unsafe_code)]
|
||||
|
|
@ -216,7 +52,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;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
145
native/src/renderer/null.rs
Normal file
145
native/src/renderer/null.rs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
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.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
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,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use std::hash::Hasher;
|
|||
/// charge of using this type in your system in any way you want.
|
||||
///
|
||||
/// [`Layout`]: struct.Layout.html
|
||||
#[derive(Debug)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct UserInterface<'a, Message, Renderer> {
|
||||
hash: u64,
|
||||
root: Element<'a, Message, Renderer>,
|
||||
|
|
@ -52,7 +52,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 +129,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 +148,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 +248,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 {
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
@ -78,9 +83,9 @@ where
|
|||
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
|
||||
/// user interface.
|
||||
///
|
||||
/// [`Node`]: ../struct.Node.html
|
||||
/// [`Node`]: ../layout/struct.Node.html
|
||||
/// [`Widget`]: trait.Widget.html
|
||||
/// [`Layout`]: ../struct.Layout.html
|
||||
/// [`Layout`]: ../layout/struct.Layout.html
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -108,7 +113,7 @@ where
|
|||
/// its value cannot affect the overall [`Layout`] of the user interface.
|
||||
///
|
||||
/// [`Widget`]: trait.Widget.html
|
||||
/// [`Layout`]: ../struct.Layout.html
|
||||
/// [`Layout`]: ../layout/struct.Layout.html
|
||||
/// [`Text`]: text/struct.Text.html
|
||||
fn hash_layout(&self, state: &mut Hasher);
|
||||
|
||||
|
|
@ -120,12 +125,13 @@ where
|
|||
/// * the current cursor position
|
||||
/// * a mutable `Message` list, allowing the [`Widget`] to produce
|
||||
/// new messages based on user interaction.
|
||||
/// * the `Renderer`
|
||||
///
|
||||
/// By default, it does nothing.
|
||||
///
|
||||
/// [`Event`]: ../enum.Event.html
|
||||
/// [`Widget`]: trait.Widget.html
|
||||
/// [`Layout`]: ../struct.Layout.html
|
||||
/// [`Layout`]: ../layout/struct.Layout.html
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_event: Event,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,138 @@
|
|||
//! 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 pressed.
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::{button, Text};
|
||||
/// #
|
||||
/// # type Button<'a, Message> =
|
||||
/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
/// enum Message {
|
||||
/// ButtonPressed,
|
||||
/// }
|
||||
///
|
||||
/// let mut state = button::State::new();
|
||||
/// let button = Button::new(&mut state, Text::new("Press me!"))
|
||||
/// .on_press(Message::ButtonPressed);
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
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,
|
||||
}
|
||||
|
||||
pub type Button<'a, Message, Renderer> =
|
||||
iced_core::Button<'a, Message, Element<'a, Message, Renderer>>;
|
||||
impl<'a, Message, Renderer> Button<'a, Message, Renderer> {
|
||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||
/// content.
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new<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
|
||||
}
|
||||
|
||||
/// 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 +147,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 +208,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 +238,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 +256,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>,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,73 @@
|
|||
//! 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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +82,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 +125,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 +167,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> {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,115 @@
|
|||
//! 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
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +173,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 +191,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;
|
||||
|
|
|
|||
|
|
@ -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_debug_implementations)]
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,55 @@
|
|||
//! 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");
|
||||
/// ```
|
||||
///
|
||||
/// <img src="https://github.com/hecrj/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
|
||||
#[derive(Debug)]
|
||||
pub struct Image {
|
||||
path: String,
|
||||
width: Length,
|
||||
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 +68,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(
|
||||
|
|
@ -32,7 +96,7 @@ where
|
|||
layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
) -> Renderer::Output {
|
||||
renderer.draw(&self, layout)
|
||||
renderer.draw(&self.path, layout)
|
||||
}
|
||||
|
||||
fn hash_layout(&self, state: &mut Hasher) {
|
||||
|
|
@ -49,18 +113,15 @@ where
|
|||
/// [`Image`]: struct.Image.html
|
||||
/// [renderer]: ../../renderer/index.html
|
||||
pub trait Renderer: crate::Renderer {
|
||||
/// Creates a [`Node`] for the provided [`Image`].
|
||||
/// Returns the dimensions of an [`Image`] located on the given path.
|
||||
///
|
||||
/// You should probably keep the original aspect ratio, if possible.
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Image`]: struct.Image.html
|
||||
fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node;
|
||||
fn dimensions(&self, path: &str) -> (u32, u32);
|
||||
|
||||
/// Draws an [`Image`].
|
||||
///
|
||||
/// [`Image`]: struct.Image.html
|
||||
fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output;
|
||||
fn draw(&mut self, path: &str, layout: Layout<'_>) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Image> for Element<'a, Message, Renderer>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,83 @@
|
|||
//! 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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +92,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 +133,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 +175,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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_debug_implementations)]
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,106 @@
|
|||
//! 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.
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +247,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 +289,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 +382,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,101 @@
|
|||
//!
|
||||
//! [`Slider`]: struct.Slider.html
|
||||
//! [`State`]: struct.State.html
|
||||
use std::hash::Hash;
|
||||
use crate::{
|
||||
input::{mouse, ButtonState},
|
||||
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
use crate::input::{mouse, ButtonState};
|
||||
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
|
||||
use std::{hash::Hash, ops::RangeInclusive};
|
||||
|
||||
pub use iced_core::slider::*;
|
||||
/// 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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,116 @@
|
|||
//! 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!")
|
||||
/// .color([0.0, 0.0, 1.0])
|
||||
/// .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 +129,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 +149,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 +177,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.
|
||||
///
|
||||
/// [`Node`]: ../../struct.Node.html
|
||||
/// [`Style`]: ../../struct.Style.html
|
||||
/// [`Text`]: struct.Text.html
|
||||
/// [`Node::with_measure`]: ../../struct.Node.html#method.with_measure
|
||||
fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node;
|
||||
fn default_size(&self) -> u16;
|
||||
|
||||
/// Measures the [`Text`] in the given bounds and returns the minimum
|
||||
/// boundaries that can fit the contents.
|
||||
///
|
||||
/// [`Text`]: struct.Text.html
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32);
|
||||
|
||||
/// Draws a [`Text`] fragment.
|
||||
///
|
||||
|
|
@ -76,7 +207,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>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,125 @@
|
|||
//! 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};
|
||||
/// #
|
||||
/// #[derive(Debug, Clone)]
|
||||
/// 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,
|
||||
/// )
|
||||
/// .padding(10);
|
||||
/// ```
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +239,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 +262,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 +312,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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
max_width=80
|
||||
wrap_comments=true
|
||||
merge_imports=true
|
||||
|
|
|
|||
190
src/application.rs
Normal file
190
src/application.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
255
src/lib.rs
255
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/master/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;
|
||||
|
|
|
|||
113
src/native.rs
Normal file
113
src/native.rs
Normal file
|
|
@ -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>;
|
||||
155
src/sandbox.rs
Normal file
155
src/sandbox.rs
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
13
src/winit.rs
13
src/winit.rs
|
|
@ -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>;
|
||||
|
|
@ -2,6 +2,12 @@ use crate::Instance;
|
|||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A publisher of messages.
|
||||
///
|
||||
/// It can be used to route messages back to the [`Application`].
|
||||
///
|
||||
/// [`Application`]: trait.Application.html
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone)]
|
||||
pub struct Bus<Message> {
|
||||
publish: Rc<Box<dyn Fn(Message, &mut dyn dodrio::RootRender)>>,
|
||||
|
|
@ -11,7 +17,7 @@ impl<Message> Bus<Message>
|
|||
where
|
||||
Message: 'static,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
publish: Rc::new(Box::new(|message, root| {
|
||||
let app = root.unwrap_mut::<Instance<Message>>();
|
||||
|
|
@ -21,10 +27,17 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Publishes a new message for the [`Application`].
|
||||
///
|
||||
/// [`Application`]: trait.Application.html
|
||||
pub fn publish(&self, message: Message, root: &mut dyn dodrio::RootRender) {
|
||||
(self.publish)(message, root);
|
||||
}
|
||||
|
||||
/// Creates a new [`Bus`] that applies the given function to the messages
|
||||
/// before publishing.
|
||||
///
|
||||
/// [`Bus`]: struct.Bus.html
|
||||
pub fn map<B>(&self, mapper: Rc<Box<dyn Fn(B) -> Message>>) -> Bus<B>
|
||||
where
|
||||
B: 'static,
|
||||
|
|
|
|||
|
|
@ -3,29 +3,39 @@ use crate::{Bus, Color, Widget};
|
|||
use dodrio::bumpalo;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A generic [`Widget`].
|
||||
///
|
||||
/// It is useful to build composable user interfaces that do not leak
|
||||
/// implementation details in their __view logic__.
|
||||
///
|
||||
/// If you have a [built-in widget], you should be able to use `Into<Element>`
|
||||
/// to turn it into an [`Element`].
|
||||
///
|
||||
/// [built-in widget]: widget/index.html
|
||||
/// [`Widget`]: widget/trait.Widget.html
|
||||
/// [`Element`]: struct.Element.html
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Element<'a, Message> {
|
||||
pub(crate) widget: Box<dyn Widget<Message> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message> Element<'a, Message> {
|
||||
/// Create a new [`Element`] containing the given [`Widget`].
|
||||
///
|
||||
/// [`Element`]: struct.Element.html
|
||||
/// [`Widget`]: widget/trait.Widget.html
|
||||
pub fn new(widget: impl Widget<Message> + 'a) -> Self {
|
||||
Self {
|
||||
widget: Box::new(widget),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node<'b>(
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
) -> dodrio::Node<'b> {
|
||||
self.widget.node(bump, bus)
|
||||
}
|
||||
|
||||
pub fn explain(self, _color: Color) -> Element<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Applies a transformation to the produced message of the [`Element`].
|
||||
///
|
||||
/// This method is useful when you want to decouple different parts of your
|
||||
/// UI and make them __composable__.
|
||||
///
|
||||
/// [`Element`]: struct.Element.html
|
||||
pub fn map<F, B>(self, f: F) -> Element<'a, B>
|
||||
where
|
||||
Message: 'static,
|
||||
|
|
@ -36,6 +46,20 @@ impl<'a, Message> Element<'a, Message> {
|
|||
widget: Box::new(Map::new(self.widget, f)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the [`Element`] as _to-be-explained_.
|
||||
pub fn explain(self, _color: Color) -> Element<'a, Message> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Produces a VDOM node for the [`Element`].
|
||||
pub fn node<'b>(
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
bus: &Bus<Message>,
|
||||
) -> dodrio::Node<'b> {
|
||||
self.widget.node(bump, bus)
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<'a, A, B> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,61 @@
|
|||
//! A web runtime for Iced, targetting the DOM.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! `iced_web` takes [`iced_core`] and builds a WebAssembly runtime on top. It
|
||||
//! achieves this by introducing a `Widget` trait that can be used to produce
|
||||
//! VDOM nodes.
|
||||
//!
|
||||
//! The crate is currently a __very experimental__, simple abstraction layer
|
||||
//! over [`dodrio`].
|
||||
//!
|
||||
//! [`iced_core`]: https://github.com/hecrj/iced/tree/master/core
|
||||
//! [`dodrio`]: https://github.com/fitzgen/dodrio
|
||||
//!
|
||||
//! # Usage
|
||||
//! The current build process is a bit involved, as [`wasm-pack`] does not
|
||||
//! currently [support building binary crates](https://github.com/rustwasm/wasm-pack/issues/734).
|
||||
//!
|
||||
//! Therefore, we instead build using the `wasm32-unknown-unknown` target and
|
||||
//! use the [`wasm-bindgen`] CLI to generate appropriate bindings.
|
||||
//!
|
||||
//! For instance, let's say we want to build the [`tour` example]:
|
||||
//!
|
||||
//! ```bash
|
||||
//! cd examples
|
||||
//! cargo build --example tour --target wasm32-unknown-unknown
|
||||
//! wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web
|
||||
//! ```
|
||||
//!
|
||||
//! Then, we need to create an `.html` file to load our application:
|
||||
//!
|
||||
//! ```html
|
||||
//! <!DOCTYPE html>
|
||||
//! <html>
|
||||
//! <head>
|
||||
//! <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
//! <title>Tour - Iced</title>
|
||||
//! </head>
|
||||
//! <body>
|
||||
//! <script type="module">
|
||||
//! import init from "./tour/tour.js";
|
||||
//!
|
||||
//! init('./tour/tour_bg.wasm');
|
||||
//! </script>
|
||||
//! </body>
|
||||
//! </html>
|
||||
//! ```
|
||||
//!
|
||||
//! Finally, we serve it using an HTTP server and access it with our browser.
|
||||
//!
|
||||
//! [`wasm-pack`]: https://github.com/rustwasm/wasm-pack
|
||||
//! [`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen
|
||||
//! [`tour` example]: https://github.com/hecrj/iced/blob/master/examples/tour.rs
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
use dodrio::bumpalo;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
|
@ -6,17 +64,50 @@ mod element;
|
|||
pub mod widget;
|
||||
|
||||
pub use bus::Bus;
|
||||
pub use dodrio;
|
||||
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::*;
|
||||
|
||||
/// An interactive web application.
|
||||
///
|
||||
/// This trait is the main entrypoint of Iced. Once implemented, you can run
|
||||
/// your GUI application by simply calling [`run`](#method.run). 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.
|
||||
pub trait Application {
|
||||
/// The type of __messages__ your [`Application`] will produce.
|
||||
///
|
||||
/// [`Application`]: trait.Application.html
|
||||
type Message;
|
||||
|
||||
/// 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);
|
||||
|
||||
fn view(&mut self) -> Element<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`].
|
||||
///
|
||||
/// [`Application`]: trait.Application.html
|
||||
fn run(self)
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,31 @@
|
|||
//! Use the built-in widgets or create your own.
|
||||
//!
|
||||
//! # Custom widgets
|
||||
//! If you want to implement a custom widget, you simply need to implement the
|
||||
//! [`Widget`] trait. You can use the API of the built-in widgets as a guide or
|
||||
//! source of inspiration.
|
||||
//!
|
||||
//! # Re-exports
|
||||
//! For convenience, the contents of this module are available at the root
|
||||
//! module. Therefore, you can directly type:
|
||||
//!
|
||||
//! ```
|
||||
//! use iced_web::{button, Button, Widget};
|
||||
//! ```
|
||||
//!
|
||||
//! [`Widget`]: trait.Widget.html
|
||||
use crate::Bus;
|
||||
use dodrio::bumpalo;
|
||||
|
||||
pub mod button;
|
||||
pub mod slider;
|
||||
pub mod text;
|
||||
|
||||
mod checkbox;
|
||||
mod column;
|
||||
mod image;
|
||||
mod radio;
|
||||
mod row;
|
||||
mod text;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use button::Button;
|
||||
|
|
@ -26,7 +42,16 @@ pub use image::Image;
|
|||
pub use radio::Radio;
|
||||
pub use row::Row;
|
||||
|
||||
/// A component that displays information and allows interaction.
|
||||
///
|
||||
/// If you want to build your own widgets, you will need to implement this
|
||||
/// trait.
|
||||
///
|
||||
/// [`Widget`]: trait.Widget.html
|
||||
pub trait Widget<Message> {
|
||||
/// Produces a VDOM node for the [`Widget`].
|
||||
///
|
||||
/// [`Widget`]: trait.Widget.html
|
||||
fn node<'b>(
|
||||
&self,
|
||||
bump: &'b bumpalo::Bump,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,122 @@
|
|||
use crate::{Bus, Element, Widget};
|
||||
//! 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, Bus, Element, Length, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
|
||||
pub use iced_core::button::State;
|
||||
/// A generic widget that produces a message when pressed.
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_web::{button, Button, Text};
|
||||
/// #
|
||||
/// enum Message {
|
||||
/// ButtonPressed,
|
||||
/// }
|
||||
///
|
||||
/// let mut state = button::State::new();
|
||||
/// let button = Button::new(&mut state, Text::new("Press me!"))
|
||||
/// .on_press(Message::ButtonPressed);
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
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,
|
||||
}
|
||||
|
||||
pub type Button<'a, Message> =
|
||||
iced_core::Button<'a, Message, Element<'a, Message>>;
|
||||
impl<'a, Message> Button<'a, Message> {
|
||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||
/// content.
|
||||
///
|
||||
/// [`Button`]: struct.Button.html
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new<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
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,63 @@
|
|||
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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,111 @@
|
|||
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
|
||||
#[allow(missing_debug_implementations)]
|
||||
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>(
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,72 @@
|
|||
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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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_debug_implementations)]
|
||||
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>(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,84 @@
|
|||
use crate::{Bus, Element, Widget};
|
||||
//! 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::{Bus, Element, Length, Widget};
|
||||
|
||||
use dodrio::bumpalo;
|
||||
use std::{ops::RangeInclusive, rc::Rc};
|
||||
|
||||
pub use iced_core::slider::*;
|
||||
/// 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_web::{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);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
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 +136,18 @@ where
|
|||
Element::new(slider)
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: struct.Slider.html
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct State;
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`].
|
||||
///
|
||||
/// [`State`]: struct.State.html
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
use crate::Transformation;
|
||||
use iced_native::Rectangle;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, collections::HashMap, mem, rc::Rc};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
cache: RefCell<HashMap<String, Memory>>,
|
||||
|
||||
|
|
@ -207,7 +205,8 @@ impl Pipeline {
|
|||
if !self.cache.borrow().contains_key(path) {
|
||||
let image = image::open(path).expect("Load image").to_bgra();
|
||||
|
||||
self.cache
|
||||
let _ = self
|
||||
.cache
|
||||
.borrow_mut()
|
||||
.insert(path.to_string(), Memory::Host { image });
|
||||
}
|
||||
|
|
@ -308,6 +307,7 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Memory {
|
||||
Host {
|
||||
image: image::ImageBuffer<image::Bgra<u8>, Vec<u8>>,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,29 @@
|
|||
//! A [`wgpu`] renderer for [`iced_native`].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! For now, it is the default renderer of [Iced] in native platforms.
|
||||
//!
|
||||
//! [`wgpu`] supports most modern graphics backends: Vulkan, Metal, DX11, and
|
||||
//! DX12 (OpenGL and WebGL are still WIP). Additionally, it will support the
|
||||
//! incoming [WebGPU API].
|
||||
//!
|
||||
//! Currently, `iced_wgpu` supports the following primitives:
|
||||
//! - Text, which is rendered using [`wgpu_glyph`]. No shaping at all.
|
||||
//! - Quads or rectangles, with rounded borders and a solid background color.
|
||||
//! - Images, lazily loaded from the filesystem.
|
||||
//! - Clip areas, useful to implement scrollables or hide overflowing content.
|
||||
//!
|
||||
//! [Iced]: https://github.com/hecrj/iced
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/
|
||||
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
mod image;
|
||||
mod primitive;
|
||||
mod quad;
|
||||
|
|
|
|||
|
|
@ -1,32 +1,58 @@
|
|||
use iced_native::{text, Background, Color, Font, Rectangle, Vector};
|
||||
use iced_native::{
|
||||
Background, Color, Font, HorizontalAlignment, Rectangle, Vector,
|
||||
VerticalAlignment,
|
||||
};
|
||||
|
||||
/// A rendering primitive.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Primitive {
|
||||
/// An empty primitive
|
||||
None,
|
||||
/// A group of primitives
|
||||
Group {
|
||||
/// The primitives of the group
|
||||
primitives: Vec<Primitive>,
|
||||
},
|
||||
/// A text primitive
|
||||
Text {
|
||||
/// The contents of the text
|
||||
content: String,
|
||||
/// The bounds of the text
|
||||
bounds: Rectangle,
|
||||
/// The color of the text
|
||||
color: Color,
|
||||
/// The size of the text
|
||||
size: f32,
|
||||
/// The font of the text
|
||||
font: Font,
|
||||
horizontal_alignment: text::HorizontalAlignment,
|
||||
vertical_alignment: text::VerticalAlignment,
|
||||
/// The horizontal alignment of the text
|
||||
horizontal_alignment: HorizontalAlignment,
|
||||
/// The vertical alignment of the text
|
||||
vertical_alignment: VerticalAlignment,
|
||||
},
|
||||
/// A quad primitive
|
||||
Quad {
|
||||
/// The bounds of the quad
|
||||
bounds: Rectangle,
|
||||
/// The background of the quad
|
||||
background: Background,
|
||||
/// The border radius of the quad
|
||||
border_radius: u16,
|
||||
},
|
||||
/// An image primitive
|
||||
Image {
|
||||
/// The path of the image
|
||||
path: String,
|
||||
/// The bounds of the image
|
||||
bounds: Rectangle,
|
||||
},
|
||||
/// A clip primitive
|
||||
Clip {
|
||||
/// The bounds of the clip
|
||||
bounds: Rectangle,
|
||||
/// The offset transformation of the clip
|
||||
offset: Vector<u32>,
|
||||
/// The content of the clip
|
||||
content: Box<Primitive>,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use iced_native::Rectangle;
|
|||
|
||||
use std::mem;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
constants: wgpu::BindGroup,
|
||||
|
|
|
|||
|
|
@ -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::{
|
||||
|
|
@ -14,6 +14,10 @@ mod widget;
|
|||
|
||||
pub use target::Target;
|
||||
|
||||
/// A [`wgpu`] renderer.
|
||||
///
|
||||
/// [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
|
||||
#[derive(Debug)]
|
||||
pub struct Renderer {
|
||||
device: Device,
|
||||
queue: Queue,
|
||||
|
|
@ -22,7 +26,7 @@ pub struct Renderer {
|
|||
text_pipeline: text::Pipeline,
|
||||
}
|
||||
|
||||
pub struct Layer<'a> {
|
||||
struct Layer<'a> {
|
||||
bounds: Rectangle<u32>,
|
||||
offset: Vector<u32>,
|
||||
quads: Vec<Quad>,
|
||||
|
|
@ -152,21 +156,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 +187,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
|
||||
}
|
||||
}),
|
||||
|
|
@ -304,7 +308,7 @@ impl Renderer {
|
|||
&mut self,
|
||||
dpi: f32,
|
||||
transformation: Transformation,
|
||||
layer: &Layer,
|
||||
layer: &Layer<'_>,
|
||||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use crate::{Renderer, Transformation};
|
|||
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
|
||||
/// A rendering target.
|
||||
#[derive(Debug)]
|
||||
pub struct Target {
|
||||
surface: wgpu::Surface,
|
||||
width: u16,
|
||||
|
|
@ -12,19 +14,19 @@ pub struct Target {
|
|||
}
|
||||
|
||||
impl Target {
|
||||
pub fn dimensions(&self) -> (u16, u16) {
|
||||
pub(crate) fn dimensions(&self) -> (u16, u16) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn dpi(&self) -> f32 {
|
||||
pub(crate) fn dpi(&self) -> f32 {
|
||||
self.dpi
|
||||
}
|
||||
|
||||
pub fn transformation(&self) -> Transformation {
|
||||
pub(crate) fn transformation(&self) -> Transformation {
|
||||
self.transformation
|
||||
}
|
||||
|
||||
pub fn next_frame(&mut self) -> wgpu::SwapChainOutput {
|
||||
pub(crate) fn next_frame(&mut self) -> wgpu::SwapChainOutput<'_> {
|
||||
self.swap_chain.get_next_texture()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)| {
|
||||
|
|
|
|||
|
|
@ -1,34 +1,15 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{image, layout, Image, Layout, Length, MouseCursor, Size};
|
||||
use iced_native::{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 {
|
||||
fn draw(&mut self, path: &str, layout: Layout<'_>) -> Self::Output {
|
||||
(
|
||||
Primitive::Image {
|
||||
path: image.path.clone(),
|
||||
path: String::from(path),
|
||||
bounds: layout.bounds(),
|
||||
},
|
||||
MouseCursor::OutOfBounds,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)| {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use crate::{Primitive, Renderer};
|
||||
use iced_native::{layout, text, Color, Layout, MouseCursor, Size, Text};
|
||||
use iced_native::{
|
||||
text, Color, Font, HorizontalAlignment, MouseCursor, Rectangle, Size,
|
||||
VerticalAlignment,
|
||||
};
|
||||
|
||||
use std::f32;
|
||||
|
||||
|
|
@ -7,30 +10,40 @@ use std::f32;
|
|||
const DEFAULT_TEXT_SIZE: f32 = 20.0;
|
||||
|
||||
impl text::Renderer for Renderer {
|
||||
fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node {
|
||||
let limits = limits.width(text.width).height(text.height);
|
||||
let size = text.size.map(f32::from).unwrap_or(DEFAULT_TEXT_SIZE);
|
||||
let bounds = limits.max();
|
||||
|
||||
let (width, height) =
|
||||
self.text_pipeline
|
||||
.measure(&text.content, size, text.font, bounds);
|
||||
|
||||
let size = limits.resolve(Size::new(width, height));
|
||||
|
||||
layout::Node::new(size)
|
||||
fn default_size(&self) -> u16 {
|
||||
DEFAULT_TEXT_SIZE as u16
|
||||
}
|
||||
|
||||
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output {
|
||||
fn measure(
|
||||
&self,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
bounds: Size,
|
||||
) -> (f32, f32) {
|
||||
self.text_pipeline
|
||||
.measure(content, f32::from(size), font, bounds)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&mut self,
|
||||
bounds: Rectangle,
|
||||
content: &str,
|
||||
size: u16,
|
||||
font: Font,
|
||||
color: Option<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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ mod font;
|
|||
|
||||
use crate::Transformation;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External {
|
||||
name: "iced_wgpu icons",
|
||||
|
|
@ -12,6 +11,7 @@ pub const BUILTIN_ICONS: iced_native::Font = iced_native::Font::External {
|
|||
|
||||
pub const CHECKMARK_ICON: char = '\u{F00C}';
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pipeline {
|
||||
draw_brush: RefCell<wgpu_glyph::GlyphBrush<'static, ()>>,
|
||||
draw_font_map: RefCell<HashMap<String, wgpu_glyph::FontId>>,
|
||||
|
|
@ -56,7 +56,7 @@ impl Pipeline {
|
|||
wgpu_glyph::FontId(0)
|
||||
}
|
||||
|
||||
pub fn queue(&mut self, section: wgpu_glyph::Section) {
|
||||
pub fn queue(&mut self, section: wgpu_glyph::Section<'_>) {
|
||||
self.draw_brush.borrow_mut().queue(section);
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +134,8 @@ impl Pipeline {
|
|||
// it uses a lifetimed `GlyphCalculatorGuard` with side-effects on drop.
|
||||
// This makes stuff quite inconvenient. A manual method for trimming the
|
||||
// cache would make our lives easier.
|
||||
self.measure_brush
|
||||
let _ = self
|
||||
.measure_brush
|
||||
.borrow_mut()
|
||||
.process_queued(|_, _| {}, |_| {})
|
||||
.expect("Trim text measurements");
|
||||
|
|
@ -154,7 +155,8 @@ impl Pipeline {
|
|||
let font_id =
|
||||
self.draw_brush.borrow_mut().add_font_bytes(bytes);
|
||||
|
||||
self.draw_font_map
|
||||
let _ = self
|
||||
.draw_font_map
|
||||
.borrow_mut()
|
||||
.insert(String::from(name), font_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,19 +6,72 @@ use crate::{
|
|||
UserInterface,
|
||||
};
|
||||
|
||||
/// An interactive, native 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). It will run in
|
||||
/// its own window.
|
||||
///
|
||||
/// An [`Application`](trait.Application.html) can execute asynchronous actions
|
||||
/// by returning a [`Command`](struct.Command.html) in some of its methods.
|
||||
pub trait Application: Sized {
|
||||
/// The renderer to use to draw the [`Application`].
|
||||
///
|
||||
/// [`Application`]: trait.Application.html
|
||||
type Renderer: Windowed;
|
||||
|
||||
/// 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>;
|
||||
|
||||
fn view(&mut self) -> Element<Self::Message, Self::Renderer>;
|
||||
/// 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, Self::Renderer>;
|
||||
|
||||
/// 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,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
use crate::input::{keyboard::KeyCode, mouse, ButtonState};
|
||||
use crate::MouseCursor;
|
||||
//! Convert [`winit`] types to [`iced_native`] types, and viceversa.
|
||||
//!
|
||||
//! [`winit`]: https://github.com/rust-windowing/winit
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
use crate::{
|
||||
input::{keyboard::KeyCode, mouse, ButtonState},
|
||||
MouseCursor,
|
||||
};
|
||||
|
||||
/// Convert a `MouseCursor` from [`iced_native`] to a [`winit`] cursor icon.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {
|
||||
match mouse_cursor {
|
||||
MouseCursor::OutOfBounds => winit::window::CursorIcon::Default,
|
||||
|
|
@ -13,6 +23,10 @@ pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a `MouseButton` from [`winit`] to an [`iced_native`] mouse button.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
|
||||
match mouse_button {
|
||||
winit::event::MouseButton::Left => mouse::Button::Left,
|
||||
|
|
@ -22,6 +36,10 @@ pub fn mouse_button(mouse_button: winit::event::MouseButton) -> mouse::Button {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert an `ElementState` from [`winit`] to an [`iced_native`] button state.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
pub fn button_state(element_state: winit::event::ElementState) -> ButtonState {
|
||||
match element_state {
|
||||
winit::event::ElementState::Pressed => ButtonState::Pressed,
|
||||
|
|
@ -29,6 +47,10 @@ pub fn button_state(element_state: winit::event::ElementState) -> ButtonState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Convert a `VirtualKeyCode` from [`winit`] to an [`iced_native`] key code.
|
||||
///
|
||||
/// [`winit`]: https://github.com/rust-windowing/winit
|
||||
/// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
pub fn key_code(virtual_keycode: winit::event::VirtualKeyCode) -> KeyCode {
|
||||
match virtual_keycode {
|
||||
winit::event::VirtualKeyCode::Key1 => KeyCode::Key1,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,27 @@
|
|||
//! A windowing shell for Iced, on top of [`winit`].
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! `iced_winit` offers some convenient abstractions on top of [`iced_native`]
|
||||
//! to quickstart development when using [`winit`].
|
||||
//!
|
||||
//! It exposes a renderer-agnostic [`Application`] trait that can be implemented
|
||||
//! and then run with a simple call. The use of this trait is optional.
|
||||
//!
|
||||
//! Additionally, a [`conversion`] module is available for users that decide to
|
||||
//! implement a custom event loop.
|
||||
//!
|
||||
//! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native
|
||||
//! [`winit`]: https://github.com/rust-windowing/winit
|
||||
//! [`Application`]: trait.Application.html
|
||||
//! [`conversion`]: conversion
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(unused_results)]
|
||||
#![deny(unsafe_code)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use iced_native::*;
|
||||
pub use winit;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue