//! 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() } }