summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml3
-rw-r--r--examples/clock/Cargo.toml13
-rw-r--r--examples/clock/src/main.rs145
-rw-r--r--src/lib.rs2
-rw-r--r--wgpu/src/widget/canvas.rs155
-rw-r--r--wgpu/src/widget/canvas/data.rs20
-rw-r--r--wgpu/src/widget/canvas/frame.rs39
-rw-r--r--wgpu/src/widget/canvas/layer.rs41
-rw-r--r--wgpu/src/widget/canvas/path.rs78
9 files changed, 494 insertions, 2 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 11cca8b3..dd099c32 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,6 +16,8 @@ categories = ["gui"]
image = ["iced_wgpu/image"]
# Enables the `Svg` widget
svg = ["iced_wgpu/svg"]
+# Enables the `Canvas` widget
+canvas = ["iced_wgpu/canvas"]
# Enables a debug view in native platforms (press F12)
debug = ["iced_winit/debug"]
# Enables `tokio` as the `executor::Default` on native platforms
@@ -36,6 +38,7 @@ members = [
"wgpu",
"winit",
"examples/bezier_tool",
+ "examples/clock",
"examples/counter",
"examples/custom_widget",
"examples/events",
diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml
new file mode 100644
index 00000000..941e2bd0
--- /dev/null
+++ b/examples/clock/Cargo.toml
@@ -0,0 +1,13 @@
+[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"] }
+chrono = "0.4"
diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs
new file mode 100644
index 00000000..958846f4
--- /dev/null
+++ b/examples/clock/src/main.rs
@@ -0,0 +1,145 @@
+use iced::{
+ canvas, executor, Application, Canvas, Color, Command, Element, Length,
+ Point, Settings,
+};
+
+use std::sync::Arc;
+
+pub fn main() {
+ Clock::run(Settings::default())
+}
+
+struct Clock {
+ local_time: Arc<LocalTime>,
+ clock: canvas::layer::Cached<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>) {
+ let now: LocalTime = chrono::Local::now().into();
+
+ (
+ Clock {
+ local_time: Arc::new(now),
+ clock: canvas::layer::Cached::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) => {}
+ }
+
+ Command::none()
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ Canvas::new()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .push(self.clock.with(&self.local_time))
+ .into()
+ }
+}
+
+#[derive(Debug)]
+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::layer::Drawable for LocalTime {
+ fn draw(&self, frame: &mut canvas::Frame) {
+ let center = frame.center();
+ let radius = frame.width().min(frame.height()) as f32 / 2.0;
+
+ let mut path = canvas::Path::new();
+
+ path.arc(canvas::path::Arc {
+ center,
+ radius,
+ start_angle: 0.0,
+ end_angle: 360.0 * 2.0 * std::f32::consts::PI,
+ });
+
+ frame.fill(
+ path,
+ canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)),
+ );
+
+ fn draw_handle(
+ n: u32,
+ total: u32,
+ length: f32,
+ path: &mut canvas::Path,
+ ) {
+ 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));
+ }
+
+ let mut path = canvas::Path::new();
+
+ path.move_to(center);
+ draw_handle(self.hour, 12, 0.6 * radius, &mut path);
+
+ path.move_to(center);
+ draw_handle(self.minute, 60, 0.9 * radius, &mut path);
+
+ frame.stroke(
+ path,
+ canvas::Stroke {
+ width: 4.0,
+ color: Color::WHITE,
+ line_cap: canvas::LineCap::Round,
+ ..canvas::Stroke::default()
+ },
+ );
+
+ let mut path = canvas::Path::new();
+
+ path.move_to(center);
+ draw_handle(self.second, 60, 0.9 * radius, &mut path);
+
+ frame.stroke(
+ path,
+ canvas::Stroke {
+ width: 2.0,
+ color: Color::WHITE,
+ line_cap: canvas::LineCap::Round,
+ ..canvas::Stroke::default()
+ },
+ );
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7e658ca2..59870692 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -204,5 +204,5 @@ use iced_web as common;
pub use common::{
futures, Align, Background, Color, Command, Font, HorizontalAlignment,
- Length, Space, Subscription, Vector, VerticalAlignment,
+ Length, Point, Space, Subscription, Vector, VerticalAlignment,
};
diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs
index ebb84135..7b84f36c 100644
--- a/wgpu/src/widget/canvas.rs
+++ b/wgpu/src/widget/canvas.rs
@@ -1,5 +1,158 @@
//! Draw freely in 2D.
+use crate::{Defaults, Primitive, Renderer};
+
+use iced_native::{
+ layout, Color, Element, Hasher, Layout, Length, MouseCursor, Point, Size,
+ Widget,
+};
+use std::hash::Hash;
+
+pub mod layer;
+pub mod path;
+
+mod data;
+mod frame;
+
+pub use data::Data;
+pub use frame::Frame;
+pub use layer::Layer;
+pub use path::Path;
/// A 2D drawable region.
#[derive(Debug)]
-pub struct Canvas;
+pub struct Canvas<'a> {
+ width: Length,
+ height: Length,
+ layers: Vec<Box<dyn Layer + 'a>>,
+}
+
+impl<'a> Canvas<'a> {
+ const DEFAULT_SIZE: u16 = 100;
+
+ pub fn new() -> Self {
+ Canvas {
+ width: Length::Units(Self::DEFAULT_SIZE),
+ height: Length::Units(Self::DEFAULT_SIZE),
+ layers: Vec::new(),
+ }
+ }
+
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ pub fn height(mut self, height: Length) -> Self {
+ self.height = height;
+ self
+ }
+
+ pub fn push(mut self, layer: impl Layer + 'a) -> Self {
+ self.layers.push(Box::new(layer));
+ self
+ }
+}
+
+impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> {
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ self.height
+ }
+
+ fn layout(
+ &self,
+ _renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let limits = limits.width(self.width).height(self.height);
+
+ let size = limits.resolve(Size::ZERO);
+
+ layout::Node::new(size)
+ }
+
+ fn draw(
+ &self,
+ _renderer: &mut Renderer,
+ _defaults: &Defaults,
+ _layout: Layout<'_>,
+ _cursor_position: Point,
+ ) -> (Primitive, MouseCursor) {
+ (Primitive::None, MouseCursor::Idle)
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ std::any::TypeId::of::<Canvas<'static>>().hash(state);
+
+ self.width.hash(state);
+ self.height.hash(state);
+ }
+}
+
+impl<'a, Message> From<Canvas<'a>> for Element<'a, Message, Renderer>
+where
+ Message: 'static,
+{
+ fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> {
+ Element::new(canvas)
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Stroke {
+ pub color: Color,
+ pub width: f32,
+ pub line_cap: LineCap,
+ pub line_join: LineJoin,
+}
+
+impl Default for Stroke {
+ fn default() -> Stroke {
+ Stroke {
+ color: Color::BLACK,
+ width: 1.0,
+ line_cap: LineCap::default(),
+ line_join: LineJoin::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum LineCap {
+ Butt,
+ Square,
+ Round,
+}
+
+impl Default for LineCap {
+ fn default() -> LineCap {
+ LineCap::Butt
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum LineJoin {
+ Miter,
+ Round,
+ Bevel,
+}
+
+impl Default for LineJoin {
+ fn default() -> LineJoin {
+ LineJoin::Miter
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Fill {
+ Color(Color),
+}
+
+impl Default for Fill {
+ fn default() -> Fill {
+ Fill::Color(Color::BLACK)
+ }
+}
diff --git a/wgpu/src/widget/canvas/data.rs b/wgpu/src/widget/canvas/data.rs
new file mode 100644
index 00000000..25d94f4c
--- /dev/null
+++ b/wgpu/src/widget/canvas/data.rs
@@ -0,0 +1,20 @@
+#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
+pub struct Data<T> {
+ raw: T,
+ version: usize,
+}
+
+impl<T> Data<T> {
+ pub fn new(data: T) -> Self {
+ Data {
+ raw: data,
+ version: 0,
+ }
+ }
+
+ pub fn update(&mut self, f: impl FnOnce(&mut T)) {
+ f(&mut self.raw);
+
+ self.version += 1;
+ }
+}
diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs
new file mode 100644
index 00000000..65c22c0c
--- /dev/null
+++ b/wgpu/src/widget/canvas/frame.rs
@@ -0,0 +1,39 @@
+use iced_native::Point;
+
+use crate::{
+ canvas::{Fill, Path, Stroke},
+ triangle,
+};
+
+#[derive(Debug)]
+pub struct Frame {
+ width: u32,
+ height: u32,
+ buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u16>,
+}
+
+impl Frame {
+ pub(crate) fn new(width: u32, height: u32) -> Frame {
+ Frame {
+ width,
+ height,
+ buffers: lyon::tessellation::VertexBuffers::new(),
+ }
+ }
+
+ pub fn width(&self) -> u32 {
+ self.width
+ }
+
+ pub fn height(&self) -> u32 {
+ self.height
+ }
+
+ pub fn center(&self) -> Point {
+ Point::new(self.width as f32 / 2.0, self.height as f32 / 2.0)
+ }
+
+ pub fn fill(&mut self, path: Path, fill: Fill) {}
+
+ pub fn stroke(&mut self, path: Path, stroke: Stroke) {}
+}
diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs
new file mode 100644
index 00000000..f97634e4
--- /dev/null
+++ b/wgpu/src/widget/canvas/layer.rs
@@ -0,0 +1,41 @@
+use crate::canvas::Frame;
+
+pub trait Layer: std::fmt::Debug {}
+
+use std::marker::PhantomData;
+use std::sync::{Arc, Weak};
+
+#[derive(Debug)]
+pub struct Cached<T: Drawable> {
+ input: PhantomData<T>,
+}
+
+impl<T> Cached<T>
+where
+ T: Drawable + std::fmt::Debug,
+{
+ pub fn new() -> Self {
+ Cached { input: PhantomData }
+ }
+
+ pub fn clear(&mut self) {}
+
+ pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a {
+ Bind {
+ cache: self,
+ input: input,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Bind<'a, T: Drawable> {
+ cache: &'a Cached<T>,
+ input: &'a T,
+}
+
+impl<'a, T> Layer for Bind<'a, T> where T: Drawable + std::fmt::Debug {}
+
+pub trait Drawable {
+ fn draw(&self, frame: &mut Frame);
+}
diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs
new file mode 100644
index 00000000..2732b1bd
--- /dev/null
+++ b/wgpu/src/widget/canvas/path.rs
@@ -0,0 +1,78 @@
+use iced_native::{Point, Vector};
+
+#[allow(missing_debug_implementations)]
+pub struct Path {
+ raw: lyon::path::Builder,
+}
+
+impl Path {
+ pub fn new() -> Path {
+ Path {
+ raw: lyon::path::Path::builder(),
+ }
+ }
+
+ #[inline]
+ pub fn move_to(&mut self, point: Point) {
+ let _ = self.raw.move_to(lyon::math::Point::new(point.x, point.y));
+ }
+
+ #[inline]
+ pub fn line_to(&mut self, point: Point) {
+ let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y));
+ }
+
+ #[inline]
+ pub fn arc(&mut self, arc: Arc) {
+ self.ellipse(arc.into())
+ }
+
+ #[inline]
+ pub fn ellipse(&mut self, ellipse: Ellipse) {
+ let arc = lyon::geom::Arc {
+ center: lyon::math::Point::new(ellipse.center.x, ellipse.center.y),
+ radii: lyon::math::Vector::new(ellipse.radii.x, ellipse.radii.y),
+ x_rotation: lyon::math::Angle::radians(ellipse.rotation),
+ start_angle: lyon::math::Angle::radians(ellipse.start_angle),
+ sweep_angle: lyon::math::Angle::radians(ellipse.end_angle),
+ };
+
+ arc.for_each_quadratic_bezier(&mut |curve| {
+ let _ = self.raw.quadratic_bezier_to(curve.ctrl, curve.to);
+ });
+ }
+
+ #[inline]
+ pub fn close(&mut self) {
+ self.raw.close()
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Arc {
+ pub center: Point,
+ pub radius: f32,
+ pub start_angle: f32,
+ pub end_angle: f32,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Ellipse {
+ pub center: Point,
+ pub radii: Vector,
+ pub rotation: f32,
+ pub start_angle: f32,
+ pub end_angle: f32,
+}
+
+impl From<Arc> for Ellipse {
+ fn from(arc: Arc) -> Ellipse {
+ Ellipse {
+ center: arc.center,
+ radii: Vector::new(arc.radius, arc.radius),
+ rotation: 0.0,
+ start_angle: arc.start_angle,
+ end_angle: arc.end_angle,
+ }
+ }
+}