Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Canvas ergonomics #293

Merged
merged 10 commits into from
Apr 15, 2020
81 changes: 35 additions & 46 deletions examples/clock/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,68 +95,57 @@ impl From<chrono::DateTime<chrono::Local>> for LocalTime {

impl canvas::Drawable for LocalTime {
fn draw(&self, frame: &mut canvas::Frame) {
use canvas::Path;

let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0;
let offset = Vector::new(center.x, center.y);

let clock = canvas::Path::new(|path| path.circle(center, radius));
let clock = Path::circle(center, radius);
frame.fill(&clock, Color::from_rgb8(0x12, 0x93, 0xD8));

frame.fill(
&clock,
canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)),
);
let short_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius));

fn draw_hand(
n: u32,
total: u32,
length: f32,
offset: Vector,
path: &mut canvas::path::Builder,
) {
let turns = n as f32 / total as f32;
let t = 2.0 * std::f32::consts::PI * (turns - 0.25);
let long_hand =
Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius));

let x = length * t.cos();
let y = length * t.sin();
let thin_stroke = canvas::Stroke {
width: radius / 100.0,
color: Color::WHITE,
line_cap: canvas::LineCap::Round,
..canvas::Stroke::default()
};

path.line_to(Point::new(x, y) + offset);
}
let wide_stroke = canvas::Stroke {
width: thin_stroke.width * 3.0,
..thin_stroke
};

let hour_and_minute_hands = canvas::Path::new(|path| {
path.move_to(center);
draw_hand(self.hour, 12, 0.5 * radius, offset, path);
frame.translate(Vector::new(center.x, center.y));

path.move_to(center);
draw_hand(self.minute, 60, 0.8 * radius, offset, path)
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.hour, 12));
frame.stroke(&short_hand, wide_stroke);
});

frame.stroke(
&hour_and_minute_hands,
canvas::Stroke {
width: radius / 100.0 * 3.0,
color: Color::WHITE,
line_cap: canvas::LineCap::Round,
..canvas::Stroke::default()
},
);

let second_hand = canvas::Path::new(|path| {
path.move_to(center);
draw_hand(self.second, 60, 0.8 * radius, offset, path)
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.minute, 60));
frame.stroke(&long_hand, wide_stroke);
});

frame.stroke(
&second_hand,
canvas::Stroke {
width: radius / 100.0,
color: Color::WHITE,
line_cap: canvas::LineCap::Round,
..canvas::Stroke::default()
},
);
frame.with_save(|frame| {
frame.rotate(hand_rotation(self.second, 60));
frame.stroke(&long_hand, thin_stroke);
});
}
}

fn hand_rotation(n: u32, total: u32) -> f32 {
let turns = n as f32 / total as f32;

2.0 * std::f32::consts::PI * turns
}

mod time {
use iced::futures;

Expand Down
47 changes: 17 additions & 30 deletions examples/solar_system/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,27 +128,25 @@ impl State {

impl canvas::Drawable for State {
fn draw(&self, frame: &mut canvas::Frame) {
use canvas::{Fill, Path, Stroke};
use canvas::{Path, Stroke};
use std::f32::consts::PI;

let center = frame.center();

let space = Path::new(|path| {
path.rectangle(Point::new(0.0, 0.0), frame.size())
});
let space = Path::rectangle(Point::new(0.0, 0.0), frame.size());

let stars = Path::new(|path| {
for (p, size) in &self.stars {
path.rectangle(*p, Size::new(*size, *size));
}
});

let sun = Path::new(|path| path.circle(center, Self::SUN_RADIUS));
let orbit = Path::new(|path| path.circle(center, Self::ORBIT_RADIUS));
let sun = Path::circle(center, Self::SUN_RADIUS);
let orbit = Path::circle(center, Self::ORBIT_RADIUS);

frame.fill(&space, Fill::Color(Color::BLACK));
frame.fill(&stars, Fill::Color(Color::WHITE));
frame.fill(&sun, Fill::Color(Color::from_rgb8(0xF9, 0xD7, 0x1C)));
frame.fill(&space, Color::BLACK);
frame.fill(&stars, Color::WHITE);
frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C));
frame.stroke(
&orbit,
Stroke {
Expand All @@ -170,21 +168,13 @@ impl canvas::Drawable for State {
);
frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0));

let earth = Path::new(|path| {
path.circle(Point::ORIGIN, Self::EARTH_RADIUS)
});

let shadow = Path::new(|path| {
path.rectangle(
Point::new(0.0, -Self::EARTH_RADIUS),
Size::new(
Self::EARTH_RADIUS * 4.0,
Self::EARTH_RADIUS * 2.0,
),
)
});
let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS);
let shadow = Path::rectangle(
Point::new(0.0, -Self::EARTH_RADIUS),
Size::new(Self::EARTH_RADIUS * 4.0, Self::EARTH_RADIUS * 2.0),
);

frame.fill(&earth, Fill::Color(Color::from_rgb8(0x6B, 0x93, 0xD6)));
frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6));

frame.with_save(|frame| {
frame.rotate(
Expand All @@ -193,19 +183,16 @@ impl canvas::Drawable for State {
);
frame.translate(Vector::new(0.0, Self::MOON_DISTANCE));

let moon = Path::new(|path| {
path.circle(Point::ORIGIN, Self::MOON_RADIUS)
});

frame.fill(&moon, Fill::Color(Color::WHITE));
let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS);
frame.fill(&moon, Color::WHITE);
});

frame.fill(
&shadow,
Fill::Color(Color {
Color {
a: 0.7,
..Color::BLACK
}),
},
);
});
}
Expand Down
6 changes: 6 additions & 0 deletions wgpu/src/widget/canvas/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ impl Default for Fill {
Fill::Color(Color::BLACK)
}
}

impl From<Color> for Fill {
fn from(color: Color) -> Fill {
Fill::Color(color)
}
}
12 changes: 8 additions & 4 deletions wgpu/src/widget/canvas/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@ impl Frame {
///
/// [`Path`]: path/struct.Path.html
/// [`Frame`]: struct.Frame.html
pub fn fill(&mut self, path: &Path, fill: Fill) {
pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) {
use lyon::tessellation::{
BuffersBuilder, FillOptions, FillTessellator,
};

let mut buffers = BuffersBuilder::new(
&mut self.buffers,
FillVertex(match fill {
FillVertex(match fill.into() {
Fill::Color(color) => color.into_linear(),
}),
);
Expand Down Expand Up @@ -127,11 +127,13 @@ impl Frame {
///
/// [`Path`]: path/struct.Path.html
/// [`Frame`]: struct.Frame.html
pub fn stroke(&mut self, path: &Path, stroke: Stroke) {
pub fn stroke(&mut self, path: &Path, stroke: impl Into<Stroke>) {
use lyon::tessellation::{
BuffersBuilder, StrokeOptions, StrokeTessellator,
};

let stroke = stroke.into();

let mut buffers = BuffersBuilder::new(
&mut self.buffers,
StrokeVertex(stroke.color.into_linear()),
Expand Down Expand Up @@ -173,9 +175,11 @@ impl Frame {
/// [`Text`]: struct.Text.html
/// [`Frame`]: struct.Frame.html
/// [`Canvas`]: struct.Canvas.html
pub fn fill_text(&mut self, text: Text) {
pub fn fill_text(&mut self, text: impl Into<Text>) {
use std::f32;

let text = text.into();

let position = if self.transforms.current.is_identity {
text.position
} else {
Expand Down
29 changes: 29 additions & 0 deletions wgpu/src/widget/canvas/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod builder;
pub use arc::Arc;
pub use builder::Builder;

use iced_native::{Point, Size};

/// An immutable set of points that may or may not be connected.
///
/// A single [`Path`] can represent different kinds of 2D shapes!
Expand All @@ -33,6 +35,33 @@ impl Path {
builder.build()
}

/// Creates a new [`Path`] representing a line segment given its starting
/// and end points.
///
/// [`Path`]: struct.Path.html
pub fn line(from: Point, to: Point) -> Self {
Self::new(|p| {
p.move_to(from);
p.line_to(to);
})
}

/// Creates a new [`Path`] representing a rectangle given its top-left
/// corner coordinate and its `Size`.
///
/// [`Path`]: struct.Path.html
pub fn rectangle(top_left: Point, size: Size) -> Self {
Self::new(|p| p.rectangle(top_left, size))
}

/// Creates a new [`Path`] representing a circle given its center
/// coordinate and its radius.
///
/// [`Path`]: struct.Path.html
pub fn circle(center: Point, radius: f32) -> Self {
Self::new(|p| p.circle(center, radius))
}

#[inline]
pub(crate) fn raw(&self) -> &lyon::path::Path {
&self.raw
Expand Down
13 changes: 8 additions & 5 deletions wgpu/src/widget/canvas/path/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,14 @@ impl Builder {
///
/// [`Path`]: struct.Path.html
#[inline]
pub fn rectangle(&mut self, p: Point, size: Size) {
self.move_to(p);
self.line_to(Point::new(p.x + size.width, p.y));
self.line_to(Point::new(p.x + size.width, p.y + size.height));
self.line_to(Point::new(p.x, p.y + size.height));
pub fn rectangle(&mut self, top_left: Point, size: Size) {
hecrj marked this conversation as resolved.
Show resolved Hide resolved
self.move_to(top_left);
self.line_to(Point::new(top_left.x + size.width, top_left.y));
self.line_to(Point::new(
top_left.x + size.width,
top_left.y + size.height,
));
self.line_to(Point::new(top_left.x, top_left.y + size.height));
self.close();
}

Expand Down
15 changes: 15 additions & 0 deletions wgpu/src/widget/canvas/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,18 @@ impl Default for Text {
}
}
}

impl From<String> for Text {
fn from(content: String) -> Text {
Text {
content,
..Default::default()
}
}
}

impl From<&str> for Text {
fn from(content: &str) -> Text {
String::from(content).into()
}
}