From e7df33d752283368c2c2aa3ca96bbb52efb8242c Mon Sep 17 00:00:00 2001 From: hatoo Date: Mon, 6 Jan 2020 17:58:17 +0900 Subject: [PATCH 1/7] 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() + } +} From 6632ce5fb06e940f6b72cfad62d9fc83fe45702f Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 19:06:43 +0900 Subject: [PATCH 2/7] 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, From a008b8b54176e7f24c553285792cf3e32a8cc0ba Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 23:41:24 +0900 Subject: [PATCH 3/7] 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")) From dd5edbc6ee791c7c4323ff4f02c21bc470d0a537 Mon Sep 17 00:00:00 2001 From: hatoo Date: Wed, 8 Jan 2020 23:42:42 +0900 Subject: [PATCH 4/7] Rename filename --- examples/{paint.rs => bezier_tool.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{paint.rs => bezier_tool.rs} (100%) diff --git a/examples/paint.rs b/examples/bezier_tool.rs similarity index 100% rename from examples/paint.rs rename to examples/bezier_tool.rs From e879982cfdf0c6a1c6781a9bc46e0a77839de88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 11 Jan 2020 00:40:27 +0100 Subject: [PATCH 5/7] 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")) From 351d90c33999179b03bdb5e9c0575a197e98eff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 11 Jan 2020 00:48:12 +0100 Subject: [PATCH 6/7] 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), ); From dba538eb4d1df62cf9b5a347f04a52e8535917ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 11 Jan 2020 01:10:59 +0100 Subject: [PATCH 7/7] 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, )