Move widgets from core to native and web

Also made fields private and improved `Renderer` traits.
This commit is contained in:
Héctor Ramón Jiménez 2019-11-21 13:47:20 +01:00
parent d3553adf27
commit 65eb218d3d
59 changed files with 2455 additions and 1942 deletions

View File

@ -16,3 +16,29 @@ pub enum Align {
/// Align at the end of the cross axis.
End,
}
/// The horizontal alignment of some resource.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HorizontalAlignment {
/// Align left
Left,
/// Horizontally centered
Center,
/// Align right
Right,
}
/// The vertical alignment of some resource.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerticalAlignment {
/// Align top
Top,
/// Vertically centered
Center,
/// Align bottom
Bottom,
}

View File

@ -12,7 +12,6 @@
#![deny(unused_results)]
#![deny(unsafe_code)]
#![deny(rust_2018_idioms)]
pub mod widget;
mod align;
mod background;
@ -23,7 +22,7 @@ mod point;
mod rectangle;
mod vector;
pub use align::Align;
pub use align::{Align, HorizontalAlignment, VerticalAlignment};
pub use background::Background;
pub use color::Color;
pub use font::Font;
@ -31,7 +30,6 @@ pub use length::Length;
pub use point::Point;
pub use rectangle::Rectangle;
pub use vector::Vector;
pub use widget::*;
#[cfg(feature = "command")]
mod command;

View File

@ -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;

View File

@ -1,114 +0,0 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`].
//!
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
use crate::{Background, Length};
/// A generic widget that produces a message when clicked.
#[allow(missing_docs)]
#[derive(Debug)]
pub struct Button<'a, Message, Element> {
pub state: &'a mut State,
pub content: Element,
pub on_press: Option<Message>,
pub width: Length,
pub min_width: u32,
pub padding: u16,
pub background: Option<Background>,
pub border_radius: u16,
}
impl<'a, Message, Element> Button<'a, Message, Element> {
/// Creates a new [`Button`] with some local [`State`] and the given
/// content.
///
/// [`Button`]: struct.Button.html
/// [`State`]: struct.State.html
pub fn new<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
}
/// Sets the minimum width of the [`Button`].
///
/// [`Button`]: struct.Button.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.min_width = min_width;
self
}
/// Sets the padding of the [`Button`].
///
/// [`Button`]: struct.Button.html
pub fn padding(mut self, padding: u16) -> Self {
self.padding = padding;
self
}
/// Sets the [`Background`] of the [`Button`].
///
/// [`Button`]: struct.Button.html
/// [`Background`]: ../../struct.Background.html
pub fn background(mut self, background: Background) -> Self {
self.background = Some(background);
self
}
/// Sets the border radius of the [`Button`].
///
/// [`Button`]: struct.Button.html
pub fn border_radius(mut self, border_radius: u16) -> Self {
self.border_radius = border_radius;
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
///
/// [`Button`]: struct.Button.html
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
}
}
/// The local state of a [`Button`].
///
/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct State {
/// Whether the [`Button`] is currently being pressed.
///
/// [`Button`]: struct.Button.html
pub is_pressed: bool,
}
impl State {
/// Creates a new [`State`].
///
/// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
}

View File

@ -1,78 +0,0 @@
//! Show toggle controls using checkboxes.
use crate::Color;
/// A box that can be checked.
///
/// # Example
///
/// ```
/// use iced_core::Checkbox;
///
/// pub enum Message {
/// CheckboxToggled(bool),
/// }
///
/// let is_checked = true;
///
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled);
/// ```
///
/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true)
pub struct Checkbox<Message> {
/// Whether the checkbox is checked or not
pub is_checked: bool,
/// Function to call when checkbox is toggled to produce a __message__.
///
/// The function should be provided `true` when the checkbox is checked
/// and `false` otherwise.
pub on_toggle: Box<dyn Fn(bool) -> Message>,
/// The label of the checkbox
pub label: String,
/// The color of the label
pub label_color: Option<Color>,
}
impl<Message> std::fmt::Debug for Checkbox<Message> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Checkbox")
.field("is_checked", &self.is_checked)
.field("label", &self.label)
.field("label_color", &self.label_color)
.finish()
}
}
impl<Message> Checkbox<Message> {
/// Creates a new [`Checkbox`].
///
/// It expects:
/// * a boolean describing whether the [`Checkbox`] is checked or not
/// * the label of the [`Checkbox`]
/// * a function that will be called when the [`Checkbox`] is toggled.
/// It will receive the new state of the [`Checkbox`] and must produce
/// a `Message`.
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: &str, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
{
Checkbox {
is_checked,
on_toggle: Box::new(f),
label: String::from(label),
label_color: None,
}
}
/// Sets the color of the label of the [`Checkbox`].
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}

View File

@ -1,126 +0,0 @@
use crate::{Align, Length};
use std::u32;
/// A container that distributes its contents vertically.
///
/// A [`Column`] will try to fill the horizontal space of its container.
///
/// [`Column`]: struct.Column.html
#[allow(missing_docs)]
pub struct Column<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()
}
}

View File

@ -1,88 +0,0 @@
use crate::{Align, Length};
use std::u32;
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[derive(Debug)]
#[allow(missing_docs)]
pub struct Container<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
}
}

View File

@ -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
}
}

View File

@ -1,88 +0,0 @@
//! Create choices using radio buttons.
use crate::Color;
/// A circular button representing a choice.
///
/// # Example
/// ```
/// use iced_core::Radio;
///
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// RadioSelected(Choice),
/// }
///
/// let selected_choice = Some(Choice::A);
///
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
///
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
/// ```
///
/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true)
pub struct Radio<Message> {
/// Whether the radio button is selected or not
pub is_selected: bool,
/// The message to produce when the radio button is clicked
pub on_click: Message,
/// The label of the radio button
pub label: String,
/// The color of the label
pub label_color: Option<Color>,
}
impl<Message> std::fmt::Debug for Radio<Message>
where
Message: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Radio")
.field("is_selected", &self.is_selected)
.field("on_click", &self.on_click)
.field("label", &self.label)
.field("label_color", &self.label_color)
.finish()
}
}
impl<Message> Radio<Message> {
/// Creates a new [`Radio`] button.
///
/// It expects:
/// * the value related to the [`Radio`] button
/// * the label of the [`Radio`] button
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
///
/// [`Radio`]: struct.Radio.html
pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self
where
V: Eq + Copy,
F: 'static + Fn(V) -> Message,
{
Radio {
is_selected: Some(value) == selected,
on_click: f(value),
label: String::from(label),
label_color: None,
}
}
/// Sets the `Color` of the label of the [`Radio`].
///
/// [`Radio`]: struct.Radio.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}

View File

@ -1,121 +0,0 @@
use crate::{Align, Length};
use std::u32;
/// A container that distributes its contents horizontally.
///
/// A [`Row`] will try to fill the horizontal space of its container.
///
/// [`Row`]: struct.Row.html
#[allow(missing_docs)]
pub struct Row<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()
}
}

View File

@ -1,175 +0,0 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::{Align, Column, Length, Point, Rectangle};
use std::u32;
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_docs)]
#[derive(Debug)]
pub struct Scrollable<'a, Element> {
pub state: &'a mut State,
pub height: Length,
pub max_height: u32,
pub content: Column<Element>,
}
impl<'a, Element> Scrollable<'a, Element> {
/// Creates a new [`Scrollable`] with the given [`State`].
///
/// [`Scrollable`]: struct.Scrollable.html
/// [`State`]: struct.State.html
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: u32::MAX,
content: Column::new(),
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.content = self.content.spacing(units);
self
}
/// Sets the padding of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn padding(mut self, units: u16) -> Self {
self.content = self.content.padding(units);
self
}
/// Sets the width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}
/// Sets the height of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
/// Adds an element to the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn push<E>(mut self, child: E) -> Scrollable<'a, Element>
where
E: Into<Element>,
{
self.content = self.content.push(child);
self
}
}
/// The local state of a [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
/// The position where the scrollbar was grabbed at, if it's currently
/// grabbed.
pub scrollbar_grabbed_at: Option<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()
}
}

View File

@ -1,120 +0,0 @@
//! Display an interactive selector of a single value from a range of values.
//!
//! A [`Slider`] has some local [`State`].
//!
//! [`Slider`]: struct.Slider.html
//! [`State`]: struct.State.html
use crate::Length;
use std::ops::RangeInclusive;
use std::rc::Rc;
/// An horizontal bar and a handle that selects a single value from a range of
/// values.
///
/// A [`Slider`] will try to fill the horizontal space of its container.
///
/// [`Slider`]: struct.Slider.html
///
/// # Example
/// ```
/// use iced_core::{slider, Slider};
///
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
/// let state = &mut slider::State::new();
/// let value = 50.0;
///
/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
/// ```
///
/// ![Slider drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/slider.png?raw=true)
#[allow(missing_docs)]
pub struct Slider<'a, Message> {
pub state: &'a mut State,
pub range: RangeInclusive<f32>,
pub value: f32,
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)
.field("width", &self.width)
.finish()
}
}
impl<'a, Message> Slider<'a, Message> {
/// Creates a new [`Slider`].
///
/// It expects:
/// * the local [`State`] of the [`Slider`]
/// * an inclusive range of possible values
/// * the current value of the [`Slider`]
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
///
/// [`Slider`]: struct.Slider.html
/// [`State`]: struct.State.html
pub fn new<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 {
/// Whether the [`Slider`] is currently being dragged or not.
///
/// [`Slider`]: struct.Slider.html
pub is_dragging: bool,
}
impl State {
/// Creates a new [`State`].
///
/// [`State`]: struct.State.html
pub fn new() -> State {
State::default()
}
/// Returns whether the associated [`Slider`] is currently being dragged or
/// not.
///
/// [`Slider`]: struct.Slider.html
pub fn is_dragging(&self) -> bool {
self.is_dragging
}
}

View File

@ -1,132 +0,0 @@
//! Write some text for your users to read.
use crate::{Color, Font, Length};
/// A paragraph of text.
///
/// # Example
///
/// ```
/// use iced_core::Text;
///
/// Text::new("I <3 iced!")
/// .size(40);
/// ```
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub struct Text {
pub content: String,
pub size: Option<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
/// [`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
}
}
/// 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,
}

View File

@ -1,220 +0,0 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
//!
//! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html
use crate::Length;
/// A widget that can be filled with text by using a keyboard.
#[allow(missing_docs)]
pub struct TextInput<'a, Message> {
pub state: &'a mut State,
pub placeholder: String,
pub value: Value,
pub width: Length,
pub max_width: Length,
pub padding: u16,
pub size: Option<u16>,
pub on_change: Box<dyn Fn(String) -> Message>,
pub on_submit: Option<Message>,
}
impl<'a, Message> TextInput<'a, Message> {
/// Creates a new [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.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> std::fmt::Debug for TextInput<'a, Message>
where
Message: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// TODO: Complete once stabilized
f.debug_struct("TextInput").finish()
}
}
/// The state of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Default, Clone)]
pub struct State {
/// Whether the [`TextInput`] is focused or not.
///
/// [`TextInput`]: struct.TextInput.html
pub is_focused: bool,
cursor_position: usize,
}
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
///
/// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
///
/// [`State`]: struct.State.html
pub fn focused() -> Self {
use std::usize;
Self {
is_focused: true,
cursor_position: usize::MAX,
}
}
/// Moves the cursor of a [`TextInput`] to the right.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_right(&mut self, value: &Value) {
let current = self.cursor_position(value);
if current < value.len() {
self.cursor_position = current + 1;
}
}
/// Moves the cursor of a [`TextInput`] to the left.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_left(&mut self, value: &Value) {
let current = self.cursor_position(value);
if current > 0 {
self.cursor_position = current - 1;
}
}
/// Moves the cursor of a [`TextInput`] to the end.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_end(&mut self, value: &Value) {
self.cursor_position = value.len();
}
/// Returns the cursor position of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
pub fn cursor_position(&self, value: &Value) -> usize {
self.cursor_position.min(value.len())
}
}
/// The value of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
// TODO: Use `unicode-segmentation`
#[derive(Debug)]
pub struct Value(Vec<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);
}
}

View File

@ -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(())

View File

@ -1,8 +1,7 @@
use iced::{
button, scrollable, slider, text::HorizontalAlignment, text_input,
Application, Background, Button, Checkbox, Color, Column, Command,
Container, Element, Image, Length, Radio, Row, Scrollable, Slider, Text,
TextInput,
button, scrollable, slider, text_input, Application, Background, Button,
Checkbox, Color, Column, Command, Container, Element, HorizontalAlignment,
Image, Length, Radio, Row, Scrollable, Slider, Text, TextInput,
};
pub fn main() {

View File

@ -17,14 +17,6 @@ pub struct Element<'a, Message, Renderer> {
pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> std::fmt::Debug for Element<'a, Message, Renderer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Element")
.field("widget", &self.widget)
.finish()
}
}
impl<'a, Message, Renderer> Element<'a, Message, Renderer>
where
Renderer: crate::Renderer,
@ -127,37 +119,7 @@ where
/// # }
/// #
/// # mod iced_wgpu {
/// # use iced_native::{
/// # text, row, layout, Text, Size, Point, Rectangle, Layout, Row
/// # };
/// # pub struct Renderer;
/// #
/// # impl iced_native::Renderer for Renderer { type Output = (); }
/// #
/// # impl iced_native::row::Renderer for Renderer {
/// # fn draw<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;
@ -273,12 +235,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>,
@ -350,17 +306,6 @@ struct Explain<'a, Message, Renderer: crate::Renderer> {
color: Color,
}
impl<'a, Message, Renderer> std::fmt::Debug for Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Explain")
.field("element", &self.element)
.finish()
}
}
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,

View File

@ -14,8 +14,8 @@
//! [repository]: https://github.com/hecrj/iced
//!
//! # Usage
//! Inspired by [The Elm Architecture], Iced expects you to split user interfaces
//! into four different concepts:
//! Inspired by [The Elm Architecture], Iced expects you to split user
//! interfaces into four different concepts:
//!
//! * __State__ — the state of your application
//! * __Messages__ — user interactions or meaningful events that you care
@ -25,8 +25,8 @@
//! * __Update logic__ — a way to react to __messages__ and update your
//! __state__
//!
//! We can build something to see how this works! Let's say we want a simple counter
//! that can be incremented and decremented using two buttons.
//! We can build something to see how this works! Let's say we want a simple
//! counter that can be incremented and decremented using two buttons.
//!
//! We start by modelling the __state__ of our application:
//!
@ -76,49 +76,7 @@
//! # }
//! #
//! # mod iced_wgpu {
//! # use iced_native::{
//! # button, text, layout, Button, Text, Point, Rectangle, Color, Layout, Size
//! # };
//! #
//! # pub struct Renderer {}
//! #
//! # impl iced_native::Renderer for Renderer {
//! # type Output = ();
//! # }
//! #
//! # impl button::Renderer for Renderer {
//! # fn layout<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<'_>,
//! # ) {
//! # }
//! # }
//! # pub use iced_native::renderer::Null as Renderer;
//! # }
//! use iced_native::{Button, Column, Text};
//! use iced_wgpu::Renderer; // Iced does not include a renderer! We need to bring our own!
@ -183,8 +141,8 @@
//! }
//! ```
//!
//! And that's everything! We just wrote a whole user interface. Iced is now able
//! to:
//! And that's everything! We just wrote a whole user interface. Iced is now
//! able to:
//!
//! 1. Take the result of our __view logic__ and layout its widgets.
//! 1. Process events from our system and produce __messages__ for our
@ -199,7 +157,7 @@
//! [examples]: https://github.com/hecrj/iced/tree/master/examples
//! [`UserInterface`]: struct.UserInterface.html
//#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
//#![deny(missing_debug_implementations)]
#![deny(unused_results)]
#![deny(unsafe_code)]
#![deny(rust_2018_idioms)]
@ -216,7 +174,8 @@ mod size;
mod user_interface;
pub use iced_core::{
Align, Background, Color, Command, Font, Length, Point, Rectangle, Vector,
Align, Background, Color, Command, Font, HorizontalAlignment, Length,
Point, Rectangle, Vector, VerticalAlignment,
};
pub use element::Element;

View File

@ -21,9 +21,13 @@
//! [`checkbox::Renderer`]: ../widget/checkbox/trait.Renderer.html
mod debugger;
#[cfg(debug_assertions)]
mod null;
mod windowed;
pub use debugger::Debugger;
#[cfg(debug_assertions)]
pub use null::Null;
pub use windowed::{Target, Windowed};
use crate::{layout, Element};

143
native/src/renderer/null.rs Normal file
View File

@ -0,0 +1,143 @@
use crate::{
button, checkbox, column, radio, row, scrollable, text, text_input,
Background, Color, Element, Font, HorizontalAlignment, Layout, Point,
Rectangle, Renderer, Size, VerticalAlignment,
};
pub struct Null;
impl Renderer for Null {
type Output = ();
}
impl column::Renderer for Null {
fn draw<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,
) {
}
}

View File

@ -10,7 +10,6 @@ use std::hash::Hasher;
/// charge of using this type in your system in any way you want.
///
/// [`Layout`]: struct.Layout.html
#[derive(Debug)]
pub struct UserInterface<'a, Message, Renderer> {
hash: u64,
root: Element<'a, Message, Renderer>,
@ -52,7 +51,7 @@ where
/// # impl iced_native::column::Renderer for Renderer {
/// # fn draw<Message>(
/// # &mut self,
/// # _column: &iced_native::Column<'_, Message, Self>,
/// # _children: &[iced_native::Element<'_, Message, Self>],
/// # _layout: iced_native::Layout<'_>,
/// # _cursor_position: iced_native::Point,
/// # ) -> Self::Output {
@ -133,8 +132,8 @@ where
/// [`Event`]: enum.Event.html
///
/// # Example
/// Let's allow our [counter](index.html#usage) to change state by completing
/// [the previous example](#example):
/// Let's allow our [counter](index.html#usage) to change state by
/// completing [the previous example](#example):
///
/// ```no_run
/// use iced_native::{UserInterface, Cache};
@ -152,7 +151,7 @@ where
/// # impl iced_native::column::Renderer for Renderer {
/// # fn draw<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 {
@ -252,7 +251,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 {

View File

@ -65,7 +65,7 @@ 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,
{

View File

@ -1,32 +1,144 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`] and a [`Class`].
//! A [`Button`] has some local [`State`].
//!
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
//! [`Class`]: enum.Class.html
use crate::input::{mouse, ButtonState};
use crate::{layout, Element, Event, Hasher, Layout, Point, Widget};
use crate::{
input::{mouse, ButtonState},
layout, Background, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Widget,
};
use std::hash::Hash;
pub use iced_core::button::State;
/// A generic widget that produces a message when clicked.
pub struct Button<'a, Message, Renderer> {
state: &'a mut State,
content: Element<'a, Message, Renderer>,
on_press: Option<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 layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let padding = f32::from(self.padding);
let limits = limits
.min_width(self.min_width)
.width(self.width)
.height(Length::Shrink)
.pad(padding);
let mut content = self.content.layout(renderer, &limits);
content.bounds.x = padding;
content.bounds.y = padding;
let size = limits.resolve(content.size()).pad(padding);
layout::Node::with_children(size, vec![content])
}
fn on_event(
@ -73,7 +185,20 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
let content = self.content.draw(
renderer,
layout.children().next().unwrap(),
cursor_position,
);
renderer.draw(
layout.bounds(),
cursor_position,
self.state.is_pressed,
self.background,
self.border_radius,
content,
)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -90,24 +215,17 @@ where
/// [`Button`]: struct.Button.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized {
/// Creates a [`Node`] for the provided [`Button`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Button`]: struct.Button.html
fn layout<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;
}
@ -115,7 +233,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>,

View File

@ -1,14 +1,72 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
input::{mouse, ButtonState},
layout, row, text, Align, Color, Element, Event, Font, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget,
};
pub use iced_core::Checkbox;
/// A box that can be checked.
///
/// # Example
///
/// ```
/// # use iced_native::Checkbox;
///
/// pub enum Message {
/// CheckboxToggled(bool),
/// }
///
/// let is_checked = true;
///
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled);
/// ```
///
/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true)
pub struct Checkbox<Message> {
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: String,
label_color: Option<Color>,
}
impl<Message> Checkbox<Message> {
/// Creates a new [`Checkbox`].
///
/// It expects:
/// * a boolean describing whether the [`Checkbox`] is checked or not
/// * the label of the [`Checkbox`]
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: &str, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
{
Checkbox {
is_checked,
on_toggle: Box::new(f),
label: String::from(label),
label_color: None,
}
}
/// Sets the color of the label of the [`Checkbox`].
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Checkbox<Message>
where
Renderer: self::Renderer,
Renderer: self::Renderer + text::Renderer + row::Renderer,
{
fn width(&self) -> Length {
Length::Fill
@ -19,7 +77,18 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let size = self::Renderer::default_size(renderer);
Row::<(), Renderer>::new()
.spacing(15)
.align_items(Align::Center)
.push(
Row::new()
.width(Length::Units(size as u16))
.height(Length::Units(size as u16)),
)
.push(Text::new(&self.label))
.layout(renderer, limits)
}
fn on_event(
@ -51,7 +120,33 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
let bounds = layout.bounds();
let mut children = layout.children();
let checkbox_layout = children.next().unwrap();
let label_layout = children.next().unwrap();
let checkbox_bounds = checkbox_layout.bounds();
let label = text::Renderer::draw(
renderer,
label_layout.bounds(),
&self.label,
text::Renderer::default_size(renderer),
Font::Default,
self.label_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
checkbox_bounds,
self.is_checked,
is_mouse_over,
label,
)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -67,15 +162,7 @@ where
/// [`Checkbox`]: struct.Checkbox.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Checkbox`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Checkbox`]: struct.Checkbox.html
fn layout<Message>(
&self,
checkbox: &Checkbox<Message>,
limits: &layout::Limits,
) -> layout::Node;
fn default_size(&self) -> u32;
/// Draws a [`Checkbox`].
///
@ -86,18 +173,19 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Checkbox`] is checked or not
///
/// [`Checkbox`]: struct.Checkbox.html
fn draw<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> {

View File

@ -1,10 +1,113 @@
use std::hash::Hash;
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
};
use std::u32;
/// A container that distributes its contents vertically.
pub type Column<'a, Message, Renderer> =
iced_core::Column<Element<'a, Message, Renderer>>;
///
/// A [`Column`] will try to fill the horizontal space of its container.
///
/// [`Column`]: struct.Column.html
pub struct Column<'a, Message, Renderer> {
spacing: u16,
padding: u16,
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Align,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn new() -> Self {
Column {
spacing: 0,
padding: 0,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the padding of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
///
/// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
self.children.push(child.into());
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Column<'a, Message, Renderer>
@ -64,7 +167,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
renderer.draw(&self.children, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -85,7 +188,7 @@ where
pub trait Renderer: crate::Renderer + Sized {
fn draw<Message>(
&mut self,
row: &Column<'_, Message, Self>,
content: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;

View File

@ -1,10 +1,94 @@
use std::hash::Hash;
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
};
/// A container that distributes its contents vertically.
pub type Container<'a, Message, Renderer> =
iced_core::Container<Element<'a, Message, Renderer>>;
use std::u32;
/// An element decorating some content.
///
/// It is normally used for alignment purposes.
#[allow(missing_docs)]
pub struct Container<'a, Message, Renderer> {
width: Length,
height: Length,
max_width: u32,
max_height: u32,
horizontal_alignment: Align,
vertical_alignment: Align,
content: Element<'a, Message, Renderer>,
}
impl<'a, Message, Renderer> Container<'a, Message, Renderer> {
/// Creates an empty [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn new<T>(content: T) -> Self
where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
horizontal_alignment: Align::Start,
vertical_alignment: Align::Start,
content: content.into(),
}
}
/// Sets the width of the [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Container`] in pixels.
///
/// [`Container`]: struct.Container.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Centers the contents in the horizontal axis of the [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn center_x(mut self) -> Self {
self.horizontal_alignment = Align::Center;
self
}
/// Centers the contents in the vertical axis of the [`Container`].
///
/// [`Container`]: struct.Container.html
pub fn center_y(mut self) -> Self {
self.vertical_alignment = Align::Center;
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Container<'a, Message, Renderer>

View File

@ -1,10 +1,58 @@
//! Display images in your user interface.
use crate::{layout, Element, Hasher, Layout, Point, Widget};
use crate::{layout, Element, Hasher, Layout, Length, Point, Size, Widget};
use std::hash::Hash;
pub use iced_core::Image;
/// A frame that displays an image while keeping aspect ratio.
///
/// # Example
///
/// ```
/// # use iced_native::Image;
///
/// let image = Image::new("resources/ferris.png");
/// ```
#[derive(Debug)]
pub struct Image {
/// The image path
pub path: String,
/// The width of the image
pub width: Length,
/// The height of the image
pub height: Length,
}
impl Image {
/// Creates a new [`Image`] with the given path.
///
/// [`Image`]: struct.Image.html
pub fn new<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
@ -15,7 +63,26 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let (width, height) = renderer.dimensions(&self.path);
let aspect_ratio = width as f32 / height as f32;
// TODO: Deal with additional cases
let (width, height) = match (self.width, self.height) {
(Length::Units(width), _) => (
self.width,
Length::Units((width as f32 / aspect_ratio).round() as u16),
),
(_, _) => {
(Length::Units(width as u16), Length::Units(height as u16))
}
};
let mut size = limits.width(width).height(height).resolve(Size::ZERO);
size.height = size.width / aspect_ratio;
layout::Node::new(size)
}
fn draw(
@ -41,13 +108,7 @@ where
/// [`Image`]: struct.Image.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Image`].
///
/// You should probably keep the original aspect ratio, if possible.
///
/// [`Node`]: ../../struct.Node.html
/// [`Image`]: struct.Image.html
fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node;
fn dimensions(&self, path: &str) -> (u32, u32);
/// Draws an [`Image`].
///

View File

@ -1,15 +1,82 @@
//! Create choices using radio buttons.
use crate::input::{mouse, ButtonState};
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
input::{mouse, ButtonState},
layout, row, text, Align, Color, Element, Event, Font, Hasher,
HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
VerticalAlignment, Widget,
};
use std::hash::Hash;
pub use iced_core::Radio;
/// A circular button representing a choice.
///
/// # Example
/// ```
/// # use iced_native::Radio;
///
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// RadioSelected(Choice),
/// }
///
/// let selected_choice = Some(Choice::A);
///
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
///
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
/// ```
///
/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true)
pub struct Radio<Message> {
is_selected: bool,
on_click: Message,
label: String,
label_color: Option<Color>,
}
impl<Message> Radio<Message> {
/// Creates a new [`Radio`] button.
///
/// It expects:
/// * the value related to the [`Radio`] button
/// * the label of the [`Radio`] button
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
///
/// [`Radio`]: struct.Radio.html
pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self
where
V: Eq + Copy,
F: 'static + Fn(V) -> Message,
{
Radio {
is_selected: Some(value) == selected,
on_click: f(value),
label: String::from(label),
label_color: None,
}
}
/// Sets the `Color` of the label of the [`Radio`].
///
/// [`Radio`]: struct.Radio.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}
impl<Message, Renderer> Widget<Message, Renderer> for Radio<Message>
where
Renderer: self::Renderer,
Message: Clone + std::fmt::Debug,
Renderer: self::Renderer + text::Renderer + row::Renderer,
Message: Clone,
{
fn width(&self) -> Length {
Length::Fill
@ -20,7 +87,18 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let size = self::Renderer::default_size(renderer);
Row::<(), Renderer>::new()
.spacing(15)
.align_items(Align::Center)
.push(
Row::new()
.width(Length::Units(size as u16))
.height(Length::Units(size as u16)),
)
.push(Text::new(&self.label))
.layout(renderer, limits)
}
fn on_event(
@ -50,7 +128,33 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
let bounds = layout.bounds();
let mut children = layout.children();
let radio_layout = children.next().unwrap();
let label_layout = children.next().unwrap();
let radio_bounds = radio_layout.bounds();
let label = text::Renderer::draw(
renderer,
label_layout.bounds(),
&self.label,
text::Renderer::default_size(renderer),
Font::Default,
self.label_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
let is_mouse_over = bounds.contains(cursor_position);
self::Renderer::draw(
renderer,
radio_bounds,
self.is_selected,
is_mouse_over,
label,
)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -66,15 +170,7 @@ where
/// [`Radio`]: struct.Radio.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Radio`]: struct.Radio.html
fn layout<Message>(
&self,
radio: &Radio<Message>,
limits: &layout::Limits,
) -> layout::Node;
fn default_size(&self) -> u32;
/// Draws a [`Radio`] button.
///
@ -85,21 +181,22 @@ pub trait Renderer: crate::Renderer {
/// * whether the [`Radio`] is selected or not
///
/// [`Radio`]: struct.Radio.html
fn draw<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)
}
}

View File

@ -1,10 +1,115 @@
use std::hash::Hash;
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
layout, Align, Element, Event, Hasher, Layout, Length, Point, Widget,
};
use std::u32;
/// A container that distributes its contents horizontally.
pub type Row<'a, Message, Renderer> =
iced_core::Row<Element<'a, Message, Renderer>>;
///
/// A [`Row`] will try to fill the horizontal space of its container.
///
/// [`Row`]: struct.Row.html
#[allow(missing_docs)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
padding: u16,
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Align,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn new() -> Self {
Row {
spacing: 0,
padding: 0,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
}
}
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the padding of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
///
/// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
///
/// [`Element`]: ../struct.Element.html
/// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message, Renderer>>,
{
self.children.push(child.into());
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Row<'a, Message, Renderer>
@ -64,7 +169,7 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
renderer.draw(&self.children, layout, cursor_position)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -86,7 +191,7 @@ where
pub trait Renderer: crate::Renderer + Sized {
fn draw<Message>(
&mut self,
row: &Row<'_, Message, Self>,
children: &[Element<'_, Message, Self>],
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;

View File

@ -1,20 +1,104 @@
use crate::{
column,
input::{mouse, ButtonState},
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
Widget,
layout, Align, Column, Element, Event, Hasher, Layout, Length, Point,
Rectangle, Size, Widget,
};
pub use iced_core::scrollable::State;
use std::{f32, hash::Hash, u32};
use std::f32;
use std::hash::Hash;
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
content: Column<'a, Message, Renderer>,
}
/// A scrollable [`Column`].
///
/// [`Column`]: ../column/struct.Column.html
pub type Scrollable<'a, Message, Renderer> =
iced_core::Scrollable<'a, Element<'a, Message, Renderer>>;
impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
///
/// [`Scrollable`]: struct.Scrollable.html
/// [`State`]: struct.State.html
pub fn new(state: &'a mut State) -> Self {
Scrollable {
state,
height: Length::Shrink,
max_height: u32::MAX,
content: Column::new(),
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.content = self.content.spacing(units);
self
}
/// Sets the padding of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn padding(mut self, units: u16) -> Self {
self.content = self.content.padding(units);
self
}
/// Sets the width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn width(mut self, width: Length) -> Self {
self.content = self.content.width(width);
self
}
/// Sets the height of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.content = self.content.max_width(max_width);
self
}
/// Sets the maximum height of the [`Scrollable`] in pixels.
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn align_items(mut self, align_items: Align) -> Self {
self.content = self.content.align_items(align_items);
self
}
/// Adds an element to the [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
pub fn push<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>
@ -153,13 +237,35 @@ where
) -> Renderer::Output {
let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap();
let content_bounds = content_layout.bounds();
let offset = self.state.offset(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar(
bounds,
content_bounds,
cursor_position,
);
let content = {
let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
};
self.content.draw(renderer, content_layout, cursor_position)
};
self::Renderer::draw(
renderer,
&self,
&self.state,
bounds,
content_layout,
cursor_position,
content_layout.bounds(),
is_mouse_over,
is_mouse_over_scrollbar,
offset,
content,
)
}
@ -173,6 +279,80 @@ where
}
}
/// The local state of a [`Scrollable`].
///
/// [`Scrollable`]: struct.Scrollable.html
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
scrollbar_grabbed_at: Option<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()
}
}
pub trait Renderer: crate::Renderer + Sized {
fn is_mouse_over_scrollbar(
&self,
@ -181,12 +361,15 @@ pub trait Renderer: crate::Renderer + Sized {
cursor_position: Point,
) -> bool;
fn draw<Message>(
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;
}

View File

@ -4,12 +4,79 @@
//!
//! [`Slider`]: struct.Slider.html
//! [`State`]: struct.State.html
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::{layout, Element, Event, Hasher, Layout, Length, Point, Widget};
use crate::{
input::{mouse, ButtonState},
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
Widget,
};
pub use iced_core::slider::*;
use std::{hash::Hash, ops::RangeInclusive};
pub struct Slider<'a, Message> {
state: &'a mut State,
range: RangeInclusive<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
@ -24,7 +91,13 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let limits = limits
.width(self.width)
.height(Length::Units(renderer.height() as u16));
let size = limits.resolve(Size::ZERO);
layout::Node::new(size)
}
fn on_event(
@ -81,7 +154,13 @@ where
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout, cursor_position)
renderer.draw(
layout.bounds(),
cursor_position,
self.range.clone(),
self.value,
self.state.is_dragging,
)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -97,15 +176,7 @@ where
/// [`Slider`]: struct.Slider.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] for the provided [`Radio`].
///
/// [`Node`]: ../../struct.Node.html
/// [`Radio`]: struct.Radio.html
fn layout<Message>(
&self,
slider: &Slider<'_, Message>,
limits: &layout::Limits,
) -> layout::Node;
fn height(&self) -> u32;
/// Draws a [`Slider`].
///
@ -119,11 +190,13 @@ pub trait Renderer: crate::Renderer {
/// [`Slider`]: struct.Slider.html
/// [`State`]: struct.State.html
/// [`Class`]: enum.Class.html
fn draw<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;
}

View File

@ -1,9 +1,113 @@
//! Write some text for your users to read.
use crate::{layout, Element, Hasher, Layout, Length, Point, Widget};
use crate::{
layout, Color, Element, Font, Hasher, HorizontalAlignment, Layout, Length,
Point, Rectangle, Size, VerticalAlignment, Widget,
};
use std::hash::Hash;
pub use iced_core::text::*;
/// A paragraph of text.
///
/// # Example
///
/// ```
/// # use iced_native::Text;
///
/// Text::new("I <3 iced!")
/// .size(40);
/// ```
#[derive(Debug, Clone)]
pub struct Text {
content: String,
size: Option<u16>,
color: Option<Color>,
font: Font,
width: Length,
height: Length,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
}
impl Text {
/// Create a new fragment of [`Text`] with the given contents.
///
/// [`Text`]: struct.Text.html
pub fn new(label: &str) -> Self {
Text {
content: String::from(label),
size: None,
color: None,
font: Font::Default,
width: Length::Fill,
height: Length::Shrink,
horizontal_alignment: HorizontalAlignment::Left,
vertical_alignment: VerticalAlignment::Top,
}
}
/// Sets the size of the [`Text`].
///
/// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
///
/// [`Text`]: struct.Text.html
/// [`Color`]: ../../struct.Color.html
pub fn color<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
@ -18,7 +122,18 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
renderer.layout(&self, limits)
let limits = limits.width(self.width).height(self.height);
let size = self.size.unwrap_or(renderer.default_size());
let bounds = limits.max();
let (width, height) =
renderer.measure(&self.content, size, self.font, bounds);
let size = limits.resolve(Size::new(width, height));
layout::Node::new(size)
}
fn draw(
@ -27,7 +142,15 @@ where
layout: Layout<'_>,
_cursor_position: Point,
) -> Renderer::Output {
renderer.draw(&self, layout)
renderer.draw(
layout.bounds(),
&self.content,
self.size.unwrap_or(renderer.default_size()),
self.font,
self.color,
self.horizontal_alignment,
self.vertical_alignment,
)
}
fn hash_layout(&self, state: &mut Hasher) {
@ -47,17 +170,15 @@ where
/// [renderer]: ../../renderer/index.html
/// [`UserInterface`]: ../../struct.UserInterface.html
pub trait Renderer: crate::Renderer {
/// Creates a [`Node`] with the given [`Style`] for the provided [`Text`]
/// contents and size.
///
/// You should probably use [`Node::with_measure`] to allow [`Text`] to
/// adapt to the dimensions of its container.
///
/// [`Node`]: ../../struct.Node.html
/// [`Style`]: ../../struct.Style.html
/// [`Text`]: struct.Text.html
/// [`Node::with_measure`]: ../../struct.Node.html#method.with_measure
fn layout(&self, text: &Text, limits: &layout::Limits) -> layout::Node;
fn default_size(&self) -> u16;
fn measure(
&self,
content: &str,
size: u16,
font: Font,
bounds: Size,
) -> (f32, f32);
/// Draws a [`Text`] fragment.
///
@ -72,7 +193,16 @@ pub trait Renderer: crate::Renderer {
/// [`Text`]: struct.Text.html
/// [`HorizontalAlignment`]: enum.HorizontalAlignment.html
/// [`VerticalAlignment`]: enum.VerticalAlignment.html
fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Output;
fn draw(
&mut self,
bounds: Rectangle,
content: &str,
size: u16,
font: Font,
color: Option<Color>,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<Text> for Element<'a, Message, Renderer>

View File

@ -1,10 +1,95 @@
//! Display fields that can be filled with text.
//!
//! A [`TextInput`] has some local [`State`].
//!
//! [`TextInput`]: struct.TextInput.html
//! [`State`]: struct.State.html
use crate::{
input::{keyboard, mouse, ButtonState},
layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size,
Widget,
};
pub use iced_core::{text_input::State, TextInput};
/// A widget that can be filled with text by using a keyboard.
pub struct TextInput<'a, Message> {
state: &'a mut State,
placeholder: String,
value: Value,
width: Length,
max_width: Length,
padding: u16,
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
}
impl<'a, Message> TextInput<'a, Message> {
/// Creates a new [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.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
@ -120,12 +205,19 @@ where
let bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds();
renderer.draw(&self, bounds, text_bounds, cursor_position)
renderer.draw(
bounds,
text_bounds,
cursor_position,
self.size.unwrap_or(renderer.default_size()),
&self.placeholder,
&self.value,
&self.state,
)
}
fn hash_layout(&self, state: &mut Hasher) {
use std::any::TypeId;
use std::hash::Hash;
use std::{any::TypeId, hash::Hash};
TypeId::of::<TextInput<'static, ()>>().hash(state);
@ -139,12 +231,15 @@ where
pub trait Renderer: crate::Renderer + Sized {
fn default_size(&self) -> u16;
fn draw<Message>(
fn draw(
&mut self,
text_input: &TextInput<'_, Message>,
bounds: Rectangle,
text_bounds: Rectangle,
cursor_position: Point,
size: u16,
placeholder: &str,
value: &Value,
state: &State,
) -> Self::Output;
}
@ -160,3 +255,125 @@ where
Element::new(text_input)
}
}
/// The state of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
#[derive(Debug, Default, Clone)]
pub struct State {
is_focused: bool,
cursor_position: usize,
}
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
///
/// [`State`]: struct.State.html
pub fn new() -> Self {
Self::default()
}
/// Creates a new [`State`], representing a focused [`TextInput`].
///
/// [`State`]: struct.State.html
pub fn focused() -> Self {
use std::usize;
Self {
is_focused: true,
cursor_position: usize::MAX,
}
}
pub fn is_focused(&self) -> bool {
self.is_focused
}
/// Returns the cursor position of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
pub fn cursor_position(&self, value: &Value) -> usize {
self.cursor_position.min(value.len())
}
/// Moves the cursor of a [`TextInput`] to the right.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_right(&mut self, value: &Value) {
let current = self.cursor_position(value);
if current < value.len() {
self.cursor_position = current + 1;
}
}
/// Moves the cursor of a [`TextInput`] to the left.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_left(&mut self, value: &Value) {
let current = self.cursor_position(value);
if current > 0 {
self.cursor_position = current - 1;
}
}
/// Moves the cursor of a [`TextInput`] to the end.
///
/// [`TextInput`]: struct.TextInput.html
pub fn move_cursor_to_end(&mut self, value: &Value) {
self.cursor_position = value.len();
}
}
/// The value of a [`TextInput`].
///
/// [`TextInput`]: struct.TextInput.html
// TODO: Use `unicode-segmentation`
#[derive(Debug)]
pub struct Value(Vec<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);
}
}

View File

@ -1,2 +1,3 @@
max_width=80
wrap_comments=true
merge_imports=true

View File

@ -1,5 +1,5 @@
#[cfg_attr(target_arch = "wasm32", path = "web.rs")]
#[cfg_attr(not(target_arch = "wasm32"), path = "winit.rs")]
#[cfg_attr(not(target_arch = "wasm32"), path = "native.rs")]
mod platform;
pub use platform::*;
@ -34,7 +34,7 @@ impl<A> iced_winit::Application for Instance<A>
where
A: Application,
{
type Renderer = Renderer;
type Renderer = iced_wgpu::Renderer;
type Message = A::Message;
fn new() -> (Self, Command<A::Message>) {

51
src/native.rs Normal file
View File

@ -0,0 +1,51 @@
pub use iced_winit::{
Align, Background, Color, Command, Font, HorizontalAlignment, Length,
VerticalAlignment,
};
pub mod widget {
pub mod button {
pub type Button<'a, Message> =
iced_winit::Button<'a, Message, iced_wgpu::Renderer>;
pub use iced_winit::button::State;
}
pub mod scrollable {
pub type Scrollable<'a, Message> =
iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>;
pub use iced_winit::scrollable::State;
}
pub mod text_input {
pub use iced_winit::text_input::{State, TextInput};
}
pub mod slider {
pub use iced_winit::slider::{Slider, State};
}
pub use iced_winit::{Checkbox, Image, Radio, Text};
#[doc(no_inline)]
pub use {
button::Button, scrollable::Scrollable, slider::Slider,
text_input::TextInput,
};
pub type Column<'a, Message> =
iced_winit::Column<'a, Message, iced_wgpu::Renderer>;
pub type Row<'a, Message> =
iced_winit::Row<'a, Message, iced_wgpu::Renderer>;
pub type Container<'a, Message> =
iced_winit::Container<'a, Message, iced_wgpu::Renderer>;
}
#[doc(no_inline)]
pub use widget::*;
pub type Element<'a, Message> =
iced_winit::Element<'a, Message, iced_wgpu::Renderer>;

View File

@ -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>;

View File

@ -7,7 +7,10 @@ pub mod widget;
pub use bus::Bus;
pub use element::Element;
pub use iced_core::{Align, Background, Color, Length};
pub use iced_core::{
Align, Background, Color, Font, HorizontalAlignment, Length,
VerticalAlignment,
};
pub use widget::*;
pub trait Application {

View File

@ -1,11 +1,103 @@
use crate::{Bus, Element, Widget};
use crate::{Background, Bus, Element, Length, Widget};
use dodrio::bumpalo;
pub use iced_core::button::State;
/// A generic widget that produces a message when clicked.
pub struct Button<'a, Message> {
content: Element<'a, Message>,
on_press: Option<Message>,
width: Length,
min_width: u32,
padding: u16,
background: Option<Background>,
border_radius: u16,
}
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

View File

@ -1,8 +1,62 @@
use crate::{Bus, Element, Widget};
use crate::{Bus, Color, Element, Widget};
use dodrio::bumpalo;
pub use iced_core::Checkbox;
/// A box that can be checked.
///
/// # Example
///
/// ```
/// # use iced_web::Checkbox;
///
/// pub enum Message {
/// CheckboxToggled(bool),
/// }
///
/// let is_checked = true;
///
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled);
/// ```
///
/// ![Checkbox drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/checkbox.png?raw=true)
pub struct Checkbox<Message> {
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: String,
label_color: Option<Color>,
}
impl<Message> Checkbox<Message> {
/// Creates a new [`Checkbox`].
///
/// It expects:
/// * a boolean describing whether the [`Checkbox`] is checked or not
/// * the label of the [`Checkbox`]
/// * a function that will be called when the [`Checkbox`] is toggled. It
/// will receive the new state of the [`Checkbox`] and must produce a
/// `Message`.
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn new<F>(is_checked: bool, label: &str, f: F) -> Self
where
F: 'static + Fn(bool) -> Message,
{
Checkbox {
is_checked,
on_toggle: Box::new(f),
label: String::from(label),
label_color: None,
}
}
/// Sets the color of the label of the [`Checkbox`].
///
/// [`Checkbox`]: struct.Checkbox.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}
impl<Message> Widget<Message> for Checkbox<Message>
where

View File

@ -1,8 +1,110 @@
use crate::{Bus, Element, Widget};
use crate::{Align, Bus, Element, Length, Widget};
use dodrio::bumpalo;
use std::u32;
pub type Column<'a, Message> = iced_core::Column<Element<'a, Message>>;
/// A container that distributes its contents vertically.
///
/// A [`Column`] will try to fill the horizontal space of its container.
///
/// [`Column`]: struct.Column.html
pub struct Column<'a, Message> {
spacing: u16,
padding: u16,
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Align,
children: Vec<Element<'a, Message>>,
}
impl<'a, Message> Column<'a, Message> {
/// Creates an empty [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn new() -> Self {
Column {
spacing: 0,
padding: 0,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
}
}
/// Sets the vertical spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the padding of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the horizontal alignment of the contents of the [`Column`] .
///
/// [`Column`]: struct.Column.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an element to the [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message>>,
{
self.children.push(child.into());
self
}
}
impl<'a, Message> Widget<Message> for Column<'a, Message> {
fn node<'b>(

View File

@ -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>(

View File

@ -1,8 +1,71 @@
use crate::{Bus, Element, Widget};
use crate::{Bus, Color, Element, Widget};
use dodrio::bumpalo;
pub use iced_core::Radio;
/// A circular button representing a choice.
///
/// # Example
/// ```
/// # use iced_web::Radio;
///
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// pub enum Choice {
/// A,
/// B,
/// }
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Message {
/// RadioSelected(Choice),
/// }
///
/// let selected_choice = Some(Choice::A);
///
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
///
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
/// ```
///
/// ![Radio buttons drawn by Coffee's renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true)
pub struct Radio<Message> {
is_selected: bool,
on_click: Message,
label: String,
label_color: Option<Color>,
}
impl<Message> Radio<Message> {
/// Creates a new [`Radio`] button.
///
/// It expects:
/// * the value related to the [`Radio`] button
/// * the label of the [`Radio`] button
/// * the current selected value
/// * a function that will be called when the [`Radio`] is selected. It
/// receives the value of the radio and must produce a `Message`.
///
/// [`Radio`]: struct.Radio.html
pub fn new<F, V>(value: V, label: &str, selected: Option<V>, f: F) -> Self
where
V: Eq + Copy,
F: 'static + Fn(V) -> Message,
{
Radio {
is_selected: Some(value) == selected,
on_click: f(value),
label: String::from(label),
label_color: None,
}
}
/// Sets the `Color` of the label of the [`Radio`].
///
/// [`Radio`]: struct.Radio.html
pub fn label_color<C: Into<Color>>(mut self, color: C) -> Self {
self.label_color = Some(color.into());
self
}
}
impl<Message> Widget<Message> for Radio<Message>
where

View File

@ -1,8 +1,112 @@
use crate::{Bus, Element, Widget};
use crate::{Align, Bus, Element, Length, Widget};
use dodrio::bumpalo;
use std::u32;
pub type Row<'a, Message> = iced_core::Row<Element<'a, Message>>;
/// A container that distributes its contents horizontally.
///
/// A [`Row`] will try to fill the horizontal space of its container.
///
/// [`Row`]: struct.Row.html
#[allow(missing_docs)]
pub struct Row<'a, Message> {
spacing: u16,
padding: u16,
width: Length,
height: Length,
max_width: u32,
max_height: u32,
align_items: Align,
children: Vec<Element<'a, Message>>,
}
impl<'a, Message> Row<'a, Message> {
/// Creates an empty [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn new() -> Self {
Row {
spacing: 0,
padding: 0,
width: Length::Fill,
height: Length::Shrink,
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
}
}
/// Sets the horizontal spacing _between_ elements.
///
/// Custom margins per element do not exist in Iced. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the padding of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn padding(mut self, units: u16) -> Self {
self.padding = units;
self
}
/// Sets the width of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the maximum width of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.max_width = max_width;
self
}
/// Sets the maximum height of the [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.max_height = max_height;
self
}
/// Sets the vertical alignment of the contents of the [`Row`] .
///
/// [`Row`]: struct.Row.html
pub fn align_items(mut self, align: Align) -> Self {
self.align_items = align;
self
}
/// Adds an [`Element`] to the [`Row`].
///
/// [`Element`]: ../struct.Element.html
/// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Self
where
E: Into<Element<'a, Message>>,
{
self.children.push(child.into());
self
}
}
impl<'a, Message> Widget<Message> for Row<'a, Message> {
fn node<'b>(

View File

@ -1,8 +1,55 @@
use crate::{Bus, Element, Widget};
use crate::{Bus, Element, Length, Widget};
use dodrio::bumpalo;
use std::{ops::RangeInclusive, rc::Rc};
pub use iced_core::slider::*;
pub struct Slider<'a, Message> {
_state: &'a mut State,
range: RangeInclusive<f32>,
value: f32,
on_change: Rc<Box<dyn Fn(f32) -> Message>>,
width: Length,
}
impl<'a, Message> Slider<'a, Message> {
/// Creates a new [`Slider`].
///
/// It expects:
/// * the local [`State`] of the [`Slider`]
/// * an inclusive range of possible values
/// * the current value of the [`Slider`]
/// * a function that will be called when the [`Slider`] is dragged.
/// It receives the new value of the [`Slider`] and must produce a
/// `Message`.
///
/// [`Slider`]: struct.Slider.html
/// [`State`]: struct.State.html
pub fn new<F>(
state: &'a mut State,
range: RangeInclusive<f32>,
value: f32,
on_change: F,
) -> Self
where
F: 'static + Fn(f32) -> Message,
{
Slider {
_state: state,
value: value.max(*range.start()).min(*range.end()),
range,
on_change: Rc::new(Box::new(on_change)),
width: Length::Fill,
}
}
/// Sets the width of the [`Slider`].
///
/// [`Slider`]: struct.Slider.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
}
impl<'a, Message> Widget<Message> for Slider<'a, Message>
where
@ -60,3 +107,11 @@ where
Element::new(slider)
}
}
pub struct State;
impl State {
pub fn new() -> Self {
Self
}
}

View File

@ -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(label: &str) -> Self {
Text {
content: String::from(label),
size: None,
color: None,
font: Font::Default,
width: Length::Fill,
height: Length::Shrink,
horizontal_alignment: HorizontalAlignment::Left,
vertical_alignment: VerticalAlignment::Top,
}
}
/// Sets the size of the [`Text`].
///
/// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = Some(size);
self
}
/// Sets the [`Color`] of the [`Text`].
///
/// [`Text`]: struct.Text.html
/// [`Color`]: ../../struct.Color.html
pub fn color<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>(

View File

@ -1,4 +1,7 @@
use iced_native::{text, Background, Color, Font, Rectangle, Vector};
use iced_native::{
Background, Color, Font, HorizontalAlignment, Rectangle, Vector,
VerticalAlignment,
};
#[derive(Debug, Clone)]
pub enum Primitive {
@ -12,8 +15,8 @@ pub enum Primitive {
color: Color,
size: f32,
font: Font,
horizontal_alignment: text::HorizontalAlignment,
vertical_alignment: text::VerticalAlignment,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
},
Quad {
bounds: Rectangle,

View File

@ -1,7 +1,7 @@
use crate::{quad, text, Image, Primitive, Quad, Transformation};
use iced_native::{
renderer::Debugger, renderer::Windowed, Background, Color, Layout,
MouseCursor, Point, Rectangle, Vector, Widget,
renderer::{Debugger, Windowed},
Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget,
};
use wgpu::{
@ -152,21 +152,21 @@ impl Renderer {
vertical_alignment,
} => {
let x = match horizontal_alignment {
iced_native::text::HorizontalAlignment::Left => bounds.x,
iced_native::text::HorizontalAlignment::Center => {
iced_native::HorizontalAlignment::Left => bounds.x,
iced_native::HorizontalAlignment::Center => {
bounds.x + bounds.width / 2.0
}
iced_native::text::HorizontalAlignment::Right => {
iced_native::HorizontalAlignment::Right => {
bounds.x + bounds.width
}
};
let y = match vertical_alignment {
iced_native::text::VerticalAlignment::Top => bounds.y,
iced_native::text::VerticalAlignment::Center => {
iced_native::VerticalAlignment::Top => bounds.y,
iced_native::VerticalAlignment::Center => {
bounds.y + bounds.height / 2.0
}
iced_native::text::VerticalAlignment::Bottom => {
iced_native::VerticalAlignment::Bottom => {
bounds.y + bounds.height
}
};
@ -183,24 +183,24 @@ impl Renderer {
font_id: self.text_pipeline.find_font(*font),
layout: wgpu_glyph::Layout::default()
.h_align(match horizontal_alignment {
iced_native::text::HorizontalAlignment::Left => {
iced_native::HorizontalAlignment::Left => {
wgpu_glyph::HorizontalAlign::Left
}
iced_native::text::HorizontalAlignment::Center => {
iced_native::HorizontalAlignment::Center => {
wgpu_glyph::HorizontalAlign::Center
}
iced_native::text::HorizontalAlignment::Right => {
iced_native::HorizontalAlignment::Right => {
wgpu_glyph::HorizontalAlign::Right
}
})
.v_align(match vertical_alignment {
iced_native::text::VerticalAlignment::Top => {
iced_native::VerticalAlignment::Top => {
wgpu_glyph::VerticalAlign::Top
}
iced_native::text::VerticalAlignment::Center => {
iced_native::VerticalAlignment::Center => {
wgpu_glyph::VerticalAlign::Center
}
iced_native::text::VerticalAlignment::Bottom => {
iced_native::VerticalAlignment::Bottom => {
wgpu_glyph::VerticalAlign::Bottom
}
}),

View File

@ -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,
],

View File

@ -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,

View File

@ -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)| {

View File

@ -1,28 +1,9 @@
use crate::{Primitive, Renderer};
use iced_native::{image, layout, Image, Layout, Length, MouseCursor, Size};
use iced_native::{image, Image, Layout, MouseCursor};
impl image::Renderer for Renderer {
fn layout(&self, image: &Image, limits: &layout::Limits) -> layout::Node {
let (width, height) = self.image_pipeline.dimensions(&image.path);
let aspect_ratio = width as f32 / height as f32;
// TODO: Deal with additional cases
let (width, height) = match (image.width, image.height) {
(Length::Units(width), _) => (
image.width,
Length::Units((width as f32 / aspect_ratio).round() as u16),
),
(_, _) => {
(Length::Units(width as u16), Length::Units(height as u16))
}
};
let mut size = limits.width(width).height(height).resolve(Size::ZERO);
size.height = size.width / aspect_ratio;
layout::Node::new(size)
fn dimensions(&self, path: &str) -> (u32, u32) {
self.image_pipeline.dimensions(path)
}
fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output {

View File

@ -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,

View File

@ -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)| {

View File

@ -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

View File

@ -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

View File

@ -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,
)

View File

@ -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 {

View File

@ -1,3 +1,4 @@
#[doc(no_inline)]
pub use iced_native::*;
pub use winit;