summaryrefslogblamecommitdiffstats
path: root/examples/bezier_tool.rs
blob: 6f99ebe210d3aaaac225c09e5fb165068e158c4e (plain) (tree)
1
2
3


                                                                               
















                                                                              
                             




                                                                      




                                                

     
                                           
                      


                                            

                 
                                              
         

                                              
                  


                                             



             
                                                                         























                                     
                                   

























                                                                                
                                           
                                                            

                                        
                   











































                                                                

































                                                                         
                                                     




                               
                                   





                                               
                                                                           






                                                                 






                       
                                                                                  








                                                         
                   




                                                                              






                                     

                                   




                                
                    










                               
                                          



                                            









                                                   

                               

                                            








                                            








                                                
















                                                                       
//! 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<dyn Fn(Point) -> Message>,
    }

    impl<'a, Message> Bezier<'a, Message> {
        pub fn new<F>(
            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<Message, Renderer> 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<Vertex2D, u16> = 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<Message>,
            _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<Element<'a, Message, Renderer>> 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<Point>,
    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<Message> {
        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()
    }
}