Decouple iced from coffee

This commit is contained in:
Héctor Ramón Jiménez 2019-07-20 19:12:31 +02:00
parent eefdcbe06c
commit 2b7ad3d50e
33 changed files with 2907 additions and 8 deletions

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
fast_finish: true
cache: cargo

View File

@ -3,7 +3,7 @@ name = "iced"
version = "0.0.0" version = "0.0.0"
authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018" edition = "2018"
description = "A simple GUI runtime inspired by Elm" description = "A customizable GUI runtime, inspired by Elm."
license = "MIT" license = "MIT"
repository = "https://github.com/hecrj/iced" repository = "https://github.com/hecrj/iced"
documentation = "https://docs.rs/iced" documentation = "https://docs.rs/iced"
@ -12,3 +12,6 @@ keywords = ["gui", "ui", "graphics", "interface", "widgets"]
categories = ["gui"] categories = ["gui"]
[dependencies] [dependencies]
stretch = "0.2"
nalgebra = "0.18"
twox-hash = "1.3"

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width=80

222
src/element.rs Normal file
View File

@ -0,0 +1,222 @@
use stretch::{geometry, result};
use crate::{Event, Hasher, Layout, MouseCursor, Node, Point, Widget};
/// A generic [`Widget`].
///
/// If you have a widget, you should be able to use `widget.into()` to turn it
/// into an [`Element`].
///
/// [`Widget`]: trait.Widget.html
/// [`Element`]: struct.Element.html
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> {
/// Create a new [`Element`] containing the given [`Widget`].
///
/// [`Element`]: struct.Element.html
/// [`Widget`]: trait.Widget.html
pub fn new(
widget: impl Widget<Message, Renderer> + 'a,
) -> Element<'a, Message, Renderer> {
Element {
widget: Box::new(widget),
}
}
/// Applies a transformation to the produced message of the [`Element`].
///
/// This method is useful when you want to decouple different parts of your
/// UI.
///
/// [`Element`]: struct.Element.html
///
/// # Example
/// TODO
pub fn map<F, B>(self, f: F) -> Element<'a, B, Renderer>
where
Message: 'static + Copy,
Renderer: 'a,
B: 'static,
F: 'static + Fn(Message) -> B,
{
Element {
widget: Box::new(Map::new(self.widget, f)),
}
}
/// Marks the [`Element`] as _to-be-explained_.
///
/// The [`Renderer`] will explain the layout of the [`Element`] graphically.
/// This can be very useful for debugging your layout!
///
/// [`Element`]: struct.Element.html
/// [`Renderer`]: trait.Renderer.html
pub fn explain(
self,
color: Renderer::Color,
) -> Element<'a, Message, Renderer>
where
Message: 'static,
Renderer: 'a + crate::Renderer,
{
Element {
widget: Box::new(Explain::new(self, color)),
}
}
pub(crate) fn compute_layout(&self, renderer: &Renderer) -> result::Layout {
let node = self.widget.node(renderer);
node.0.compute_layout(geometry::Size::undefined()).unwrap()
}
pub(crate) fn hash(&self, state: &mut Hasher) {
self.widget.hash(state);
}
}
struct Map<'a, A, B, Renderer> {
widget: Box<dyn Widget<A, Renderer> + 'a>,
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>,
mapper: F,
) -> Map<'a, A, B, Renderer>
where
F: 'static + Fn(A) -> B,
{
Map {
widget,
mapper: Box::new(mapper),
}
}
}
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
where
A: Copy,
{
fn node(&self, renderer: &Renderer) -> Node {
self.widget.node(renderer)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<B>,
) {
let mut original_messages = Vec::new();
self.widget.on_event(
event,
layout,
cursor_position,
&mut original_messages,
);
original_messages
.iter()
.cloned()
.for_each(|message| messages.push((self.mapper)(message)));
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
self.widget.draw(renderer, layout, cursor_position)
}
fn hash(&self, state: &mut Hasher) {
self.widget.hash(state);
}
}
struct Explain<'a, Message, Renderer: crate::Renderer> {
element: Element<'a, Message, Renderer>,
color: Renderer::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,
{
fn new(
element: Element<'a, Message, Renderer>,
color: Renderer::Color,
) -> Self {
Explain { element, color }
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Explain<'a, Message, Renderer>
where
Renderer: crate::Renderer,
{
fn node(&self, renderer: &Renderer) -> Node {
self.element.widget.node(renderer)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
self.element
.widget
.on_event(event, layout, cursor_position, messages)
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
renderer.explain(&layout, self.color);
self.element.widget.draw(renderer, layout, cursor_position)
}
fn hash(&self, state: &mut Hasher) {
self.element.widget.hash(state);
}
}

11
src/event.rs Normal file
View File

@ -0,0 +1,11 @@
use crate::input::{keyboard, mouse};
/// A user interface event.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Event {
/// A keyboard event
Keyboard(keyboard::Event),
/// A mouse event
Mouse(mouse::Event),
}

2
src/hasher.rs Normal file
View File

@ -0,0 +1,2 @@
/// The hasher used to compare layouts.
pub type Hasher = twox_hash::XxHash;

6
src/input.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod keyboard;
pub mod mouse;
mod button_state;
pub use button_state::ButtonState;

View File

@ -0,0 +1,5 @@
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
pub enum ButtonState {
Pressed,
Released,
}

5
src/input/keyboard.rs Normal file
View File

@ -0,0 +1,5 @@
mod event;
mod key_code;
pub use event::Event;
pub use key_code::KeyCode;

View File

@ -0,0 +1,21 @@
use super::KeyCode;
use crate::input::ButtonState;
#[derive(Debug, Clone, Copy, PartialEq)]
/// A keyboard event.
pub enum Event {
/// A keyboard key was pressed or released.
Input {
/// The state of the key
state: ButtonState,
/// The key identifier
key_code: KeyCode,
},
/// Text was entered.
TextEntered {
/// The character entered
character: char,
},
}

View File

@ -0,0 +1,197 @@
/// The symbolic name of a keyboard key
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum KeyCode {
/// The '1' key over the letters.
Key1,
/// The '2' key over the letters.
Key2,
/// The '3' key over the letters.
Key3,
/// The '4' key over the letters.
Key4,
/// The '5' key over the letters.
Key5,
/// The '6' key over the letters.
Key6,
/// The '7' key over the letters.
Key7,
/// The '8' key over the letters.
Key8,
/// The '9' key over the letters.
Key9,
/// The '0' key over the 'O' and 'P' keys.
Key0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
/// The Escape key, next to F1.
Escape,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Snapshot,
/// Scroll Lock.
Scroll,
/// Pause/Break key, next to Scroll lock.
Pause,
/// `Insert`, next to Backspace.
Insert,
Home,
Delete,
End,
PageDown,
PageUp,
Left,
Up,
Right,
Down,
/// The Backspace key, right over Enter.
// TODO: rename
Back,
/// The Enter key.
Return,
/// The space bar.
Space,
/// The "Compose" key on Linux.
Compose,
Caret,
Numlock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
AbntC1,
AbntC2,
Add,
Apostrophe,
Apps,
At,
Ax,
Backslash,
Calculator,
Capital,
Colon,
Comma,
Convert,
Decimal,
Divide,
Equals,
Grave,
Kana,
Kanji,
LAlt,
LBracket,
LControl,
LShift,
LWin,
Mail,
MediaSelect,
MediaStop,
Minus,
Multiply,
Mute,
MyComputer,
NavigateForward, // also called "Prior"
NavigateBackward, // also called "Next"
NextTrack,
NoConvert,
NumpadComma,
NumpadEnter,
NumpadEquals,
OEM102,
Period,
PlayPause,
Power,
PrevTrack,
RAlt,
RBracket,
RControl,
RShift,
RWin,
Semicolon,
Slash,
Sleep,
Stop,
Subtract,
Sysrq,
Tab,
Underline,
Unlabeled,
VolumeDown,
VolumeUp,
Wake,
WebBack,
WebFavorites,
WebForward,
WebHome,
WebRefresh,
WebSearch,
WebStop,
Yen,
Copy,
Paste,
Cut,
}

5
src/input/mouse.rs Normal file
View File

@ -0,0 +1,5 @@
mod button;
mod event;
pub use button::Button;
pub use event::Event;

View File

@ -0,0 +1,7 @@
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum Button {
Left,
Right,
Middle,
Other(u8),
}

39
src/input/mouse/event.rs Normal file
View File

@ -0,0 +1,39 @@
use super::Button;
use crate::input::ButtonState;
/// A mouse event.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Event {
/// The mouse cursor entered the window.
CursorEntered,
/// The mouse cursor left the window.
CursorLeft,
/// The mouse cursor was moved
CursorMoved {
/// The X coordinate of the mouse position
x: f32,
/// The Y coordinate of the mouse position
y: f32,
},
/// A mouse button was pressed or released.
Input {
/// The state of the button
state: ButtonState,
/// The button identifier
button: Button,
},
/// The mouse wheel was scrolled.
WheelScrolled {
/// The number of horizontal lines scrolled
delta_x: f32,
/// The number of vertical lines scrolled
delta_y: f32,
},
}

77
src/interface.rs Normal file
View File

@ -0,0 +1,77 @@
use std::hash::Hasher;
use stretch::result;
use crate::{Element, Event, Layout, MouseCursor, Point};
pub struct Interface<'a, Message, Renderer> {
hash: u64,
root: Element<'a, Message, Renderer>,
layout: result::Layout,
}
pub struct Cache {
hash: u64,
layout: result::Layout,
}
impl<'a, Message, Renderer> Interface<'a, Message, Renderer> {
pub fn compute(
root: Element<'a, Message, Renderer>,
renderer: &Renderer,
) -> Interface<'a, Message, Renderer> {
let hasher = &mut crate::Hasher::default();
root.hash(hasher);
let hash = hasher.finish();
let layout = root.compute_layout(renderer);
Interface { hash, root, layout }
}
pub fn compute_with_cache(
root: Element<'a, Message, Renderer>,
renderer: &Renderer,
cache: Cache,
) -> Interface<'a, Message, Renderer> {
let hasher = &mut crate::Hasher::default();
root.hash(hasher);
let hash = hasher.finish();
let layout = if hash == cache.hash {
cache.layout
} else {
root.compute_layout(renderer)
};
Interface { hash, root, layout }
}
pub fn on_event(&mut self, event: Event, cursor_position: Point, messages: &mut Vec<Message>) {
let Interface { root, layout, .. } = self;
root.widget
.on_event(event, Self::layout(layout), cursor_position, messages);
}
pub fn draw(&self, renderer: &mut Renderer, cursor_position: Point) -> MouseCursor {
let Interface { root, layout, .. } = self;
let cursor = root
.widget
.draw(renderer, Self::layout(layout), cursor_position);
cursor
}
pub fn cache(self) -> Cache {
Cache {
hash: self.hash,
layout: self.layout,
}
}
fn layout(layout: &result::Layout) -> Layout<'_> {
Layout::new(layout, Point::new(0.0, 0.0))
}
}

55
src/layout.rs Normal file
View File

@ -0,0 +1,55 @@
use stretch::result;
use crate::{Point, Rectangle, Vector};
/// The computed bounds of a [`Node`] and its children.
///
/// This type is provided by the GUI runtime to [`Widget::on_event`] and
/// [`Widget::draw`], describing the layout of the produced [`Node`] by
/// [`Widget::node`].
///
/// [`Node`]: struct.Node.html
/// [`Widget::on_event`]: trait.Widget.html#method.on_event
/// [`Widget::draw`]: trait.Widget.html#tymethod.draw
/// [`Widget::node`]: trait.Widget.html#tymethod.node
#[derive(Debug)]
pub struct Layout<'a> {
layout: &'a result::Layout,
position: Point,
}
impl<'a> Layout<'a> {
pub(crate) fn new(layout: &'a result::Layout, parent_position: Point) -> Self {
let position = parent_position + Vector::new(layout.location.x, layout.location.y);
Layout { layout, position }
}
/// Gets the bounds of the [`Layout`].
///
/// The returned [`Rectangle`] describes the position and size of a
/// [`Node`].
///
/// [`Layout`]: struct.Layout.html
/// [`Rectangle`]: ../../graphics/struct.Rectangle.html
/// [`Node`]: struct.Node.html
pub fn bounds(&self) -> Rectangle<f32> {
Rectangle {
x: self.position.x,
y: self.position.y,
width: self.layout.size.width,
height: self.layout.size.height,
}
}
/// Returns an iterator over the [`Layout`] of the children of a [`Node`].
///
/// [`Layout`]: struct.Layout.html
/// [`Node`]: struct.Node.html
pub fn children(&'a self) -> impl Iterator<Item = Layout<'a>> {
self.layout
.children
.iter()
.map(move |layout| Layout::new(layout, self.position))
}
}

View File

@ -1,7 +1,37 @@
#[cfg(test)] //#![deny(missing_docs)]
mod tests { //#![deny(missing_debug_implementations)]
#[test] #![deny(unused_results)]
fn it_works() { #![deny(unsafe_code)]
assert_eq!(2 + 2, 4); #![deny(rust_2018_idioms)]
} pub mod input;
} pub mod widget;
mod element;
mod event;
mod hasher;
mod interface;
mod layout;
mod mouse_cursor;
mod node;
mod point;
mod rectangle;
mod renderer;
mod style;
mod vector;
#[doc(no_inline)]
pub use stretch::{geometry::Size, number::Number};
pub use element::Element;
pub use event::Event;
pub use hasher::Hasher;
pub use interface::Interface;
pub use layout::Layout;
pub use mouse_cursor::MouseCursor;
pub use node::Node;
pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
pub use style::{Align, Justify, Style};
pub use vector::Vector;
pub use widget::*;

21
src/mouse_cursor.rs Normal file
View File

@ -0,0 +1,21 @@
/// The state of the mouse cursor.
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum MouseCursor {
/// The cursor is out of the bounds of the user interface.
OutOfBounds,
/// The cursor is over a non-interactive widget.
Idle,
/// The cursor is over a clickable widget.
Pointer,
/// The cursor is over a busy widget.
Working,
/// The cursor is over a grabbable widget.
Grab,
/// The cursor is grabbing a widget.
Grabbing,
}

60
src/node.rs Normal file
View File

@ -0,0 +1,60 @@
use stretch::node;
use crate::{Number, Size, Style};
/// The visual requirements of a [`Widget`] and its children.
///
/// When there have been changes and the [`Layout`] needs to be recomputed, the
/// runtime obtains a [`Node`] by calling [`Widget::node`].
///
/// [`Style`]: struct.Style.html
/// [`Widget`]: trait.Widget.html
/// [`Node`]: struct.Node.html
/// [`Widget::node`]: trait.Widget.html#tymethod.node
/// [`Layout`]: struct.Layout.html
#[derive(Debug)]
pub struct Node(pub(crate) node::Node);
impl Node {
/// Creates a new [`Node`] with the given [`Style`].
///
/// [`Node`]: struct.Node.html
/// [`Style`]: struct.Style.html
pub fn new(style: Style) -> Node {
Self::with_children(style, Vec::new())
}
/// Creates a new [`Node`] with the given [`Style`] and children.
///
/// [`Node`]: struct.Node.html
/// [`Style`]: struct.Style.html
pub(crate) fn with_children(style: Style, children: Vec<Node>) -> Node {
Node(node::Node::new(
style.0,
children.iter().map(|c| &c.0).collect(),
))
}
/// Creates a new [`Node`] with the given [`Style`] and a measure function.
///
/// This type of node cannot have any children.
///
/// You should use this when your [`Widget`] can adapt its contents to the
/// size of its container. The measure function will receive the container
/// size as a parameter and must compute the size of the [`Node`] inside
/// the given bounds (if the `Number` for a dimension is `Undefined` it
/// means that it has no boundary).
///
/// [`Node`]: struct.Node.html
/// [`Style`]: struct.Style.html
/// [`Widget`]: trait.Widget.html
pub fn with_measure<F>(style: Style, measure: F) -> Node
where
F: 'static + Fn(Size<Number>) -> Size<f32>,
{
Node(node::Node::new_leaf(
style.0,
Box::new(move |size| Ok(measure(size))),
))
}
}

1
src/point.rs Normal file
View File

@ -0,0 +1 @@
pub type Point = nalgebra::Point2<f32>;

30
src/rectangle.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::Point;
/// A generic rectangle.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Rectangle<T> {
/// X coordinate of the top-left corner.
pub x: T,
/// Y coordinate of the top-left corner.
pub y: T,
/// Width of the rectangle.
pub width: T,
/// Height of the rectangle.
pub height: T,
}
impl Rectangle<f32> {
/// Returns true if the given [`Point`] is contained in the [`Rectangle`].
///
/// [`Point`]: type.Point.html
/// [`Rectangle`]: struct.Rectangle.html
pub fn contains(&self, point: Point) -> bool {
self.x <= point.x
&& point.x <= self.x + self.width
&& self.y <= point.y
&& point.y <= self.y + self.height
}
}

15
src/renderer.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::Layout;
pub trait Renderer {
type Color: Copy;
/// Explains the [`Layout`] of an [`Element`] for debugging purposes.
///
/// This will be called when [`Element::explain`] has been used. It should
/// _explain_ the [`Layout`] graphically.
///
/// [`Layout`]: struct.Layout.html
/// [`Element`]: struct.Element.html
/// [`Element::explain`]: struct.Element.html#method.explain
fn explain(&mut self, layout: &Layout<'_>, color: Self::Color);
}

260
src/style.rs Normal file
View File

@ -0,0 +1,260 @@
use std::hash::{Hash, Hasher};
use stretch::{geometry, style};
/// The appearance of a [`Node`].
///
/// [`Node`]: struct.Node.html
#[derive(Debug, Clone, Copy)]
pub struct Style(pub(crate) style::Style);
impl Style {
/// Defines the width of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn width(mut self, width: u32) -> Self {
self.0.size.width = style::Dimension::Points(width as f32);
self
}
/// Defines the height of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn height(mut self, height: u32) -> Self {
self.0.size.height = style::Dimension::Points(height as f32);
self
}
/// Defines the minimum width of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn min_width(mut self, min_width: u32) -> Self {
self.0.min_size.width = style::Dimension::Points(min_width as f32);
self
}
/// Defines the maximum width of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.0.max_size.width = style::Dimension::Points(max_width as f32);
self.fill_width()
}
/// Defines the minimum height of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn min_height(mut self, min_height: u32) -> Self {
self.0.min_size.height = style::Dimension::Points(min_height as f32);
self
}
/// Defines the maximum height of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.0.max_size.height = style::Dimension::Points(max_height as f32);
self.fill_height()
}
/// Makes a [`Node`] fill all the horizontal available space.
///
/// [`Node`]: struct.Node.html
pub fn fill_width(mut self) -> Self {
self.0.size.width = stretch::style::Dimension::Percent(1.0);
self
}
/// Makes a [`Node`] fill all the vertical available space.
///
/// [`Node`]: struct.Node.html
pub fn fill_height(mut self) -> Self {
self.0.size.height = stretch::style::Dimension::Percent(1.0);
self
}
pub(crate) fn align_items(mut self, align: Align) -> Self {
self.0.align_items = align.into();
self
}
pub(crate) fn justify_content(mut self, justify: Justify) -> Self {
self.0.justify_content = justify.into();
self
}
/// Sets the alignment of a [`Node`].
///
/// If the [`Node`] is inside a...
///
/// * [`Column`], this setting will affect its __horizontal__ alignment.
/// * [`Row`], this setting will affect its __vertical__ alignment.
///
/// [`Node`]: struct.Node.html
/// [`Column`]: widget/struct.Column.html
/// [`Row`]: widget/struct.Row.html
pub fn align_self(mut self, align: Align) -> Self {
self.0.align_self = align.into();
self
}
/// Sets the padding of a [`Node`] in pixels.
///
/// [`Node`]: struct.Node.html
pub fn padding(mut self, px: u32) -> Self {
self.0.padding = stretch::geometry::Rect {
start: style::Dimension::Points(px as f32),
end: style::Dimension::Points(px as f32),
top: style::Dimension::Points(px as f32),
bottom: style::Dimension::Points(px as f32),
};
self
}
}
impl Default for Style {
fn default() -> Style {
Style(style::Style {
align_items: style::AlignItems::FlexStart,
justify_content: style::JustifyContent::FlexStart,
..style::Style::default()
})
}
}
impl Hash for Style {
fn hash<H: Hasher>(&self, state: &mut H) {
hash_size(&self.0.size, state);
hash_size(&self.0.min_size, state);
hash_size(&self.0.max_size, state);
hash_rect(&self.0.margin, state);
(self.0.flex_direction as u8).hash(state);
(self.0.align_items as u8).hash(state);
(self.0.justify_content as u8).hash(state);
(self.0.align_self as u8).hash(state);
(self.0.flex_grow as u32).hash(state);
}
}
fn hash_size<H: Hasher>(
size: &geometry::Size<style::Dimension>,
state: &mut H,
) {
hash_dimension(size.width, state);
hash_dimension(size.height, state);
}
fn hash_rect<H: Hasher>(
rect: &geometry::Rect<style::Dimension>,
state: &mut H,
) {
hash_dimension(rect.start, state);
hash_dimension(rect.end, state);
hash_dimension(rect.top, state);
hash_dimension(rect.bottom, state);
}
fn hash_dimension<H: Hasher>(dimension: style::Dimension, state: &mut H) {
match dimension {
style::Dimension::Undefined => state.write_u8(0),
style::Dimension::Auto => state.write_u8(1),
style::Dimension::Points(points) => {
state.write_u8(2);
(points as u32).hash(state);
}
style::Dimension::Percent(percent) => {
state.write_u8(3);
(percent as u32).hash(state);
}
}
}
/// Alignment on the cross axis of a container.
///
/// * On a [`Column`], it describes __horizontal__ alignment.
/// * On a [`Row`], it describes __vertical__ alignment.
///
/// [`Column`]: widget/struct.Column.html
/// [`Row`]: widget/struct.Row.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Align {
/// Align at the start of the cross axis.
Start,
/// Align at the center of the cross axis.
Center,
/// Align at the end of the cross axis.
End,
/// Stretch over the cross axis.
Stretch,
}
#[doc(hidden)]
impl From<Align> for style::AlignItems {
fn from(align: Align) -> Self {
match align {
Align::Start => style::AlignItems::FlexStart,
Align::Center => style::AlignItems::Center,
Align::End => style::AlignItems::FlexEnd,
Align::Stretch => style::AlignItems::Stretch,
}
}
}
#[doc(hidden)]
impl From<Align> for style::AlignSelf {
fn from(align: Align) -> Self {
match align {
Align::Start => style::AlignSelf::FlexStart,
Align::Center => style::AlignSelf::Center,
Align::End => style::AlignSelf::FlexEnd,
Align::Stretch => style::AlignSelf::Stretch,
}
}
}
/// Distribution on the main axis of a container.
///
/// * On a [`Column`], it describes __vertical__ distribution.
/// * On a [`Row`], it describes __horizontal__ distribution.
///
/// [`Column`]: widget/struct.Column.html
/// [`Row`]: widget/struct.Row.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Justify {
/// Place items at the start of the main axis.
Start,
/// Place items at the center of the main axis.
Center,
/// Place items at the end of the main axis.
End,
/// Place items with space between.
SpaceBetween,
/// Place items with space around.
SpaceAround,
/// Place items with evenly distributed space.
SpaceEvenly,
}
#[doc(hidden)]
impl From<Justify> for style::JustifyContent {
fn from(justify: Justify) -> Self {
match justify {
Justify::Start => style::JustifyContent::FlexStart,
Justify::Center => style::JustifyContent::Center,
Justify::End => style::JustifyContent::FlexEnd,
Justify::SpaceBetween => style::JustifyContent::SpaceBetween,
Justify::SpaceAround => style::JustifyContent::SpaceAround,
Justify::SpaceEvenly => style::JustifyContent::SpaceEvenly,
}
}
}

2
src/vector.rs Normal file
View File

@ -0,0 +1,2 @@
/// A 2D vector.
pub type Vector = nalgebra::Vector2<f32>;

103
src/widget.rs Normal file
View File

@ -0,0 +1,103 @@
//! Use the built-in widgets in your user interface.
//!
//! # Customization
//! Every drawable widget has its own module with a `Renderer` trait that must
//! be implemented by a custom renderer before being able to use the
//! widget.
//!
//! The built-in [`Renderer`] supports all the widgets in this module!
//!
//! [`ui` module]: ../index.html
//! [`Row`]: struct.Row.html
//! [`Column`]: struct.Column.html
//! [`Renderer`]: ../struct.Renderer.html
mod column;
mod row;
pub mod button;
pub mod checkbox;
pub mod radio;
pub mod slider;
pub mod text;
pub use button::Button;
pub use checkbox::Checkbox;
pub use column::Column;
pub use radio::Radio;
pub use row::Row;
pub use slider::Slider;
pub use text::Text;
use crate::{Event, Hasher, Layout, MouseCursor, Node, Point};
/// A component that displays information or allows interaction.
///
/// If you want to build a custom widget, you will need to implement this trait.
/// Additionally, remember to also provide [`Into<Element>`] so your users can
/// easily turn your [`Widget`] into a generic [`Element`]
///
/// [`Into<Element>`]: struct.Element.html
/// [`Widget`]: trait.Widget.html
/// [`Element`]: struct.Element.html
pub trait Widget<Message, Renderer>: std::fmt::Debug {
/// Returns the [`Node`] of the [`Widget`].
///
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
/// user interface.
///
/// [`Node`]: struct.Node.html
/// [`Widget`]: trait.Widget.html
/// [`Layout`]: struct.Layout.html
fn node(&self, renderer: &Renderer) -> Node;
/// Draws the [`Widget`] using the associated `Renderer`.
///
/// It must return the [`MouseCursor`] state for the [`Widget`].
///
/// [`Widget`]: trait.Widget.html
/// [`MouseCursor`]: enum.MouseCursor.html
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor;
/// Computes the _layout_ hash of the [`Widget`].
///
/// The produced hash is used by the runtime to decide if the [`Layout`]
/// needs to be recomputed between frames. Therefore, to ensure maximum
/// efficiency, the hash should only be affected by the properties of the
/// [`Widget`] that can affect layouting.
///
/// For example, the [`Text`] widget does not hash its color property, as
/// its value cannot affect the overall [`Layout`] of the user interface.
///
/// [`Widget`]: trait.Widget.html
/// [`Layout`]: struct.Layout.html
/// [`Text`]: ../widget/text/struct.Text.html
fn hash(&self, state: &mut Hasher);
/// Processes a runtime [`Event`].
///
/// It receives:
/// * an [`Event`] describing user interaction
/// * the computed [`Layout`] of the [`Widget`]
/// * the current cursor position
/// * a mutable `Message` vector, allowing the [`Widget`] to produce
/// new messages based on user interaction.
///
/// By default, it does nothing.
///
/// [`Event`]: enum.Event.html
/// [`Widget`]: trait.Widget.html
/// [`Layout`]: struct.Layout.html
fn on_event(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor_position: Point,
_messages: &mut Vec<Message>,
) {
}
}

281
src/widget/button.rs Normal file
View File

@ -0,0 +1,281 @@
//! Allow your users to perform actions by pressing a button.
//!
//! A [`Button`] has some local [`State`] and a [`Class`].
//!
//! [`Button`]: struct.Button.html
//! [`State`]: struct.State.html
//! [`Class`]: enum.Class.html
use crate::input::{mouse, ButtonState};
use crate::{
Align, Element, Event, Hasher, Layout, MouseCursor, Node, Point, Rectangle,
Style, Widget,
};
use std::hash::Hash;
/// A generic widget that produces a message when clicked.
///
/// It implements [`Widget`] when the associated [`core::Renderer`] implements
/// the [`button::Renderer`] trait.
///
/// [`Widget`]: ../../core/trait.Widget.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
/// [`button::Renderer`]: trait.Renderer.html
///
/// # Example
///
/// ```
/// use iced::{button, Button};
///
/// pub enum Message {
/// ButtonClicked,
/// }
///
/// let state = &mut button::State::new();
///
/// Button::new(state, "Click me!")
/// .on_press(Message::ButtonClicked);
/// ```
pub struct Button<'a, Message> {
state: &'a mut State,
label: String,
class: Class,
on_press: Option<Message>,
style: Style,
}
impl<'a, Message> std::fmt::Debug for Button<'a, Message>
where
Message: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Button")
.field("state", &self.state)
.field("label", &self.label)
.field("class", &self.class)
.field("on_press", &self.on_press)
.field("style", &self.style)
.finish()
}
}
impl<'a, Message> Button<'a, Message> {
/// Creates a new [`Button`] with some local [`State`] and the given label.
///
/// The default [`Class`] of a new [`Button`] is [`Class::Primary`].
///
/// [`Button`]: struct.Button.html
/// [`State`]: struct.State.html
/// [`Class`]: enum.Class.html
/// [`Class::Primary`]: enum.Class.html#variant.Primary
pub fn new(state: &'a mut State, label: &str) -> Self {
Button {
state,
label: String::from(label),
class: Class::Primary,
on_press: None,
style: Style::default().min_width(100),
}
}
/// Sets the width of the [`Button`] in pixels.
///
/// [`Button`]: struct.Button.html
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
/// Makes the [`Button`] fill the horizontal space of its container.
///
/// [`Button`]: struct.Button.html
pub fn fill_width(mut self) -> Self {
self.style = self.style.fill_width();
self
}
/// Sets the alignment of the [`Button`] itself.
///
/// This is useful if you want to override the default alignment given by
/// the parent container.
///
/// [`Button`]: struct.Button.html
pub fn align_self(mut self, align: Align) -> Self {
self.style = self.style.align_self(align);
self
}
/// Sets the [`Class`] of the [`Button`].
///
///
/// [`Button`]: struct.Button.html
/// [`Class`]: enum.Class.html
pub fn class(mut self, class: Class) -> Self {
self.class = class;
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
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Button<'a, Message>
where
Renderer: self::Renderer,
Message: Copy + std::fmt::Debug,
{
fn node(&self, _renderer: &Renderer) -> Node {
Node::new(self.style.height(50))
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
match event {
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left,
state,
}) => {
if let Some(on_press) = self.on_press {
let bounds = layout.bounds();
match state {
ButtonState::Pressed => {
self.state.is_pressed =
bounds.contains(cursor_position);
}
ButtonState::Released => {
let is_clicked = self.state.is_pressed
&& bounds.contains(cursor_position);
self.state.is_pressed = false;
if is_clicked {
messages.push(on_press);
}
}
}
}
}
_ => {}
}
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
renderer.draw(
cursor_position,
layout.bounds(),
self.state,
&self.label,
self.class,
)
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
}
}
/// 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()
}
/// Returns whether the associated [`Button`] is currently being pressed or
/// not.
///
/// [`Button`]: struct.Button.html
pub fn is_pressed(&self) -> bool {
self.is_pressed
}
}
/// The type of a [`Button`].
///
/// ![Different buttons drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/button_classes.png?raw=true)
///
/// [`Button`]: struct.Button.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Class {
/// The [`Button`] performs the main action.
///
/// [`Button`]: struct.Button.html
Primary,
/// The [`Button`] performs an alternative action.
///
/// [`Button`]: struct.Button.html
Secondary,
/// The [`Button`] performs a productive action.
///
/// [`Button`]: struct.Button.html
Positive,
}
/// The renderer of a [`Button`].
///
/// Your [`core::Renderer`] will need to implement this trait before being
/// able to use a [`Button`] in your user interface.
///
/// [`Button`]: struct.Button.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
pub trait Renderer {
/// Draws a [`Button`].
///
/// It receives:
/// * the current cursor position
/// * the bounds of the [`Button`]
/// * the local state of the [`Button`]
/// * the label of the [`Button`]
/// * the [`Class`] of the [`Button`]
///
/// [`Button`]: struct.Button.html
/// [`State`]: struct.State.html
/// [`Class`]: enum.Class.html
fn draw(
&mut self,
cursor_position: Point,
bounds: Rectangle<f32>,
state: &State,
label: &str,
class: Class,
) -> MouseCursor;
}
impl<'a, Message, Renderer> From<Button<'a, Message>>
for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Message: 'static + Copy + std::fmt::Debug,
{
fn from(button: Button<'a, Message>) -> Element<'a, Message, Renderer> {
Element::new(button)
}
}

213
src/widget/checkbox.rs Normal file
View File

@ -0,0 +1,213 @@
//! Show toggle controls using checkboxes.
use std::hash::Hash;
use crate::input::{mouse, ButtonState};
use crate::widget::{text, Column, Row, Text};
use crate::{
Align, Element, Event, Hasher, Layout, MouseCursor, Node, Point, Rectangle,
Widget,
};
/// A box that can be checked.
///
/// It implements [`Widget`] when the [`core::Renderer`] implements the
/// [`checkbox::Renderer`] trait.
///
/// [`Widget`]: ../../core/trait.Widget.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
/// [`checkbox::Renderer`]: trait.Renderer.html
///
/// # Example
///
/// ```
/// use iced::Checkbox;
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Color {
/// Black,
/// White,
/// }
///
/// impl Default for Color {
/// fn default() -> Color {
/// Color::Black
/// }
/// }
///
/// pub enum Message {
/// CheckboxToggled(bool),
/// }
///
/// fn some_checkbox(is_checked: bool) -> Checkbox<Color, Message> {
/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled)
/// .label_color(Color::White)
/// }
/// ```
pub struct Checkbox<Color, Message> {
is_checked: bool,
on_toggle: Box<dyn Fn(bool) -> Message>,
label: String,
label_color: Color,
}
impl<Color, Message> std::fmt::Debug for Checkbox<Color, Message>
where
Color: std::fmt::Debug,
{
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<Color, Message> Checkbox<Color, Message>
where
Color: Default,
{
/// 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 receives 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: Color::default(),
}
}
/// Sets the [`Color`] of the label of the [`Checkbox`].
///
/// [`Color`]: ../../../../graphics/struct.Color.html
/// [`Checkbox`]: struct.Checkbox.html
pub fn label_color(mut self, color: Color) -> Self {
self.label_color = color;
self
}
}
impl<Color, Message, Renderer> Widget<Message, Renderer>
for Checkbox<Color, Message>
where
Color: 'static + Copy + Default + std::fmt::Debug,
Renderer: self::Renderer + text::Renderer<Color>,
{
fn node(&self, renderer: &Renderer) -> Node {
Row::<(), Renderer>::new()
.spacing(15)
.align_items(Align::Center)
.push(Column::new().width(28).height(28))
.push(Text::new(&self.label))
.node(renderer)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
match event {
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left,
state: ButtonState::Pressed,
}) => {
let mouse_over = layout
.children()
.any(|child| child.bounds().contains(cursor_position));
if mouse_over {
messages.push((self.on_toggle)(!self.is_checked));
}
}
_ => {}
}
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let children: Vec<_> = layout.children().collect();
let text_bounds = children[1].bounds();
text::Renderer::draw(
renderer,
text_bounds,
&self.label,
20.0,
self.label_color,
text::HorizontalAlignment::Left,
text::VerticalAlignment::Top,
);
self::Renderer::draw(
renderer,
cursor_position,
children[0].bounds(),
text_bounds,
self.is_checked,
)
}
fn hash(&self, state: &mut Hasher) {
self.label.hash(state);
}
}
/// The renderer of a [`Checkbox`].
///
/// Your [`core::Renderer`] will need to implement this trait before being
/// able to use a [`Checkbox`] in your user interface.
///
/// [`Checkbox`]: struct.Checkbox.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
pub trait Renderer {
/// Draws a [`Checkbox`].
///
/// It receives:
/// * the current cursor position
/// * the bounds of the [`Checkbox`]
/// * the bounds of the label of the [`Checkbox`]
/// * whether the [`Checkbox`] is checked or not
///
/// [`Checkbox`]: struct.Checkbox.html
fn draw(
&mut self,
cursor_position: Point,
bounds: Rectangle<f32>,
label_bounds: Rectangle<f32>,
is_checked: bool,
) -> MouseCursor;
}
impl<'a, Color, Message, Renderer> From<Checkbox<Color, Message>>
for Element<'a, Message, Renderer>
where
Color: 'static + Copy + Default + std::fmt::Debug,
Renderer: self::Renderer + text::Renderer<Color>,
Message: 'static,
{
fn from(
checkbox: Checkbox<Color, Message>,
) -> Element<'a, Message, Renderer> {
Element::new(checkbox)
}
}

223
src/widget/column.rs Normal file
View File

@ -0,0 +1,223 @@
use std::hash::Hash;
use crate::{
Align, Element, Event, Hasher, Justify, Layout, MouseCursor, Node, Point,
Style, Widget,
};
/// A container that places its contents vertically.
///
/// A [`Column`] will try to fill the horizontal space of its container.
///
/// [`Column`]: struct.Column.html
pub struct Column<'a, Message, Renderer> {
style: Style,
spacing: u16,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> std::fmt::Debug for Column<'a, Message, Renderer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Column")
.field("style", &self.style)
.field("spacing", &self.spacing)
.field("children", &self.children)
.finish()
}
}
impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
/// Creates an empty [`Column`].
///
/// [`Column`]: struct.Column.html
pub fn new() -> Self {
let mut style = Style::default().fill_width();
style.0.flex_direction = stretch::style::FlexDirection::Column;
Column {
style,
spacing: 0,
children: Vec::new(),
}
}
/// Sets the vertical spacing _between_ elements in pixels.
///
/// Custom margins per element do not exist in Coffee. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, px: u16) -> Self {
self.spacing = px;
self
}
/// Sets the padding of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn padding(mut self, px: u32) -> Self {
self.style = self.style.padding(px);
self
}
/// Sets the width of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
/// Sets the height of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn height(mut self, height: u32) -> Self {
self.style = self.style.height(height);
self
}
/// Sets the maximum width of the [`Column`] in pixels.
///
/// [`Column`]: struct.Column.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.style = self.style.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.style = self.style.max_height(max_height);
self
}
/// Sets the alignment of the [`Column`] itself.
///
/// This is useful if you want to override the default alignment given by
/// the parent container.
///
/// [`Column`]: struct.Column.html
pub fn align_self(mut self, align: Align) -> Self {
self.style = self.style.align_self(align);
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.style = self.style.align_items(align);
self
}
/// Sets the vertical distribution strategy for the contents of the
/// [`Column`] .
///
/// [`Column`]: struct.Column.html
pub fn justify_content(mut self, justify: Justify) -> Self {
self.style = self.style.justify_content(justify);
self
}
/// Adds an [`Element`] to the [`Column`].
///
/// [`Element`]: ../core/struct.Element.html
/// [`Column`]: struct.Column.html
pub fn push<E>(mut self, child: E) -> Column<'a, Message, Renderer>
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>
{
fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
.map(|child| {
let mut node = child.widget.node(renderer);
let mut style = node.0.style();
style.margin.bottom =
stretch::style::Dimension::Points(self.spacing as f32);
node.0.set_style(style);
node
})
.collect();
if let Some(node) = children.last_mut() {
let mut style = node.0.style();
style.margin.bottom = stretch::style::Dimension::Undefined;
node.0.set_style(style);
}
Node::with_children(self.style, children)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
child
.widget
.on_event(event, layout, cursor_position, messages)
},
);
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let mut cursor = MouseCursor::OutOfBounds;
self.children.iter().zip(layout.children()).for_each(
|(child, layout)| {
let new_cursor =
child.widget.draw(renderer, layout, cursor_position);
if new_cursor != MouseCursor::OutOfBounds {
cursor = new_cursor;
}
},
);
cursor
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
self.spacing.hash(state);
for child in &self.children {
child.widget.hash(state);
}
}
}
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a,
Message: 'static,
{
fn from(
column: Column<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(column)
}
}

94
src/widget/panel.rs Normal file
View File

@ -0,0 +1,94 @@
use std::hash::Hash;
use crate::graphics::{Point, Rectangle};
use crate::ui::core::{
Event, Hasher, Layout, MouseCursor, Node, Style, Widget,
};
pub struct Panel<'a, Message, Renderer> {
style: Style,
content: Box<Widget<Message, Renderer> + 'a>,
}
impl<'a, Message, Renderer> Panel<'a, Message, Renderer> {
pub fn new(content: impl Widget<Message, Renderer> + 'a) -> Self {
Panel {
style: Style::default().padding(20),
content: Box::new(content),
}
}
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
pub fn max_width(mut self, max_width: u32) -> Self {
self.style = self.style.max_width(max_width);
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Panel<'a, Message, Renderer>
where
Renderer: self::Renderer,
{
fn node(&self, renderer: &Renderer) -> Node {
Node::with_children(self.style, vec![self.content.node(renderer)])
}
fn on_event(
&mut self,
event: Event,
layout: Layout,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
[&mut self.content]
.iter_mut()
.zip(layout.children())
.for_each(|(child, layout)| {
child.on_event(event, layout, cursor_position, messages)
});
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout,
cursor_position: Point,
) -> MouseCursor {
let bounds = layout.bounds();
let mut cursor = MouseCursor::OutOfBounds;
renderer.draw(bounds);
[&self.content].iter().zip(layout.children()).for_each(
|(child, layout)| {
let new_cursor = child.draw(renderer, layout, cursor_position);
if new_cursor != MouseCursor::OutOfBounds {
cursor = new_cursor;
}
},
);
if cursor == MouseCursor::OutOfBounds {
if bounds.contains(cursor_position) {
MouseCursor::Idle
} else {
MouseCursor::OutOfBounds
}
} else {
cursor
}
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
}
}
pub trait Renderer {
fn draw(&mut self, bounds: Rectangle<f32>);
}

219
src/widget/radio.rs Normal file
View File

@ -0,0 +1,219 @@
//! Create choices using radio buttons.
use crate::input::{mouse, ButtonState};
use crate::widget::{text, Column, Row, Text};
use crate::{
Align, Element, Event, Hasher, Layout, MouseCursor, Node, Point, Rectangle,
Widget,
};
use std::hash::Hash;
/// A circular button representing a choice.
///
/// It implements [`Widget`] when the [`core::Renderer`] implements the
/// [`radio::Renderer`] trait.
///
/// [`Widget`]: ../../core/trait.Widget.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
/// [`radio::Renderer`]: trait.Renderer.html
///
/// # Example
/// ```
/// use iced::{Column, Radio};
///
/// #[derive(Debug, Clone, Copy)]
/// pub enum Color {
/// Black,
/// }
///
/// impl Default for Color {
/// fn default() -> Color {
/// Color::Black
/// }
/// }
///
/// #[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::<Color, Message>::new(Choice::A, "This is A", selected_choice, Message::RadioSelected)
/// .label_color(Color::Black);
/// ```
///
/// ![Checkbox drawn by the built-in renderer](https://github.com/hecrj/coffee/blob/bda9818f823dfcb8a7ad0ff4940b4d4b387b5208/images/ui/radio.png?raw=true)
pub struct Radio<Color, Message> {
is_selected: bool,
on_click: Message,
label: String,
label_color: Color,
}
impl<Color, Message> std::fmt::Debug for Radio<Color, Message>
where
Color: std::fmt::Debug,
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<Color, Message> Radio<Color, Message>
where
Color: Default,
{
/// 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: Color::default(),
}
}
/// Sets the [`Color`] of the label of the [`Radio`].
///
/// [`Color`]: ../../../../graphics/struct.Color.html
/// [`Radio`]: struct.Radio.html
pub fn label_color(mut self, color: Color) -> Self {
self.label_color = color;
self
}
}
impl<Color, Message, Renderer> Widget<Message, Renderer>
for Radio<Color, Message>
where
Color: 'static + Copy + Default + std::fmt::Debug,
Renderer: self::Renderer + text::Renderer<Color>,
Message: Copy + std::fmt::Debug,
{
fn node(&self, renderer: &Renderer) -> Node {
Row::<(), Renderer>::new()
.spacing(15)
.align_items(Align::Center)
.push(Column::new().width(28).height(28))
.push(Text::new(&self.label))
.node(renderer)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
match event {
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left,
state: ButtonState::Pressed,
}) => {
if layout.bounds().contains(cursor_position) {
messages.push(self.on_click);
}
}
_ => {}
}
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let children: Vec<_> = layout.children().collect();
let mut text_bounds = children[1].bounds();
text_bounds.y -= 2.0;
text::Renderer::draw(
renderer,
text_bounds,
&self.label,
20.0,
self.label_color,
text::HorizontalAlignment::Left,
text::VerticalAlignment::Top,
);
self::Renderer::draw(
renderer,
cursor_position,
children[0].bounds(),
layout.bounds(),
self.is_selected,
)
}
fn hash(&self, state: &mut Hasher) {
self.label.hash(state);
}
}
/// The renderer of a [`Radio`] button.
///
/// Your [`core::Renderer`] will need to implement this trait before being
/// able to use a [`Radio`] button in your user interface.
///
/// [`Radio`]: struct.Radio.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
pub trait Renderer {
/// Draws a [`Radio`] button.
///
/// It receives:
/// * the current cursor position
/// * the bounds of the [`Radio`]
/// * the bounds of the label of the [`Radio`]
/// * whether the [`Radio`] is selected or not
///
/// [`Radio`]: struct.Radio.html
fn draw(
&mut self,
cursor_position: Point,
bounds: Rectangle<f32>,
label_bounds: Rectangle<f32>,
is_selected: bool,
) -> MouseCursor;
}
impl<'a, Color, Message, Renderer> From<Radio<Color, Message>>
for Element<'a, Message, Renderer>
where
Color: 'static + Copy + Default + std::fmt::Debug,
Renderer: self::Renderer + text::Renderer<Color>,
Message: 'static + Copy + std::fmt::Debug,
{
fn from(checkbox: Radio<Color, Message>) -> Element<'a, Message, Renderer> {
Element::new(checkbox)
}
}

218
src/widget/row.rs Normal file
View File

@ -0,0 +1,218 @@
use std::hash::Hash;
use crate::{
Align, Element, Event, Hasher, Justify, Layout, MouseCursor, Node, Point,
Style, Widget,
};
/// A container that places its contents horizontally.
///
/// A [`Row`] will try to fill the horizontal space of its container.
///
/// [`Row`]: struct.Row.html
pub struct Row<'a, Message, Renderer> {
style: Style,
spacing: u16,
children: Vec<Element<'a, Message, Renderer>>,
}
impl<'a, Message, Renderer> std::fmt::Debug for Row<'a, Message, Renderer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Row")
.field("style", &self.style)
.field("spacing", &self.spacing)
.field("children", &self.children)
.finish()
}
}
impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
/// Creates an empty [`Row`].
///
/// [`Row`]: struct.Row.html
pub fn new() -> Self {
Row {
style: Style::default().fill_width(),
spacing: 0,
children: Vec::new(),
}
}
/// Sets the horizontal spacing _between_ elements in pixels.
///
/// Custom margins per element do not exist in Coffee. You should use this
/// method instead! While less flexible, it helps you keep spacing between
/// elements consistent.
pub fn spacing(mut self, px: u16) -> Self {
self.spacing = px;
self
}
/// Sets the padding of the [`Row`] in pixels.
///
/// [`Row`]: struct.Row.html
pub fn padding(mut self, px: u32) -> Self {
self.style = self.style.padding(px);
self
}
/// Sets the width of the [`Row`] in pixels.
///
/// [`Row`]: struct.Row.html
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
/// Sets the height of the [`Row`] in pixels.
///
/// [`Row`]: struct.Row.html
pub fn height(mut self, height: u32) -> Self {
self.style = self.style.height(height);
self
}
/// Sets the maximum width of the [`Row`] in pixels.
///
/// [`Row`]: struct.Row.html
pub fn max_width(mut self, max_width: u32) -> Self {
self.style = self.style.max_width(max_width);
self
}
/// Sets the maximum height of the [`Row`] in pixels.
///
/// [`Row`]: struct.Row.html
pub fn max_height(mut self, max_height: u32) -> Self {
self.style = self.style.max_height(max_height);
self
}
/// Sets the alignment of the [`Row`] itself.
///
/// This is useful if you want to override the default alignment given by
/// the parent container.
///
/// [`Row`]: struct.Row.html
pub fn align_self(mut self, align: Align) -> Self {
self.style = self.style.align_self(align);
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.style = self.style.align_items(align);
self
}
/// Sets the horizontal distribution strategy for the contents of the
/// [`Row`] .
///
/// [`Row`]: struct.Row.html
pub fn justify_content(mut self, justify: Justify) -> Self {
self.style = self.style.justify_content(justify);
self
}
/// Adds an [`Element`] to the [`Row`].
///
/// [`Element`]: ../core/struct.Element.html
/// [`Row`]: struct.Row.html
pub fn push<E>(mut self, child: E) -> Row<'a, Message, Renderer>
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>
{
fn node(&self, renderer: &Renderer) -> Node {
let mut children: Vec<Node> = self
.children
.iter()
.map(|child| {
let mut node = child.widget.node(renderer);
let mut style = node.0.style();
style.margin.end =
stretch::style::Dimension::Points(self.spacing as f32);
node.0.set_style(style);
node
})
.collect();
if let Some(node) = children.last_mut() {
let mut style = node.0.style();
style.margin.end = stretch::style::Dimension::Undefined;
node.0.set_style(style);
}
Node::with_children(self.style, children)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
self.children.iter_mut().zip(layout.children()).for_each(
|(child, layout)| {
child
.widget
.on_event(event, layout, cursor_position, messages)
},
);
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
let mut cursor = MouseCursor::OutOfBounds;
self.children.iter().zip(layout.children()).for_each(
|(child, layout)| {
let new_cursor =
child.widget.draw(renderer, layout, cursor_position);
if new_cursor != MouseCursor::OutOfBounds {
cursor = new_cursor;
}
},
);
cursor
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
self.spacing.hash(state);
for child in &self.children {
child.widget.hash(state);
}
}
}
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a,
Message: 'static,
{
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
Element::new(row)
}
}

240
src/widget/slider.rs Normal file
View File

@ -0,0 +1,240 @@
//! 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 std::hash::Hash;
use std::ops::RangeInclusive;
use crate::input::{mouse, ButtonState};
use crate::{
Element, Event, Hasher, Layout, MouseCursor, Node, Point, Rectangle, Style,
Widget,
};
/// 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.
///
/// It implements [`Widget`] when the associated [`core::Renderer`] implements
/// the [`slider::Renderer`] trait.
///
/// [`Slider`]: struct.Slider.html
/// [`Widget`]: ../../core/trait.Widget.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
/// [`slider::Renderer`]: trait.Renderer.html
///
/// # Example
/// ```
/// use iced::{slider, Slider};
///
/// pub enum Message {
/// SliderChanged(f32),
/// }
///
/// let state = &mut slider::State::new();
/// let value = 50.0;
///
/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
/// ```
pub struct Slider<'a, Message> {
state: &'a mut State,
range: RangeInclusive<f32>,
value: f32,
on_change: Box<dyn Fn(f32) -> Message>,
style: Style,
}
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("style", &self.style)
.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: Box::new(on_change),
style: Style::default().min_width(100).fill_width(),
}
}
/// Sets the width of the [`Slider`] in pixels.
///
/// [`Slider`]: struct.Slider.html
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer> for Slider<'a, Message>
where
Renderer: self::Renderer,
{
fn node(&self, _renderer: &Renderer) -> Node {
Node::new(self.style.height(25))
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
let mut change = || {
let bounds = layout.bounds();
if cursor_position.x <= bounds.x {
messages.push((self.on_change)(*self.range.start()));
} else if cursor_position.x >= bounds.x + bounds.width {
messages.push((self.on_change)(*self.range.end()));
} else {
let percent = (cursor_position.x - bounds.x) / bounds.width;
let value = (self.range.end() - self.range.start()) * percent
+ self.range.start();
messages.push((self.on_change)(value));
}
};
match event {
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left,
state,
}) => match state {
ButtonState::Pressed => {
if layout.bounds().contains(cursor_position) {
change();
self.state.is_dragging = true;
}
}
ButtonState::Released => {
self.state.is_dragging = false;
}
},
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
if self.state.is_dragging {
change();
}
}
_ => {}
}
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
cursor_position: Point,
) -> MouseCursor {
renderer.draw(
cursor_position,
layout.bounds(),
self.state,
self.range.clone(),
self.value,
)
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
}
}
/// 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()
}
/// Returns whether the associated [`Slider`] is currently being dragged or
/// not.
///
/// [`Slider`]: struct.Slider.html
pub fn is_dragging(&self) -> bool {
self.is_dragging
}
}
/// The renderer of a [`Slider`].
///
/// Your [`core::Renderer`] will need to implement this trait before being
/// able to use a [`Slider`] in your user interface.
///
/// [`Slider`]: struct.Slider.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
pub trait Renderer {
/// Draws a [`Slider`].
///
/// It receives:
/// * the current cursor position
/// * the bounds of the [`Slider`]
/// * the local state of the [`Slider`]
/// * the range of values of the [`Slider`]
/// * the current value of the [`Slider`]
///
/// [`Slider`]: struct.Slider.html
/// [`State`]: struct.State.html
/// [`Class`]: enum.Class.html
fn draw(
&mut self,
cursor_position: Point,
bounds: Rectangle<f32>,
state: &State,
range: RangeInclusive<f32>,
value: f32,
) -> MouseCursor;
}
impl<'a, Message, Renderer> From<Slider<'a, Message>>
for Element<'a, Message, Renderer>
where
Renderer: self::Renderer,
Message: 'static,
{
fn from(slider: Slider<'a, Message>) -> Element<'a, Message, Renderer> {
Element::new(slider)
}
}

223
src/widget/text.rs Normal file
View File

@ -0,0 +1,223 @@
//! Write some text for your users to read.
use crate::{
Element, Hasher, Layout, MouseCursor, Node, Point, Rectangle, Style, Widget,
};
use std::hash::Hash;
/// A fragment of text.
///
/// It implements [`Widget`] when the associated [`core::Renderer`] implements
/// the [`text::Renderer`] trait.
///
/// [`Widget`]: ../../core/trait.Widget.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
/// [`text::Renderer`]: trait.Renderer.html
///
/// # Example
///
/// ```
/// use iced::Text;
///
/// Text::<(f32, f32, f32)>::new("I <3 iced!")
/// .size(40)
/// .color((0.0, 0.0, 1.0));
/// ```
#[derive(Debug, Clone)]
pub struct Text<Color> {
content: String,
size: u16,
color: Color,
style: Style,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
}
impl<Color> Text<Color>
where
Color: Default,
{
/// 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: 20,
color: Color::default(),
style: Style::default().fill_width(),
horizontal_alignment: HorizontalAlignment::Left,
vertical_alignment: VerticalAlignment::Top,
}
}
/// Sets the size of the [`Text`] in pixels.
///
/// [`Text`]: struct.Text.html
pub fn size(mut self, size: u16) -> Self {
self.size = size;
self
}
/// Sets the [`Color`] of the [`Text`].
///
/// [`Text`]: struct.Text.html
/// [`Color`]: ../../../graphics/struct.Color.html
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
/// Sets the width of the [`Text`] boundaries in pixels.
///
/// [`Text`]: struct.Text.html
pub fn width(mut self, width: u32) -> Self {
self.style = self.style.width(width);
self
}
/// Sets the height of the [`Text`] boundaries in pixels.
///
/// [`Text`]: struct.Text.html
pub fn height(mut self, height: u32) -> Self {
self.style = self.style.height(height);
self
}
/// Sets the [`HorizontalAlignment`] of the [`Text`].
///
/// [`Text`]: struct.Text.html
/// [`HorizontalAlignment`]: ../../../graphics/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`]: ../../../graphics/enum.VerticalAlignment.html
pub fn vertical_alignment(mut self, alignment: VerticalAlignment) -> Self {
self.vertical_alignment = alignment;
self
}
}
impl<Message, Renderer, Color> Widget<Message, Renderer> for Text<Color>
where
Color: Copy + std::fmt::Debug,
Renderer: self::Renderer<Color>,
{
fn node(&self, renderer: &Renderer) -> Node {
renderer.node(self.style, &self.content, self.size as f32)
}
fn draw(
&self,
renderer: &mut Renderer,
layout: Layout<'_>,
_cursor_position: Point,
) -> MouseCursor {
renderer.draw(
layout.bounds(),
&self.content,
self.size as f32,
self.color,
self.horizontal_alignment,
self.vertical_alignment,
);
MouseCursor::OutOfBounds
}
fn hash(&self, state: &mut Hasher) {
self.style.hash(state);
self.content.hash(state);
self.size.hash(state);
}
}
/// The renderer of a [`Text`] fragment.
///
/// Your [`core::Renderer`] will need to implement this trait before being
/// able to use a [`Text`] in your user interface.
///
/// [`Text`]: struct.Text.html
/// [`core::Renderer`]: ../../core/trait.Renderer.html
pub trait Renderer<Color> {
/// 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`]: ../../core/struct.Node.html
/// [`Style`]: ../../core/struct.Style.html
/// [`Text`]: struct.Text.html
/// [`Node::with_measure`]: ../../core/struct.Node.html#method.with_measure
fn node(&self, style: Style, content: &str, size: f32) -> Node;
/// Draws a [`Text`] fragment.
///
/// It receives:
/// * the bounds of the [`Text`]
/// * the contents of the [`Text`]
/// * the size of the [`Text`]
/// * the color of the [`Text`]
/// * the [`HorizontalAlignment`] of the [`Text`]
/// * the [`VerticalAlignment`] of the [`Text`]
///
/// [`Text`]: struct.Text.html
/// [`HorizontalAlignment`]: ../../../graphics/enum.HorizontalAlignment.html
/// [`VerticalAlignment`]: ../../../graphics/enum.VerticalAlignment.html
fn draw(
&mut self,
bounds: Rectangle<f32>,
content: &str,
size: f32,
color: Color,
horizontal_alignment: HorizontalAlignment,
vertical_alignment: VerticalAlignment,
);
}
impl<'a, Message, Renderer, Color> From<Text<Color>>
for Element<'a, Message, Renderer>
where
Color: 'static + Copy + std::fmt::Debug,
Renderer: self::Renderer<Color>,
{
fn from(text: Text<Color>) -> Element<'a, Message, Renderer> {
Element::new(text)
}
}
/// 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,
}