summaryrefslogtreecommitdiffstats
path: root/examples/bezier_tool
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-01-20 06:27:01 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-01-20 06:27:01 +0100
commit7cea7371150e6de28032827519936008592f112d (patch)
tree3bfd82272094ef69493de622af6c9b06389a7c27 /examples/bezier_tool
parent04086a90c9e933ebfb42de378054e1115b33529d (diff)
downloadiced-7cea7371150e6de28032827519936008592f112d.tar.gz
iced-7cea7371150e6de28032827519936008592f112d.tar.bz2
iced-7cea7371150e6de28032827519936008592f112d.zip
Package examples and remove `dev-dependencies`
Diffstat (limited to 'examples/bezier_tool')
-rw-r--r--examples/bezier_tool/Cargo.toml12
-rw-r--r--examples/bezier_tool/src/main.rs366
2 files changed, 378 insertions, 0 deletions
diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml
new file mode 100644
index 00000000..b13a0aa5
--- /dev/null
+++ b/examples/bezier_tool/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "bezier_tool"
+version = "0.1.0"
+authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../.." }
+iced_native = { path = "../../native" }
+iced_wgpu = { path = "../../wgpu" }
+lyon = "0.15"
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
new file mode 100644
index 00000000..043d265c
--- /dev/null
+++ b/examples/bezier_tool/src/main.rs
@@ -0,0 +1,366 @@
+//! 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, Color, Element, Event, Font, Hasher,
+ HorizontalAlignment, Layout, Length, MouseCursor, Point, Size, Vector,
+ VerticalAlignment, Widget,
+ };
+ use iced_wgpu::{
+ triangle::{Mesh2D, Vertex2D},
+ Defaults, Primitive, Renderer,
+ };
+ use lyon::tessellation::{
+ basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions,
+ StrokeTessellator, VertexBuffers,
+ };
+ use std::sync::Arc;
+
+ pub struct Bezier<'a, Message> {
+ state: &'a mut State,
+ curves: &'a [Curve],
+ // [from, to, ctrl]
+ on_click: Box<dyn Fn(Curve) -> Message>,
+ }
+
+ #[derive(Debug, Clone, Copy)]
+ pub struct Curve {
+ from: Point,
+ to: Point,
+ control: Point,
+ }
+
+ #[derive(Default)]
+ pub struct State {
+ pending: Option<Pending>,
+ }
+
+ enum Pending {
+ One { from: Point },
+ Two { from: Point, to: Point },
+ }
+
+ impl<'a, Message> Bezier<'a, Message> {
+ pub fn new<F>(
+ state: &'a mut State,
+ curves: &'a [Curve],
+ on_click: F,
+ ) -> Self
+ where
+ F: 'static + Fn(Curve) -> Message,
+ {
+ Self {
+ state,
+ curves,
+ 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,
+ defaults: &Defaults,
+ 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 curve in self.curves {
+ path_builder.move_to(lyon::math::Point::new(
+ curve.from.x + bounds.x,
+ curve.from.y + bounds.y,
+ ));
+
+ path_builder.quadratic_bezier_to(
+ lyon::math::Point::new(
+ curve.control.x + bounds.x,
+ curve.control.y + bounds.y,
+ ),
+ lyon::math::Point::new(
+ curve.to.x + bounds.x,
+ curve.to.y + bounds.y,
+ ),
+ );
+ }
+
+ match self.state.pending {
+ None => {}
+ Some(Pending::One { from }) => {
+ path_builder.move_to(lyon::math::Point::new(
+ from.x + bounds.x,
+ from.y + bounds.y,
+ ));
+ path_builder.line_to(lyon::math::Point::new(
+ cursor_position.x,
+ cursor_position.y,
+ ));
+ }
+ Some(Pending::Two { from, to }) => {
+ path_builder.move_to(lyon::math::Point::new(
+ from.x + bounds.x,
+ from.y + bounds.y,
+ ));
+ path_builder.quadratic_bezier_to(
+ lyon::math::Point::new(
+ cursor_position.x,
+ cursor_position.y,
+ ),
+ 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();
+
+ let mesh = Primitive::Mesh2D(Arc::new(Mesh2D {
+ vertices: buffer.vertices,
+ indices: buffer.indices,
+ }));
+
+ (
+ Primitive::Clip {
+ bounds,
+ offset: Vector::new(0, 0),
+ 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,
+ )
+ }
+
+ 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();
+
+ 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,
+ }));
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+
+ 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, Column, Container, Element, Length, Sandbox,
+ Settings, Text,
+};
+
+pub fn main() {
+ Example::run(Settings::default())
+}
+
+#[derive(Default)]
+struct Example {
+ bezier: bezier::State,
+ curves: Vec<bezier::Curve>,
+ button_state: button::State,
+}
+
+#[derive(Debug, Clone, Copy)]
+enum Message {
+ AddCurve(bezier::Curve),
+ 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::AddCurve(curve) => {
+ self.curves.push(curve);
+ }
+ Message::Clear => {
+ self.bezier = bezier::State::default();
+ self.curves.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(
+ &mut self.bezier,
+ self.curves.as_slice(),
+ Message::AddCurve,
+ ))
+ .push(
+ Button::new(&mut self.button_state, Text::new("Clear"))
+ .padding(8)
+ .on_press(Message::Clear),
+ );
+
+ Container::new(content)
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .center_x()
+ .center_y()
+ .into()
+ }
+}