From 25f9346160e80a51d4d88866ce6effa1abd8a02b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Tue, 18 Feb 2020 14:35:00 -0800 Subject: [PATCH] Add an incomplete port of the NanoVG example app --- Cargo.lock | 17 + Cargo.toml | 1 + examples/canvas_nanovg/Cargo.toml | 33 ++ examples/canvas_nanovg/LICENSE | 18 + examples/canvas_nanovg/src/main.rs | 562 +++++++++++++++++++++++++++++ 5 files changed, 631 insertions(+) create mode 100644 examples/canvas_nanovg/Cargo.toml create mode 100644 examples/canvas_nanovg/LICENSE create mode 100644 examples/canvas_nanovg/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 2a2a1fa3..08e1b167 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -267,6 +267,23 @@ dependencies = [ "sdl2-sys 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "canvas_nanovg" +version = "0.1.0" +dependencies = [ + "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "gl 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pathfinder_canvas 0.1.0", + "pathfinder_color 0.1.0", + "pathfinder_content 0.1.0", + "pathfinder_geometry 0.4.0", + "pathfinder_gl 0.1.0", + "pathfinder_gpu 0.1.0", + "pathfinder_renderer 0.1.0", + "sdl2 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2-sys 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "canvas_text" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 411822e5..db945194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "examples/canvas_metal_minimal", "examples/canvas_minimal", "examples/canvas_moire", + "examples/canvas_nanovg", "examples/canvas_text", "examples/lottie_basic", "examples/swf_basic", diff --git a/examples/canvas_nanovg/Cargo.toml b/examples/canvas_nanovg/Cargo.toml new file mode 100644 index 00000000..0a76cc63 --- /dev/null +++ b/examples/canvas_nanovg/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "canvas_nanovg" +version = "0.1.0" +authors = ["Patrick Walton "] +edition = "2018" + +[dependencies] +arrayvec = "0.5" +gl = "0.14" +sdl2 = "0.33" +sdl2-sys = "0.33" + +[dependencies.pathfinder_canvas] +path = "../../canvas" +features = ["pf-text"] + +[dependencies.pathfinder_color] +path = "../../color" + +[dependencies.pathfinder_content] +path = "../../content" + +[dependencies.pathfinder_geometry] +path = "../../geometry" + +[dependencies.pathfinder_gl] +path = "../../gl" + +[dependencies.pathfinder_gpu] +path = "../../gpu" + +[dependencies.pathfinder_renderer] +path = "../../renderer" diff --git a/examples/canvas_nanovg/LICENSE b/examples/canvas_nanovg/LICENSE new file mode 100644 index 00000000..2a03a1a6 --- /dev/null +++ b/examples/canvas_nanovg/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2013 Mikko Mononen memon@inside.org + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/examples/canvas_nanovg/src/main.rs b/examples/canvas_nanovg/src/main.rs new file mode 100644 index 00000000..3906574e --- /dev/null +++ b/examples/canvas_nanovg/src/main.rs @@ -0,0 +1,562 @@ +// pathfinder/examples/canvas_nanovg/src/main.rs +// +// Copyright © 2020 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use arrayvec::ArrayVec; +use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, LineJoin, Path2D}; +use pathfinder_color::{ColorF, ColorU}; +use pathfinder_content::fill::FillRule; +use pathfinder_content::gradient::{ColorStop, Gradient}; +use pathfinder_content::outline::ArcDirection; +use pathfinder_content::stroke::LineCap; +use pathfinder_geometry::angle; +use pathfinder_geometry::line_segment::LineSegment2F; +use pathfinder_geometry::rect::RectF; +use pathfinder_geometry::transform2d::Transform2F; +use pathfinder_geometry::util; +use pathfinder_geometry::vector::{Vector2F, Vector2I}; +use pathfinder_gl::{GLDevice, GLVersion}; +use pathfinder_gpu::resources::FilesystemResourceLoader; +use pathfinder_renderer::concurrent::rayon::RayonExecutor; +use pathfinder_renderer::concurrent::scene_proxy::SceneProxy; +use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; +use pathfinder_renderer::gpu::renderer::Renderer; +use pathfinder_renderer::options::BuildOptions; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use sdl2::video::GLProfile; +use std::f32::consts::PI; +use std::time::Instant; + +// TODO(pcwalton): See if we can reduce the amount of code by using the canvas shadow feature. + +const PI_2: f32 = PI * 2.0; + +static PARAGRAPH_TEXT: &'static str = "This is a longer chunk of text. + +I would have used lorem ipsum, but she was busy jumping over the lazy dog with the fox and all \ +the men who came to the aid of the party."; + +fn render_demo(canvas: &mut CanvasRenderingContext2D, + mouse_position: Vector2F, + window_size: Vector2F, + time: f32) { + draw_eyes(canvas, + RectF::new(Vector2F::new(window_size.x() - 250.0, 50.0), + Vector2F::new(150.0, 100.0)), + mouse_position, + time); + /* + FIXME(pcwalton): Too slow. See https://github.com/linebender/skribo/issues/30 + draw_paragraph(canvas, + RectF::new(Vector2F::new(window_size.x() - 450.0, 50.0), + Vector2F::new(150.0, 100.0))); + */ + draw_graph(canvas, + RectF::new(window_size.scale_xy(Vector2F::new(0.0, 0.5)), + window_size.scale_xy(Vector2F::new(1.0, 0.5))), + time); + draw_color_wheel(canvas, + RectF::new(window_size - Vector2F::splat(300.0), Vector2F::splat(250.0)), + time); + draw_lines(canvas, + RectF::new(Vector2F::new(120.0, window_size.y() - 50.0), + Vector2F::new(600.0, 50.0)), + time); + draw_caps(canvas, RectF::new(Vector2F::new(10.0, 300.0), Vector2F::new(30.0, 40.0))); + draw_clip(canvas, Vector2F::new(50.0, window_size.y() - 80.0), time); +} + +fn draw_eyes(canvas: &mut CanvasRenderingContext2D, + rect: RectF, + mouse_position: Vector2F, + time: f32) { + let eyes_radii = rect.size().scale_xy(Vector2F::new(0.23, 0.5)); + let eyes_left_position = rect.origin() + eyes_radii; + let eyes_right_position = rect.origin() + Vector2F::new(rect.width() - eyes_radii.x(), + eyes_radii.y()); + let eyes_center = f32::min(eyes_radii.x(), eyes_radii.y()) * 0.5; + let blink = 1.0 - f32::powf(f32::sin(time * 0.5), 200.0) * 0.8; + + let mut gradient = + Gradient::linear(LineSegment2F::new(Vector2F::new(0.0, rect.height() * 0.5), + rect.size().scale_xy(Vector2F::new(0.1, 1.0))) + + rect.origin()); + gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 32), 0.0)); + gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 16), 1.0)); + let mut path = Path2D::new(); + path.ellipse(eyes_left_position + Vector2F::new(3.0, 16.0), eyes_radii, 0.0, 0.0, PI_2); + path.ellipse(eyes_right_position + Vector2F::new(3.0, 16.0), eyes_radii, 0.0, 0.0, PI_2); + canvas.set_fill_style(FillStyle::Gradient(gradient)); + canvas.fill_path(path, FillRule::Winding); + + let mut gradient = + Gradient::linear(LineSegment2F::new(Vector2F::new(0.0, rect.height() * 0.25), + rect.size().scale_xy(Vector2F::new(0.1, 1.0))) + + rect.origin()); + gradient.add_color_stop(ColorStop::new(ColorU::new(220, 220, 220, 255), 0.0)); + gradient.add_color_stop(ColorStop::new(ColorU::new(128, 128, 128, 255), 1.0)); + let mut path = Path2D::new(); + path.ellipse(eyes_left_position, eyes_radii, 0.0, 0.0, PI_2); + path.ellipse(eyes_right_position, eyes_radii, 0.0, 0.0, PI_2); + canvas.set_fill_style(FillStyle::Gradient(gradient)); + canvas.fill_path(path, FillRule::Winding); + + let mut delta = (mouse_position - eyes_right_position) / eyes_radii.scale(10.0); + let distance = delta.length(); + if distance > 1.0 { + delta = delta.scale(1.0 / distance); + } + delta = delta.scale_xy(eyes_radii).scale_xy(Vector2F::new(0.4, 0.5)); + let mut path = Path2D::new(); + path.ellipse(eyes_left_position + + delta + + Vector2F::new(0.0, eyes_radii.y() * 0.25 * (1.0 - blink)), + Vector2F::new(eyes_center, eyes_center * blink), + 0.0, + 0.0, + PI_2); + path.ellipse(eyes_right_position + + delta + + Vector2F::new(0.0, eyes_radii.y() * 0.25 * (1.0 - blink)), + Vector2F::new(eyes_center, eyes_center * blink), + 0.0, + 0.0, + PI_2); + canvas.set_fill_style(FillStyle::Color(ColorU::new(32, 32, 32, 255))); + canvas.fill_path(path, FillRule::Winding); + + let gloss_position = eyes_left_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); + let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), + eyes_radii.x() * 0.1, + eyes_radii.x() * 0.75); + gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); + gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); + canvas.set_fill_style(FillStyle::Gradient(gloss)); + let mut path = Path2D::new(); + path.ellipse(eyes_left_position, eyes_radii, 0.0, 0.0, PI_2); + canvas.fill_path(path, FillRule::Winding); + + let gloss_position = eyes_right_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); + let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), + eyes_radii.x() * 0.1, + eyes_radii.x() * 0.75); + gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); + gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); + canvas.set_fill_style(FillStyle::Gradient(gloss)); + let mut path = Path2D::new(); + path.ellipse(eyes_right_position, eyes_radii, 0.0, 0.0, PI_2); + canvas.fill_path(path, FillRule::Winding); +} + +// This is nowhere near correct line layout, but it suffices to more or less match what NanoVG +// does. +fn draw_paragraph(canvas: &mut CanvasRenderingContext2D, rect: RectF) { + const LINE_HEIGHT: f32 = 24.0; + + canvas.save(); + + canvas.set_font_size(18.0); + + let mut cursor = rect.origin(); + next_line(canvas, &mut cursor, rect); + + let space_width = canvas.measure_text("A B").width - canvas.measure_text("AB").width; + + for space_separated in PARAGRAPH_TEXT.split(' ') { + let mut first = true; + for word in space_separated.split('\n') { + if !first { + next_line(canvas, &mut cursor, rect); + } + first = false; + + let word_width = canvas.measure_text(word).width; + if cursor.x() + space_width + word_width > rect.max_x() { + next_line(canvas, &mut cursor, rect); + } else if cursor.x() > rect.min_x() { + cursor = cursor + Vector2F::new(space_width, 0.0); + } + + canvas.set_fill_style(FillStyle::Color(ColorU::white())); + canvas.fill_text(word, cursor); + + cursor = cursor + Vector2F::new(word_width, 0.0); + } + } + + canvas.restore(); + + fn next_line(canvas: &mut CanvasRenderingContext2D, cursor: &mut Vector2F, rect: RectF) { + cursor.set_x(rect.min_x()); + + canvas.set_fill_style(FillStyle::Color(ColorU::new(255, 255, 255, 16))); + canvas.fill_rect(RectF::new(*cursor, Vector2F::new(rect.width(), LINE_HEIGHT))); + + *cursor = *cursor + Vector2F::new(0.0, LINE_HEIGHT); + } +} + +fn draw_graph(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f32) { + let sample_spread = rect.width() / 5.0; + + let samples = [ + (1.0 + f32::sin(time * 1.2345 + f32::cos(time * 0.33457) * 0.44)) * 0.5, + (1.0 + f32::sin(time * 0.68363 + f32::cos(time * 1.30) * 1.55)) * 0.5, + (1.0 + f32::sin(time * 1.1642 + f32::cos(time * 0.33457) * 1.24)) * 0.5, + (1.0 + f32::sin(time * 0.56345 + f32::cos(time * 1.63) * 0.14)) * 0.5, + (1.0 + f32::sin(time * 1.6245 + f32::cos(time * 0.254) * 0.3)) * 0.5, + (1.0 + f32::sin(time * 0.345 + f32::cos(time * 0.03) * 0.6)) * 0.5, + ]; + + let sample_scale = Vector2F::new(sample_spread, rect.height() * 0.8); + let sample_points: ArrayVec<[Vector2F; 6]> = samples.iter() + .enumerate() + .map(|(index, &sample)| { + rect.origin() + Vector2F::new(index as f32, sample).scale_xy(sample_scale) + }).collect(); + + // Draw graph background. + let mut background = Gradient::linear(LineSegment2F::new(Vector2F::default(), + Vector2F::new(0.0, rect.height())) + + rect.origin()); + background.add_color_stop(ColorStop::new(ColorU::new(0, 160, 192, 0), 0.0)); + background.add_color_stop(ColorStop::new(ColorU::new(0, 160, 192, 64), 1.0)); + canvas.set_fill_style(FillStyle::Gradient(background)); + let mut path = create_graph_path(&sample_points, sample_spread, Vector2F::default()); + path.line_to(rect.lower_right()); + path.line_to(rect.lower_left()); + canvas.fill_path(path, FillRule::Winding); + + // Draw graph line shadow. + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 0, 0, 32))); + canvas.set_line_width(3.0); + let path = create_graph_path(&sample_points, sample_spread, Vector2F::new(0.0, 2.0)); + canvas.stroke_path(path); + + // Draw graph line. + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 160, 192, 255))); + canvas.set_line_width(3.0); + let path = create_graph_path(&sample_points, sample_spread, Vector2F::default()); + canvas.stroke_path(path); + + // Draw sample position highlights. + for &sample_point in &sample_points { + let gradient_center = sample_point + Vector2F::new(0.0, 2.0); + let mut background = Gradient::radial(LineSegment2F::new(gradient_center, gradient_center), + 3.0, + 8.0); + background.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 32), 0.0)); + background.add_color_stop(ColorStop::new(ColorU::transparent_black(), 1.0)); + canvas.set_fill_style(FillStyle::Gradient(background)); + canvas.fill_rect(RectF::new(sample_point + Vector2F::new(-10.0, -10.0 + 2.0), + Vector2F::splat(20.0))); + } + + // Draw sample positions. + canvas.set_fill_style(FillStyle::Color(ColorU::new(0, 160, 192, 255))); + let mut path = Path2D::new(); + for &sample_point in &sample_points { + path.ellipse(sample_point, Vector2F::splat(4.0), 0.0, 0.0, PI_2); + } + canvas.fill_path(path, FillRule::Winding); + canvas.set_fill_style(FillStyle::Color(ColorU::new(220, 220, 220, 255))); + let mut path = Path2D::new(); + for &sample_point in &sample_points { + path.ellipse(sample_point, Vector2F::splat(2.0), 0.0, 0.0, PI_2); + } + canvas.fill_path(path, FillRule::Winding); + + // Reset state. + canvas.set_line_width(1.0); +} + +fn draw_color_wheel(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f32) { + let hue = time * 0.12; + + canvas.save(); + + let center = rect.center(); + let outer_radius = f32::min(rect.width(), rect.height()) * 0.5 - 5.0; + let inner_radius = outer_radius - 20.0; + + // Half a pixel arc length in radians. + let half_arc_len = 0.5 / outer_radius; + + // Draw outer circle. + for segment in 0..6 { + let start_angle = segment as f32 / 6.0 * PI_2 - half_arc_len; + let end_angle = (segment + 1) as f32 / 6.0 * PI_2 + half_arc_len; + let line = LineSegment2F::new(Vector2F::new(f32::cos(start_angle), f32::sin(start_angle)), + Vector2F::new(f32::cos(end_angle), f32::sin(end_angle))); + let scale = util::lerp(inner_radius, outer_radius, 0.5); + let mut gradient = Gradient::linear(line.scale(scale) + center); + let start_color = ColorF::from_hsl(start_angle, 1.0, 0.55).to_u8(); + let end_color = ColorF::from_hsl(end_angle, 1.0, 0.55).to_u8(); + gradient.add_color_stop(ColorStop::new(start_color, 0.0)); + gradient.add_color_stop(ColorStop::new(end_color, 1.0)); + canvas.set_fill_style(FillStyle::Gradient(gradient)); + let mut path = Path2D::new(); + path.arc(center, inner_radius, start_angle, end_angle, ArcDirection::CW); + path.arc(center, outer_radius, end_angle, start_angle, ArcDirection::CCW); + path.close_path(); + canvas.fill_path(path, FillRule::Winding); + } + + // Stroke outer circle. + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 0, 0, 64))); + canvas.set_line_width(1.0); + let mut path = Path2D::new(); + path.ellipse(center, Vector2F::splat(inner_radius - 0.5), 0.0, 0.0, PI_2); + path.ellipse(center, Vector2F::splat(outer_radius + 0.5), 0.0, 0.0, PI_2); + canvas.stroke_path(path); + + // Prepare to draw the selector. + canvas.save(); + canvas.set_current_transform(&(Transform2F::from_translation(center) * + Transform2F::from_rotation(hue))); + + canvas.set_stroke_style(FillStyle::Color(ColorU::new(255, 255, 255, 192))); + canvas.set_line_width(2.0); + canvas.stroke_rect(RectF::new(Vector2F::new(inner_radius - 1.0, -3.0), + Vector2F::new(outer_radius - inner_radius + 2.0, 6.0))); + + // TODO(pcwalton): Marker fill with box gradient + + // Draw center triangle. + let triangle_radius = inner_radius - 6.0; + let triangle_vertex_a = Vector2F::new(triangle_radius, 0.0); + let triangle_vertex_b = Vector2F::new(f32::cos(PI * 2.0 / 3.0), + f32::sin(PI * 2.0 / 3.0)).scale(triangle_radius); + let triangle_vertex_c = Vector2F::new(f32::cos(PI * -2.0 / 3.0), + f32::sin(PI * -2.0 / 3.0)).scale(triangle_radius); + let mut gradient_0 = Gradient::linear(LineSegment2F::new(triangle_vertex_a, + triangle_vertex_b)); + gradient_0.add_color_stop(ColorStop::new(ColorF::from_hsl(hue, 1.0, 0.5).to_u8(), 0.0)); + gradient_0.add_color_stop(ColorStop::new(ColorU::white(), 1.0)); + let mut gradient_1 = + Gradient::linear(LineSegment2F::new(triangle_vertex_a.lerp(triangle_vertex_b, 0.5), + triangle_vertex_c)); + gradient_1.add_color_stop(ColorStop::new(ColorU::transparent_black(), 0.0)); + gradient_1.add_color_stop(ColorStop::new(ColorU::black(), 1.0)); + let mut path = Path2D::new(); + path.move_to(triangle_vertex_a); + path.line_to(triangle_vertex_b); + path.line_to(triangle_vertex_c); + path.close_path(); + canvas.set_fill_style(FillStyle::Gradient(gradient_0)); + canvas.fill_path(path.clone(), FillRule::Winding); + canvas.set_fill_style(FillStyle::Gradient(gradient_1)); + canvas.fill_path(path.clone(), FillRule::Winding); + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 0, 0, 64))); + canvas.stroke_path(path); + + // Stroke the selection circle on the triangle. + let selection_circle_center = + Vector2F::new(f32::cos(PI_2 / 3.0), + f32::sin(PI_2 / 3.0)).scale(triangle_radius) + .scale_xy(Vector2F::new(0.3, 0.4)); + canvas.set_stroke_style(FillStyle::Color(ColorU::new(255, 255, 255, 192))); + canvas.set_line_width(2.0); + let mut path = Path2D::new(); + path.ellipse(selection_circle_center, Vector2F::splat(5.0), 0.0, 0.0, PI_2); + canvas.stroke_path(path); + + // Fill the selection circle. + let mut gradient = Gradient::radial(LineSegment2F::new(selection_circle_center, + selection_circle_center), + 7.0, + 9.0); + gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 64), 0.0)); + gradient.add_color_stop(ColorStop::new(ColorU::transparent_black(), 1.0)); + canvas.set_fill_style(FillStyle::Gradient(gradient)); + let mut path = Path2D::new(); + path.rect(RectF::new(selection_circle_center - Vector2F::splat(20.0), Vector2F::splat(40.0))); + path.ellipse(selection_circle_center, Vector2F::splat(7.0), 0.0, 0.0, PI_2); + canvas.fill_path(path, FillRule::EvenOdd); + + canvas.restore(); + canvas.restore(); +} + +fn draw_lines(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f32) { + const PADDING: f32 = 5.0; + + let spacing = rect.width() / 9.0 - PADDING * 2.0; + + canvas.save(); + + let points = [ + Vector2F::new(-spacing * 0.25 + f32::cos(time * 0.3) * spacing * 0.5, + f32::sin(time * 0.3) * spacing * 0.5), + Vector2F::new(-spacing * 0.25, 0.0), + Vector2F::new( spacing * 0.25, 0.0), + Vector2F::new( spacing * 0.25 + f32::cos(time * -0.3) * spacing * 0.5, + f32::sin(time * -0.3) * spacing * 0.5), + ]; + + for (cap_index, &cap) in [LineCap::Butt, LineCap::Round, LineCap::Square].iter().enumerate() { + for (join_index, &join) in [ + LineJoin::Miter, LineJoin::Miter /* FIXME(pcwalton): Round crashes */, LineJoin::Bevel + ].iter().enumerate() { + let origin = rect.origin() + + Vector2F::new(spacing, -spacing).scale(0.5) + + Vector2F::new((cap_index * 3 + join_index) as f32 / 9.0 * rect.width(), 0.0) + + Vector2F::splat(PADDING); + + canvas.set_line_cap(cap); + canvas.set_line_join(join); + canvas.set_line_width(spacing * 0.3); + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 0, 0, 160))); + + let mut path = Path2D::new(); + path.move_to(points[0] + origin); + path.line_to(points[1] + origin); + path.line_to(points[2] + origin); + path.line_to(points[3] + origin); + canvas.stroke_path(path.clone()); + + canvas.set_line_cap(LineCap::Butt); + canvas.set_line_join(LineJoin::Bevel); + canvas.set_line_width(1.0); + canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 192, 255, 255))); + + canvas.stroke_path(path); + } + } + + canvas.restore(); +} + +fn draw_caps(canvas: &mut CanvasRenderingContext2D, rect: RectF) { + const LINE_WIDTH: f32 = 8.0; + + canvas.save(); + + canvas.set_fill_style(FillStyle::Color(ColorU::new(255, 255, 255, 32))); + canvas.fill_rect(rect.dilate(Vector2F::new(LINE_WIDTH / 2.0, 0.0))); + canvas.fill_rect(rect); + + canvas.set_line_width(LINE_WIDTH); + for (cap_index, &cap) in [LineCap::Butt, LineCap::Round, LineCap::Square].iter().enumerate() { + canvas.set_line_cap(cap); + canvas.set_stroke_style(FillStyle::Color(ColorU::black())); + let offset = cap_index as f32 * 10.0 + 5.0; + let mut path = Path2D::new(); + path.move_to(rect.origin() + Vector2F::new(0.0, offset)); + path.line_to(rect.upper_right() + Vector2F::new(0.0, offset)); + canvas.stroke_path(path); + } + + canvas.restore(); +} + +fn draw_clip(canvas: &mut CanvasRenderingContext2D, origin: Vector2F, time: f32) { + canvas.save(); + + // Draw first rect. + let transform_a = Transform2F::from_translation(origin) * + Transform2F::from_rotation(angle::angle_from_degrees(5.0)); + canvas.set_current_transform(&transform_a); + canvas.set_fill_style(FillStyle::Color(ColorU::new(255, 0, 0, 255))); + let mut clip_path = Path2D::new(); + clip_path.rect(RectF::new(Vector2F::splat(-20.0), Vector2F::new(60.0, 40.0))); + canvas.fill_path(clip_path.clone(), FillRule::Winding); + + // Draw second rectangle with no clip. + let transform_b = transform_a * Transform2F::from_translation(Vector2F::new(40.0, 0.0)) * + Transform2F::from_rotation(time); + canvas.set_current_transform(&transform_b); + canvas.set_fill_style(FillStyle::Color(ColorU::new(255, 128, 0, 64))); + let fill_rect = RectF::new(Vector2F::new(-20.0, -10.0), Vector2F::new(60.0, 30.0)); + canvas.fill_rect(fill_rect); + + // Draw second rectangle with clip. + canvas.set_current_transform(&transform_a); + canvas.clip_path(clip_path, FillRule::Winding); + canvas.set_current_transform(&transform_b); + canvas.set_fill_style(FillStyle::Color(ColorU::new(255, 128, 0, 255))); + canvas.fill_rect(fill_rect); + + canvas.restore(); +} + +fn create_graph_path(sample_points: &[Vector2F], sample_spread: f32, offset: Vector2F) -> Path2D { + let mut path = Path2D::new(); + path.move_to(sample_points[0] + Vector2F::new(0.0, 2.0)); + for pair in sample_points.windows(2) { + path.bezier_curve_to(pair[0] + offset + Vector2F::new(sample_spread * 0.5, 0.0), + pair[1] + offset - Vector2F::new(sample_spread * 0.5, 0.0), + pair[1] + offset); + } + path +} + +fn main() { + // Set up SDL2. + let sdl_context = sdl2::init().unwrap(); + let video = sdl_context.video().unwrap(); + + // Make sure we have at least a GL 3.0 context. Pathfinder requires this. + let gl_attributes = video.gl_attr(); + gl_attributes.set_context_profile(GLProfile::Core); + gl_attributes.set_context_version(3, 3); + + // Open a window. + let window_size = Vector2I::new(800, 600); + let window = + video.window("NanoVG example port", window_size.x() as u32, window_size.y() as u32) + .opengl() + .build() + .unwrap(); + + // Create the GL context, and make it current. + let gl_context = window.gl_create_context().unwrap(); + gl::load_with(|name| video.gl_get_proc_address(name) as *const _); + window.gl_make_current(&gl_context).unwrap(); + + // Create a Pathfinder renderer. + let mut renderer = Renderer::new(GLDevice::new(GLVersion::GL3, 0), + &FilesystemResourceLoader::locate(), + DestFramebuffer::full_window(window_size), + RendererOptions { + background_color: Some(ColorF::new(0.3, 0.3, 0.32, 1.0)), + }); + + // Initialize state. + let mut event_pump = sdl_context.event_pump().unwrap(); + let mut mouse_position = Vector2F::default(); + let start_time = Instant::now(); + + // Enter the main loop. + loop { + // Make a canvas. + let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(), + window_size.to_f32()); + + // Render the demo. + let time = (Instant::now() - start_time).as_secs_f32(); + render_demo(&mut canvas, mouse_position, window_size.to_f32(), time); + + // Render the canvas to screen. + let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor); + scene.build_and_render(&mut renderer, BuildOptions::default()); + window.gl_swap_window(); + + for event in event_pump.poll_iter() { + match event { + Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return, + Event::MouseMotion { x, y, .. } => { + mouse_position = Vector2I::new(x, y).to_f32(); + } + _ => {} + } + } + } +}