summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón <hector0193@gmail.com>2020-02-20 05:51:18 +0100
committerLibravatar GitHub <noreply@github.com>2020-02-20 05:51:18 +0100
commit17271eae671a933a862dc85aa5b9956a7da70b28 (patch)
tree1a24e3eebb544b0bc8b0ebf2d92f330141f699bf /examples
parent8d63c49ba1aa43407e0dab0a8e69d3f316a79279 (diff)
parent6f7247ca13181bcdfe1a3065215c1b3204723b84 (diff)
downloadiced-17271eae671a933a862dc85aa5b9956a7da70b28.tar.gz
iced-17271eae671a933a862dc85aa5b9956a7da70b28.tar.bz2
iced-17271eae671a933a862dc85aa5b9956a7da70b28.zip
Merge pull request #193 from hecrj/feature/canvas
Canvas widget for 2D graphics
Diffstat (limited to '')
-rw-r--r--examples/bezier_tool/src/main.rs59
-rw-r--r--examples/clock/Cargo.toml15
-rw-r--r--examples/clock/src/main.rs193
-rw-r--r--examples/geometry/src/main.rs123
-rw-r--r--examples/solar_system/Cargo.toml15
-rw-r--r--examples/solar_system/src/main.rs247
6 files changed, 559 insertions, 93 deletions
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 043d265c..023eb0f7 100644
--- a/examples/bezier_tool/src/main.rs
+++ b/examples/bezier_tool/src/main.rs
@@ -94,7 +94,7 @@ mod bezier {
layout: Layout<'_>,
cursor_position: Point,
) -> (Primitive, MouseCursor) {
- let mut buffer: VertexBuffers<Vertex2D, u16> = VertexBuffers::new();
+ let mut buffer: VertexBuffers<Vertex2D, u32> = VertexBuffers::new();
let mut path_builder = lyon::path::Path::builder();
let bounds = layout.bounds();
@@ -102,7 +102,7 @@ mod bezier {
// Draw rectangle border with lyon.
basic_shapes::stroke_rectangle(
&lyon::math::Rect::new(
- lyon::math::Point::new(bounds.x + 0.5, bounds.y + 0.5),
+ lyon::math::Point::new(0.5, 0.5),
lyon::math::Size::new(
bounds.width - 1.0,
bounds.height - 1.0,
@@ -121,48 +121,35 @@ mod bezier {
for curve in self.curves {
path_builder.move_to(lyon::math::Point::new(
- curve.from.x + bounds.x,
- curve.from.y + bounds.y,
+ curve.from.x,
+ curve.from.y,
));
path_builder.quadratic_bezier_to(
- lyon::math::Point::new(
- curve.control.x + bounds.x,
- curve.control.y + bounds.y,
- ),
- lyon::math::Point::new(
- curve.to.x + bounds.x,
- curve.to.y + bounds.y,
- ),
+ 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 + bounds.x,
- from.y + bounds.y,
- ));
+ path_builder
+ .move_to(lyon::math::Point::new(from.x, from.y));
path_builder.line_to(lyon::math::Point::new(
- cursor_position.x,
- cursor_position.y,
+ 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 + bounds.x,
- from.y + bounds.y,
- ));
+ path_builder
+ .move_to(lyon::math::Point::new(from.x, from.y));
path_builder.quadratic_bezier_to(
lyon::math::Point::new(
- cursor_position.x,
- cursor_position.y,
- ),
- lyon::math::Point::new(
- to.x + bounds.x,
- to.y + bounds.y,
+ cursor_position.x - bounds.x,
+ cursor_position.y - bounds.y,
),
+ lyon::math::Point::new(to.x, to.y),
);
}
}
@@ -186,10 +173,13 @@ mod bezier {
)
.unwrap();
- let mesh = Primitive::Mesh2D(Arc::new(Mesh2D {
- vertices: buffer.vertices,
- indices: buffer.indices,
- }));
+ let mesh = Primitive::Mesh2D {
+ origin: Point::new(bounds.x, bounds.y),
+ buffers: Arc::new(Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ }),
+ };
(
Primitive::Clip {
@@ -296,7 +286,10 @@ use iced::{
};
pub fn main() {
- Example::run(Settings::default())
+ Example::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ });
}
#[derive(Default)]
diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml
new file mode 100644
index 00000000..308cbfbb
--- /dev/null
+++ b/examples/clock/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "clock"
+version = "0.1.0"
+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" }
+chrono = "0.4"
+async-std = { version = "1.0", features = ["unstable"] }
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
new file mode 100644
index 00000000..d8266f06
--- /dev/null
+++ b/examples/clock/src/main.rs
@@ -0,0 +1,193 @@
+use iced::{
+ canvas, executor, Application, Canvas, Color, Command, Container, Element,
+ Length, Point, Settings, Subscription, Vector,
+};
+
+pub fn main() {
+ Clock::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+struct Clock {
+ now: LocalTime,
+ clock: canvas::layer::Cache<LocalTime>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ Tick(chrono::DateTime<chrono::Local>),
+}
+
+impl Application for Clock {
+ type Executor = executor::Default;
+ type Message = Message;
+
+ fn new() -> (Self, Command<Message>) {
+ (
+ Clock {
+ now: chrono::Local::now().into(),
+ clock: canvas::layer::Cache::new(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Clock - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Tick(local_time) => {
+ let now = local_time.into();
+
+ if now != self.now {
+ self.now = now;
+ self.clock.clear();
+ }
+ }
+ }
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ time::every(std::time::Duration::from_millis(500)).map(Message::Tick)
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let canvas = Canvas::new()
+ .width(Length::Units(400))
+ .height(Length::Units(400))
+ .push(self.clock.with(&self.now));
+
+ Container::new(canvas)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .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 {
+ use chrono::Timelike;
+
+ LocalTime {
+ hour: date_time.hour(),
+ minute: date_time.minute(),
+ second: date_time.second(),
+ }
+ }
+}
+
+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 hour_and_minute_hands = canvas::Path::new(|path| {
+ path.move_to(center);
+ draw_hand(self.hour, 12, 0.5 * radius, offset, path);
+
+ path.move_to(center);
+ draw_hand(self.minute, 60, 0.8 * radius, offset, path)
+ });
+
+ frame.stroke(
+ &hour_and_minute_hands,
+ canvas::Stroke {
+ width: 6.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)
+ });
+
+ frame.stroke(
+ &second_hand,
+ canvas::Stroke {
+ width: 3.0,
+ color: Color::WHITE,
+ line_cap: canvas::LineCap::Round,
+ ..canvas::Stroke::default()
+ },
+ );
+ }
+}
+
+mod time {
+ use iced::futures;
+
+ 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()
+ }
+ }
+}
diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs
index 9d5fd611..795c6a71 100644
--- a/examples/geometry/src/main.rs
+++ b/examples/geometry/src/main.rs
@@ -69,72 +69,75 @@ mod rainbow {
let posn_center = {
if b.contains(cursor_position) {
- [cursor_position.x, cursor_position.y]
+ [cursor_position.x - b.x, cursor_position.y - b.y]
} else {
- [b.x + (b.width / 2.0), b.y + (b.height / 2.0)]
+ [b.width / 2.0, b.height / 2.0]
}
};
- let posn_tl = [b.x, b.y];
- let posn_t = [b.x + (b.width / 2.0), b.y];
- let posn_tr = [b.x + b.width, b.y];
- let posn_r = [b.x + b.width, b.y + (b.height / 2.0)];
- let posn_br = [b.x + b.width, b.y + b.height];
- let posn_b = [b.x + (b.width / 2.0), b.y + b.height];
- let posn_bl = [b.x, b.y + b.height];
- let posn_l = [b.x, b.y + (b.height / 2.0)];
+ let posn_tl = [0.0, 0.0];
+ let posn_t = [b.width / 2.0, 0.0];
+ let posn_tr = [b.width, 0.0];
+ let posn_r = [b.width, b.height / 2.0];
+ let posn_br = [b.width, b.height];
+ let posn_b = [(b.width / 2.0), b.height];
+ let posn_bl = [0.0, b.height];
+ let posn_l = [0.0, b.height / 2.0];
(
- Primitive::Mesh2D(std::sync::Arc::new(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::Mesh2D {
+ origin: Point::new(b.x, b.y),
+ buffers: std::sync::Arc::new(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,
)
}
diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml
new file mode 100644
index 00000000..c88cda50
--- /dev/null
+++ b/examples/solar_system/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "solar_system"
+version = "0.1.0"
+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"] }
+rand = "0.7"
diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs
new file mode 100644
index 00000000..4c239806
--- /dev/null
+++ b/examples/solar_system/src/main.rs
@@ -0,0 +1,247 @@
+//! An animated solar system.
+//!
+//! This example showcases how to use a `Canvas` widget with transforms to draw
+//! using different coordinate systems.
+//!
+//! Inspired by the example found in the MDN docs[1].
+//!
+//! [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,
+};
+
+use std::time::Instant;
+
+pub fn main() {
+ SolarSystem::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+struct SolarSystem {
+ state: State,
+ solar_system: canvas::layer::Cache<State>,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ Tick(Instant),
+}
+
+impl Application for SolarSystem {
+ type Executor = executor::Default;
+ type Message = Message;
+
+ fn new() -> (Self, Command<Message>) {
+ (
+ SolarSystem {
+ state: State::new(),
+ solar_system: canvas::layer::Cache::new(),
+ },
+ Command::none(),
+ )
+ }
+
+ fn title(&self) -> String {
+ String::from("Solar system - Iced")
+ }
+
+ fn update(&mut self, message: Message) -> Command<Message> {
+ match message {
+ Message::Tick(instant) => {
+ self.state.update(instant);
+ self.solar_system.clear();
+ }
+ }
+
+ Command::none()
+ }
+
+ fn subscription(&self) -> Subscription<Message> {
+ time::every(std::time::Duration::from_millis(10))
+ .map(|instant| Message::Tick(instant))
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let canvas = Canvas::new()
+ .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 {
+ start: Instant,
+ current: Instant,
+ stars: Vec<(Point, f32)>,
+}
+
+impl State {
+ const SUN_RADIUS: f32 = 70.0;
+ const ORBIT_RADIUS: f32 = 150.0;
+ const EARTH_RADIUS: f32 = 12.0;
+ const MOON_RADIUS: f32 = 4.0;
+ const MOON_DISTANCE: f32 = 28.0;
+
+ pub fn new() -> State {
+ let now = Instant::now();
+ let (width, height) = Settings::default().window.size;
+
+ State {
+ 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()
+ },
+ }
+ }
+
+ pub fn update(&mut self, now: Instant) {
+ self.current = now;
+ }
+}
+
+impl canvas::Drawable for State {
+ fn draw(&self, frame: &mut canvas::Frame) {
+ use canvas::{Fill, Path, Stroke};
+ use std::f32::consts::PI;
+
+ let center = frame.center();
+
+ 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 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 elapsed = self.current - self.start;
+ let elapsed_seconds = elapsed.as_secs() as f32;
+ let elapsed_millis = elapsed.subsec_millis() as f32;
+
+ 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.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));
+
+ let earth = Path::new(|path| {
+ path.circle(Point::ORIGIN, Self::EARTH_RADIUS)
+ });
+
+ let shadow = Path::new(|path| {
+ 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.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));
+
+ let moon = Path::new(|path| {
+ path.circle(Point::ORIGIN, Self::MOON_RADIUS)
+ });
+
+ frame.fill(&moon, Fill::Color(Color::WHITE));
+ });
+
+ 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()
+ }
+ }
+}