From e7df33d752283368c2c2aa3ca96bbb52efb8242c Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 6 Jan 2020 17:58:17 +0900 Subject: Add paint example --- Cargo.toml | 1 + examples/paint.rs | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 examples/paint.rs diff --git a/Cargo.toml b/Cargo.toml index ebd6412e..ceeab365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ futures = "0.3" async-std = { version = "1.3", features = ["unstable"] } surf = "1.0" rand = "0.7" +lyon = "0.15" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen = "0.2.51" diff --git a/examples/paint.rs b/examples/paint.rs new file mode 100644 index 00000000..a97638c6 --- /dev/null +++ b/examples/paint.rs @@ -0,0 +1,275 @@ +//! This example showcases a simple native MS paint like application. +mod paint { + // 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, Element, Event, Hasher, Layout, Length, + MouseCursor, Point, Size, Vector, Widget, + }; + use iced_wgpu::{ + triangle::{Mesh2D, Vertex2D}, + Primitive, Renderer, + }; + use lyon::lyon_tessellation::{ + basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, + StrokeTessellator, VertexBuffers, + }; + use std::sync::Arc; + + pub struct Paint<'a, Message> { + state: &'a mut State, + strokes: &'a [(Point, Point)], + on_stroke: Box Message>, + } + + impl<'a, Message> Paint<'a, Message> { + pub fn new( + strokes: &'a [(Point, Point)], + state: &'a mut State, + on_stroke: F, + ) -> Self + where + F: 'static + Fn((Point, Point)) -> Message, + { + Self { + state, + strokes, + on_stroke: Box::new(on_stroke), + } + } + } + + #[derive(Debug, Clone, Copy, Default)] + pub struct State { + is_dragging: bool, + previous_mouse_pos: Option, + } + + impl<'a, Message> Widget for Paint<'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, + layout: Layout<'_>, + _cursor_position: Point, + ) -> (Primitive, MouseCursor) { + let mut buffer: VertexBuffers = 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(bounds.x + 0.5, bounds.y + 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 (from, to) in self.strokes { + path_builder.move_to(lyon::math::Point::new( + from.x + bounds.x, + from.y + bounds.y, + )); + path_builder.line_to(lyon::math::Point::new( + to.x + bounds.x, + to.y + bounds.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(); + + ( + Primitive::Clip { + bounds, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Mesh2D(Arc::new(Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }))), + }, + MouseCursor::OutOfBounds, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + self.strokes.len().hash(state); + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + _cursor_position: Point, + messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + match event { + Event::Mouse(input::mouse::Event::CursorMoved { x, y }) + if bounds.contains(Point::new(x, y)) => + { + let pos = Point::new(x - bounds.x, y - bounds.y); + if self.state.is_dragging { + if let Some(prev) = self.state.previous_mouse_pos { + messages.push((self.on_stroke)((prev, pos))); + } + } + self.state.previous_mouse_pos = Some(pos); + } + Event::Mouse(input::mouse::Event::Input { state, .. }) => { + match state { + input::ButtonState::Pressed => { + self.state.is_dragging = true; + } + input::ButtonState::Released => { + self.state.is_dragging = false; + } + } + } + _ => {} + } + } + } + + impl<'a, Message> Into> for Paint<'a, Message> + where + Message: 'static, + { + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } + } +} + +use iced::{ + button, Align, Button, Color, Column, Container, Element, Length, Sandbox, + Settings, Text, +}; +use iced_native::Point; +use paint::Paint; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + paint_state: paint::State, + strokes: Vec<(Point, Point)>, + button_state: button::State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Stroke((Point, Point)), + Clear, +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Example::default() + } + + fn title(&self) -> String { + String::from("Paint - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::Stroke(stroke) => { + self.strokes.push(stroke); + } + Message::Clear => { + self.strokes.clear(); + } + } + } + + fn view(&mut self) -> Element { + let content = Column::new() + .padding(20) + .spacing(20) + .align_items(Align::Center) + .push(Text::new("Paint example").width(Length::Shrink).size(50)) + .push(Paint::new( + self.strokes.as_slice(), + &mut self.paint_state, + Message::Stroke, + )) + .push( + Button::new(&mut self.button_state, Text::new("Clear")) + .padding(8) + .background(Color::from_rgb(0.5, 0.5, 0.5)) + .border_radius(4) + .on_press(Message::Clear), + ); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} -- cgit From 6632ce5fb06e940f6b72cfad62d9fc83fe45702f Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 19:06:43 +0900 Subject: Remove hash_layout --- examples/paint.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/paint.rs b/examples/paint.rs index a97638c6..18ac2d02 100644 --- a/examples/paint.rs +++ b/examples/paint.rs @@ -147,11 +147,7 @@ mod paint { ) } - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - self.strokes.len().hash(state); - } + fn hash_layout(&self, _state: &mut Hasher) {} fn on_event( &mut self, -- cgit From a008b8b54176e7f24c553285792cf3e32a8cc0ba Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 23:41:24 +0900 Subject: Change to bezier tool --- examples/paint.rs | 166 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 103 insertions(+), 63 deletions(-) diff --git a/examples/paint.rs b/examples/paint.rs index 18ac2d02..6f99ebe2 100644 --- a/examples/paint.rs +++ b/examples/paint.rs @@ -1,5 +1,6 @@ -//! This example showcases a simple native MS paint like application. -mod paint { +//! 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. // @@ -17,42 +18,39 @@ mod paint { triangle::{Mesh2D, Vertex2D}, Primitive, Renderer, }; - use lyon::lyon_tessellation::{ + use lyon::tessellation::{ basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, StrokeTessellator, VertexBuffers, }; use std::sync::Arc; - pub struct Paint<'a, Message> { - state: &'a mut State, - strokes: &'a [(Point, Point)], - on_stroke: Box Message>, + pub struct Bezier<'a, Message> { + pending_points: &'a [Point], + // [from, to, ctrl] + bezier_points: &'a [[Point; 3]], + on_click: Box Message>, } - impl<'a, Message> Paint<'a, Message> { + impl<'a, Message> Bezier<'a, Message> { pub fn new( - strokes: &'a [(Point, Point)], - state: &'a mut State, - on_stroke: F, + bezier_points: &'a [[Point; 3]], + pending_points: &'a [Point], + on_click: F, ) -> Self where - F: 'static + Fn((Point, Point)) -> Message, + F: 'static + Fn(Point) -> Message, { + assert!(pending_points.len() < 3); + Self { - state, - strokes, - on_stroke: Box::new(on_stroke), + bezier_points, + pending_points, + on_click: Box::new(on_click), } } } - #[derive(Debug, Clone, Copy, Default)] - pub struct State { - is_dragging: bool, - previous_mouse_pos: Option, - } - - impl<'a, Message> Widget for Paint<'a, Message> { + impl<'a, Message> Widget for Bezier<'a, Message> { fn width(&self) -> Length { Length::Fill } @@ -77,7 +75,7 @@ mod paint { &self, _renderer: &mut Renderer, layout: Layout<'_>, - _cursor_position: Point, + cursor_position: Point, ) -> (Primitive, MouseCursor) { let mut buffer: VertexBuffers = VertexBuffers::new(); let mut path_builder = lyon::path::Path::builder(); @@ -104,15 +102,55 @@ mod paint { ) .unwrap(); - for (from, to) in self.strokes { + for pts in self.bezier_points { path_builder.move_to(lyon::math::Point::new( - from.x + bounds.x, - from.y + bounds.y, - )); - path_builder.line_to(lyon::math::Point::new( - to.x + bounds.x, - to.y + bounds.y, + pts[0].x + bounds.x, + pts[0].y + bounds.y, )); + + path_builder.quadratic_bezier_to( + lyon::math::Point::new( + pts[2].x + bounds.x, + pts[2].y + bounds.y, + ), + lyon::math::Point::new( + pts[1].x + bounds.x, + pts[1].y + bounds.y, + ), + ); + } + + match self.pending_points.len() { + 0 => {} + 1 => { + path_builder.move_to(lyon::math::Point::new( + self.pending_points[0].x + bounds.x, + self.pending_points[0].y + bounds.y, + )); + path_builder.line_to(lyon::math::Point::new( + cursor_position.x, + cursor_position.y, + )); + } + 2 => { + path_builder.move_to(lyon::math::Point::new( + self.pending_points[0].x + bounds.x, + self.pending_points[0].y + bounds.y, + )); + path_builder.quadratic_bezier_to( + lyon::math::Point::new( + cursor_position.x, + cursor_position.y, + ), + lyon::math::Point::new( + self.pending_points[1].x + bounds.x, + self.pending_points[1].y + bounds.y, + ), + ); + } + _ => { + unreachable!(); + } } let mut tessellator = StrokeTessellator::new(); @@ -153,32 +191,21 @@ mod paint { &mut self, event: Event, layout: Layout<'_>, - _cursor_position: Point, + cursor_position: Point, messages: &mut Vec, _renderer: &Renderer, _clipboard: Option<&dyn Clipboard>, ) { let bounds = layout.bounds(); match event { - Event::Mouse(input::mouse::Event::CursorMoved { x, y }) - if bounds.contains(Point::new(x, y)) => - { - let pos = Point::new(x - bounds.x, y - bounds.y); - if self.state.is_dragging { - if let Some(prev) = self.state.previous_mouse_pos { - messages.push((self.on_stroke)((prev, pos))); - } - } - self.state.previous_mouse_pos = Some(pos); - } Event::Mouse(input::mouse::Event::Input { state, .. }) => { - match state { - input::ButtonState::Pressed => { - self.state.is_dragging = true; - } - input::ButtonState::Released => { - self.state.is_dragging = false; - } + if state == input::ButtonState::Pressed + && bounds.contains(cursor_position) + { + messages.push((self.on_click)(Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ))); } } _ => {} @@ -186,7 +213,7 @@ mod paint { } } - impl<'a, Message> Into> for Paint<'a, Message> + impl<'a, Message> Into> for Bezier<'a, Message> where Message: 'static, { @@ -196,12 +223,12 @@ mod paint { } } +use bezier::Bezier; use iced::{ button, Align, Button, Color, Column, Container, Element, Length, Sandbox, Settings, Text, }; use iced_native::Point; -use paint::Paint; pub fn main() { Example::run(Settings::default()) @@ -209,14 +236,14 @@ pub fn main() { #[derive(Default)] struct Example { - paint_state: paint::State, - strokes: Vec<(Point, Point)>, + bezier_points: Vec<[Point; 3]>, + pending_points: Vec, button_state: button::State, } #[derive(Debug, Clone, Copy)] enum Message { - Stroke((Point, Point)), + AddPoint(Point), Clear, } @@ -228,16 +255,25 @@ impl Sandbox for Example { } fn title(&self) -> String { - String::from("Paint - Iced") + String::from("Bezier tool - Iced") } fn update(&mut self, message: Message) { match message { - Message::Stroke(stroke) => { - self.strokes.push(stroke); + Message::AddPoint(point) => { + self.pending_points.push(point); + if self.pending_points.len() == 3 { + self.bezier_points.push([ + self.pending_points[0], + self.pending_points[1], + self.pending_points[2], + ]); + self.pending_points.clear(); + } } Message::Clear => { - self.strokes.clear(); + self.bezier_points.clear(); + self.pending_points.clear(); } } } @@ -247,11 +283,15 @@ impl Sandbox for Example { .padding(20) .spacing(20) .align_items(Align::Center) - .push(Text::new("Paint example").width(Length::Shrink).size(50)) - .push(Paint::new( - self.strokes.as_slice(), - &mut self.paint_state, - Message::Stroke, + .push( + Text::new("Bezier tool example") + .width(Length::Shrink) + .size(50), + ) + .push(Bezier::new( + self.bezier_points.as_slice(), + self.pending_points.as_slice(), + Message::AddPoint, )) .push( Button::new(&mut self.button_state, Text::new("Clear")) -- cgit From dd5edbc6ee791c7c4323ff4f02c21bc470d0a537 Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 23:42:42 +0900 Subject: Rename filename --- examples/bezier_tool.rs | 311 ++++++++++++++++++++++++++++++++++++++++++++++++ examples/paint.rs | 311 ------------------------------------------------ 2 files changed, 311 insertions(+), 311 deletions(-) create mode 100644 examples/bezier_tool.rs delete mode 100644 examples/paint.rs diff --git a/examples/bezier_tool.rs b/examples/bezier_tool.rs new file mode 100644 index 00000000..6f99ebe2 --- /dev/null +++ b/examples/bezier_tool.rs @@ -0,0 +1,311 @@ +//! 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, Element, Event, Hasher, Layout, Length, + MouseCursor, Point, Size, Vector, Widget, + }; + use iced_wgpu::{ + triangle::{Mesh2D, Vertex2D}, + Primitive, Renderer, + }; + use lyon::tessellation::{ + basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, + StrokeTessellator, VertexBuffers, + }; + use std::sync::Arc; + + pub struct Bezier<'a, Message> { + pending_points: &'a [Point], + // [from, to, ctrl] + bezier_points: &'a [[Point; 3]], + on_click: Box Message>, + } + + impl<'a, Message> Bezier<'a, Message> { + pub fn new( + bezier_points: &'a [[Point; 3]], + pending_points: &'a [Point], + on_click: F, + ) -> Self + where + F: 'static + Fn(Point) -> Message, + { + assert!(pending_points.len() < 3); + + Self { + bezier_points, + pending_points, + on_click: Box::new(on_click), + } + } + } + + impl<'a, Message> Widget 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, + layout: Layout<'_>, + cursor_position: Point, + ) -> (Primitive, MouseCursor) { + let mut buffer: VertexBuffers = 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(bounds.x + 0.5, bounds.y + 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 pts in self.bezier_points { + path_builder.move_to(lyon::math::Point::new( + pts[0].x + bounds.x, + pts[0].y + bounds.y, + )); + + path_builder.quadratic_bezier_to( + lyon::math::Point::new( + pts[2].x + bounds.x, + pts[2].y + bounds.y, + ), + lyon::math::Point::new( + pts[1].x + bounds.x, + pts[1].y + bounds.y, + ), + ); + } + + match self.pending_points.len() { + 0 => {} + 1 => { + path_builder.move_to(lyon::math::Point::new( + self.pending_points[0].x + bounds.x, + self.pending_points[0].y + bounds.y, + )); + path_builder.line_to(lyon::math::Point::new( + cursor_position.x, + cursor_position.y, + )); + } + 2 => { + path_builder.move_to(lyon::math::Point::new( + self.pending_points[0].x + bounds.x, + self.pending_points[0].y + bounds.y, + )); + path_builder.quadratic_bezier_to( + lyon::math::Point::new( + cursor_position.x, + cursor_position.y, + ), + lyon::math::Point::new( + self.pending_points[1].x + bounds.x, + self.pending_points[1].y + bounds.y, + ), + ); + } + _ => { + unreachable!(); + } + } + + 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(); + + ( + Primitive::Clip { + bounds, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Mesh2D(Arc::new(Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }))), + }, + MouseCursor::OutOfBounds, + ) + } + + fn hash_layout(&self, _state: &mut Hasher) {} + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + match event { + Event::Mouse(input::mouse::Event::Input { state, .. }) => { + if state == input::ButtonState::Pressed + && bounds.contains(cursor_position) + { + messages.push((self.on_click)(Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ))); + } + } + _ => {} + } + } + } + + impl<'a, Message> Into> for Bezier<'a, Message> + where + Message: 'static, + { + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } + } +} + +use bezier::Bezier; +use iced::{ + button, Align, Button, Color, Column, Container, Element, Length, Sandbox, + Settings, Text, +}; +use iced_native::Point; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + bezier_points: Vec<[Point; 3]>, + pending_points: Vec, + button_state: button::State, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + AddPoint(Point), + Clear, +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Example::default() + } + + fn title(&self) -> String { + String::from("Bezier tool - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::AddPoint(point) => { + self.pending_points.push(point); + if self.pending_points.len() == 3 { + self.bezier_points.push([ + self.pending_points[0], + self.pending_points[1], + self.pending_points[2], + ]); + self.pending_points.clear(); + } + } + Message::Clear => { + self.bezier_points.clear(); + self.pending_points.clear(); + } + } + } + + fn view(&mut self) -> Element { + let content = Column::new() + .padding(20) + .spacing(20) + .align_items(Align::Center) + .push( + Text::new("Bezier tool example") + .width(Length::Shrink) + .size(50), + ) + .push(Bezier::new( + self.bezier_points.as_slice(), + self.pending_points.as_slice(), + Message::AddPoint, + )) + .push( + Button::new(&mut self.button_state, Text::new("Clear")) + .padding(8) + .background(Color::from_rgb(0.5, 0.5, 0.5)) + .border_radius(4) + .on_press(Message::Clear), + ); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/paint.rs b/examples/paint.rs deleted file mode 100644 index 6f99ebe2..00000000 --- a/examples/paint.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! 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, Element, Event, Hasher, Layout, Length, - MouseCursor, Point, Size, Vector, Widget, - }; - use iced_wgpu::{ - triangle::{Mesh2D, Vertex2D}, - Primitive, Renderer, - }; - use lyon::tessellation::{ - basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, - StrokeTessellator, VertexBuffers, - }; - use std::sync::Arc; - - pub struct Bezier<'a, Message> { - pending_points: &'a [Point], - // [from, to, ctrl] - bezier_points: &'a [[Point; 3]], - on_click: Box Message>, - } - - impl<'a, Message> Bezier<'a, Message> { - pub fn new( - bezier_points: &'a [[Point; 3]], - pending_points: &'a [Point], - on_click: F, - ) -> Self - where - F: 'static + Fn(Point) -> Message, - { - assert!(pending_points.len() < 3); - - Self { - bezier_points, - pending_points, - on_click: Box::new(on_click), - } - } - } - - impl<'a, Message> Widget 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, - layout: Layout<'_>, - cursor_position: Point, - ) -> (Primitive, MouseCursor) { - let mut buffer: VertexBuffers = 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(bounds.x + 0.5, bounds.y + 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 pts in self.bezier_points { - path_builder.move_to(lyon::math::Point::new( - pts[0].x + bounds.x, - pts[0].y + bounds.y, - )); - - path_builder.quadratic_bezier_to( - lyon::math::Point::new( - pts[2].x + bounds.x, - pts[2].y + bounds.y, - ), - lyon::math::Point::new( - pts[1].x + bounds.x, - pts[1].y + bounds.y, - ), - ); - } - - match self.pending_points.len() { - 0 => {} - 1 => { - path_builder.move_to(lyon::math::Point::new( - self.pending_points[0].x + bounds.x, - self.pending_points[0].y + bounds.y, - )); - path_builder.line_to(lyon::math::Point::new( - cursor_position.x, - cursor_position.y, - )); - } - 2 => { - path_builder.move_to(lyon::math::Point::new( - self.pending_points[0].x + bounds.x, - self.pending_points[0].y + bounds.y, - )); - path_builder.quadratic_bezier_to( - lyon::math::Point::new( - cursor_position.x, - cursor_position.y, - ), - lyon::math::Point::new( - self.pending_points[1].x + bounds.x, - self.pending_points[1].y + bounds.y, - ), - ); - } - _ => { - unreachable!(); - } - } - - 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(); - - ( - Primitive::Clip { - bounds, - offset: Vector::new(0, 0), - content: Box::new(Primitive::Mesh2D(Arc::new(Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }))), - }, - MouseCursor::OutOfBounds, - ) - } - - fn hash_layout(&self, _state: &mut Hasher) {} - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - let bounds = layout.bounds(); - match event { - Event::Mouse(input::mouse::Event::Input { state, .. }) => { - if state == input::ButtonState::Pressed - && bounds.contains(cursor_position) - { - messages.push((self.on_click)(Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ))); - } - } - _ => {} - } - } - } - - impl<'a, Message> Into> for Bezier<'a, Message> - where - Message: 'static, - { - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } - } -} - -use bezier::Bezier; -use iced::{ - button, Align, Button, Color, Column, Container, Element, Length, Sandbox, - Settings, Text, -}; -use iced_native::Point; - -pub fn main() { - Example::run(Settings::default()) -} - -#[derive(Default)] -struct Example { - bezier_points: Vec<[Point; 3]>, - pending_points: Vec, - button_state: button::State, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - AddPoint(Point), - Clear, -} - -impl Sandbox for Example { - type Message = Message; - - fn new() -> Self { - Example::default() - } - - fn title(&self) -> String { - String::from("Bezier tool - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::AddPoint(point) => { - self.pending_points.push(point); - if self.pending_points.len() == 3 { - self.bezier_points.push([ - self.pending_points[0], - self.pending_points[1], - self.pending_points[2], - ]); - self.pending_points.clear(); - } - } - Message::Clear => { - self.bezier_points.clear(); - self.pending_points.clear(); - } - } - } - - fn view(&mut self) -> Element { - let content = Column::new() - .padding(20) - .spacing(20) - .align_items(Align::Center) - .push( - Text::new("Bezier tool example") - .width(Length::Shrink) - .size(50), - ) - .push(Bezier::new( - self.bezier_points.as_slice(), - self.pending_points.as_slice(), - Message::AddPoint, - )) - .push( - Button::new(&mut self.button_state, Text::new("Clear")) - .padding(8) - .background(Color::from_rgb(0.5, 0.5, 0.5)) - .border_radius(4) - .on_press(Message::Clear), - ); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} -- cgit From e879982cfdf0c6a1c6781a9bc46e0a77839de88f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Jan 2020 00:40:27 +0100 Subject: Improve state guarantees in `bezier_tool` --- examples/bezier_tool.rs | 141 ++++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/examples/bezier_tool.rs b/examples/bezier_tool.rs index 6f99ebe2..fbf5879b 100644 --- a/examples/bezier_tool.rs +++ b/examples/bezier_tool.rs @@ -25,26 +25,41 @@ mod bezier { use std::sync::Arc; pub struct Bezier<'a, Message> { - pending_points: &'a [Point], + state: &'a mut State, + curves: &'a [Curve], // [from, to, ctrl] - bezier_points: &'a [[Point; 3]], - on_click: Box Message>, + on_click: Box Message>, + } + + #[derive(Debug, Clone, Copy)] + pub struct Curve { + from: Point, + to: Point, + control: Point, + } + + #[derive(Default)] + pub struct State { + pending: Option, + } + + enum Pending { + One { from: Point }, + Two { from: Point, to: Point }, } impl<'a, Message> Bezier<'a, Message> { pub fn new( - bezier_points: &'a [[Point; 3]], - pending_points: &'a [Point], + state: &'a mut State, + curves: &'a [Curve], on_click: F, ) -> Self where - F: 'static + Fn(Point) -> Message, + F: 'static + Fn(Curve) -> Message, { - assert!(pending_points.len() < 3); - Self { - bezier_points, - pending_points, + state, + curves, on_click: Box::new(on_click), } } @@ -102,40 +117,40 @@ mod bezier { ) .unwrap(); - for pts in self.bezier_points { + for curve in self.curves { path_builder.move_to(lyon::math::Point::new( - pts[0].x + bounds.x, - pts[0].y + bounds.y, + curve.from.x + bounds.x, + curve.from.y + bounds.y, )); path_builder.quadratic_bezier_to( lyon::math::Point::new( - pts[2].x + bounds.x, - pts[2].y + bounds.y, + curve.control.x + bounds.x, + curve.control.y + bounds.y, ), lyon::math::Point::new( - pts[1].x + bounds.x, - pts[1].y + bounds.y, + curve.to.x + bounds.x, + curve.to.y + bounds.y, ), ); } - match self.pending_points.len() { - 0 => {} - 1 => { + match self.state.pending { + None => {} + Some(Pending::One { from }) => { path_builder.move_to(lyon::math::Point::new( - self.pending_points[0].x + bounds.x, - self.pending_points[0].y + bounds.y, + from.x + bounds.x, + from.y + bounds.y, )); path_builder.line_to(lyon::math::Point::new( cursor_position.x, cursor_position.y, )); } - 2 => { + Some(Pending::Two { from, to }) => { path_builder.move_to(lyon::math::Point::new( - self.pending_points[0].x + bounds.x, - self.pending_points[0].y + bounds.y, + from.x + bounds.x, + from.y + bounds.y, )); path_builder.quadratic_bezier_to( lyon::math::Point::new( @@ -143,14 +158,11 @@ mod bezier { cursor_position.y, ), lyon::math::Point::new( - self.pending_points[1].x + bounds.x, - self.pending_points[1].y + bounds.y, + to.x + bounds.x, + to.y + bounds.y, ), ); } - _ => { - unreachable!(); - } } let mut tessellator = StrokeTessellator::new(); @@ -197,18 +209,42 @@ mod bezier { _clipboard: Option<&dyn Clipboard>, ) { let bounds = layout.bounds(); - match event { - Event::Mouse(input::mouse::Event::Input { state, .. }) => { - if state == input::ButtonState::Pressed - && bounds.contains(cursor_position) - { - messages.push((self.on_click)(Point::new( + + 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, + })); + } + } } + _ => {} } - _ => {} } } } @@ -228,7 +264,6 @@ use iced::{ button, Align, Button, Color, Column, Container, Element, Length, Sandbox, Settings, Text, }; -use iced_native::Point; pub fn main() { Example::run(Settings::default()) @@ -236,14 +271,14 @@ pub fn main() { #[derive(Default)] struct Example { - bezier_points: Vec<[Point; 3]>, - pending_points: Vec, + bezier: bezier::State, + curves: Vec, button_state: button::State, } #[derive(Debug, Clone, Copy)] enum Message { - AddPoint(Point), + AddCurve(bezier::Curve), Clear, } @@ -260,20 +295,12 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { - Message::AddPoint(point) => { - self.pending_points.push(point); - if self.pending_points.len() == 3 { - self.bezier_points.push([ - self.pending_points[0], - self.pending_points[1], - self.pending_points[2], - ]); - self.pending_points.clear(); - } + Message::AddCurve(curve) => { + self.curves.push(curve); } Message::Clear => { - self.bezier_points.clear(); - self.pending_points.clear(); + self.bezier = bezier::State::default(); + self.curves.clear(); } } } @@ -289,9 +316,9 @@ impl Sandbox for Example { .size(50), ) .push(Bezier::new( - self.bezier_points.as_slice(), - self.pending_points.as_slice(), - Message::AddPoint, + &mut self.bezier, + self.curves.as_slice(), + Message::AddCurve, )) .push( Button::new(&mut self.button_state, Text::new("Clear")) -- cgit From 351d90c33999179b03bdb5e9c0575a197e98eff8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Jan 2020 00:48:12 +0100 Subject: Fix `bezier_tool` example --- examples/bezier_tool.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/bezier_tool.rs b/examples/bezier_tool.rs index fbf5879b..4cb6312f 100644 --- a/examples/bezier_tool.rs +++ b/examples/bezier_tool.rs @@ -16,7 +16,7 @@ mod bezier { }; use iced_wgpu::{ triangle::{Mesh2D, Vertex2D}, - Primitive, Renderer, + Defaults, Primitive, Renderer, }; use lyon::tessellation::{ basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, @@ -89,6 +89,7 @@ mod bezier { fn draw( &self, _renderer: &mut Renderer, + _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, ) -> (Primitive, MouseCursor) { @@ -261,7 +262,7 @@ mod bezier { use bezier::Bezier; use iced::{ - button, Align, Button, Color, Column, Container, Element, Length, Sandbox, + button, Align, Button, Column, Container, Element, Length, Sandbox, Settings, Text, }; @@ -323,8 +324,6 @@ impl Sandbox for Example { .push( Button::new(&mut self.button_state, Text::new("Clear")) .padding(8) - .background(Color::from_rgb(0.5, 0.5, 0.5)) - .border_radius(4) .on_press(Message::Clear), ); -- cgit From dba538eb4d1df62cf9b5a347f04a52e8535917ad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 11 Jan 2020 01:10:59 +0100 Subject: Add instructions to `bezier_tool` example --- examples/bezier_tool.rs | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/examples/bezier_tool.rs b/examples/bezier_tool.rs index 4cb6312f..043d265c 100644 --- a/examples/bezier_tool.rs +++ b/examples/bezier_tool.rs @@ -11,8 +11,9 @@ mod bezier { // 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, Element, Event, Hasher, Layout, Length, - MouseCursor, Point, Size, Vector, Widget, + input, layout, Clipboard, Color, Element, Event, Font, Hasher, + HorizontalAlignment, Layout, Length, MouseCursor, Point, Size, Vector, + VerticalAlignment, Widget, }; use iced_wgpu::{ triangle::{Mesh2D, Vertex2D}, @@ -89,7 +90,7 @@ mod bezier { fn draw( &self, _renderer: &mut Renderer, - _defaults: &Defaults, + defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, ) -> (Primitive, MouseCursor) { @@ -185,14 +186,42 @@ mod bezier { ) .unwrap(); + let mesh = Primitive::Mesh2D(Arc::new(Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + })); + ( Primitive::Clip { bounds, offset: Vector::new(0, 0), - content: Box::new(Primitive::Mesh2D(Arc::new(Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - }))), + 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, ) -- cgit