summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md28
-rw-r--r--examples/bezier_tool/Cargo.toml5
-rw-r--r--examples/bezier_tool/README.md3
-rw-r--r--examples/bezier_tool/src/main.rs477
-rw-r--r--examples/clock/Cargo.toml7
-rw-r--r--examples/clock/src/main.rs176
-rw-r--r--examples/color_palette/Cargo.toml10
-rw-r--r--examples/color_palette/README.md15
-rw-r--r--examples/color_palette/screenshot.pngbin0 -> 105201 bytes
-rw-r--r--examples/color_palette/src/main.rs462
-rw-r--r--examples/counter/src/main.rs2
-rw-r--r--examples/custom_widget/Cargo.toml2
-rw-r--r--examples/custom_widget/src/main.rs65
-rw-r--r--examples/download_progress/Cargo.toml12
-rw-r--r--examples/download_progress/README.md17
-rw-r--r--examples/download_progress/src/download.rs112
-rw-r--r--examples/download_progress/src/main.rs144
-rw-r--r--examples/events/src/main.rs5
-rw-r--r--examples/game_of_life/Cargo.toml12
-rw-r--r--examples/game_of_life/README.md22
-rw-r--r--examples/game_of_life/src/main.rs885
-rw-r--r--examples/game_of_life/src/preset.rs142
-rw-r--r--examples/game_of_life/src/style.rs188
-rw-r--r--examples/geometry/Cargo.toml2
-rw-r--r--examples/geometry/src/main.rs142
-rw-r--r--examples/integration/Cargo.toml2
-rw-r--r--examples/integration/src/controls.rs67
-rw-r--r--examples/integration/src/main.rs252
-rw-r--r--examples/integration/src/scene.rs113
-rw-r--r--examples/pane_grid/Cargo.toml10
-rw-r--r--examples/pane_grid/README.md28
-rw-r--r--examples/pane_grid/src/main.rs372
-rw-r--r--examples/pick_list/Cargo.toml9
-rw-r--r--examples/pick_list/README.md18
-rw-r--r--examples/pick_list/src/main.rs113
-rw-r--r--examples/pokedex/Cargo.toml2
-rw-r--r--examples/pokedex/src/main.rs9
-rw-r--r--examples/progress_bar/src/main.rs17
-rw-r--r--examples/qr_code/Cargo.toml9
-rw-r--r--examples/qr_code/README.md18
-rw-r--r--examples/qr_code/src/main.rs81
-rw-r--r--examples/scrollable/Cargo.toml9
-rw-r--r--examples/scrollable/README.md15
-rw-r--r--examples/scrollable/screenshot.pngbin0 -> 148253 bytes
-rw-r--r--examples/scrollable/src/main.rs184
-rw-r--r--examples/scrollable/src/style.rs190
-rw-r--r--examples/solar_system/Cargo.toml7
-rw-r--r--examples/solar_system/src/main.rs227
-rw-r--r--examples/stopwatch/Cargo.toml5
-rw-r--r--examples/stopwatch/src/main.rs51
-rw-r--r--examples/styling/src/main.rs122
-rw-r--r--examples/svg/Cargo.toml1
-rw-r--r--examples/svg/src/main.rs28
-rw-r--r--examples/todos/Cargo.toml4
-rw-r--r--examples/todos/src/main.rs15
-rw-r--r--examples/tour/Cargo.toml2
-rw-r--r--examples/tour/src/main.rs84
57 files changed, 3945 insertions, 1054 deletions
diff --git a/examples/README.md b/examples/README.md
index 04399b93..32ccf724 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -50,6 +50,27 @@ We have not yet implemented a `LocalStorage` version of the auto-save feature. T
[TodoMVC]: http://todomvc.com/
+## [Game of Life](game_of_life)
+An interactive version of the [Game of Life], invented by [John Horton Conway].
+
+It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support.
+
+The relevant code is located in the __[`main`](game_of_life/src/main.rs)__ file.
+
+<div align="center">
+ <a href="https://gfycat.com/briefaccurateaardvark">
+ <img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package game_of_life
+```
+
+[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
+[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway
+
## [Styling](styling)
An example showcasing custom styling with a light and dark theme.
@@ -69,15 +90,20 @@ cargo run --package styling
## Extras
A bunch of simpler examples exist:
-- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using [`lyon`].
+- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using the `Canvas` widget.
- [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time.
+- [`color_palette`](color_palette), a color palette generator based on a user-defined root color.
- [`counter`](counter), the classic counter example explained in the [`README`](../README.md).
- [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle.
+- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
- [`events`](events), a log of native events displayed using a conditional `Subscription`.
- [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu).
- [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application.
+- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized.
+- [`pick_list`](pick_list), a dropdown list of selectable options.
- [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI].
- [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider.
+- [`scrollable`](scrollable), a showcase of the various scrollbar width options.
- [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms.
- [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time.
- [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget.
diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml
index b13a0aa5..a88975a7 100644
--- a/examples/bezier_tool/Cargo.toml
+++ b/examples/bezier_tool/Cargo.toml
@@ -6,7 +6,4 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../.." }
-iced_native = { path = "../../native" }
-iced_wgpu = { path = "../../wgpu" }
-lyon = "0.15"
+iced = { path = "../..", features = ["canvas"] }
diff --git a/examples/bezier_tool/README.md b/examples/bezier_tool/README.md
index 933f2120..ebbb12cc 100644
--- a/examples/bezier_tool/README.md
+++ b/examples/bezier_tool/README.md
@@ -1,6 +1,6 @@
## Bézier tool
-A Paint-like tool for drawing Bézier curves using [`lyon`].
+A Paint-like tool for drawing Bézier curves using the `Canvas` widget.
The __[`main`]__ file contains all the code of the example.
@@ -16,4 +16,3 @@ cargo run --package bezier_tool
```
[`main`]: src/main.rs
-[`lyon`]: https://github.com/nical/lyon
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index c3fbf276..97832e01 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -1,294 +1,13 @@
-//! This example showcases a simple native custom widget that renders arbitrary
-//! path with `lyon`.
-mod bezier {
- // For now, to implement a custom native widget you will need to add
- // `iced_native` and `iced_wgpu` to your dependencies.
- //
- // Then, you simply need to define your widget type and implement the
- // `iced_native::Widget` trait with the `iced_wgpu::Renderer`.
- //
- // Of course, you can choose to make the implementation renderer-agnostic,
- // if you wish to, by creating your own `Renderer` trait, which could be
- // implemented by `iced_wgpu` and other renderers.
- use iced_native::{
- input, layout, Clipboard, Color, Element, Event, Font, Hasher,
- HorizontalAlignment, Layout, Length, MouseCursor, Point, Size, Vector,
- VerticalAlignment, Widget,
- };
- use iced_wgpu::{
- triangle::{Mesh2D, Vertex2D},
- Defaults, Primitive, Renderer,
- };
- use lyon::tessellation::{
- basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions,
- StrokeTessellator, VertexBuffers,
- };
-
- pub struct Bezier<'a, Message> {
- state: &'a mut State,
- curves: &'a [Curve],
- // [from, to, ctrl]
- on_click: Box<dyn Fn(Curve) -> Message>,
- }
-
- #[derive(Debug, Clone, Copy)]
- pub struct Curve {
- from: Point,
- to: Point,
- control: Point,
- }
-
- #[derive(Default)]
- pub struct State {
- pending: Option<Pending>,
- }
-
- enum Pending {
- One { from: Point },
- Two { from: Point, to: Point },
- }
-
- impl<'a, Message> Bezier<'a, Message> {
- pub fn new<F>(
- state: &'a mut State,
- curves: &'a [Curve],
- on_click: F,
- ) -> Self
- where
- F: 'static + Fn(Curve) -> Message,
- {
- Self {
- state,
- curves,
- on_click: Box::new(on_click),
- }
- }
- }
-
- impl<'a, Message> Widget<Message, Renderer> for Bezier<'a, Message> {
- fn width(&self) -> Length {
- Length::Fill
- }
-
- fn height(&self) -> Length {
- Length::Fill
- }
-
- fn layout(
- &self,
- _renderer: &Renderer,
- limits: &layout::Limits,
- ) -> layout::Node {
- let size = limits
- .height(Length::Fill)
- .width(Length::Fill)
- .resolve(Size::ZERO);
- layout::Node::new(size)
- }
-
- fn draw(
- &self,
- _renderer: &mut Renderer,
- defaults: &Defaults,
- layout: Layout<'_>,
- cursor_position: Point,
- ) -> (Primitive, MouseCursor) {
- let mut buffer: VertexBuffers<Vertex2D, u32> = VertexBuffers::new();
- let mut path_builder = lyon::path::Path::builder();
-
- let bounds = layout.bounds();
-
- // Draw rectangle border with lyon.
- basic_shapes::stroke_rectangle(
- &lyon::math::Rect::new(
- lyon::math::Point::new(0.5, 0.5),
- lyon::math::Size::new(
- bounds.width - 1.0,
- bounds.height - 1.0,
- ),
- ),
- &StrokeOptions::default().with_line_width(1.0),
- &mut BuffersBuilder::new(
- &mut buffer,
- |pos: lyon::math::Point, _: StrokeAttributes| Vertex2D {
- position: pos.to_array(),
- color: [0.0, 0.0, 0.0, 1.0],
- },
- ),
- )
- .unwrap();
-
- for curve in self.curves {
- path_builder.move_to(lyon::math::Point::new(
- curve.from.x,
- curve.from.y,
- ));
-
- path_builder.quadratic_bezier_to(
- lyon::math::Point::new(curve.control.x, curve.control.y),
- lyon::math::Point::new(curve.to.x, curve.to.y),
- );
- }
-
- match self.state.pending {
- None => {}
- Some(Pending::One { from }) => {
- path_builder
- .move_to(lyon::math::Point::new(from.x, from.y));
- path_builder.line_to(lyon::math::Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- ));
- }
- Some(Pending::Two { from, to }) => {
- path_builder
- .move_to(lyon::math::Point::new(from.x, from.y));
- path_builder.quadratic_bezier_to(
- lyon::math::Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- ),
- lyon::math::Point::new(to.x, to.y),
- );
- }
- }
-
- let mut tessellator = StrokeTessellator::new();
-
- // Draw strokes with lyon.
- tessellator
- .tessellate(
- &path_builder.build(),
- &StrokeOptions::default().with_line_width(3.0),
- &mut BuffersBuilder::new(
- &mut buffer,
- |pos: lyon::math::Point, _: StrokeAttributes| {
- Vertex2D {
- position: pos.to_array(),
- color: [0.0, 0.0, 0.0, 1.0],
- }
- },
- ),
- )
- .unwrap();
-
- let mesh = Primitive::Mesh2D {
- origin: Point::new(bounds.x, bounds.y),
- buffers: Mesh2D {
- vertices: buffer.vertices,
- indices: buffer.indices,
- },
- };
-
- (
- Primitive::Clip {
- bounds,
- offset: Vector::new(0, 0),
- content: Box::new(
- if self.curves.is_empty()
- && self.state.pending.is_none()
- {
- let instructions = Primitive::Text {
- bounds,
- color: Color {
- a: defaults.text.color.a * 0.7,
- ..defaults.text.color
- },
- content: String::from(
- "Click to create bezier curves!",
- ),
- font: Font::Default,
- size: 30.0,
- horizontal_alignment:
- HorizontalAlignment::Center,
- vertical_alignment: VerticalAlignment::Center,
- };
-
- Primitive::Group {
- primitives: vec![mesh, instructions],
- }
- } else {
- mesh
- },
- ),
- },
- MouseCursor::OutOfBounds,
- )
- }
-
- fn hash_layout(&self, _state: &mut Hasher) {}
-
- fn on_event(
- &mut self,
- event: Event,
- layout: Layout<'_>,
- cursor_position: Point,
- messages: &mut Vec<Message>,
- _renderer: &Renderer,
- _clipboard: Option<&dyn Clipboard>,
- ) {
- let bounds = layout.bounds();
-
- if bounds.contains(cursor_position) {
- match event {
- Event::Mouse(input::mouse::Event::Input {
- state: input::ButtonState::Pressed,
- ..
- }) => {
- let new_point = Point::new(
- cursor_position.x - bounds.x,
- cursor_position.y - bounds.y,
- );
-
- match self.state.pending {
- None => {
- self.state.pending =
- Some(Pending::One { from: new_point });
- }
- Some(Pending::One { from }) => {
- self.state.pending = Some(Pending::Two {
- from,
- to: new_point,
- });
- }
- Some(Pending::Two { from, to }) => {
- self.state.pending = None;
-
- messages.push((self.on_click)(Curve {
- from,
- to,
- control: new_point,
- }));
- }
- }
- }
- _ => {}
- }
- }
- }
- }
-
- impl<'a, Message> Into<Element<'a, Message, Renderer>> for Bezier<'a, Message>
- where
- Message: 'static,
- {
- fn into(self) -> Element<'a, Message, Renderer> {
- Element::new(self)
- }
- }
-}
-
-use bezier::Bezier;
+//! This example showcases an interactive `Canvas` for drawing Bézier curves.
use iced::{
- button, Align, Button, Column, Container, Element, Length, Sandbox,
- Settings, Text,
+ button, Align, Button, Column, Element, Length, Sandbox, Settings, Text,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Example::run(Settings {
antialiasing: true,
..Settings::default()
- });
+ })
}
#[derive(Default)]
@@ -319,6 +38,7 @@ impl Sandbox for Example {
match message {
Message::AddCurve(curve) => {
self.curves.push(curve);
+ self.bezier.request_redraw();
}
Message::Clear => {
self.bezier = bezier::State::default();
@@ -328,7 +48,7 @@ impl Sandbox for Example {
}
fn view(&mut self) -> Element<Message> {
- let content = Column::new()
+ Column::new()
.padding(20)
.spacing(20)
.align_items(Align::Center)
@@ -337,22 +57,189 @@ impl Sandbox for Example {
.width(Length::Shrink)
.size(50),
)
- .push(Bezier::new(
- &mut self.bezier,
- self.curves.as_slice(),
- Message::AddCurve,
- ))
+ .push(self.bezier.view(&self.curves).map(Message::AddCurve))
.push(
Button::new(&mut self.button_state, Text::new("Clear"))
.padding(8)
.on_press(Message::Clear),
- );
+ )
+ .into()
+ }
+}
+
+mod bezier {
+ use iced::{
+ canvas::event::{self, Event},
+ canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke},
+ mouse, Element, Length, Point, Rectangle,
+ };
+
+ #[derive(Default)]
+ pub struct State {
+ pending: Option<Pending>,
+ cache: canvas::Cache,
+ }
- Container::new(content)
+ impl State {
+ pub fn view<'a>(
+ &'a mut self,
+ curves: &'a [Curve],
+ ) -> Element<'a, Curve> {
+ Canvas::new(Bezier {
+ state: self,
+ curves,
+ })
.width(Length::Fill)
.height(Length::Fill)
- .center_x()
- .center_y()
.into()
+ }
+
+ pub fn request_redraw(&mut self) {
+ self.cache.clear()
+ }
+ }
+
+ struct Bezier<'a> {
+ state: &'a mut State,
+ curves: &'a [Curve],
+ }
+
+ impl<'a> canvas::Program<Curve> for Bezier<'a> {
+ fn update(
+ &mut self,
+ event: Event,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> (event::Status, Option<Curve>) {
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
+
+ match event {
+ Event::Mouse(mouse_event) => {
+ let message = match mouse_event {
+ mouse::Event::ButtonPressed(mouse::Button::Left) => {
+ match self.state.pending {
+ None => {
+ self.state.pending = Some(Pending::One {
+ from: cursor_position,
+ });
+
+ None
+ }
+ Some(Pending::One { from }) => {
+ self.state.pending = Some(Pending::Two {
+ from,
+ to: cursor_position,
+ });
+
+ None
+ }
+ Some(Pending::Two { from, to }) => {
+ self.state.pending = None;
+
+ Some(Curve {
+ from,
+ to,
+ control: cursor_position,
+ })
+ }
+ }
+ }
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
+ _ => (event::Status::Ignored, None),
+ }
+ }
+
+ fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
+ let content =
+ self.state.cache.draw(bounds.size(), |frame: &mut Frame| {
+ Curve::draw_all(self.curves, frame);
+
+ frame.stroke(
+ &Path::rectangle(Point::ORIGIN, frame.size()),
+ Stroke::default(),
+ );
+ });
+
+ if let Some(pending) = &self.state.pending {
+ let pending_curve = pending.draw(bounds, cursor);
+
+ vec![content, pending_curve]
+ } else {
+ vec![content]
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> mouse::Interaction {
+ if cursor.is_over(&bounds) {
+ mouse::Interaction::Crosshair
+ } else {
+ mouse::Interaction::default()
+ }
+ }
+ }
+
+ #[derive(Debug, Clone, Copy)]
+ pub struct Curve {
+ from: Point,
+ to: Point,
+ control: Point,
+ }
+
+ impl Curve {
+ fn draw_all(curves: &[Curve], frame: &mut Frame) {
+ let curves = Path::new(|p| {
+ for curve in curves {
+ p.move_to(curve.from);
+ p.quadratic_curve_to(curve.control, curve.to);
+ }
+ });
+
+ frame.stroke(&curves, Stroke::default().with_width(2.0));
+ }
+ }
+
+ #[derive(Debug, Clone, Copy)]
+ enum Pending {
+ One { from: Point },
+ Two { from: Point, to: Point },
+ }
+
+ impl Pending {
+ fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry {
+ let mut frame = Frame::new(bounds.size());
+
+ if let Some(cursor_position) = cursor.position_in(&bounds) {
+ match *self {
+ Pending::One { from } => {
+ let line = Path::line(from, cursor_position);
+ frame.stroke(&line, Stroke::default().with_width(2.0));
+ }
+ Pending::Two { from, to } => {
+ let curve = Curve {
+ from,
+ to,
+ control: cursor_position,
+ };
+
+ Curve::draw_all(&[curve], &mut frame);
+ }
+ };
+ }
+
+ frame.into_geometry()
+ }
}
}
diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml
index 308cbfbb..c6e32379 100644
--- a/examples/clock/Cargo.toml
+++ b/examples/clock/Cargo.toml
@@ -5,11 +5,6 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
publish = false
-[features]
-canvas = []
-
[dependencies]
-iced = { path = "../..", features = ["canvas", "async-std", "debug"] }
-iced_native = { path = "../../native" }
+iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
chrono = "0.4"
-async-std = { version = "1.0", features = ["unstable"] }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
index d8266f06..b317ac00 100644
--- a/examples/clock/src/main.rs
+++ b/examples/clock/src/main.rs
@@ -1,9 +1,10 @@
use iced::{
- canvas, executor, Application, Canvas, Color, Command, Container, Element,
- Length, Point, Settings, Subscription, Vector,
+ canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke},
+ executor, time, Application, Color, Command, Container, Element, Length,
+ Point, Rectangle, Settings, Subscription, Vector,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Clock::run(Settings {
antialiasing: true,
..Settings::default()
@@ -11,8 +12,8 @@ pub fn main() {
}
struct Clock {
- now: LocalTime,
- clock: canvas::layer::Cache<LocalTime>,
+ now: chrono::DateTime<chrono::Local>,
+ clock: Cache,
}
#[derive(Debug, Clone, Copy)]
@@ -23,12 +24,13 @@ enum Message {
impl Application for Clock {
type Executor = executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Self, Command<Message>) {
+ fn new(_flags: ()) -> (Self, Command<Message>) {
(
Clock {
- now: chrono::Local::now().into(),
- clock: canvas::layer::Cache::new(),
+ now: chrono::Local::now(),
+ clock: Default::default(),
},
Command::none(),
)
@@ -41,7 +43,7 @@ impl Application for Clock {
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::Tick(local_time) => {
- let now = local_time.into();
+ let now = local_time;
if now != self.now {
self.now = now;
@@ -54,140 +56,78 @@ impl Application for Clock {
}
fn subscription(&self) -> Subscription<Message> {
- time::every(std::time::Duration::from_millis(500)).map(Message::Tick)
+ time::every(std::time::Duration::from_millis(500))
+ .map(|_| Message::Tick(chrono::Local::now()))
}
fn view(&mut self) -> Element<Message> {
- let canvas = Canvas::new()
+ let canvas = Canvas::new(self)
.width(Length::Units(400))
- .height(Length::Units(400))
- .push(self.clock.with(&self.now));
+ .height(Length::Units(400));
Container::new(canvas)
.width(Length::Fill)
.height(Length::Fill)
+ .padding(20)
.center_x()
.center_y()
.into()
}
}
-#[derive(Debug, PartialEq, Eq)]
-struct LocalTime {
- hour: u32,
- minute: u32,
- second: u32,
-}
-
-impl From<chrono::DateTime<chrono::Local>> for LocalTime {
- fn from(date_time: chrono::DateTime<chrono::Local>) -> LocalTime {
+impl canvas::Program<Message> for Clock {
+ fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
use chrono::Timelike;
- LocalTime {
- hour: date_time.hour(),
- minute: date_time.minute(),
- second: date_time.second(),
- }
- }
-}
+ let clock = self.clock.draw(bounds.size(), |frame| {
+ let center = frame.center();
+ let radius = frame.width().min(frame.height()) / 2.0;
-impl canvas::Drawable for LocalTime {
- fn draw(&self, frame: &mut canvas::Frame) {
- let center = frame.center();
- let radius = frame.width().min(frame.height()) / 2.0;
- let offset = Vector::new(center.x, center.y);
-
- let clock = canvas::Path::new(|path| path.circle(center, radius));
-
- frame.fill(
- &clock,
- canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)),
- );
-
- fn draw_hand(
- n: u32,
- total: u32,
- length: f32,
- offset: Vector,
- path: &mut canvas::path::Builder,
- ) {
- let turns = n as f32 / total as f32;
- let t = 2.0 * std::f32::consts::PI * (turns - 0.25);
-
- let x = length * t.cos();
- let y = length * t.sin();
-
- path.line_to(Point::new(x, y) + offset);
- }
+ let background = Path::circle(center, radius);
+ frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8));
- let hour_and_minute_hands = canvas::Path::new(|path| {
- path.move_to(center);
- draw_hand(self.hour, 12, 0.5 * radius, offset, path);
+ let short_hand =
+ Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius));
- path.move_to(center);
- draw_hand(self.minute, 60, 0.8 * radius, offset, path)
- });
+ let long_hand =
+ Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));
- frame.stroke(
- &hour_and_minute_hands,
- canvas::Stroke {
- width: 6.0,
+ let thin_stroke = Stroke {
+ width: radius / 100.0,
color: Color::WHITE,
- line_cap: canvas::LineCap::Round,
- ..canvas::Stroke::default()
- },
- );
-
- let second_hand = canvas::Path::new(|path| {
- path.move_to(center);
- draw_hand(self.second, 60, 0.8 * radius, offset, path)
+ line_cap: LineCap::Round,
+ ..Stroke::default()
+ };
+
+ let wide_stroke = Stroke {
+ width: thin_stroke.width * 3.0,
+ ..thin_stroke
+ };
+
+ frame.translate(Vector::new(center.x, center.y));
+
+ frame.with_save(|frame| {
+ frame.rotate(hand_rotation(self.now.hour(), 12));
+ frame.stroke(&short_hand, wide_stroke);
+ });
+
+ frame.with_save(|frame| {
+ frame.rotate(hand_rotation(self.now.minute(), 60));
+ frame.stroke(&long_hand, wide_stroke);
+ });
+
+ frame.with_save(|frame| {
+ frame.rotate(hand_rotation(self.now.second(), 60));
+ frame.stroke(&long_hand, thin_stroke);
+ })
});
- frame.stroke(
- &second_hand,
- canvas::Stroke {
- width: 3.0,
- color: Color::WHITE,
- line_cap: canvas::LineCap::Round,
- ..canvas::Stroke::default()
- },
- );
+ vec![clock]
}
}
-mod time {
- use iced::futures;
+fn hand_rotation(n: u32, total: u32) -> f32 {
+ let turns = n as f32 / total as f32;
- pub fn every(
- duration: std::time::Duration,
- ) -> iced::Subscription<chrono::DateTime<chrono::Local>> {
- iced::Subscription::from_recipe(Every(duration))
- }
-
- struct Every(std::time::Duration);
-
- impl<H, I> iced_native::subscription::Recipe<H, I> for Every
- where
- H: std::hash::Hasher,
- {
- type Output = chrono::DateTime<chrono::Local>;
-
- fn hash(&self, state: &mut H) {
- use std::hash::Hash;
-
- std::any::TypeId::of::<Self>().hash(state);
- self.0.hash(state);
- }
-
- fn stream(
- self: Box<Self>,
- _input: futures::stream::BoxStream<'static, I>,
- ) -> futures::stream::BoxStream<'static, Self::Output> {
- use futures::stream::StreamExt;
-
- async_std::stream::interval(self.0)
- .map(|_| chrono::Local::now())
- .boxed()
- }
- }
+ 2.0 * std::f32::consts::PI * turns
}
diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml
new file mode 100644
index 00000000..00f33e20
--- /dev/null
+++ b/examples/color_palette/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "color_palette"
+version = "0.1.0"
+authors = ["Clark Moody <clark@clarkmoody.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "palette"] }
+palette = "0.5.0"
diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md
new file mode 100644
index 00000000..e70188f8
--- /dev/null
+++ b/examples/color_palette/README.md
@@ -0,0 +1,15 @@
+## Color palette
+
+A color palette generator, based on a user-defined root color.
+
+<div align="center">
+ <a href="https://gfycat.com/dirtylonebighornsheep">
+ <img src="https://github.com/hecrj/iced/raw/1a8d253611d3796b0a32b2f096bb54565a5292e0/examples/color_palette/screenshot.png">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+
+```
+cargo run --package color_palette
+```
diff --git a/examples/color_palette/screenshot.png b/examples/color_palette/screenshot.png
new file mode 100644
index 00000000..aa4772e0
--- /dev/null
+++ b/examples/color_palette/screenshot.png
Binary files differ
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
new file mode 100644
index 00000000..bb2c61cb
--- /dev/null
+++ b/examples/color_palette/src/main.rs
@@ -0,0 +1,462 @@
+use iced::canvas::{self, Cursor, Frame, Geometry, Path};
+use iced::{
+ slider, Align, Canvas, Color, Column, Element, HorizontalAlignment, Length,
+ Point, Rectangle, Row, Sandbox, Settings, Size, Slider, Text, Vector,
+ VerticalAlignment,
+};
+use palette::{self, Hsl, Limited, Srgb};
+use std::marker::PhantomData;
+use std::ops::RangeInclusive;
+
+pub fn main() -> iced::Result {
+ ColorPalette::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+#[derive(Default)]
+pub struct ColorPalette {
+ theme: Theme,
+ rgb: ColorPicker<Color>,
+ hsl: ColorPicker<palette::Hsl>,
+ hsv: ColorPicker<palette::Hsv>,
+ hwb: ColorPicker<palette::Hwb>,
+ lab: ColorPicker<palette::Lab>,
+ lch: ColorPicker<palette::Lch>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Message {
+ RgbColorChanged(Color),
+ HslColorChanged(palette::Hsl),
+ HsvColorChanged(palette::Hsv),
+ HwbColorChanged(palette::Hwb),
+ LabColorChanged(palette::Lab),
+ LchColorChanged(palette::Lch),
+}
+
+impl Sandbox for ColorPalette {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Color palette - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ let srgb = match message {
+ Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
+ Message::HslColorChanged(hsl) => palette::Srgb::from(hsl),
+ Message::HsvColorChanged(hsv) => palette::Srgb::from(hsv),
+ Message::HwbColorChanged(hwb) => palette::Srgb::from(hwb),
+ Message::LabColorChanged(lab) => palette::Srgb::from(lab),
+ Message::LchColorChanged(lch) => palette::Srgb::from(lch),
+ };
+
+ self.theme = Theme::new(srgb.clamp());
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let base = self.theme.base;
+
+ let srgb = palette::Srgb::from(base);
+ let hsl = palette::Hsl::from(srgb);
+ let hsv = palette::Hsv::from(srgb);
+ let hwb = palette::Hwb::from(srgb);
+ let lab = palette::Lab::from(srgb);
+ let lch = palette::Lch::from(srgb);
+
+ Column::new()
+ .padding(10)
+ .spacing(10)
+ .push(self.rgb.view(base).map(Message::RgbColorChanged))
+ .push(self.hsl.view(hsl).map(Message::HslColorChanged))
+ .push(self.hsv.view(hsv).map(Message::HsvColorChanged))
+ .push(self.hwb.view(hwb).map(Message::HwbColorChanged))
+ .push(self.lab.view(lab).map(Message::LabColorChanged))
+ .push(self.lch.view(lch).map(Message::LchColorChanged))
+ .push(self.theme.view())
+ .into()
+ }
+}
+
+#[derive(Debug)]
+pub struct Theme {
+ lower: Vec<Color>,
+ base: Color,
+ higher: Vec<Color>,
+ canvas_cache: canvas::Cache,
+}
+
+impl Theme {
+ pub fn new(base: impl Into<Color>) -> Theme {
+ use palette::{Hue, Shade};
+
+ let base = base.into();
+
+ // Convert to HSL color for manipulation
+ let hsl = Hsl::from(Srgb::from(base));
+
+ let lower = [
+ hsl.shift_hue(-135.0).lighten(0.075),
+ hsl.shift_hue(-120.0),
+ hsl.shift_hue(-105.0).darken(0.075),
+ hsl.darken(0.075),
+ ];
+
+ let higher = [
+ hsl.lighten(0.075),
+ hsl.shift_hue(105.0).darken(0.075),
+ hsl.shift_hue(120.0),
+ hsl.shift_hue(135.0).lighten(0.075),
+ ];
+
+ Theme {
+ lower: lower
+ .iter()
+ .map(|&color| Srgb::from(color).clamp().into())
+ .collect(),
+ base,
+ higher: higher
+ .iter()
+ .map(|&color| Srgb::from(color).clamp().into())
+ .collect(),
+ canvas_cache: canvas::Cache::default(),
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.lower.len() + self.higher.len() + 1
+ }
+
+ pub fn colors(&self) -> impl Iterator<Item = &Color> {
+ self.lower
+ .iter()
+ .chain(std::iter::once(&self.base))
+ .chain(self.higher.iter())
+ }
+
+ pub fn view(&mut self) -> Element<Message> {
+ Canvas::new(self)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+
+ fn draw(&self, frame: &mut Frame) {
+ let pad = 20.0;
+
+ let box_size = Size {
+ width: frame.width() / self.len() as f32,
+ height: frame.height() / 2.0 - pad,
+ };
+
+ let triangle = Path::new(|path| {
+ path.move_to(Point { x: 0.0, y: -0.5 });
+ path.line_to(Point { x: -0.5, y: 0.0 });
+ path.line_to(Point { x: 0.5, y: 0.0 });
+ path.close();
+ });
+
+ let mut text = canvas::Text {
+ horizontal_alignment: HorizontalAlignment::Center,
+ vertical_alignment: VerticalAlignment::Top,
+ size: 15.0,
+ ..canvas::Text::default()
+ };
+
+ for (i, &color) in self.colors().enumerate() {
+ let anchor = Point {
+ x: (i as f32) * box_size.width,
+ y: 0.0,
+ };
+ frame.fill_rectangle(anchor, box_size, color);
+
+ // We show a little indicator for the base color
+ if color == self.base {
+ let triangle_x = anchor.x + box_size.width / 2.0;
+
+ frame.with_save(|frame| {
+ frame.translate(Vector::new(triangle_x, 0.0));
+ frame.scale(10.0);
+ frame.rotate(std::f32::consts::PI);
+
+ frame.fill(&triangle, Color::WHITE);
+ });
+
+ frame.with_save(|frame| {
+ frame.translate(Vector::new(triangle_x, box_size.height));
+ frame.scale(10.0);
+
+ frame.fill(&triangle, Color::WHITE);
+ });
+ }
+
+ frame.fill_text(canvas::Text {
+ content: color_hex_string(&color),
+ position: Point {
+ x: anchor.x + box_size.width / 2.0,
+ y: box_size.height,
+ },
+ ..text
+ });
+ }
+
+ text.vertical_alignment = VerticalAlignment::Bottom;
+
+ let hsl = Hsl::from(Srgb::from(self.base));
+ for i in 0..self.len() {
+ let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
+ let graded = Hsl {
+ lightness: 1.0 - pct,
+ ..hsl
+ };
+ let color: Color = Srgb::from(graded.clamp()).into();
+
+ let anchor = Point {
+ x: (i as f32) * box_size.width,
+ y: box_size.height + 2.0 * pad,
+ };
+
+ frame.fill_rectangle(anchor, box_size, color);
+
+ frame.fill_text(canvas::Text {
+ content: color_hex_string(&color),
+ position: Point {
+ x: anchor.x + box_size.width / 2.0,
+ y: box_size.height + 2.0 * pad,
+ },
+ ..text
+ });
+ }
+ }
+}
+
+impl canvas::Program<Message> for Theme {
+ fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {
+ let theme = self.canvas_cache.draw(bounds.size(), |frame| {
+ self.draw(frame);
+ });
+
+ vec![theme]
+ }
+}
+
+impl Default for Theme {
+ fn default() -> Self {
+ Theme::new(Color::from_rgb8(75, 128, 190))
+ }
+}
+
+fn color_hex_string(color: &Color) -> String {
+ format!(
+ "#{:x}{:x}{:x}",
+ (255.0 * color.r).round() as u8,
+ (255.0 * color.g).round() as u8,
+ (255.0 * color.b).round() as u8
+ )
+}
+
+#[derive(Default)]
+struct ColorPicker<C: ColorSpace> {
+ sliders: [slider::State; 3],
+ color_space: PhantomData<C>,
+}
+
+trait ColorSpace: Sized {
+ const LABEL: &'static str;
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3];
+
+ fn new(a: f32, b: f32, c: f32) -> Self;
+
+ fn components(&self) -> [f32; 3];
+
+ fn to_string(&self) -> String;
+}
+
+impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
+ fn view(&mut self, color: C) -> Element<C> {
+ let [c1, c2, c3] = color.components();
+ let [s1, s2, s3] = &mut self.sliders;
+ let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
+
+ fn slider<C: Clone>(
+ state: &mut slider::State,
+ range: RangeInclusive<f64>,
+ component: f32,
+ update: impl Fn(f32) -> C + 'static,
+ ) -> Slider<f64, C> {
+ Slider::new(state, range, f64::from(component), move |v| {
+ update(v as f32)
+ })
+ .step(0.01)
+ }
+
+ Row::new()
+ .spacing(10)
+ .align_items(Align::Center)
+ .push(Text::new(C::LABEL).width(Length::Units(50)))
+ .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3)))
+ .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3)))
+ .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v)))
+ .push(
+ Text::new(color.to_string())
+ .width(Length::Units(185))
+ .size(14),
+ )
+ .into()
+ }
+}
+
+impl ColorSpace for Color {
+ const LABEL: &'static str = "RGB";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=1.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(r: f32, g: f32, b: f32) -> Self {
+ Color::from_rgb(r, g, b)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.r, self.g, self.b]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "rgb({:.0}, {:.0}, {:.0})",
+ 255.0 * self.r,
+ 255.0 * self.g,
+ 255.0 * self.b
+ )
+ }
+}
+
+impl ColorSpace for palette::Hsl {
+ const LABEL: &'static str = "HSL";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
+ palette::Hsl::new(
+ palette::RgbHue::from_degrees(hue),
+ saturation,
+ lightness,
+ )
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [
+ self.hue.to_positive_degrees(),
+ self.saturation,
+ self.lightness,
+ ]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hsl({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.saturation,
+ 100.0 * self.lightness
+ )
+ }
+}
+
+impl ColorSpace for palette::Hsv {
+ const LABEL: &'static str = "HSV";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, saturation: f32, value: f32) -> Self {
+ palette::Hsv::new(palette::RgbHue::from_degrees(hue), saturation, value)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.hue.to_positive_degrees(), self.saturation, self.value]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hsv({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.saturation,
+ 100.0 * self.value
+ )
+ }
+}
+
+impl ColorSpace for palette::Hwb {
+ const LABEL: &'static str = "HWB";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, whiteness: f32, blackness: f32) -> Self {
+ palette::Hwb::new(
+ palette::RgbHue::from_degrees(hue),
+ whiteness,
+ blackness,
+ )
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [
+ self.hue.to_positive_degrees(),
+ self.whiteness,
+ self.blackness,
+ ]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hwb({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.whiteness,
+ 100.0 * self.blackness
+ )
+ }
+}
+
+impl ColorSpace for palette::Lab {
+ const LABEL: &'static str = "Lab";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=100.0, -128.0..=127.0, -128.0..=127.0];
+
+ fn new(l: f32, a: f32, b: f32) -> Self {
+ palette::Lab::new(l, a, b)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.l, self.a, self.b]
+ }
+
+ fn to_string(&self) -> String {
+ format!("Lab({:.1}, {:.1}, {:.1})", self.l, self.a, self.b)
+ }
+}
+
+impl ColorSpace for palette::Lch {
+ const LABEL: &'static str = "Lch";
+ const COMPONENT_RANGES: [RangeInclusive<f64>; 3] =
+ [0.0..=100.0, 0.0..=128.0, 0.0..=360.0];
+
+ fn new(l: f32, chroma: f32, hue: f32) -> Self {
+ palette::Lch::new(l, chroma, palette::LabHue::from_degrees(hue))
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.l, self.chroma, self.hue.to_positive_degrees()]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "Lch({:.1}, {:.1}, {:.1})",
+ self.l,
+ self.chroma,
+ self.hue.to_positive_degrees()
+ )
+ }
+}
diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs
index bde0ea94..e0b2ebd6 100644
--- a/examples/counter/src/main.rs
+++ b/examples/counter/src/main.rs
@@ -1,6 +1,6 @@
use iced::{button, Align, Button, Column, Element, Sandbox, Settings, Text};
-pub fn main() {
+pub fn main() -> iced::Result {
Counter::run(Settings::default())
}
diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml
index 30747dc0..3942538d 100644
--- a/examples/custom_widget/Cargo.toml
+++ b/examples/custom_widget/Cargo.toml
@@ -8,4 +8,4 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
-iced_wgpu = { path = "../../wgpu" }
+iced_graphics = { path = "../../graphics" }
diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs
index 0a570745..36f468c7 100644
--- a/examples/custom_widget/src/main.rs
+++ b/examples/custom_widget/src/main.rs
@@ -9,23 +9,26 @@ mod circle {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
+ use iced_graphics::{Backend, Defaults, Primitive, Renderer};
use iced_native::{
- layout, Background, Color, Element, Hasher, Layout, Length,
- MouseCursor, Point, Size, Widget,
+ layout, mouse, Background, Color, Element, Hasher, Layout, Length,
+ Point, Rectangle, Size, Widget,
};
- use iced_wgpu::{Defaults, Primitive, Renderer};
pub struct Circle {
- radius: u16,
+ radius: f32,
}
impl Circle {
- pub fn new(radius: u16) -> Self {
+ pub fn new(radius: f32) -> Self {
Self { radius }
}
}
- impl<Message> Widget<Message, Renderer> for Circle {
+ impl<Message, B> Widget<Message, Renderer<B>> for Circle
+ where
+ B: Backend,
+ {
fn width(&self) -> Length {
Length::Shrink
}
@@ -36,43 +39,44 @@ mod circle {
fn layout(
&self,
- _renderer: &Renderer,
+ _renderer: &Renderer<B>,
_limits: &layout::Limits,
) -> layout::Node {
- layout::Node::new(Size::new(
- f32::from(self.radius) * 2.0,
- f32::from(self.radius) * 2.0,
- ))
+ layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0))
}
fn hash_layout(&self, state: &mut Hasher) {
use std::hash::Hash;
- self.radius.hash(state);
+ self.radius.to_bits().hash(state);
}
fn draw(
&self,
- _renderer: &mut Renderer,
+ _renderer: &mut Renderer<B>,
_defaults: &Defaults,
layout: Layout<'_>,
_cursor_position: Point,
- ) -> (Primitive, MouseCursor) {
+ _viewport: &Rectangle,
+ ) -> (Primitive, mouse::Interaction) {
(
Primitive::Quad {
bounds: layout.bounds(),
background: Background::Color(Color::BLACK),
border_radius: self.radius,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
- MouseCursor::OutOfBounds,
+ mouse::Interaction::default(),
)
}
}
- impl<'a, Message> Into<Element<'a, Message, Renderer>> for Circle {
- fn into(self) -> Element<'a, Message, Renderer> {
+ impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle
+ where
+ B: Backend,
+ {
+ fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}
@@ -84,12 +88,12 @@ use iced::{
Slider, Text,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Example::run(Settings::default())
}
struct Example {
- radius: u16,
+ radius: f32,
slider: slider::State,
}
@@ -103,7 +107,7 @@ impl Sandbox for Example {
fn new() -> Self {
Example {
- radius: 50,
+ radius: 50.0,
slider: slider::State::new(),
}
}
@@ -115,7 +119,7 @@ impl Sandbox for Example {
fn update(&mut self, message: Message) {
match message {
Message::RadiusChanged(radius) => {
- self.radius = radius.round() as u16;
+ self.radius = radius;
}
}
}
@@ -127,13 +131,16 @@ impl Sandbox for Example {
.max_width(500)
.align_items(Align::Center)
.push(Circle::new(self.radius))
- .push(Text::new(format!("Radius: {}", self.radius.to_string())))
- .push(Slider::new(
- &mut self.slider,
- 1.0..=100.0,
- f32::from(self.radius),
- Message::RadiusChanged,
- ));
+ .push(Text::new(format!("Radius: {:.2}", self.radius)))
+ .push(
+ Slider::new(
+ &mut self.slider,
+ 1.0..=100.0,
+ self.radius,
+ Message::RadiusChanged,
+ )
+ .step(0.01),
+ );
Container::new(content)
.width(Length::Fill)
diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml
new file mode 100644
index 00000000..4b05e7dc
--- /dev/null
+++ b/examples/download_progress/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "download_progress"
+version = "0.1.0"
+authors = ["Songtronix <contact@songtronix.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["tokio_old"] }
+iced_native = { path = "../../native" }
+iced_futures = { path = "../../futures" }
+reqwest = "0.10"
diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md
new file mode 100644
index 00000000..c606c5f9
--- /dev/null
+++ b/examples/download_progress/README.md
@@ -0,0 +1,17 @@
+## Download progress
+
+A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
+
+The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress.
+
+<div align="center">
+ <a href="https://gfycat.com/wildearlyafricanwilddog">
+ <img src="https://thumbs.gfycat.com/WildEarlyAfricanwilddog-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+
+```
+cargo run --package download_progress
+```
diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs
new file mode 100644
index 00000000..f46a01f7
--- /dev/null
+++ b/examples/download_progress/src/download.rs
@@ -0,0 +1,112 @@
+use iced_futures::futures;
+
+// Just a little utility function
+pub fn file<T: ToString>(url: T) -> iced::Subscription<Progress> {
+ iced::Subscription::from_recipe(Download {
+ url: url.to_string(),
+ })
+}
+
+pub struct Download {
+ url: String,
+}
+
+// Make sure iced can use our download stream
+impl<H, I> iced_native::subscription::Recipe<H, I> for Download
+where
+ H: std::hash::Hasher,
+{
+ type Output = Progress;
+
+ fn hash(&self, state: &mut H) {
+ use std::hash::Hash;
+
+ std::any::TypeId::of::<Self>().hash(state);
+ self.url.hash(state);
+ }
+
+ fn stream(
+ self: Box<Self>,
+ _input: futures::stream::BoxStream<'static, I>,
+ ) -> futures::stream::BoxStream<'static, Self::Output> {
+ Box::pin(futures::stream::unfold(
+ State::Ready(self.url),
+ |state| async move {
+ match state {
+ State::Ready(url) => {
+ let response = reqwest::get(&url).await;
+
+ match response {
+ Ok(response) => {
+ if let Some(total) = response.content_length() {
+ Some((
+ Progress::Started,
+ State::Downloading {
+ response,
+ total,
+ downloaded: 0,
+ },
+ ))
+ } else {
+ Some((Progress::Errored, State::Finished))
+ }
+ }
+ Err(_) => {
+ Some((Progress::Errored, State::Finished))
+ }
+ }
+ }
+ State::Downloading {
+ mut response,
+ total,
+ downloaded,
+ } => match response.chunk().await {
+ Ok(Some(chunk)) => {
+ let downloaded = downloaded + chunk.len() as u64;
+
+ let percentage =
+ (downloaded as f32 / total as f32) * 100.0;
+
+ Some((
+ Progress::Advanced(percentage),
+ State::Downloading {
+ response,
+ total,
+ downloaded,
+ },
+ ))
+ }
+ Ok(None) => Some((Progress::Finished, State::Finished)),
+ Err(_) => Some((Progress::Errored, State::Finished)),
+ },
+ State::Finished => {
+ // We do not let the stream die, as it would start a
+ // new download repeatedly if the user is not careful
+ // in case of errors.
+ let _: () = iced::futures::future::pending().await;
+
+ None
+ }
+ }
+ },
+ ))
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum Progress {
+ Started,
+ Advanced(f32),
+ Finished,
+ Errored,
+}
+
+pub enum State {
+ Ready(String),
+ Downloading {
+ response: reqwest::Response,
+ total: u64,
+ downloaded: u64,
+ },
+ Finished,
+}
diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs
new file mode 100644
index 00000000..77b01354
--- /dev/null
+++ b/examples/download_progress/src/main.rs
@@ -0,0 +1,144 @@
+use iced::{
+ button, executor, Align, Application, Button, Column, Command, Container,
+ Element, Length, ProgressBar, Settings, Subscription, Text,
+};
+
+mod download;
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+#[derive(Debug)]
+enum Example {
+ Idle { button: button::State },
+ Downloading { progress: f32 },
+ Finished { button: button::State },
+ Errored { button: button::State },
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ Download,
+ DownloadProgressed(download::Progress),
+}
+
+impl Application for Example {
+ type Executor = executor::Default;
+ type Message = Message;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Example, Command<Message>) {
+ (
+ Example::Idle {
+ button: button::State::new(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Download progress - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Download => match self {
+ Example::Idle { .. }
+ | Example::Finished { .. }
+ | Example::Errored { .. } => {
+ *self = Example::Downloading { progress: 0.0 };
+ }
+ _ => {}
+ },
+ Message::DownloadProgressed(message) => match self {
+ Example::Downloading { progress } => match message {
+ download::Progress::Started => {
+ *progress = 0.0;
+ }
+ download::Progress::Advanced(percentage) => {
+ *progress = percentage;
+ }
+ download::Progress::Finished => {
+ *self = Example::Finished {
+ button: button::State::new(),
+ }
+ }
+ download::Progress::Errored => {
+ *self = Example::Errored {
+ button: button::State::new(),
+ };
+ }
+ },
+ _ => {}
+ },
+ };
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ match self {
+ Example::Downloading { .. } => {
+ download::file("https://speed.hetzner.de/100MB.bin")
+ .map(Message::DownloadProgressed)
+ }
+ _ => Subscription::none(),
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let current_progress = match self {
+ Example::Idle { .. } => 0.0,
+ Example::Downloading { progress } => *progress,
+ Example::Finished { .. } => 100.0,
+ Example::Errored { .. } => 0.0,
+ };
+
+ let progress_bar = ProgressBar::new(0.0..=100.0, current_progress);
+
+ let control: Element<_> = match self {
+ Example::Idle { button } => {
+ Button::new(button, Text::new("Start the download!"))
+ .on_press(Message::Download)
+ .into()
+ }
+ Example::Finished { button } => Column::new()
+ .spacing(10)
+ .align_items(Align::Center)
+ .push(Text::new("Download finished!"))
+ .push(
+ Button::new(button, Text::new("Start again"))
+ .on_press(Message::Download),
+ )
+ .into(),
+ Example::Downloading { .. } => {
+ Text::new(format!("Downloading... {:.2}%", current_progress))
+ .into()
+ }
+ Example::Errored { button } => Column::new()
+ .spacing(10)
+ .align_items(Align::Center)
+ .push(Text::new("Something went wrong :("))
+ .push(
+ Button::new(button, Text::new("Try again"))
+ .on_press(Message::Download),
+ )
+ .into(),
+ };
+
+ let content = Column::new()
+ .spacing(10)
+ .padding(10)
+ .align_items(Align::Center)
+ .push(progress_bar)
+ .push(control);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index 0c9dca05..6eba6aad 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -3,7 +3,7 @@ use iced::{
Element, Length, Settings, Subscription, Text,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Events::run(Settings::default())
}
@@ -22,8 +22,9 @@ enum Message {
impl Application for Events {
type Executor = executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Events, Command<Message>) {
+ fn new(_flags: ()) -> (Events, Command<Message>) {
(Events::default(), Command::none())
}
diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml
new file mode 100644
index 00000000..9c4172c4
--- /dev/null
+++ b/examples/game_of_life/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "game_of_life"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
+tokio = { version = "0.3", features = ["sync"] }
+itertools = "0.9"
+rustc-hash = "1.1"
diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md
new file mode 100644
index 00000000..aa39201c
--- /dev/null
+++ b/examples/game_of_life/README.md
@@ -0,0 +1,22 @@
+## Game of Life
+
+An interactive version of the [Game of Life], invented by [John Horton Conway].
+
+It runs a simulation in a background thread while allowing interaction with a `Canvas` that displays an infinite grid with zooming, panning, and drawing support.
+
+The __[`main`]__ file contains the relevant code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/WhichPaltryChick">
+ <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package game_of_life
+```
+
+[`main`]: src/main.rs
+[Game of Life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
+[John Horton Conway]: https://en.wikipedia.org/wiki/John_Horton_Conway
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
new file mode 100644
index 00000000..e18bd6e0
--- /dev/null
+++ b/examples/game_of_life/src/main.rs
@@ -0,0 +1,885 @@
+//! This example showcases an interactive version of the Game of Life, invented
+//! by John Conway. It leverages a `Canvas` together with other widgets.
+mod preset;
+mod style;
+
+use grid::Grid;
+use iced::button::{self, Button};
+use iced::executor;
+use iced::pick_list::{self, PickList};
+use iced::slider::{self, Slider};
+use iced::time;
+use iced::{
+ Align, Application, Checkbox, Column, Command, Container, Element, Length,
+ Row, Settings, Subscription, Text,
+};
+use preset::Preset;
+use std::time::{Duration, Instant};
+
+pub fn main() -> iced::Result {
+ GameOfLife::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+#[derive(Default)]
+struct GameOfLife {
+ grid: Grid,
+ controls: Controls,
+ is_playing: bool,
+ queued_ticks: usize,
+ speed: usize,
+ next_speed: Option<usize>,
+ version: usize,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ Grid(grid::Message, usize),
+ Tick(Instant),
+ TogglePlayback,
+ ToggleGrid(bool),
+ Next,
+ Clear,
+ SpeedChanged(f32),
+ PresetPicked(Preset),
+}
+
+impl Application for GameOfLife {
+ type Message = Message;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ (
+ Self {
+ speed: 5,
+ ..Self::default()
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Game of Life - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Grid(message, version) => {
+ if version == self.version {
+ self.grid.update(message);
+ }
+ }
+ Message::Tick(_) | Message::Next => {
+ self.queued_ticks = (self.queued_ticks + 1).min(self.speed);
+
+ if let Some(task) = self.grid.tick(self.queued_ticks) {
+ if let Some(speed) = self.next_speed.take() {
+ self.speed = speed;
+ }
+
+ self.queued_ticks = 0;
+
+ let version = self.version;
+
+ return Command::perform(task, move |message| {
+ Message::Grid(message, version)
+ });
+ }
+ }
+ Message::TogglePlayback => {
+ self.is_playing = !self.is_playing;
+ }
+ Message::ToggleGrid(show_grid_lines) => {
+ self.grid.toggle_lines(show_grid_lines);
+ }
+ Message::Clear => {
+ self.grid.clear();
+ self.version += 1;
+ }
+ Message::SpeedChanged(speed) => {
+ if self.is_playing {
+ self.next_speed = Some(speed.round() as usize);
+ } else {
+ self.speed = speed.round() as usize;
+ }
+ }
+ Message::PresetPicked(new_preset) => {
+ self.grid = Grid::from_preset(new_preset);
+ self.version += 1;
+ }
+ }
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ if self.is_playing {
+ time::every(Duration::from_millis(1000 / self.speed as u64))
+ .map(Message::Tick)
+ } else {
+ Subscription::none()
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let version = self.version;
+ let selected_speed = self.next_speed.unwrap_or(self.speed);
+ let controls = self.controls.view(
+ self.is_playing,
+ self.grid.are_lines_visible(),
+ selected_speed,
+ self.grid.preset(),
+ );
+
+ let content = Column::new()
+ .push(
+ self.grid
+ .view()
+ .map(move |message| Message::Grid(message, version)),
+ )
+ .push(controls);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .style(style::Container)
+ .into()
+ }
+}
+
+mod grid {
+ use crate::Preset;
+ use iced::{
+ canvas::event::{self, Event},
+ canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text},
+ mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle,
+ Size, Vector, VerticalAlignment,
+ };
+ use rustc_hash::{FxHashMap, FxHashSet};
+ use std::future::Future;
+ use std::ops::RangeInclusive;
+ use std::time::{Duration, Instant};
+
+ pub struct Grid {
+ state: State,
+ preset: Preset,
+ interaction: Interaction,
+ life_cache: Cache,
+ grid_cache: Cache,
+ translation: Vector,
+ scaling: f32,
+ show_lines: bool,
+ last_tick_duration: Duration,
+ last_queued_ticks: usize,
+ }
+
+ #[derive(Debug, Clone)]
+ pub enum Message {
+ Populate(Cell),
+ Unpopulate(Cell),
+ Ticked {
+ result: Result<Life, TickError>,
+ tick_duration: Duration,
+ },
+ }
+
+ #[derive(Debug, Clone)]
+ pub enum TickError {
+ JoinFailed,
+ }
+
+ impl Default for Grid {
+ fn default() -> Self {
+ Self::from_preset(Preset::default())
+ }
+ }
+
+ impl Grid {
+ const MIN_SCALING: f32 = 0.1;
+ const MAX_SCALING: f32 = 2.0;
+
+ pub fn from_preset(preset: Preset) -> Self {
+ Self {
+ state: State::with_life(
+ preset
+ .life()
+ .into_iter()
+ .map(|(i, j)| Cell { i, j })
+ .collect(),
+ ),
+ preset,
+ interaction: Interaction::None,
+ life_cache: Cache::default(),
+ grid_cache: Cache::default(),
+ translation: Vector::default(),
+ scaling: 1.0,
+ show_lines: true,
+ last_tick_duration: Duration::default(),
+ last_queued_ticks: 0,
+ }
+ }
+
+ pub fn tick(
+ &mut self,
+ amount: usize,
+ ) -> Option<impl Future<Output = Message>> {
+ let tick = self.state.tick(amount)?;
+
+ self.last_queued_ticks = amount;
+
+ Some(async move {
+ let start = Instant::now();
+ let result = tick.await;
+ let tick_duration = start.elapsed() / amount as u32;
+
+ Message::Ticked {
+ result,
+ tick_duration,
+ }
+ })
+ }
+
+ pub fn update(&mut self, message: Message) {
+ match message {
+ Message::Populate(cell) => {
+ self.state.populate(cell);
+ self.life_cache.clear();
+
+ self.preset = Preset::Custom;
+ }
+ Message::Unpopulate(cell) => {
+ self.state.unpopulate(&cell);
+ self.life_cache.clear();
+
+ self.preset = Preset::Custom;
+ }
+ Message::Ticked {
+ result: Ok(life),
+ tick_duration,
+ } => {
+ self.state.update(life);
+ self.life_cache.clear();
+
+ self.last_tick_duration = tick_duration;
+ }
+ Message::Ticked {
+ result: Err(error), ..
+ } => {
+ dbg!(error);
+ }
+ }
+ }
+
+ pub fn view<'a>(&'a mut self) -> Element<'a, Message> {
+ Canvas::new(self)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .into()
+ }
+
+ pub fn clear(&mut self) {
+ self.state = State::default();
+ self.preset = Preset::Custom;
+
+ self.life_cache.clear();
+ }
+
+ pub fn preset(&self) -> Preset {
+ self.preset
+ }
+
+ pub fn toggle_lines(&mut self, enabled: bool) {
+ self.show_lines = enabled;
+ }
+
+ pub fn are_lines_visible(&self) -> bool {
+ self.show_lines
+ }
+
+ fn visible_region(&self, size: Size) -> Region {
+ let width = size.width / self.scaling;
+ let height = size.height / self.scaling;
+
+ Region {
+ x: -self.translation.x - width / 2.0,
+ y: -self.translation.y - height / 2.0,
+ width,
+ height,
+ }
+ }
+
+ fn project(&self, position: Point, size: Size) -> Point {
+ let region = self.visible_region(size);
+
+ Point::new(
+ position.x / self.scaling + region.x,
+ position.y / self.scaling + region.y,
+ )
+ }
+ }
+
+ impl<'a> canvas::Program<Message> for Grid {
+ fn update(
+ &mut self,
+ event: Event,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> (event::Status, Option<Message>) {
+ if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event {
+ self.interaction = Interaction::None;
+ }
+
+ let cursor_position =
+ if let Some(position) = cursor.position_in(&bounds) {
+ position
+ } else {
+ return (event::Status::Ignored, None);
+ };
+
+ let cell = Cell::at(self.project(cursor_position, bounds.size()));
+ let is_populated = self.state.contains(&cell);
+
+ let (populate, unpopulate) = if is_populated {
+ (None, Some(Message::Unpopulate(cell)))
+ } else {
+ (Some(Message::Populate(cell)), None)
+ };
+
+ match event {
+ Event::Mouse(mouse_event) => match mouse_event {
+ mouse::Event::ButtonPressed(button) => {
+ let message = match button {
+ mouse::Button::Left => {
+ self.interaction = if is_populated {
+ Interaction::Erasing
+ } else {
+ Interaction::Drawing
+ };
+
+ populate.or(unpopulate)
+ }
+ mouse::Button::Right => {
+ self.interaction = Interaction::Panning {
+ translation: self.translation,
+ start: cursor_position,
+ };
+
+ None
+ }
+ _ => None,
+ };
+
+ (event::Status::Captured, message)
+ }
+ mouse::Event::CursorMoved { .. } => {
+ let message = match self.interaction {
+ Interaction::Drawing => populate,
+ Interaction::Erasing => unpopulate,
+ Interaction::Panning { translation, start } => {
+ self.translation = translation
+ + (cursor_position - start)
+ * (1.0 / self.scaling);
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+
+ None
+ }
+ _ => None,
+ };
+
+ let event_status = match self.interaction {
+ Interaction::None => event::Status::Ignored,
+ _ => event::Status::Captured,
+ };
+
+ (event_status, message)
+ }
+ mouse::Event::WheelScrolled { delta } => match delta {
+ mouse::ScrollDelta::Lines { y, .. }
+ | mouse::ScrollDelta::Pixels { y, .. } => {
+ if y < 0.0 && self.scaling > Self::MIN_SCALING
+ || y > 0.0 && self.scaling < Self::MAX_SCALING
+ {
+ let old_scaling = self.scaling;
+
+ self.scaling = (self.scaling
+ * (1.0 + y / 30.0))
+ .max(Self::MIN_SCALING)
+ .min(Self::MAX_SCALING);
+
+ if let Some(cursor_to_center) =
+ cursor.position_from(bounds.center())
+ {
+ let factor = self.scaling - old_scaling;
+
+ self.translation = self.translation
+ - Vector::new(
+ cursor_to_center.x * factor
+ / (old_scaling * old_scaling),
+ cursor_to_center.y * factor
+ / (old_scaling * old_scaling),
+ );
+ }
+
+ self.life_cache.clear();
+ self.grid_cache.clear();
+ }
+
+ (event::Status::Captured, None)
+ }
+ },
+ _ => (event::Status::Ignored, None),
+ },
+ _ => (event::Status::Ignored, None),
+ }
+ }
+
+ fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> {
+ let center = Vector::new(bounds.width / 2.0, bounds.height / 2.0);
+
+ let life = self.life_cache.draw(bounds.size(), |frame| {
+ let background = Path::rectangle(Point::ORIGIN, frame.size());
+ frame.fill(&background, Color::from_rgb8(0x40, 0x44, 0x4B));
+
+ frame.with_save(|frame| {
+ frame.translate(center);
+ frame.scale(self.scaling);
+ frame.translate(self.translation);
+ frame.scale(Cell::SIZE as f32);
+
+ let region = self.visible_region(frame.size());
+
+ for cell in region.cull(self.state.cells()) {
+ frame.fill_rectangle(
+ Point::new(cell.j as f32, cell.i as f32),
+ Size::UNIT,
+ Color::WHITE,
+ );
+ }
+ });
+ });
+
+ let overlay = {
+ let mut frame = Frame::new(bounds.size());
+
+ let hovered_cell =
+ cursor.position_in(&bounds).map(|position| {
+ Cell::at(self.project(position, frame.size()))
+ });
+
+ if let Some(cell) = hovered_cell {
+ frame.with_save(|frame| {
+ frame.translate(center);
+ frame.scale(self.scaling);
+ frame.translate(self.translation);
+ frame.scale(Cell::SIZE as f32);
+
+ frame.fill_rectangle(
+ Point::new(cell.j as f32, cell.i as f32),
+ Size::UNIT,
+ Color {
+ a: 0.5,
+ ..Color::BLACK
+ },
+ );
+ });
+ }
+
+ let text = Text {
+ color: Color::WHITE,
+ size: 14.0,
+ position: Point::new(frame.width(), frame.height()),
+ horizontal_alignment: HorizontalAlignment::Right,
+ vertical_alignment: VerticalAlignment::Bottom,
+ ..Text::default()
+ };
+
+ if let Some(cell) = hovered_cell {
+ frame.fill_text(Text {
+ content: format!("({}, {})", cell.j, cell.i),
+ position: text.position - Vector::new(0.0, 16.0),
+ ..text
+ });
+ }
+
+ let cell_count = self.state.cell_count();
+
+ frame.fill_text(Text {
+ content: format!(
+ "{} cell{} @ {:?} ({})",
+ cell_count,
+ if cell_count == 1 { "" } else { "s" },
+ self.last_tick_duration,
+ self.last_queued_ticks
+ ),
+ ..text
+ });
+
+ frame.into_geometry()
+ };
+
+ if self.scaling < 0.2 || !self.show_lines {
+ vec![life, overlay]
+ } else {
+ let grid = self.grid_cache.draw(bounds.size(), |frame| {
+ frame.translate(center);
+ frame.scale(self.scaling);
+ frame.translate(self.translation);
+ frame.scale(Cell::SIZE as f32);
+
+ let region = self.visible_region(frame.size());
+ let rows = region.rows();
+ let columns = region.columns();
+ let (total_rows, total_columns) =
+ (rows.clone().count(), columns.clone().count());
+ let width = 2.0 / Cell::SIZE as f32;
+ let color = Color::from_rgb8(70, 74, 83);
+
+ frame.translate(Vector::new(-width / 2.0, -width / 2.0));
+
+ for row in region.rows() {
+ frame.fill_rectangle(
+ Point::new(*columns.start() as f32, row as f32),
+ Size::new(total_columns as f32, width),
+ color,
+ );
+ }
+
+ for column in region.columns() {
+ frame.fill_rectangle(
+ Point::new(column as f32, *rows.start() as f32),
+ Size::new(width, total_rows as f32),
+ color,
+ );
+ }
+ });
+
+ vec![life, grid, overlay]
+ }
+ }
+
+ fn mouse_interaction(
+ &self,
+ bounds: Rectangle,
+ cursor: Cursor,
+ ) -> mouse::Interaction {
+ match self.interaction {
+ Interaction::Drawing => mouse::Interaction::Crosshair,
+ Interaction::Erasing => mouse::Interaction::Crosshair,
+ Interaction::Panning { .. } => mouse::Interaction::Grabbing,
+ Interaction::None if cursor.is_over(&bounds) => {
+ mouse::Interaction::Crosshair
+ }
+ _ => mouse::Interaction::default(),
+ }
+ }
+ }
+
+ #[derive(Default)]
+ struct State {
+ life: Life,
+ births: FxHashSet<Cell>,
+ is_ticking: bool,
+ }
+
+ impl State {
+ pub fn with_life(life: Life) -> Self {
+ Self {
+ life,
+ ..Self::default()
+ }
+ }
+
+ fn cell_count(&self) -> usize {
+ self.life.len() + self.births.len()
+ }
+
+ fn contains(&self, cell: &Cell) -> bool {
+ self.life.contains(cell) || self.births.contains(cell)
+ }
+
+ fn cells(&self) -> impl Iterator<Item = &Cell> {
+ self.life.iter().chain(self.births.iter())
+ }
+
+ fn populate(&mut self, cell: Cell) {
+ if self.is_ticking {
+ self.births.insert(cell);
+ } else {
+ self.life.populate(cell);
+ }
+ }
+
+ fn unpopulate(&mut self, cell: &Cell) {
+ if self.is_ticking {
+ let _ = self.births.remove(cell);
+ } else {
+ self.life.unpopulate(cell);
+ }
+ }
+
+ fn update(&mut self, mut life: Life) {
+ self.births.drain().for_each(|cell| life.populate(cell));
+
+ self.life = life;
+ self.is_ticking = false;
+ }
+
+ fn tick(
+ &mut self,
+ amount: usize,
+ ) -> Option<impl Future<Output = Result<Life, TickError>>> {
+ if self.is_ticking {
+ return None;
+ }
+
+ self.is_ticking = true;
+
+ let mut life = self.life.clone();
+
+ Some(async move {
+ tokio::task::spawn_blocking(move || {
+ for _ in 0..amount {
+ life.tick();
+ }
+
+ life
+ })
+ .await
+ .map_err(|_| TickError::JoinFailed)
+ })
+ }
+ }
+
+ #[derive(Clone, Default)]
+ pub struct Life {
+ cells: FxHashSet<Cell>,
+ }
+
+ impl Life {
+ fn len(&self) -> usize {
+ self.cells.len()
+ }
+
+ fn contains(&self, cell: &Cell) -> bool {
+ self.cells.contains(cell)
+ }
+
+ fn populate(&mut self, cell: Cell) {
+ self.cells.insert(cell);
+ }
+
+ fn unpopulate(&mut self, cell: &Cell) {
+ let _ = self.cells.remove(cell);
+ }
+
+ fn tick(&mut self) {
+ let mut adjacent_life = FxHashMap::default();
+
+ for cell in &self.cells {
+ let _ = adjacent_life.entry(*cell).or_insert(0);
+
+ for neighbor in Cell::neighbors(*cell) {
+ let amount = adjacent_life.entry(neighbor).or_insert(0);
+
+ *amount += 1;
+ }
+ }
+
+ for (cell, amount) in adjacent_life.iter() {
+ match amount {
+ 2 => {}
+ 3 => {
+ let _ = self.cells.insert(*cell);
+ }
+ _ => {
+ let _ = self.cells.remove(cell);
+ }
+ }
+ }
+ }
+
+ pub fn iter(&self) -> impl Iterator<Item = &Cell> {
+ self.cells.iter()
+ }
+ }
+
+ impl std::iter::FromIterator<Cell> for Life {
+ fn from_iter<I: IntoIterator<Item = Cell>>(iter: I) -> Self {
+ Life {
+ cells: iter.into_iter().collect(),
+ }
+ }
+ }
+
+ impl std::fmt::Debug for Life {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Life")
+ .field("cells", &self.cells.len())
+ .finish()
+ }
+ }
+
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+ pub struct Cell {
+ i: isize,
+ j: isize,
+ }
+
+ impl Cell {
+ const SIZE: usize = 20;
+
+ fn at(position: Point) -> Cell {
+ let i = (position.y / Cell::SIZE as f32).ceil() as isize;
+ let j = (position.x / Cell::SIZE as f32).ceil() as isize;
+
+ Cell {
+ i: i.saturating_sub(1),
+ j: j.saturating_sub(1),
+ }
+ }
+
+ fn cluster(cell: Cell) -> impl Iterator<Item = Cell> {
+ use itertools::Itertools;
+
+ let rows = cell.i.saturating_sub(1)..=cell.i.saturating_add(1);
+ let columns = cell.j.saturating_sub(1)..=cell.j.saturating_add(1);
+
+ rows.cartesian_product(columns).map(|(i, j)| Cell { i, j })
+ }
+
+ fn neighbors(cell: Cell) -> impl Iterator<Item = Cell> {
+ Cell::cluster(cell).filter(move |candidate| *candidate != cell)
+ }
+ }
+
+ pub struct Region {
+ x: f32,
+ y: f32,
+ width: f32,
+ height: f32,
+ }
+
+ impl Region {
+ fn rows(&self) -> RangeInclusive<isize> {
+ let first_row = (self.y / Cell::SIZE as f32).floor() as isize;
+
+ let visible_rows =
+ (self.height / Cell::SIZE as f32).ceil() as isize;
+
+ first_row..=first_row + visible_rows
+ }
+
+ fn columns(&self) -> RangeInclusive<isize> {
+ let first_column = (self.x / Cell::SIZE as f32).floor() as isize;
+
+ let visible_columns =
+ (self.width / Cell::SIZE as f32).ceil() as isize;
+
+ first_column..=first_column + visible_columns
+ }
+
+ fn cull<'a>(
+ &self,
+ cells: impl Iterator<Item = &'a Cell>,
+ ) -> impl Iterator<Item = &'a Cell> {
+ let rows = self.rows();
+ let columns = self.columns();
+
+ cells.filter(move |cell| {
+ rows.contains(&cell.i) && columns.contains(&cell.j)
+ })
+ }
+ }
+
+ enum Interaction {
+ None,
+ Drawing,
+ Erasing,
+ Panning { translation: Vector, start: Point },
+ }
+}
+
+#[derive(Default)]
+struct Controls {
+ toggle_button: button::State,
+ next_button: button::State,
+ clear_button: button::State,
+ speed_slider: slider::State,
+ preset_list: pick_list::State<Preset>,
+}
+
+impl Controls {
+ fn view<'a>(
+ &'a mut self,
+ is_playing: bool,
+ is_grid_enabled: bool,
+ speed: usize,
+ preset: Preset,
+ ) -> Element<'a, Message> {
+ let playback_controls = Row::new()
+ .spacing(10)
+ .push(
+ Button::new(
+ &mut self.toggle_button,
+ Text::new(if is_playing { "Pause" } else { "Play" }),
+ )
+ .on_press(Message::TogglePlayback)
+ .style(style::Button),
+ )
+ .push(
+ Button::new(&mut self.next_button, Text::new("Next"))
+ .on_press(Message::Next)
+ .style(style::Button),
+ );
+
+ let speed_controls = Row::new()
+ .width(Length::Fill)
+ .align_items(Align::Center)
+ .spacing(10)
+ .push(
+ Slider::new(
+ &mut self.speed_slider,
+ 1.0..=1000.0,
+ speed as f32,
+ Message::SpeedChanged,
+ )
+ .style(style::Slider),
+ )
+ .push(Text::new(format!("x{}", speed)).size(16));
+
+ Row::new()
+ .padding(10)
+ .spacing(20)
+ .align_items(Align::Center)
+ .push(playback_controls)
+ .push(speed_controls)
+ .push(
+ Checkbox::new(is_grid_enabled, "Grid", Message::ToggleGrid)
+ .size(16)
+ .spacing(5)
+ .text_size(16),
+ )
+ .push(
+ PickList::new(
+ &mut self.preset_list,
+ preset::ALL,
+ Some(preset),
+ Message::PresetPicked,
+ )
+ .padding(8)
+ .text_size(16)
+ .style(style::PickList),
+ )
+ .push(
+ Button::new(&mut self.clear_button, Text::new("Clear"))
+ .on_press(Message::Clear)
+ .style(style::Clear),
+ )
+ .into()
+ }
+}
diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs
new file mode 100644
index 00000000..05157b6a
--- /dev/null
+++ b/examples/game_of_life/src/preset.rs
@@ -0,0 +1,142 @@
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Preset {
+ Custom,
+ XKCD,
+ Glider,
+ SmallExploder,
+ Exploder,
+ TenCellRow,
+ LightweightSpaceship,
+ Tumbler,
+ GliderGun,
+ Acorn,
+}
+
+pub static ALL: &[Preset] = &[
+ Preset::Custom,
+ Preset::XKCD,
+ Preset::Glider,
+ Preset::SmallExploder,
+ Preset::Exploder,
+ Preset::TenCellRow,
+ Preset::LightweightSpaceship,
+ Preset::Tumbler,
+ Preset::GliderGun,
+ Preset::Acorn,
+];
+
+impl Preset {
+ pub fn life(self) -> Vec<(isize, isize)> {
+ #[rustfmt::skip]
+ let cells = match self {
+ Preset::Custom => vec![],
+ Preset::XKCD => vec![
+ " xxx ",
+ " x x ",
+ " x x ",
+ " x ",
+ "x xxx ",
+ " x x x ",
+ " x x",
+ " x x ",
+ " x x ",
+ ],
+ Preset::Glider => vec![
+ " x ",
+ " x",
+ "xxx"
+ ],
+ Preset::SmallExploder => vec![
+ " x ",
+ "xxx",
+ "x x",
+ " x ",
+ ],
+ Preset::Exploder => vec![
+ "x x x",
+ "x x",
+ "x x",
+ "x x",
+ "x x x",
+ ],
+ Preset::TenCellRow => vec![
+ "xxxxxxxxxx",
+ ],
+ Preset::LightweightSpaceship => vec![
+ " xxxxx",
+ "x x",
+ " x",
+ "x x ",
+ ],
+ Preset::Tumbler => vec![
+ " xx xx ",
+ " xx xx ",
+ " x x ",
+ "x x x x",
+ "x x x x",
+ "xx xx",
+ ],
+ Preset::GliderGun => vec![
+ " x ",
+ " x x ",
+ " xx xx xx",
+ " x x xx xx",
+ "xx x x xx ",
+ "xx x x xx x x ",
+ " x x x ",
+ " x x ",
+ " xx ",
+ ],
+ Preset::Acorn => vec![
+ " x ",
+ " x ",
+ "xx xxx",
+ ],
+ };
+
+ let start_row = -(cells.len() as isize / 2);
+
+ cells
+ .into_iter()
+ .enumerate()
+ .flat_map(|(i, cells)| {
+ let start_column = -(cells.len() as isize / 2);
+
+ cells
+ .chars()
+ .enumerate()
+ .filter(|(_, c)| !c.is_whitespace())
+ .map(move |(j, _)| {
+ (start_row + i as isize, start_column + j as isize)
+ })
+ })
+ .collect()
+ }
+}
+
+impl Default for Preset {
+ fn default() -> Preset {
+ Preset::XKCD
+ }
+}
+
+impl std::fmt::Display for Preset {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Preset::Custom => "Custom",
+ Preset::XKCD => "xkcd #2293",
+ Preset::Glider => "Glider",
+ Preset::SmallExploder => "Small Exploder",
+ Preset::Exploder => "Exploder",
+ Preset::TenCellRow => "10 Cell Row",
+ Preset::LightweightSpaceship => "Lightweight spaceship",
+ Preset::Tumbler => "Tumbler",
+ Preset::GliderGun => "Gosper Glider Gun",
+ Preset::Acorn => "Acorn",
+ }
+ )
+ }
+}
diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs
new file mode 100644
index 00000000..6605826f
--- /dev/null
+++ b/examples/game_of_life/src/style.rs
@@ -0,0 +1,188 @@
+use iced::{button, container, pick_list, slider, Background, Color};
+
+const ACTIVE: Color = Color::from_rgb(
+ 0x72 as f32 / 255.0,
+ 0x89 as f32 / 255.0,
+ 0xDA as f32 / 255.0,
+);
+
+const DESTRUCTIVE: Color = Color::from_rgb(
+ 0xC0 as f32 / 255.0,
+ 0x47 as f32 / 255.0,
+ 0x47 as f32 / 255.0,
+);
+
+const HOVERED: Color = Color::from_rgb(
+ 0x67 as f32 / 255.0,
+ 0x7B as f32 / 255.0,
+ 0xC4 as f32 / 255.0,
+);
+
+const BACKGROUND: Color = Color::from_rgb(
+ 0x2F as f32 / 255.0,
+ 0x31 as f32 / 255.0,
+ 0x36 as f32 / 255.0,
+);
+
+pub struct Container;
+
+impl container::StyleSheet for Container {
+ fn style(&self) -> container::Style {
+ container::Style {
+ background: Some(Background::Color(Color::from_rgb8(
+ 0x36, 0x39, 0x3F,
+ ))),
+ text_color: Some(Color::WHITE),
+ ..container::Style::default()
+ }
+ }
+}
+
+pub struct Button;
+
+impl button::StyleSheet for Button {
+ fn active(&self) -> button::Style {
+ button::Style {
+ background: Some(Background::Color(ACTIVE)),
+ border_radius: 3.0,
+ text_color: Color::WHITE,
+ ..button::Style::default()
+ }
+ }
+
+ fn hovered(&self) -> button::Style {
+ button::Style {
+ background: Some(Background::Color(HOVERED)),
+ text_color: Color::WHITE,
+ ..self.active()
+ }
+ }
+
+ fn pressed(&self) -> button::Style {
+ button::Style {
+ border_width: 1.0,
+ border_color: Color::WHITE,
+ ..self.hovered()
+ }
+ }
+}
+
+pub struct Clear;
+
+impl button::StyleSheet for Clear {
+ fn active(&self) -> button::Style {
+ button::Style {
+ background: Some(Background::Color(DESTRUCTIVE)),
+ border_radius: 3.0,
+ text_color: Color::WHITE,
+ ..button::Style::default()
+ }
+ }
+
+ fn hovered(&self) -> button::Style {
+ button::Style {
+ background: Some(Background::Color(Color {
+ a: 0.5,
+ ..DESTRUCTIVE
+ })),
+ text_color: Color::WHITE,
+ ..self.active()
+ }
+ }
+
+ fn pressed(&self) -> button::Style {
+ button::Style {
+ border_width: 1.0,
+ border_color: Color::WHITE,
+ ..self.hovered()
+ }
+ }
+}
+
+pub struct Slider;
+
+impl slider::StyleSheet for Slider {
+ fn active(&self) -> slider::Style {
+ slider::Style {
+ rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
+ handle: slider::Handle {
+ shape: slider::HandleShape::Circle { radius: 9.0 },
+ color: ACTIVE,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ }
+ }
+
+ fn hovered(&self) -> slider::Style {
+ let active = self.active();
+
+ slider::Style {
+ handle: slider::Handle {
+ color: HOVERED,
+ ..active.handle
+ },
+ ..active
+ }
+ }
+
+ fn dragging(&self) -> slider::Style {
+ let active = self.active();
+
+ slider::Style {
+ handle: slider::Handle {
+ color: Color::from_rgb(0.85, 0.85, 0.85),
+ ..active.handle
+ },
+ ..active
+ }
+ }
+}
+
+pub struct PickList;
+
+impl pick_list::StyleSheet for PickList {
+ fn menu(&self) -> pick_list::Menu {
+ pick_list::Menu {
+ text_color: Color::WHITE,
+ background: BACKGROUND.into(),
+ border_width: 1.0,
+ border_color: Color {
+ a: 0.7,
+ ..Color::BLACK
+ },
+ selected_background: Color {
+ a: 0.5,
+ ..Color::BLACK
+ }
+ .into(),
+ selected_text_color: Color::WHITE,
+ }
+ }
+
+ fn active(&self) -> pick_list::Style {
+ pick_list::Style {
+ text_color: Color::WHITE,
+ background: BACKGROUND.into(),
+ border_width: 1.0,
+ border_color: Color {
+ a: 0.6,
+ ..Color::BLACK
+ },
+ border_radius: 2.0,
+ icon_size: 0.5,
+ }
+ }
+
+ fn hovered(&self) -> pick_list::Style {
+ let active = self.active();
+
+ pick_list::Style {
+ border_color: Color {
+ a: 0.9,
+ ..Color::BLACK
+ },
+ ..active
+ }
+ }
+}
diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml
index 9df52454..34eec4fb 100644
--- a/examples/geometry/Cargo.toml
+++ b/examples/geometry/Cargo.toml
@@ -8,4 +8,4 @@ publish = false
[dependencies]
iced = { path = "../.." }
iced_native = { path = "../../native" }
-iced_wgpu = { path = "../../wgpu" }
+iced_graphics = { path = "../../graphics" }
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 13a687ab..f650b2c1 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -10,13 +10,13 @@ mod rainbow {
// Of course, you can choose to make the implementation renderer-agnostic,
// if you wish to, by creating your own `Renderer` trait, which could be
// implemented by `iced_wgpu` and other renderers.
- use iced_native::{
- layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size,
- Widget,
- };
- use iced_wgpu::{
+ use iced_graphics::{
triangle::{Mesh2D, Vertex2D},
- Defaults, Primitive, Renderer,
+ Backend, Defaults, Primitive, Renderer,
+ };
+ use iced_native::{
+ layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size,
+ Vector, Widget,
};
pub struct Rainbow;
@@ -27,7 +27,10 @@ mod rainbow {
}
}
- impl<Message> Widget<Message, Renderer> for Rainbow {
+ impl<Message, B> Widget<Message, Renderer<B>> for Rainbow
+ where
+ B: Backend,
+ {
fn width(&self) -> Length {
Length::Fill
}
@@ -38,7 +41,7 @@ mod rainbow {
fn layout(
&self,
- _renderer: &Renderer,
+ _renderer: &Renderer<B>,
limits: &layout::Limits,
) -> layout::Node {
let size = limits.width(Length::Fill).resolve(Size::ZERO);
@@ -50,11 +53,12 @@ mod rainbow {
fn draw(
&self,
- _renderer: &mut Renderer,
+ _renderer: &mut Renderer<B>,
_defaults: &Defaults,
layout: Layout<'_>,
cursor_position: Point,
- ) -> (Primitive, MouseCursor) {
+ _viewport: &Rectangle,
+ ) -> (Primitive, mouse::Interaction) {
let b = layout.bounds();
// R O Y G B I V
@@ -85,66 +89,72 @@ mod rainbow {
let posn_l = [0.0, b.height / 2.0];
(
- Primitive::Mesh2D {
- origin: Point::new(b.x, b.y),
- buffers: Mesh2D {
- vertices: vec![
- Vertex2D {
- position: posn_center,
- color: [1.0, 1.0, 1.0, 1.0],
- },
- Vertex2D {
- position: posn_tl,
- color: color_r,
- },
- Vertex2D {
- position: posn_t,
- color: color_o,
- },
- Vertex2D {
- position: posn_tr,
- color: color_y,
- },
- Vertex2D {
- position: posn_r,
- color: color_g,
- },
- Vertex2D {
- position: posn_br,
- color: color_gb,
- },
- Vertex2D {
- position: posn_b,
- color: color_b,
- },
- Vertex2D {
- position: posn_bl,
- color: color_i,
- },
- Vertex2D {
- position: posn_l,
- color: color_v,
- },
- ],
- indices: vec![
- 0, 1, 2, // TL
- 0, 2, 3, // T
- 0, 3, 4, // TR
- 0, 4, 5, // R
- 0, 5, 6, // BR
- 0, 6, 7, // B
- 0, 7, 8, // BL
- 0, 8, 1, // L
- ],
- },
+ Primitive::Translate {
+ translation: Vector::new(b.x, b.y),
+ content: Box::new(Primitive::Mesh2D {
+ size: b.size(),
+ buffers: Mesh2D {
+ vertices: vec![
+ Vertex2D {
+ position: posn_center,
+ color: [1.0, 1.0, 1.0, 1.0],
+ },
+ Vertex2D {
+ position: posn_tl,
+ color: color_r,
+ },
+ Vertex2D {
+ position: posn_t,
+ color: color_o,
+ },
+ Vertex2D {
+ position: posn_tr,
+ color: color_y,
+ },
+ Vertex2D {
+ position: posn_r,
+ color: color_g,
+ },
+ Vertex2D {
+ position: posn_br,
+ color: color_gb,
+ },
+ Vertex2D {
+ position: posn_b,
+ color: color_b,
+ },
+ Vertex2D {
+ position: posn_bl,
+ color: color_i,
+ },
+ Vertex2D {
+ position: posn_l,
+ color: color_v,
+ },
+ ],
+ indices: vec![
+ 0, 1, 2, // TL
+ 0, 2, 3, // T
+ 0, 3, 4, // TR
+ 0, 4, 5, // R
+ 0, 5, 6, // BR
+ 0, 6, 7, // B
+ 0, 7, 8, // BL
+ 0, 8, 1, // L
+ ],
+ },
+ }),
},
- MouseCursor::OutOfBounds,
+ mouse::Interaction::default(),
)
}
}
- impl<'a, Message> Into<Element<'a, Message, Renderer>> for Rainbow {
- fn into(self) -> Element<'a, Message, Renderer> {
+ impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Rainbow
+ where
+ B: Backend,
+ {
+ fn into(self) -> Element<'a, Message, Renderer<B>> {
Element::new(self)
}
}
@@ -156,7 +166,7 @@ use iced::{
};
use rainbow::Rainbow;
-pub fn main() {
+pub fn main() -> iced::Result {
Example::run(Settings::default())
}
diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml
index afc2c791..4515502f 100644
--- a/examples/integration/Cargo.toml
+++ b/examples/integration/Cargo.toml
@@ -8,4 +8,4 @@ publish = false
[dependencies]
iced_winit = { path = "../../winit" }
iced_wgpu = { path = "../../wgpu" }
-env_logger = "0.7"
+env_logger = "0.8"
diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs
index 0457a058..824f9f53 100644
--- a/examples/integration/src/controls.rs
+++ b/examples/integration/src/controls.rs
@@ -1,15 +1,15 @@
-use crate::Scene;
-
use iced_wgpu::Renderer;
use iced_winit::{
- slider, Align, Color, Column, Element, Length, Row, Slider, Text,
+ slider, Align, Color, Column, Command, Element, Length, Program, Row,
+ Slider, Text,
};
pub struct Controls {
+ background_color: Color,
sliders: [slider::State; 3],
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum Message {
BackgroundColorChanged(Color),
}
@@ -17,61 +17,64 @@ pub enum Message {
impl Controls {
pub fn new() -> Controls {
Controls {
+ background_color: Color::BLACK,
sliders: Default::default(),
}
}
- pub fn update(&self, message: Message, scene: &mut Scene) {
+ pub fn background_color(&self) -> Color {
+ self.background_color
+ }
+}
+
+impl Program for Controls {
+ type Renderer = Renderer;
+ type Message = Message;
+
+ fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::BackgroundColorChanged(color) => {
- scene.background_color = color;
+ self.background_color = color;
}
}
+
+ Command::none()
}
- pub fn view<'a>(
- &'a mut self,
- scene: &Scene,
- ) -> Element<'a, Message, Renderer> {
+ fn view(&mut self) -> Element<Message, Renderer> {
let [r, g, b] = &mut self.sliders;
- let background_color = scene.background_color;
+ let background_color = self.background_color;
let sliders = Row::new()
.width(Length::Units(500))
.spacing(20)
- .push(Slider::new(
- r,
- 0.0..=1.0,
- scene.background_color.r,
- move |r| {
+ .push(
+ Slider::new(r, 0.0..=1.0, background_color.r, move |r| {
Message::BackgroundColorChanged(Color {
r,
..background_color
})
- },
- ))
- .push(Slider::new(
- g,
- 0.0..=1.0,
- scene.background_color.g,
- move |g| {
+ })
+ .step(0.01),
+ )
+ .push(
+ Slider::new(g, 0.0..=1.0, background_color.g, move |g| {
Message::BackgroundColorChanged(Color {
g,
..background_color
})
- },
- ))
- .push(Slider::new(
- b,
- 0.0..=1.0,
- scene.background_color.b,
- move |b| {
+ })
+ .step(0.01),
+ )
+ .push(
+ Slider::new(b, 0.0..=1.0, background_color.b, move |b| {
Message::BackgroundColorChanged(Color {
b,
..background_color
})
- },
- ));
+ })
+ .step(0.01),
+ );
Row::new()
.width(Length::Fill)
diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs
index 2cb89ffc..9b52f3a5 100644
--- a/examples/integration/src/main.rs
+++ b/examples/integration/src/main.rs
@@ -4,12 +4,12 @@ mod scene;
use controls::Controls;
use scene::Scene;
-use iced_wgpu::{
- wgpu, window::SwapChain, Primitive, Renderer, Settings, Target,
-};
-use iced_winit::{winit, Cache, Clipboard, MouseCursor, Size, UserInterface};
+use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport};
+use iced_winit::{conversion, futures, program, winit, Debug, Size};
+use futures::task::SpawnExt;
use winit::{
+ dpi::PhysicalPosition,
event::{Event, ModifiersState, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
@@ -20,45 +20,79 @@ pub fn main() {
// Initialize winit
let event_loop = EventLoop::new();
let window = winit::window::Window::new(&event_loop).unwrap();
- let mut logical_size =
- window.inner_size().to_logical(window.scale_factor());
- let mut modifiers = ModifiersState::default();
- // Initialize WGPU
- let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::Default,
- backends: wgpu::BackendBit::PRIMARY,
- })
- .expect("Request adapter");
+ let physical_size = window.inner_size();
+ let mut viewport = Viewport::with_physical_size(
+ Size::new(physical_size.width, physical_size.height),
+ window.scale_factor(),
+ );
+ let mut cursor_position = PhysicalPosition::new(-1.0, -1.0);
+ let mut modifiers = ModifiersState::default();
- let (mut device, mut queue) =
- adapter.request_device(&wgpu::DeviceDescriptor {
- extensions: wgpu::Extensions {
- anisotropic_filtering: false,
- },
- limits: wgpu::Limits::default(),
- });
+ // Initialize wgpu
+ let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
+ let surface = unsafe { instance.create_surface(&window) };
+
+ let (mut device, queue) = futures::executor::block_on(async {
+ let adapter = instance
+ .request_adapter(&wgpu::RequestAdapterOptions {
+ power_preference: wgpu::PowerPreference::Default,
+ compatible_surface: Some(&surface),
+ })
+ .await
+ .expect("Request adapter");
+
+ adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ features: wgpu::Features::empty(),
+ limits: wgpu::Limits::default(),
+ shader_validation: false,
+ },
+ None,
+ )
+ .await
+ .expect("Request device")
+ });
- let surface = wgpu::Surface::create(&window);
let format = wgpu::TextureFormat::Bgra8UnormSrgb;
let mut swap_chain = {
let size = window.inner_size();
- SwapChain::new(&device, &surface, format, size.width, size.height)
+ device.create_swap_chain(
+ &surface,
+ &wgpu::SwapChainDescriptor {
+ usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+ format: format,
+ width: size.width,
+ height: size.height,
+ present_mode: wgpu::PresentMode::Mailbox,
+ },
+ )
};
let mut resized = false;
- // Initialize iced
- let mut events = Vec::new();
- let mut cache = Some(Cache::default());
- let mut renderer = Renderer::new(&mut device, Settings::default());
- let mut output = (Primitive::None, MouseCursor::OutOfBounds);
- let clipboard = Clipboard::new(&window);
+ // Initialize staging belt and local pool
+ let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024);
+ let mut local_pool = futures::executor::LocalPool::new();
// Initialize scene and GUI controls
- let mut scene = Scene::new(&device);
- let mut controls = Controls::new();
+ let scene = Scene::new(&mut device);
+ let controls = Controls::new();
+
+ // Initialize iced
+ let mut debug = Debug::new();
+ let mut renderer =
+ Renderer::new(Backend::new(&mut device, Settings::default()));
+
+ let mut state = program::State::new(
+ controls,
+ viewport.logical_size(),
+ conversion::cursor_position(cursor_position, viewport.scale_factor()),
+ &mut renderer,
+ &mut debug,
+ );
// Run event loop
event_loop.run(move |event, _, control_flow| {
@@ -68,135 +102,121 @@ pub fn main() {
match event {
Event::WindowEvent { event, .. } => {
match event {
+ WindowEvent::CursorMoved { position, .. } => {
+ cursor_position = position;
+ }
WindowEvent::ModifiersChanged(new_modifiers) => {
modifiers = new_modifiers;
}
WindowEvent::Resized(new_size) => {
- logical_size =
- new_size.to_logical(window.scale_factor());
+ viewport = Viewport::with_physical_size(
+ Size::new(new_size.width, new_size.height),
+ window.scale_factor(),
+ );
+
resized = true;
}
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
-
_ => {}
}
// Map window event to iced event
if let Some(event) = iced_winit::conversion::window_event(
- event,
+ &event,
window.scale_factor(),
modifiers,
) {
- events.push(event);
+ state.queue_event(event);
}
}
Event::MainEventsCleared => {
- // If no relevant events happened, we can simply skip this
- if events.is_empty() {
- return;
- }
-
- // We need to:
- // 1. Process events of our user interface.
- // 2. Update state as a result of any interaction.
- // 3. Generate a new output for our renderer.
-
- // First, we build our user interface.
- let mut user_interface = UserInterface::build(
- controls.view(&scene),
- Size::new(logical_size.width, logical_size.height),
- cache.take().unwrap(),
- &mut renderer,
- );
-
- // Then, we process the events, obtaining messages in return.
- let messages = user_interface.update(
- events.drain(..),
- clipboard.as_ref().map(|c| c as _),
- &renderer,
- );
-
- let user_interface = if messages.is_empty() {
- // If there are no messages, no interactions we care about have
- // happened. We can simply leave our user interface as it is.
- user_interface
- } else {
- // If there are messages, we need to update our state
- // accordingly and rebuild our user interface.
- // We can only do this if we drop our user interface first
- // by turning it into its cache.
- cache = Some(user_interface.into_cache());
-
- // In this example, `Controls` is the only part that cares
- // about messages, so updating our state is pretty
- // straightforward.
- for message in messages {
- controls.update(message, &mut scene);
- }
-
- // Once the state has been changed, we rebuild our updated
- // user interface.
- UserInterface::build(
- controls.view(&scene),
- Size::new(logical_size.width, logical_size.height),
- cache.take().unwrap(),
+ // If there are events pending
+ if !state.is_queue_empty() {
+ // We update iced
+ let _ = state.update(
+ viewport.logical_size(),
+ conversion::cursor_position(
+ cursor_position,
+ viewport.scale_factor(),
+ ),
+ None,
&mut renderer,
- )
- };
-
- // Finally, we just need to draw a new output for our renderer,
- output = user_interface.draw(&mut renderer);
-
- // update our cache,
- cache = Some(user_interface.into_cache());
+ &mut debug,
+ );
- // and request a redraw
- window.request_redraw();
+ // and request a redraw
+ window.request_redraw();
+ }
}
Event::RedrawRequested(_) => {
if resized {
let size = window.inner_size();
- swap_chain = SwapChain::new(
- &device,
+ swap_chain = device.create_swap_chain(
&surface,
- format,
- size.width,
- size.height,
+ &wgpu::SwapChainDescriptor {
+ usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT,
+ format: format,
+ width: size.width,
+ height: size.height,
+ present_mode: wgpu::PresentMode::Mailbox,
+ },
);
+
+ resized = false;
}
- let (frame, viewport) = swap_chain.next_frame();
+ let frame = swap_chain.get_current_frame().expect("Next frame");
let mut encoder = device.create_command_encoder(
- &wgpu::CommandEncoderDescriptor { todo: 0 },
+ &wgpu::CommandEncoderDescriptor { label: None },
);
- // We draw the scene first
- scene.draw(&mut encoder, &frame.view);
+ let program = state.program();
+
+ {
+ // We clear the frame
+ let mut render_pass = scene.clear(
+ &frame.output.view,
+ &mut encoder,
+ program.background_color(),
+ );
+
+ // Draw the scene
+ scene.draw(&mut render_pass);
+ }
// And then iced on top
- let mouse_cursor = renderer.draw(
+ let mouse_interaction = renderer.backend_mut().draw(
&mut device,
+ &mut staging_belt,
&mut encoder,
- Target {
- texture: &frame.view,
- viewport,
- },
- &output,
- window.scale_factor(),
- &["Some debug information!"],
+ &frame.output.view,
+ &viewport,
+ state.primitive(),
+ &debug.overlay(),
);
// Then we submit the work
- queue.submit(&[encoder.finish()]);
+ staging_belt.finish();
+ queue.submit(Some(encoder.finish()));
+
+ // Update the mouse cursor
+ window.set_cursor_icon(
+ iced_winit::conversion::mouse_interaction(
+ mouse_interaction,
+ ),
+ );
+
+ // And recall staging buffers
+ local_pool
+ .spawner()
+ .spawn(staging_belt.recall())
+ .expect("Recall staging buffers");
- // And update the mouse cursor
- window.set_cursor_icon(iced_winit::conversion::mouse_cursor(
- mouse_cursor,
- ));
+ local_pool.run_until_stalled();
}
_ => {}
}
diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs
index efb1921b..03a338c6 100644
--- a/examples/integration/src/scene.rs
+++ b/examples/integration/src/scene.rs
@@ -2,89 +2,68 @@ use iced_wgpu::wgpu;
use iced_winit::Color;
pub struct Scene {
- pub background_color: Color,
pipeline: wgpu::RenderPipeline,
- bind_group: wgpu::BindGroup,
}
impl Scene {
pub fn new(device: &wgpu::Device) -> Scene {
- let (pipeline, bind_group) = build_pipeline(device);
+ let pipeline = build_pipeline(device);
- Scene {
- background_color: Color::BLACK,
- pipeline,
- bind_group,
- }
+ Scene { pipeline }
}
- pub fn draw(
+ pub fn clear<'a>(
&self,
- encoder: &mut wgpu::CommandEncoder,
- target: &wgpu::TextureView,
- ) {
- let mut rpass =
- encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- color_attachments: &[
- wgpu::RenderPassColorAttachmentDescriptor {
- attachment: target,
- resolve_target: None,
- load_op: wgpu::LoadOp::Clear,
- store_op: wgpu::StoreOp::Store,
- clear_color: {
- let [r, g, b, a] =
- self.background_color.into_linear();
+ target: &'a wgpu::TextureView,
+ encoder: &'a mut wgpu::CommandEncoder,
+ background_color: Color,
+ ) -> wgpu::RenderPass<'a> {
+ encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
+ attachment: target,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear({
+ let [r, g, b, a] = background_color.into_linear();
- wgpu::Color {
- r: r as f64,
- g: g as f64,
- b: b as f64,
- a: a as f64,
- }
- },
- },
- ],
- depth_stencil_attachment: None,
- });
+ wgpu::Color {
+ r: r as f64,
+ g: g as f64,
+ b: b as f64,
+ a: a as f64,
+ }
+ }),
+ store: true,
+ },
+ }],
+ depth_stencil_attachment: None,
+ })
+ }
- rpass.set_pipeline(&self.pipeline);
- rpass.set_bind_group(0, &self.bind_group, &[]);
- rpass.draw(0..3, 0..1);
+ pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
+ render_pass.set_pipeline(&self.pipeline);
+ render_pass.draw(0..3, 0..1);
}
}
-fn build_pipeline(
- device: &wgpu::Device,
-) -> (wgpu::RenderPipeline, wgpu::BindGroup) {
- let vs = include_bytes!("shader/vert.spv");
- let fs = include_bytes!("shader/frag.spv");
+fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline {
+ let vs_module =
+ device.create_shader_module(wgpu::include_spirv!("shader/vert.spv"));
- let vs_module = device.create_shader_module(
- &wgpu::read_spirv(std::io::Cursor::new(&vs[..])).unwrap(),
- );
-
- let fs_module = device.create_shader_module(
- &wgpu::read_spirv(std::io::Cursor::new(&fs[..])).unwrap(),
- );
-
- let bind_group_layout =
- device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
- bindings: &[],
- });
-
- let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
- layout: &bind_group_layout,
- bindings: &[],
- });
+ let fs_module =
+ device.create_shader_module(wgpu::include_spirv!("shader/frag.spv"));
let pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
- bind_group_layouts: &[&bind_group_layout],
+ label: None,
+ push_constant_ranges: &[],
+ bind_group_layouts: &[],
});
let pipeline =
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
- layout: &pipeline_layout,
+ label: None,
+ layout: Some(&pipeline_layout),
vertex_stage: wgpu::ProgrammableStageDescriptor {
module: &vs_module,
entry_point: "main",
@@ -96,9 +75,7 @@ fn build_pipeline(
rasterization_state: Some(wgpu::RasterizationStateDescriptor {
front_face: wgpu::FrontFace::Ccw,
cull_mode: wgpu::CullMode::None,
- depth_bias: 0,
- depth_bias_slope_scale: 0.0,
- depth_bias_clamp: 0.0,
+ ..Default::default()
}),
primitive_topology: wgpu::PrimitiveTopology::TriangleList,
color_states: &[wgpu::ColorStateDescriptor {
@@ -108,12 +85,14 @@ fn build_pipeline(
write_mask: wgpu::ColorWrite::ALL,
}],
depth_stencil_state: None,
- index_format: wgpu::IndexFormat::Uint16,
- vertex_buffers: &[],
+ vertex_state: wgpu::VertexStateDescriptor {
+ index_format: wgpu::IndexFormat::Uint16,
+ vertex_buffers: &[],
+ },
sample_count: 1,
sample_mask: !0,
alpha_to_coverage_enabled: false,
});
- (pipeline, bind_group)
+ pipeline
}
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml
new file mode 100644
index 00000000..e489f210
--- /dev/null
+++ b/examples/pane_grid/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "pane_grid"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
+iced_native = { path = "../../native" }
diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md
new file mode 100644
index 00000000..a4cfcb7d
--- /dev/null
+++ b/examples/pane_grid/README.md
@@ -0,0 +1,28 @@
+## Pane grid
+
+A grid of panes that can be split, resized, and reorganized.
+
+This example showcases the `PaneGrid` widget, which features:
+
+* Vertical and horizontal splits
+* Tracking of the last active pane
+* Mouse-based resizing
+* Drag and drop to reorganize panes
+* Hotkey support
+* Configurable modifier keys
+* API to perform actions programmatically (`split`, `swap`, `resize`, etc.)
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/frailfreshairedaleterrier">
+ <img src="https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package pane_grid
+```
+
+[`main`]: src/main.rs
diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs
new file mode 100644
index 00000000..3c3256cf
--- /dev/null
+++ b/examples/pane_grid/src/main.rs
@@ -0,0 +1,372 @@
+use iced::{
+ button, executor, keyboard, pane_grid, scrollable, Align, Application,
+ Button, Column, Command, Container, Element, HorizontalAlignment, Length,
+ PaneGrid, Scrollable, Settings, Subscription, Text,
+};
+use iced_native::{event, subscription, Event};
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+struct Example {
+ panes: pane_grid::State<Content>,
+ panes_created: usize,
+ focus: Option<pane_grid::Pane>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ Split(pane_grid::Axis, pane_grid::Pane),
+ SplitFocused(pane_grid::Axis),
+ FocusAdjacent(pane_grid::Direction),
+ Clicked(pane_grid::Pane),
+ Dragged(pane_grid::DragEvent),
+ Resized(pane_grid::ResizeEvent),
+ Close(pane_grid::Pane),
+ CloseFocused,
+}
+
+impl Application for Example {
+ type Message = Message;
+ type Executor = executor::Default;
+ type Flags = ();
+
+ fn new(_flags: ()) -> (Self, Command<Message>) {
+ let (panes, _) = pane_grid::State::new(Content::new(0));
+
+ (
+ Example {
+ panes,
+ panes_created: 1,
+ focus: None,
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Pane grid - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Split(axis, pane) => {
+ let result = self.panes.split(
+ axis,
+ &pane,
+ Content::new(self.panes_created),
+ );
+
+ if let Some((pane, _)) = result {
+ self.focus = Some(pane);
+ }
+
+ self.panes_created += 1;
+ }
+ Message::SplitFocused(axis) => {
+ if let Some(pane) = self.focus {
+ let result = self.panes.split(
+ axis,
+ &pane,
+ Content::new(self.panes_created),
+ );
+
+ if let Some((pane, _)) = result {
+ self.focus = Some(pane);
+ }
+
+ self.panes_created += 1;
+ }
+ }
+ Message::FocusAdjacent(direction) => {
+ if let Some(pane) = self.focus {
+ if let Some(adjacent) =
+ self.panes.adjacent(&pane, direction)
+ {
+ self.focus = Some(adjacent);
+ }
+ }
+ }
+ Message::Clicked(pane) => {
+ self.focus = Some(pane);
+ }
+ Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
+ self.panes.resize(&split, ratio);
+ }
+ Message::Dragged(pane_grid::DragEvent::Dropped {
+ pane,
+ target,
+ }) => {
+ self.panes.swap(&pane, &target);
+ }
+ Message::Dragged(_) => {}
+ Message::Close(pane) => {
+ if let Some((_, sibling)) = self.panes.close(&pane) {
+ self.focus = Some(sibling);
+ }
+ }
+ Message::CloseFocused => {
+ if let Some(pane) = self.focus {
+ if let Some((_, sibling)) = self.panes.close(&pane) {
+ self.focus = Some(sibling);
+ }
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ subscription::events_with(|event, status| {
+ if let event::Status::Captured = status {
+ return None;
+ }
+
+ match event {
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ modifiers,
+ key_code,
+ }) if modifiers.is_command_pressed() => handle_hotkey(key_code),
+ _ => None,
+ }
+ })
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let focus = self.focus;
+ let total_panes = self.panes.len();
+
+ let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| {
+ let is_focused = focus == Some(pane);
+
+ let title_bar =
+ pane_grid::TitleBar::new(format!("Pane {}", content.id))
+ .padding(10)
+ .style(style::TitleBar { is_focused });
+
+ pane_grid::Content::new(content.view(pane, total_panes))
+ .title_bar(title_bar)
+ .style(style::Pane { is_focused })
+ })
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .spacing(10)
+ .on_click(Message::Clicked)
+ .on_drag(Message::Dragged)
+ .on_resize(10, Message::Resized);
+
+ Container::new(pane_grid)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(10)
+ .into()
+ }
+}
+
+fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> {
+ use keyboard::KeyCode;
+ use pane_grid::{Axis, Direction};
+
+ let direction = match key_code {
+ KeyCode::Up => Some(Direction::Up),
+ KeyCode::Down => Some(Direction::Down),
+ KeyCode::Left => Some(Direction::Left),
+ KeyCode::Right => Some(Direction::Right),
+ _ => None,
+ };
+
+ match key_code {
+ KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)),
+ KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)),
+ KeyCode::W => Some(Message::CloseFocused),
+ _ => direction.map(Message::FocusAdjacent),
+ }
+}
+
+struct Content {
+ id: usize,
+ scroll: scrollable::State,
+ split_horizontally: button::State,
+ split_vertically: button::State,
+ close: button::State,
+}
+
+impl Content {
+ fn new(id: usize) -> Self {
+ Content {
+ id,
+ scroll: scrollable::State::new(),
+ split_horizontally: button::State::new(),
+ split_vertically: button::State::new(),
+ close: button::State::new(),
+ }
+ }
+ fn view(
+ &mut self,
+ pane: pane_grid::Pane,
+ total_panes: usize,
+ ) -> Element<Message> {
+ let Content {
+ scroll,
+ split_horizontally,
+ split_vertically,
+ close,
+ ..
+ } = self;
+
+ let button = |state, label, message, style| {
+ Button::new(
+ state,
+ Text::new(label)
+ .width(Length::Fill)
+ .horizontal_alignment(HorizontalAlignment::Center)
+ .size(16),
+ )
+ .width(Length::Fill)
+ .padding(8)
+ .on_press(message)
+ .style(style)
+ };
+
+ let mut controls = Column::new()
+ .spacing(5)
+ .max_width(150)
+ .push(button(
+ split_horizontally,
+ "Split horizontally",
+ Message::Split(pane_grid::Axis::Horizontal, pane),
+ style::Button::Primary,
+ ))
+ .push(button(
+ split_vertically,
+ "Split vertically",
+ Message::Split(pane_grid::Axis::Vertical, pane),
+ style::Button::Primary,
+ ));
+
+ if total_panes > 1 {
+ controls = controls.push(button(
+ close,
+ "Close",
+ Message::Close(pane),
+ style::Button::Destructive,
+ ));
+ }
+
+ let content = Scrollable::new(scroll)
+ .width(Length::Fill)
+ .spacing(10)
+ .align_items(Align::Center)
+ .push(controls);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(5)
+ .center_y()
+ .into()
+ }
+}
+
+mod style {
+ use iced::{button, container, Background, Color, Vector};
+
+ const SURFACE: Color = Color::from_rgb(
+ 0xF2 as f32 / 255.0,
+ 0xF3 as f32 / 255.0,
+ 0xF5 as f32 / 255.0,
+ );
+
+ const ACTIVE: Color = Color::from_rgb(
+ 0x72 as f32 / 255.0,
+ 0x89 as f32 / 255.0,
+ 0xDA as f32 / 255.0,
+ );
+
+ const HOVERED: Color = Color::from_rgb(
+ 0x67 as f32 / 255.0,
+ 0x7B as f32 / 255.0,
+ 0xC4 as f32 / 255.0,
+ );
+
+ pub struct TitleBar {
+ pub is_focused: bool,
+ }
+
+ impl container::StyleSheet for TitleBar {
+ fn style(&self) -> container::Style {
+ let pane = Pane {
+ is_focused: self.is_focused,
+ }
+ .style();
+
+ container::Style {
+ text_color: Some(Color::WHITE),
+ background: Some(pane.border_color.into()),
+ ..Default::default()
+ }
+ }
+ }
+
+ pub struct Pane {
+ pub is_focused: bool,
+ }
+
+ impl container::StyleSheet for Pane {
+ fn style(&self) -> container::Style {
+ container::Style {
+ background: Some(Background::Color(SURFACE)),
+ border_width: 2.0,
+ border_color: if self.is_focused {
+ Color::BLACK
+ } else {
+ Color::from_rgb(0.7, 0.7, 0.7)
+ },
+ ..Default::default()
+ }
+ }
+ }
+
+ pub enum Button {
+ Primary,
+ Destructive,
+ }
+
+ impl button::StyleSheet for Button {
+ fn active(&self) -> button::Style {
+ let (background, text_color) = match self {
+ Button::Primary => (Some(ACTIVE), Color::WHITE),
+ Button::Destructive => {
+ (None, Color::from_rgb8(0xFF, 0x47, 0x47))
+ }
+ };
+
+ button::Style {
+ text_color,
+ background: background.map(Background::Color),
+ border_radius: 5.0,
+ shadow_offset: Vector::new(0.0, 0.0),
+ ..button::Style::default()
+ }
+ }
+
+ fn hovered(&self) -> button::Style {
+ let active = self.active();
+
+ let background = match self {
+ Button::Primary => Some(HOVERED),
+ Button::Destructive => Some(Color {
+ a: 0.2,
+ ..active.text_color
+ }),
+ };
+
+ button::Style {
+ background: background.map(Background::Color),
+ ..active
+ }
+ }
+ }
+}
diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml
new file mode 100644
index 00000000..a87d7217
--- /dev/null
+++ b/examples/pick_list/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "pick_list"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["debug"] }
diff --git a/examples/pick_list/README.md b/examples/pick_list/README.md
new file mode 100644
index 00000000..6dc80bf4
--- /dev/null
+++ b/examples/pick_list/README.md
@@ -0,0 +1,18 @@
+## Pick-list
+
+A dropdown list of selectable options.
+
+It displays and positions an overlay based on the window position of the widget.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <img src="https://user-images.githubusercontent.com/518289/87125075-2c232e80-c28a-11ea-95c2-769c610b8843.gif">
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package pick_list
+```
+
+[`main`]: src/main.rs
diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs
new file mode 100644
index 00000000..68662602
--- /dev/null
+++ b/examples/pick_list/src/main.rs
@@ -0,0 +1,113 @@
+use iced::{
+ pick_list, scrollable, Align, Container, Element, Length, PickList,
+ Sandbox, Scrollable, Settings, Space, Text,
+};
+
+pub fn main() -> iced::Result {
+ Example::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Example {
+ scroll: scrollable::State,
+ pick_list: pick_list::State<Language>,
+ selected_language: Language,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ LanguageSelected(Language),
+}
+
+impl Sandbox for Example {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Pick list - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::LanguageSelected(language) => {
+ self.selected_language = language;
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let pick_list = PickList::new(
+ &mut self.pick_list,
+ &Language::ALL[..],
+ Some(self.selected_language),
+ Message::LanguageSelected,
+ );
+
+ let mut content = Scrollable::new(&mut self.scroll)
+ .width(Length::Fill)
+ .align_items(Align::Center)
+ .spacing(10)
+ .push(Space::with_height(Length::Units(600)))
+ .push(Text::new("Which is your favorite language?"))
+ .push(pick_list);
+
+ content = content.push(Space::with_height(Length::Units(600)));
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Language {
+ Rust,
+ Elm,
+ Ruby,
+ Haskell,
+ C,
+ Javascript,
+ Other,
+}
+
+impl Language {
+ const ALL: [Language; 7] = [
+ Language::C,
+ Language::Elm,
+ Language::Ruby,
+ Language::Haskell,
+ Language::Rust,
+ Language::Javascript,
+ Language::Other,
+ ];
+}
+
+impl Default for Language {
+ fn default() -> Language {
+ Language::Rust
+ }
+}
+
+impl std::fmt::Display for Language {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Language::Rust => "Rust",
+ Language::Elm => "Elm",
+ Language::Ruby => "Ruby",
+ Language::Haskell => "Haskell",
+ Language::C => "C",
+ Language::Javascript => "Javascript",
+ Language::Other => "Some other language",
+ }
+ )
+ }
+}
diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml
index 94320086..05e73992 100644
--- a/examples/pokedex/Cargo.toml
+++ b/examples/pokedex/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["image", "debug", "tokio"] }
+iced = { path = "../..", features = ["image", "debug", "tokio_old"] }
serde_json = "1.0"
[dependencies.serde]
diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs
index 4449b901..187e5dee 100644
--- a/examples/pokedex/src/main.rs
+++ b/examples/pokedex/src/main.rs
@@ -3,7 +3,7 @@ use iced::{
Container, Element, Image, Length, Row, Settings, Text,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Pokedex::run(Settings::default())
}
@@ -29,8 +29,9 @@ enum Message {
impl Application for Pokedex {
type Executor = iced::executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Pokedex, Command<Message>) {
+ fn new(_flags: ()) -> (Pokedex, Command<Message>) {
(
Pokedex::Loading,
Command::perform(Pokemon::search(), Message::PokemonFound),
@@ -225,7 +226,7 @@ enum Error {
impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Error {
- dbg!(&error);
+ dbg!(error);
Error::APIError
}
@@ -250,7 +251,7 @@ mod style {
background: Some(Background::Color(match self {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs
index 43b09928..c9a8e798 100644
--- a/examples/progress_bar/src/main.rs
+++ b/examples/progress_bar/src/main.rs
@@ -1,6 +1,6 @@
use iced::{slider, Column, Element, ProgressBar, Sandbox, Settings, Slider};
-pub fn main() {
+pub fn main() -> iced::Result {
Progress::run(Settings::default())
}
@@ -36,12 +36,15 @@ impl Sandbox for Progress {
Column::new()
.padding(20)
.push(ProgressBar::new(0.0..=100.0, self.value))
- .push(Slider::new(
- &mut self.progress_bar_slider,
- 0.0..=100.0,
- self.value,
- Message::SliderChanged,
- ))
+ .push(
+ Slider::new(
+ &mut self.progress_bar_slider,
+ 0.0..=100.0,
+ self.value,
+ Message::SliderChanged,
+ )
+ .step(0.01),
+ )
.into()
}
}
diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml
new file mode 100644
index 00000000..7f2d4e42
--- /dev/null
+++ b/examples/qr_code/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "qr_code"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["qr_code"] }
diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md
new file mode 100644
index 00000000..2dd89c26
--- /dev/null
+++ b/examples/qr_code/README.md
@@ -0,0 +1,18 @@
+## QR Code Generator
+
+A basic QR code generator that showcases the `QRCode` widget.
+
+The __[`main`]__ file contains all the code of the example.
+
+<div align="center">
+ <a href="https://gfycat.com/heavyexhaustedaracari">
+ <img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package qr_code
+```
+
+[`main`]: src/main.rs
diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs
new file mode 100644
index 00000000..37b4855d
--- /dev/null
+++ b/examples/qr_code/src/main.rs
@@ -0,0 +1,81 @@
+use iced::qr_code::{self, QRCode};
+use iced::text_input::{self, TextInput};
+use iced::{
+ Align, Column, Container, Element, Length, Sandbox, Settings, Text,
+};
+
+pub fn main() -> iced::Result {
+ QRGenerator::run(Settings::default())
+}
+
+#[derive(Default)]
+struct QRGenerator {
+ data: String,
+ input: text_input::State,
+ qr_code: Option<qr_code::State>,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ DataChanged(String),
+}
+
+impl Sandbox for QRGenerator {
+ type Message = Message;
+
+ fn new() -> Self {
+ QRGenerator {
+ qr_code: qr_code::State::new("").ok(),
+ ..Self::default()
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("QR Code Generator - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::DataChanged(mut data) => {
+ data.truncate(100);
+
+ self.qr_code = qr_code::State::new(&data).ok();
+ self.data = data;
+ }
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let title = Text::new("QR Code Generator")
+ .size(70)
+ .color([0.5, 0.5, 0.5]);
+
+ let input = TextInput::new(
+ &mut self.input,
+ "Type the data of your QR code here...",
+ &self.data,
+ Message::DataChanged,
+ )
+ .size(30)
+ .padding(15);
+
+ let mut content = Column::new()
+ .width(Length::Units(700))
+ .spacing(20)
+ .align_items(Align::Center)
+ .push(title)
+ .push(input);
+
+ if let Some(qr_code) = self.qr_code.as_mut() {
+ content = content.push(QRCode::new(qr_code).cell_size(10));
+ }
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .padding(20)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}
diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml
new file mode 100644
index 00000000..12753fb6
--- /dev/null
+++ b/examples/scrollable/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "scrollable"
+version = "0.1.0"
+authors = ["Clark Moody <clark@clarkmoody.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
diff --git a/examples/scrollable/README.md b/examples/scrollable/README.md
new file mode 100644
index 00000000..ed0e31b5
--- /dev/null
+++ b/examples/scrollable/README.md
@@ -0,0 +1,15 @@
+# Scrollable
+An example showcasing the various size and style options for the Scrollable.
+
+All the example code is located in the __[`main`](src/main.rs)__ file.
+
+<div align="center">
+ <a href="./screenshot.png">
+ <img src="./screenshot.png" height="640px">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+```
+cargo run --package scrollable
+```
diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png
new file mode 100644
index 00000000..2d800251
--- /dev/null
+++ b/examples/scrollable/screenshot.png
Binary files differ
diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs
new file mode 100644
index 00000000..8dd2e20c
--- /dev/null
+++ b/examples/scrollable/src/main.rs
@@ -0,0 +1,184 @@
+mod style;
+
+use iced::{
+ scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox,
+ Scrollable, Settings, Space, Text,
+};
+
+pub fn main() -> iced::Result {
+ ScrollableDemo::run(Settings::default())
+}
+
+struct ScrollableDemo {
+ theme: style::Theme,
+ variants: Vec<Variant>,
+}
+
+#[derive(Debug, Clone)]
+enum Message {
+ ThemeChanged(style::Theme),
+}
+
+impl Sandbox for ScrollableDemo {
+ type Message = Message;
+
+ fn new() -> Self {
+ ScrollableDemo {
+ theme: Default::default(),
+ variants: Variant::all(),
+ }
+ }
+
+ fn title(&self) -> String {
+ String::from("Scrollable - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ match message {
+ Message::ThemeChanged(theme) => self.theme = theme,
+ }
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let ScrollableDemo {
+ theme, variants, ..
+ } = self;
+
+ let choose_theme = style::Theme::ALL.iter().fold(
+ Column::new().spacing(10).push(Text::new("Choose a theme:")),
+ |column, option| {
+ column.push(
+ Radio::new(
+ *option,
+ &format!("{:?}", option),
+ Some(*theme),
+ Message::ThemeChanged,
+ )
+ .style(*theme),
+ )
+ },
+ );
+
+ let scrollable_row = Row::with_children(
+ variants
+ .iter_mut()
+ .map(|variant| {
+ let mut scrollable = Scrollable::new(&mut variant.state)
+ .padding(10)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .style(*theme)
+ .push(Text::new(variant.title));
+
+ if let Some(scrollbar_width) = variant.scrollbar_width {
+ scrollable = scrollable
+ .scrollbar_width(scrollbar_width)
+ .push(Text::new(format!(
+ "scrollbar_width: {:?}",
+ scrollbar_width
+ )));
+ }
+
+ if let Some(scrollbar_margin) = variant.scrollbar_margin {
+ scrollable = scrollable
+ .scrollbar_margin(scrollbar_margin)
+ .push(Text::new(format!(
+ "scrollbar_margin: {:?}",
+ scrollbar_margin
+ )));
+ }
+
+ if let Some(scroller_width) = variant.scroller_width {
+ scrollable = scrollable
+ .scroller_width(scroller_width)
+ .push(Text::new(format!(
+ "scroller_width: {:?}",
+ scroller_width
+ )));
+ }
+
+ scrollable = scrollable
+ .push(Space::with_height(Length::Units(100)))
+ .push(Text::new(
+ "Some content that should wrap within the \
+ scrollable. Let's output a lot of short words, so \
+ that we'll make sure to see how wrapping works \
+ with these scrollbars.",
+ ))
+ .push(Space::with_height(Length::Units(1200)))
+ .push(Text::new("Middle"))
+ .push(Space::with_height(Length::Units(1200)))
+ .push(Text::new("The End."));
+
+ Container::new(scrollable)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .style(*theme)
+ .into()
+ })
+ .collect(),
+ )
+ .spacing(20)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ let content = Column::new()
+ .spacing(20)
+ .padding(20)
+ .push(choose_theme)
+ .push(Rule::horizontal(20).style(self.theme))
+ .push(scrollable_row);
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .style(self.theme)
+ .into()
+ }
+}
+
+/// A version of a scrollable
+struct Variant {
+ title: &'static str,
+ state: scrollable::State,
+ scrollbar_width: Option<u16>,
+ scrollbar_margin: Option<u16>,
+ scroller_width: Option<u16>,
+}
+
+impl Variant {
+ pub fn all() -> Vec<Self> {
+ vec![
+ Self {
+ title: "Default Scrollbar",
+ state: scrollable::State::new(),
+ scrollbar_width: None,
+ scrollbar_margin: None,
+ scroller_width: None,
+ },
+ Self {
+ title: "Slimmed & Margin",
+ state: scrollable::State::new(),
+ scrollbar_width: Some(4),
+ scrollbar_margin: Some(3),
+ scroller_width: Some(4),
+ },
+ Self {
+ title: "Wide Scroller",
+ state: scrollable::State::new(),
+ scrollbar_width: Some(4),
+ scrollbar_margin: None,
+ scroller_width: Some(10),
+ },
+ Self {
+ title: "Narrow Scroller",
+ state: scrollable::State::new(),
+ scrollbar_width: Some(10),
+ scrollbar_margin: None,
+ scroller_width: Some(4),
+ },
+ ]
+ }
+}
diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs
new file mode 100644
index 00000000..ae449141
--- /dev/null
+++ b/examples/scrollable/src/style.rs
@@ -0,0 +1,190 @@
+use iced::{container, radio, rule, scrollable};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Theme {
+ Light,
+ Dark,
+}
+
+impl Theme {
+ pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark];
+}
+
+impl Default for Theme {
+ fn default() -> Theme {
+ Theme::Light
+ }
+}
+
+impl From<Theme> for Box<dyn container::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Container.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn radio::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Radio.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn scrollable::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Scrollable.into(),
+ }
+ }
+}
+
+impl From<Theme> for Box<dyn rule::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Rule.into(),
+ }
+ }
+}
+
+mod dark {
+ use iced::{container, radio, rule, scrollable, Color};
+
+ const BACKGROUND: Color = Color::from_rgb(
+ 0x36 as f32 / 255.0,
+ 0x39 as f32 / 255.0,
+ 0x3F as f32 / 255.0,
+ );
+
+ const SURFACE: Color = Color::from_rgb(
+ 0x40 as f32 / 255.0,
+ 0x44 as f32 / 255.0,
+ 0x4B as f32 / 255.0,
+ );
+
+ const ACCENT: Color = Color::from_rgb(
+ 0x6F as f32 / 255.0,
+ 0xFF as f32 / 255.0,
+ 0xE9 as f32 / 255.0,
+ );
+
+ const ACTIVE: Color = Color::from_rgb(
+ 0x72 as f32 / 255.0,
+ 0x89 as f32 / 255.0,
+ 0xDA as f32 / 255.0,
+ );
+
+ const SCROLLBAR: Color = Color::from_rgb(
+ 0x2E as f32 / 255.0,
+ 0x33 as f32 / 255.0,
+ 0x38 as f32 / 255.0,
+ );
+
+ const SCROLLER: Color = Color::from_rgb(
+ 0x20 as f32 / 255.0,
+ 0x22 as f32 / 255.0,
+ 0x25 as f32 / 255.0,
+ );
+
+ pub struct Container;
+
+ impl container::StyleSheet for Container {
+ fn style(&self) -> container::Style {
+ container::Style {
+ background: Color {
+ a: 0.99,
+ ..BACKGROUND
+ }
+ .into(),
+ text_color: Color::WHITE.into(),
+ ..container::Style::default()
+ }
+ }
+ }
+
+ pub struct Radio;
+
+ impl radio::StyleSheet for Radio {
+ fn active(&self) -> radio::Style {
+ radio::Style {
+ background: SURFACE.into(),
+ dot_color: ACTIVE,
+ border_width: 1.0,
+ border_color: ACTIVE,
+ }
+ }
+
+ fn hovered(&self) -> radio::Style {
+ radio::Style {
+ background: Color { a: 0.5, ..SURFACE }.into(),
+ ..self.active()
+ }
+ }
+ }
+
+ pub struct Scrollable;
+
+ impl scrollable::StyleSheet for Scrollable {
+ fn active(&self) -> scrollable::Scrollbar {
+ scrollable::Scrollbar {
+ background: Color {
+ a: 0.8,
+ ..SCROLLBAR
+ }
+ .into(),
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ scroller: scrollable::Scroller {
+ color: Color { a: 0.7, ..SCROLLER },
+ border_radius: 2.0,
+ border_width: 0.0,
+ border_color: Color::TRANSPARENT,
+ },
+ }
+ }
+
+ fn hovered(&self) -> scrollable::Scrollbar {
+ let active = self.active();
+
+ scrollable::Scrollbar {
+ background: SCROLLBAR.into(),
+ scroller: scrollable::Scroller {
+ color: SCROLLER,
+ ..active.scroller
+ },
+ ..active
+ }
+ }
+
+ fn dragging(&self) -> scrollable::Scrollbar {
+ let hovered = self.hovered();
+
+ scrollable::Scrollbar {
+ scroller: scrollable::Scroller {
+ color: ACCENT,
+ ..hovered.scroller
+ },
+ ..hovered
+ }
+ }
+ }
+
+ pub struct Rule;
+
+ impl rule::StyleSheet for Rule {
+ fn style(&self) -> rule::Style {
+ rule::Style {
+ color: SURFACE,
+ width: 2,
+ radius: 1.0,
+ fill_mode: rule::FillMode::Percent(30.0),
+ }
+ }
+ }
+}
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
index c88cda50..44ced729 100644
--- a/examples/solar_system/Cargo.toml
+++ b/examples/solar_system/Cargo.toml
@@ -5,11 +5,6 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
edition = "2018"
publish = false
-[features]
-canvas = []
-
[dependencies]
-iced = { path = "../..", features = ["canvas", "async-std", "debug"] }
-iced_native = { path = "../../native" }
-async-std = { version = "1.0", features = ["unstable"] }
+iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
rand = "0.7"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
index 4c239806..6a2de736 100644
--- a/examples/solar_system/src/main.rs
+++ b/examples/solar_system/src/main.rs
@@ -7,13 +7,14 @@
//!
//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system
use iced::{
- canvas, executor, Application, Canvas, Color, Command, Container, Element,
- Length, Point, Settings, Size, Subscription, Vector,
+ canvas::{self, Cursor, Path, Stroke},
+ executor, time, window, Application, Canvas, Color, Command, Element,
+ Length, Point, Rectangle, Settings, Size, Subscription, Vector,
};
use std::time::Instant;
-pub fn main() {
+pub fn main() -> iced::Result {
SolarSystem::run(Settings {
antialiasing: true,
..Settings::default()
@@ -22,7 +23,6 @@ pub fn main() {
struct SolarSystem {
state: State,
- solar_system: canvas::layer::Cache<State>,
}
#[derive(Debug, Clone, Copy)]
@@ -33,12 +33,12 @@ enum Message {
impl Application for SolarSystem {
type Executor = executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Self, Command<Message>) {
+ fn new(_flags: ()) -> (Self, Command<Message>) {
(
SolarSystem {
state: State::new(),
- solar_system: canvas::layer::Cache::new(),
},
Command::none(),
)
@@ -52,7 +52,6 @@ impl Application for SolarSystem {
match message {
Message::Tick(instant) => {
self.state.update(instant);
- self.solar_system.clear();
}
}
@@ -65,24 +64,20 @@ impl Application for SolarSystem {
}
fn view(&mut self) -> Element<Message> {
- let canvas = Canvas::new()
+ Canvas::new(&mut self.state)
.width(Length::Fill)
.height(Length::Fill)
- .push(self.solar_system.with(&self.state));
-
- Container::new(canvas)
- .width(Length::Fill)
- .height(Length::Fill)
- .center_x()
- .center_y()
.into()
}
}
#[derive(Debug)]
struct State {
+ space_cache: canvas::Cache,
+ system_cache: canvas::Cache,
+ cursor_position: Point,
start: Instant,
- current: Instant,
+ now: Instant,
stars: Vec<(Point, f32)>,
}
@@ -95,153 +90,125 @@ impl State {
pub fn new() -> State {
let now = Instant::now();
- let (width, height) = Settings::default().window.size;
+ let (width, height) = window::Settings::default().size;
State {
+ space_cache: Default::default(),
+ system_cache: Default::default(),
+ cursor_position: Point::ORIGIN,
start: now,
- current: now,
- stars: {
- use rand::Rng;
-
- let mut rng = rand::thread_rng();
-
- (0..100)
- .map(|_| {
- (
- Point::new(
- rng.gen_range(0.0, width as f32),
- rng.gen_range(0.0, height as f32),
- ),
- rng.gen_range(0.5, 1.0),
- )
- })
- .collect()
- },
+ now,
+ stars: Self::generate_stars(width, height),
}
}
pub fn update(&mut self, now: Instant) {
- self.current = now;
+ self.now = now;
+ self.system_cache.clear();
+ }
+
+ fn generate_stars(width: u32, height: u32) -> Vec<(Point, f32)> {
+ use rand::Rng;
+
+ let mut rng = rand::thread_rng();
+
+ (0..100)
+ .map(|_| {
+ (
+ Point::new(
+ rng.gen_range(
+ -(width as f32) / 2.0,
+ width as f32 / 2.0,
+ ),
+ rng.gen_range(
+ -(height as f32) / 2.0,
+ height as f32 / 2.0,
+ ),
+ ),
+ rng.gen_range(0.5, 1.0),
+ )
+ })
+ .collect()
}
}
-impl canvas::Drawable for State {
- fn draw(&self, frame: &mut canvas::Frame) {
- use canvas::{Fill, Path, Stroke};
+impl<Message> canvas::Program<Message> for State {
+ fn draw(
+ &self,
+ bounds: Rectangle,
+ _cursor: Cursor,
+ ) -> Vec<canvas::Geometry> {
use std::f32::consts::PI;
- let center = frame.center();
+ let background = self.space_cache.draw(bounds.size(), |frame| {
+ let space = Path::rectangle(Point::new(0.0, 0.0), frame.size());
- let space = Path::new(|path| {
- path.rectangle(Point::new(0.0, 0.0), frame.size())
- });
+ let stars = Path::new(|path| {
+ for (p, size) in &self.stars {
+ path.rectangle(*p, Size::new(*size, *size));
+ }
+ });
- let stars = Path::new(|path| {
- for (p, size) in &self.stars {
- path.rectangle(*p, Size::new(*size, *size));
- }
+ frame.fill(&space, Color::BLACK);
+
+ frame.translate(frame.center() - Point::ORIGIN);
+ frame.fill(&stars, Color::WHITE);
});
- let sun = Path::new(|path| path.circle(center, Self::SUN_RADIUS));
- let orbit = Path::new(|path| path.circle(center, Self::ORBIT_RADIUS));
-
- frame.fill(&space, Fill::Color(Color::BLACK));
- frame.fill(&stars, Fill::Color(Color::WHITE));
- frame.fill(&sun, Fill::Color(Color::from_rgb8(0xF9, 0xD7, 0x1C)));
- frame.stroke(
- &orbit,
- Stroke {
- width: 1.0,
- color: Color::from_rgba8(0, 153, 255, 0.1),
- ..Stroke::default()
- },
- );
+ let system = self.system_cache.draw(bounds.size(), |frame| {
+ let center = frame.center();
- let elapsed = self.current - self.start;
- let elapsed_seconds = elapsed.as_secs() as f32;
- let elapsed_millis = elapsed.subsec_millis() as f32;
+ let sun = Path::circle(center, Self::SUN_RADIUS);
+ let orbit = Path::circle(center, Self::ORBIT_RADIUS);
- frame.with_save(|frame| {
- frame.translate(Vector::new(center.x, center.y));
- frame.rotate(
- (2.0 * PI / 60.0) * elapsed_seconds
- + (2.0 * PI / 60_000.0) * elapsed_millis,
+ frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C));
+ frame.stroke(
+ &orbit,
+ Stroke {
+ width: 1.0,
+ color: Color::from_rgba8(0, 153, 255, 0.1),
+ ..Stroke::default()
+ },
);
- frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
- let earth = Path::new(|path| {
- path.circle(Point::ORIGIN, Self::EARTH_RADIUS)
- });
+ let elapsed = self.now - self.start;
+ let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32
+ + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;
- let shadow = Path::new(|path| {
- path.rectangle(
+ frame.with_save(|frame| {
+ frame.translate(Vector::new(center.x, center.y));
+ frame.rotate(rotation);
+ frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
+
+ let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
+ let shadow = Path::rectangle(
Point::new(0.0, -Self::EARTH_RADIUS),
Size::new(
Self::EARTH_RADIUS * 4.0,
Self::EARTH_RADIUS * 2.0,
),
- )
- });
+ );
- frame.fill(&earth, Fill::Color(Color::from_rgb8(0x6B, 0x93, 0xD6)));
+ frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));
- frame.with_save(|frame| {
- frame.rotate(
- ((2.0 * PI) / 6.0) * elapsed_seconds
- + ((2.0 * PI) / 6_000.0) * elapsed_millis,
- );
- frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
+ frame.with_save(|frame| {
+ frame.rotate(rotation * 10.0);
+ frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));
- let moon = Path::new(|path| {
- path.circle(Point::ORIGIN, Self::MOON_RADIUS)
+ let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
+ frame.fill(&moon, Color::WHITE);
});
- frame.fill(&moon, Fill::Color(Color::WHITE));
+ frame.fill(
+ &shadow,
+ Color {
+ a: 0.7,
+ ..Color::BLACK
+ },
+ );
});
-
- frame.fill(
- &shadow,
- Fill::Color(Color {
- a: 0.7,
- ..Color::BLACK
- }),
- );
});
- }
-}
-
-mod time {
- use iced::futures;
- use std::time::Instant;
- pub fn every(duration: std::time::Duration) -> iced::Subscription<Instant> {
- iced::Subscription::from_recipe(Every(duration))
- }
-
- struct Every(std::time::Duration);
-
- impl<H, I> iced_native::subscription::Recipe<H, I> for Every
- where
- H: std::hash::Hasher,
- {
- type Output = Instant;
-
- fn hash(&self, state: &mut H) {
- use std::hash::Hash;
-
- std::any::TypeId::of::<Self>().hash(state);
- self.0.hash(state);
- }
-
- fn stream(
- self: Box<Self>,
- _input: futures::stream::BoxStream<'static, I>,
- ) -> futures::stream::BoxStream<'static, Self::Output> {
- use futures::stream::StreamExt;
-
- async_std::stream::interval(self.0)
- .map(|_| Instant::now())
- .boxed()
- }
+ vec![background, system]
}
}
diff --git a/examples/stopwatch/Cargo.toml b/examples/stopwatch/Cargo.toml
index 1dae3b83..075aa111 100644
--- a/examples/stopwatch/Cargo.toml
+++ b/examples/stopwatch/Cargo.toml
@@ -6,7 +6,4 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../.." }
-iced_native = { path = "../../native" }
-iced_futures = { path = "../../futures", features = ["async-std"] }
-async-std = { version = "1.0", features = ["unstable"] }
+iced = { path = "../..", features = ["tokio"] }
diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs
index d84c4817..983cf3e6 100644
--- a/examples/stopwatch/src/main.rs
+++ b/examples/stopwatch/src/main.rs
@@ -1,10 +1,11 @@
use iced::{
- button, Align, Application, Button, Column, Command, Container, Element,
- HorizontalAlignment, Length, Row, Settings, Subscription, Text,
+ button, executor, time, Align, Application, Button, Column, Command,
+ Container, Element, HorizontalAlignment, Length, Row, Settings,
+ Subscription, Text,
};
use std::time::{Duration, Instant};
-pub fn main() {
+pub fn main() -> iced::Result {
Stopwatch::run(Settings::default())
}
@@ -28,10 +29,11 @@ enum Message {
}
impl Application for Stopwatch {
- type Executor = iced_futures::executor::AsyncStd;
+ type Executor = executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Stopwatch, Command<Message>) {
+ fn new(_flags: ()) -> (Stopwatch, Command<Message>) {
(
Stopwatch {
duration: Duration::default(),
@@ -142,43 +144,6 @@ impl Application for Stopwatch {
}
}
-mod time {
- use iced::futures;
-
- pub fn every(
- duration: std::time::Duration,
- ) -> iced::Subscription<std::time::Instant> {
- iced::Subscription::from_recipe(Every(duration))
- }
-
- struct Every(std::time::Duration);
-
- impl<H, I> iced_native::subscription::Recipe<H, I> for Every
- where
- H: std::hash::Hasher,
- {
- type Output = std::time::Instant;
-
- fn hash(&self, state: &mut H) {
- use std::hash::Hash;
-
- std::any::TypeId::of::<Self>().hash(state);
- self.0.hash(state);
- }
-
- fn stream(
- self: Box<Self>,
- _input: futures::stream::BoxStream<'static, I>,
- ) -> futures::stream::BoxStream<'static, Self::Output> {
- use futures::stream::StreamExt;
-
- async_std::stream::interval(self.0)
- .map(|_| std::time::Instant::now())
- .boxed()
- }
- }
-}
-
mod style {
use iced::{button, Background, Color, Vector};
@@ -196,7 +161,7 @@ mod style {
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::WHITE,
..button::Style::default()
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index d6f41b04..8975fd9a 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -1,10 +1,10 @@
use iced::{
button, scrollable, slider, text_input, Align, Button, Checkbox, Column,
- Container, Element, Length, ProgressBar, Radio, Row, Sandbox, Scrollable,
- Settings, Slider, Space, Text, TextInput,
+ Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox,
+ Scrollable, Settings, Slider, Space, Text, TextInput,
};
-pub fn main() {
+pub fn main() -> iced::Result {
Styling::run(Settings::default())
}
@@ -113,14 +113,17 @@ impl Sandbox for Styling {
.padding(20)
.max_width(600)
.push(choose_theme)
+ .push(Rule::horizontal(38).style(self.theme))
.push(Row::new().spacing(10).push(text_input).push(button))
.push(slider)
.push(progress_bar)
.push(
Row::new()
.spacing(10)
+ .height(Length::Units(100))
.align_items(Align::Center)
.push(scrollable)
+ .push(Rule::vertical(38).style(self.theme))
.push(checkbox),
);
@@ -136,8 +139,8 @@ impl Sandbox for Styling {
mod style {
use iced::{
- button, checkbox, container, progress_bar, radio, scrollable, slider,
- text_input,
+ button, checkbox, container, progress_bar, radio, rule, scrollable,
+ slider, text_input,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -228,18 +231,25 @@ mod style {
}
}
+ impl From<Theme> for Box<dyn rule::StyleSheet> {
+ fn from(theme: Theme) -> Self {
+ match theme {
+ Theme::Light => Default::default(),
+ Theme::Dark => dark::Rule.into(),
+ }
+ }
+ }
+
mod light {
- use iced::{button, Background, Color, Vector};
+ use iced::{button, Color, Vector};
pub struct Button;
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(Color::from_rgb(
- 0.11, 0.42, 0.87,
- ))),
- border_radius: 12,
+ background: Color::from_rgb(0.11, 0.42, 0.87).into(),
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()
@@ -258,8 +268,8 @@ mod style {
mod dark {
use iced::{
- button, checkbox, container, progress_bar, radio, scrollable,
- slider, text_input, Background, Color,
+ button, checkbox, container, progress_bar, radio, rule, scrollable,
+ slider, text_input, Color,
};
const SURFACE: Color = Color::from_rgb(
@@ -291,10 +301,8 @@ mod style {
impl container::StyleSheet for Container {
fn style(&self) -> container::Style {
container::Style {
- background: Some(Background::Color(Color::from_rgb8(
- 0x36, 0x39, 0x3F,
- ))),
- text_color: Some(Color::WHITE),
+ background: Color::from_rgb8(0x36, 0x39, 0x3F).into(),
+ text_color: Color::WHITE.into(),
..container::Style::default()
}
}
@@ -305,16 +313,16 @@ mod style {
impl radio::StyleSheet for Radio {
fn active(&self) -> radio::Style {
radio::Style {
- background: Background::Color(SURFACE),
+ background: SURFACE.into(),
dot_color: ACTIVE,
- border_width: 1,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
fn hovered(&self) -> radio::Style {
radio::Style {
- background: Background::Color(Color { a: 0.5, ..SURFACE }),
+ background: Color { a: 0.5, ..SURFACE }.into(),
..self.active()
}
}
@@ -325,16 +333,16 @@ mod style {
impl text_input::StyleSheet for TextInput {
fn active(&self) -> text_input::Style {
text_input::Style {
- background: Background::Color(SURFACE),
- border_radius: 2,
- border_width: 0,
+ background: SURFACE.into(),
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
}
}
fn focused(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: ACCENT,
..self.active()
}
@@ -342,7 +350,7 @@ mod style {
fn hovered(&self) -> text_input::Style {
text_input::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color { a: 0.3, ..ACCENT },
..self.focused()
}
@@ -355,6 +363,10 @@ mod style {
fn value_color(&self) -> Color {
Color::WHITE
}
+
+ fn selection_color(&self) -> Color {
+ ACTIVE
+ }
}
pub struct Button;
@@ -362,8 +374,8 @@ mod style {
impl button::StyleSheet for Button {
fn active(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(ACTIVE)),
- border_radius: 3,
+ background: ACTIVE.into(),
+ border_radius: 3.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -371,7 +383,7 @@ mod style {
fn hovered(&self) -> button::Style {
button::Style {
- background: Some(Background::Color(HOVERED)),
+ background: HOVERED.into(),
text_color: Color::WHITE,
..self.active()
}
@@ -379,7 +391,7 @@ mod style {
fn pressed(&self) -> button::Style {
button::Style {
- border_width: 1,
+ border_width: 1.0,
border_color: Color::WHITE,
..self.hovered()
}
@@ -391,14 +403,14 @@ mod style {
impl scrollable::StyleSheet for Scrollable {
fn active(&self) -> scrollable::Scrollbar {
scrollable::Scrollbar {
- background: Some(Background::Color(SURFACE)),
- border_radius: 2,
- border_width: 0,
+ background: SURFACE.into(),
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
scroller: scrollable::Scroller {
color: ACTIVE,
- border_radius: 2,
- border_width: 0,
+ border_radius: 2.0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -408,10 +420,7 @@ mod style {
let active = self.active();
scrollable::Scrollbar {
- background: Some(Background::Color(Color {
- a: 0.5,
- ..SURFACE
- })),
+ background: Color { a: 0.5, ..SURFACE }.into(),
scroller: scrollable::Scroller {
color: HOVERED,
..active.scroller
@@ -440,9 +449,9 @@ mod style {
slider::Style {
rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }),
handle: slider::Handle {
- shape: slider::HandleShape::Circle { radius: 9 },
+ shape: slider::HandleShape::Circle { radius: 9.0 },
color: ACTIVE,
- border_width: 0,
+ border_width: 0.0,
border_color: Color::TRANSPARENT,
},
}
@@ -478,9 +487,9 @@ mod style {
impl progress_bar::StyleSheet for ProgressBar {
fn style(&self) -> progress_bar::Style {
progress_bar::Style {
- background: Background::Color(SURFACE),
- bar: Background::Color(ACTIVE),
- border_radius: 10,
+ background: SURFACE.into(),
+ bar: ACTIVE.into(),
+ border_radius: 10.0,
}
}
}
@@ -490,27 +499,38 @@ mod style {
impl checkbox::StyleSheet for Checkbox {
fn active(&self, is_checked: bool) -> checkbox::Style {
checkbox::Style {
- background: Background::Color(if is_checked {
- ACTIVE
- } else {
- SURFACE
- }),
+ background: if is_checked { ACTIVE } else { SURFACE }
+ .into(),
checkmark_color: Color::WHITE,
- border_radius: 2,
- border_width: 1,
+ border_radius: 2.0,
+ border_width: 1.0,
border_color: ACTIVE,
}
}
fn hovered(&self, is_checked: bool) -> checkbox::Style {
checkbox::Style {
- background: Background::Color(Color {
+ background: Color {
a: 0.8,
..if is_checked { ACTIVE } else { SURFACE }
- }),
+ }
+ .into(),
..self.active(is_checked)
}
}
}
+
+ pub struct Rule;
+
+ impl rule::StyleSheet for Rule {
+ fn style(&self) -> rule::Style {
+ rule::Style {
+ color: SURFACE,
+ width: 2,
+ radius: 1.0,
+ fill_mode: rule::FillMode::Padded(15),
+ }
+ }
+ }
}
}
diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml
index 161ee6a8..d8f83ac2 100644
--- a/examples/svg/Cargo.toml
+++ b/examples/svg/Cargo.toml
@@ -7,4 +7,3 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["svg"] }
-env_logger = "0.7"
diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs
index 811fdfb5..8707fa3b 100644
--- a/examples/svg/src/main.rs
+++ b/examples/svg/src/main.rs
@@ -1,19 +1,16 @@
-use iced::{Column, Container, Element, Length, Sandbox, Settings, Svg};
-
-pub fn main() {
- env_logger::init();
+use iced::{Container, Element, Length, Sandbox, Settings, Svg};
+pub fn main() -> iced::Result {
Tiger::run(Settings::default())
}
-#[derive(Default)]
struct Tiger;
impl Sandbox for Tiger {
type Message = ();
fn new() -> Self {
- Self::default()
+ Tiger
}
fn title(&self) -> String {
@@ -23,18 +20,17 @@ impl Sandbox for Tiger {
fn update(&mut self, _message: ()) {}
fn view(&mut self) -> Element<()> {
- let content = Column::new().padding(20).push(
- Svg::new(format!(
- "{}/resources/tiger.svg",
- env!("CARGO_MANIFEST_DIR")
- ))
- .width(Length::Fill)
- .height(Length::Fill),
- );
-
- Container::new(content)
+ let svg = Svg::from_path(format!(
+ "{}/resources/tiger.svg",
+ env!("CARGO_MANIFEST_DIR")
+ ))
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ Container::new(svg)
.width(Length::Fill)
.height(Length::Fill)
+ .padding(20)
.center_x()
.center_y()
.into()
diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml
index f945cde5..c8926c33 100644
--- a/examples/todos/Cargo.toml
+++ b/examples/todos/Cargo.toml
@@ -6,13 +6,13 @@ edition = "2018"
publish = false
[dependencies]
-iced = { path = "../..", features = ["async-std"] }
+iced = { path = "../..", features = ["async-std", "debug"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-std = "1.0"
-directories = "2.0"
+directories-next = "2.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features = ["Window", "Storage"] }
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 7e866b19..ccee2703 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -5,7 +5,7 @@ use iced::{
};
use serde::{Deserialize, Serialize};
-pub fn main() {
+pub fn main() -> iced::Result {
Todos::run(Settings::default())
}
@@ -40,8 +40,9 @@ enum Message {
impl Application for Todos {
type Executor = iced::executor::Default;
type Message = Message;
+ type Flags = ();
- fn new() -> (Todos, Command<Message>) {
+ fn new(_flags: ()) -> (Todos, Command<Message>) {
(
Todos::Loading,
Command::perform(SavedState::load(), Message::Loaded),
@@ -424,7 +425,7 @@ impl Filter {
}
}
-fn loading_message() -> Element<'static, Message> {
+fn loading_message<'a>() -> Element<'a, Message> {
Container::new(
Text::new("Loading...")
.horizontal_alignment(HorizontalAlignment::Center)
@@ -436,7 +437,7 @@ fn loading_message() -> Element<'static, Message> {
.into()
}
-fn empty_message(message: &str) -> Element<'static, Message> {
+fn empty_message<'a>(message: &str) -> Element<'a, Message> {
Container::new(
Text::new(message)
.width(Length::Fill)
@@ -498,7 +499,7 @@ enum SaveError {
impl SavedState {
fn path() -> std::path::PathBuf {
let mut path = if let Some(project_dirs) =
- directories::ProjectDirs::from("rs", "Iced", "Todos")
+ directories_next::ProjectDirs::from("rs", "Iced", "Todos")
{
project_dirs.data_dir().into()
} else {
@@ -610,7 +611,7 @@ mod style {
background: Some(Background::Color(
Color::from_rgb(0.2, 0.2, 0.7),
)),
- border_radius: 10,
+ border_radius: 10.0,
text_color: Color::WHITE,
..button::Style::default()
}
@@ -626,7 +627,7 @@ mod style {
background: Some(Background::Color(Color::from_rgb(
0.8, 0.2, 0.2,
))),
- border_radius: 5,
+ border_radius: 5.0,
text_color: Color::WHITE,
shadow_offset: Vector::new(1.0, 1.0),
..button::Style::default()
diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml
index 96749e90..bc7fac11 100644
--- a/examples/tour/Cargo.toml
+++ b/examples/tour/Cargo.toml
@@ -7,4 +7,4 @@ publish = false
[dependencies]
iced = { path = "../..", features = ["image", "debug"] }
-env_logger = "0.7"
+env_logger = "0.8"
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index 800254ed..e8755d39 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -4,7 +4,7 @@ use iced::{
Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput,
};
-pub fn main() {
+pub fn main() -> iced::Result {
env_logger::init();
Tour::run(Settings::default())
@@ -190,7 +190,7 @@ enum Step {
Welcome,
Slider {
state: slider::State,
- value: u16,
+ value: u8,
},
RowsAndColumns {
layout: Layout,
@@ -222,13 +222,13 @@ enum Step {
#[derive(Debug, Clone)]
pub enum StepMessage {
- SliderChanged(f32),
+ SliderChanged(u8),
LayoutChanged(Layout),
- SpacingChanged(f32),
- TextSizeChanged(f32),
+ SpacingChanged(u16),
+ TextSizeChanged(u16),
TextColorChanged(Color),
LanguageSelected(Language),
- ImageWidthChanged(f32),
+ ImageWidthChanged(u16),
InputChanged(String),
ToggleSecureInput(bool),
DebugToggled(bool),
@@ -249,12 +249,12 @@ impl<'a> Step {
}
StepMessage::SliderChanged(new_value) => {
if let Step::Slider { value, .. } = self {
- *value = new_value.round() as u16;
+ *value = new_value;
}
}
StepMessage::TextSizeChanged(new_size) => {
if let Step::Text { size, .. } = self {
- *size = new_size.round() as u16;
+ *size = new_size;
}
}
StepMessage::TextColorChanged(new_color) => {
@@ -269,12 +269,12 @@ impl<'a> Step {
}
StepMessage::SpacingChanged(new_spacing) => {
if let Step::RowsAndColumns { spacing, .. } = self {
- *spacing = new_spacing.round() as u16;
+ *spacing = new_spacing;
}
}
StepMessage::ImageWidthChanged(new_width) => {
if let Step::Image { width, .. } = self {
- *width = new_width.round() as u16;
+ *width = new_width;
}
}
StepMessage::InputChanged(new_value) => {
@@ -384,7 +384,7 @@ impl<'a> Step {
fn slider(
state: &'a mut slider::State,
- value: u16,
+ value: u8,
) -> Column<'a, StepMessage> {
Self::container("Slider")
.push(Text::new(
@@ -397,8 +397,8 @@ impl<'a> Step {
))
.push(Slider::new(
state,
- 0.0..=100.0,
- value as f32,
+ 0..=100,
+ value,
StepMessage::SliderChanged,
))
.push(
@@ -444,8 +444,8 @@ impl<'a> Step {
.spacing(10)
.push(Slider::new(
spacing_slider,
- 0.0..=80.0,
- spacing as f32,
+ 0..=80,
+ spacing,
StepMessage::SpacingChanged,
))
.push(
@@ -486,30 +486,25 @@ impl<'a> Step {
)
.push(Slider::new(
size_slider,
- 10.0..=70.0,
- size as f32,
+ 10..=70,
+ size,
StepMessage::TextSizeChanged,
));
let [red, green, blue] = color_sliders;
+
+ let color_sliders = Row::new()
+ .spacing(10)
+ .push(color_slider(red, color.r, move |r| Color { r, ..color }))
+ .push(color_slider(green, color.g, move |g| Color { g, ..color }))
+ .push(color_slider(blue, color.b, move |b| Color { b, ..color }));
+
let color_section = Column::new()
.padding(20)
.spacing(20)
.push(Text::new("And its color:"))
.push(Text::new(&format!("{:?}", color)).color(color))
- .push(
- Row::new()
- .spacing(10)
- .push(Slider::new(red, 0.0..=1.0, color.r, move |r| {
- StepMessage::TextColorChanged(Color { r, ..color })
- }))
- .push(Slider::new(green, 0.0..=1.0, color.g, move |g| {
- StepMessage::TextColorChanged(Color { g, ..color })
- }))
- .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| {
- StepMessage::TextColorChanged(Color { b, ..color })
- })),
- );
+ .push(color_sliders);
Self::container("Text")
.push(Text::new(
@@ -530,7 +525,7 @@ impl<'a> Step {
|choices, language| {
choices.push(Radio::new(
language,
- language.into(),
+ language,
selection,
StepMessage::LanguageSelected,
))
@@ -559,8 +554,8 @@ impl<'a> Step {
.push(ferris(width))
.push(Slider::new(
slider,
- 100.0..=500.0,
- width as f32,
+ 100..=500,
+ width,
StepMessage::ImageWidthChanged,
))
.push(
@@ -694,7 +689,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> {
.center_x()
}
-fn button<'a, Message>(
+fn button<'a, Message: Clone>(
state: &'a mut button::State,
label: &str,
) -> Button<'a, Message> {
@@ -706,6 +701,17 @@ fn button<'a, Message>(
.min_width(100)
}
+fn color_slider(
+ state: &mut slider::State,
+ component: f32,
+ update: impl Fn(f32) -> Color + 'static,
+) -> Slider<f64, StepMessage> {
+ Slider::new(state, 0.0..=1.0, f64::from(component), move |c| {
+ StepMessage::TextColorChanged(update(c as f32))
+ })
+ .step(0.01)
+}
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Language {
Rust,
@@ -729,16 +735,16 @@ impl Language {
}
}
-impl From<Language> for &str {
- fn from(language: Language) -> &'static str {
- match language {
+impl From<Language> for String {
+ fn from(language: Language) -> String {
+ String::from(match language {
Language::Rust => "Rust",
Language::Elm => "Elm",
Language::Ruby => "Ruby",
Language::Haskell => "Haskell",
Language::C => "C",
Language::Other => "Other",
- }
+ })
}
}
@@ -763,7 +769,7 @@ mod style {
Button::Primary => Color::from_rgb(0.11, 0.42, 0.87),
Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5),
})),
- border_radius: 12,
+ border_radius: 12.0,
shadow_offset: Vector::new(1.0, 1.0),
text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE),
..button::Style::default()