commit
bad1bab9e8
@ -48,6 +48,7 @@ futures = "0.3"
|
|||||||
async-std = { version = "1.3", features = ["unstable"] }
|
async-std = { version = "1.3", features = ["unstable"] }
|
||||||
surf = "1.0"
|
surf = "1.0"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
|
lyon = "0.15"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
wasm-bindgen = "0.2.51"
|
wasm-bindgen = "0.2.51"
|
||||||
|
366
examples/bezier_tool.rs
Normal file
366
examples/bezier_tool.rs
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user