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

implement polyline, improve shape highlighting #770

Merged
merged 10 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions crates/rnote-compose/src/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod penpathbuildable;
mod penpathcurvedbuilder;
mod penpathmodeledbuilder;
mod penpathsimplebuilder;
mod polylinebuilder;
mod quadbezbuilder;
mod quadrantcoordsystem2dbuilder;
mod rectanglebuilder;
Expand All @@ -31,6 +32,7 @@ pub use penpathbuildable::PenPathBuilderProgress;
pub use penpathcurvedbuilder::PenPathCurvedBuilder;
pub use penpathmodeledbuilder::PenPathModeledBuilder;
pub use penpathsimplebuilder::PenPathSimpleBuilder;
pub use polylinebuilder::PolylineBuilder;
pub use quadbezbuilder::QuadBezBuilder;
pub use quadrantcoordsystem2dbuilder::QuadrantCoordSystem2DBuilder;
pub use rectanglebuilder::RectangleBuilder;
Expand Down Expand Up @@ -75,12 +77,15 @@ pub enum ShapeBuilderType {
/// A foci ellipse builder
#[serde(rename = "foci_ellipse")]
FociEllipse,
/// An quadbez builder
/// A quadbez builder
#[serde(rename = "quadbez")]
QuadBez,
/// An cubic bezier builder
/// A cubic bezier builder
#[serde(rename = "cubbez")]
CubBez,
/// A poyline builder
#[serde(rename = "polyline")]
Polyline,
}

impl ShapeBuilderType {
Expand All @@ -98,6 +103,7 @@ impl ShapeBuilderType {
"shapebuilder-fociellipse-symbolic" => Some(Self::FociEllipse),
"shapebuilder-quadbez-symbolic" => Some(Self::QuadBez),
"shapebuilder-cubbez-symbolic" => Some(Self::CubBez),
"shapebuilder-polyline-symbolic" => Some(Self::Polyline),
_ => None,
}
}
Expand All @@ -118,6 +124,7 @@ impl ShapeBuilderType {
Self::FociEllipse => String::from("shapebuilder-fociellipse-symbolic"),
Self::QuadBez => String::from("shapebuilder-quadbez-symbolic"),
Self::CubBez => String::from("shapebuilder-cubbez-symbolic"),
Self::Polyline => String::from("shapebuilder-polyline-symbolic"),
}
}
}
Expand Down
153 changes: 153 additions & 0 deletions crates/rnote-compose/src/builders/polylinebuilder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Imports
use super::shapebuildable::{ShapeBuilderCreator, ShapeBuilderProgress};
use super::ShapeBuildable;
use crate::constraints::ConstraintRatio;
use crate::penevents::{KeyboardKey, PenEvent, PenState};
use crate::penpath::Element;
use crate::shapes::Polyline;
use crate::style::{indicators, Composer};
use crate::Constraints;
use crate::{Shape, Style};
use p2d::bounding_volume::{Aabb, BoundingVolume};
use piet::RenderContext;
use std::time::Instant;

/// Line builder.
#[derive(Debug, Clone)]
pub struct PolylineBuilder {
/// Start position.
start: na::Vector2<f64>,
/// Position of the next/current path segment.
current: na::Vector2<f64>,
/// Path.
path: Vec<na::Vector2<f64>>,
/// Pen state.
pen_state: PenState,
/// Pen position.
pen_pos: na::Vector2<f64>,
/// Finish the polyline on the next `PenEvent::Up`.
finish: bool,
}

impl ShapeBuilderCreator for PolylineBuilder {
fn start(element: Element, _now: Instant) -> Self {
Self {
start: element.pos,
current: element.pos,
path: Vec::new(),
pen_state: PenState::Down,
pen_pos: element.pos,
finish: false,
}
}
}

impl ShapeBuildable for PolylineBuilder {
fn handle_event(
&mut self,
event: PenEvent,
_now: Instant,
mut constraints: Constraints,
) -> ShapeBuilderProgress {
// we always want to allow horizontal and vertical constraints while building a polyline
constraints.ratios.insert(ConstraintRatio::Horizontal);
constraints.ratios.insert(ConstraintRatio::Vertical);

match event {
PenEvent::Down { element, .. } => {
if (self.pen_state == PenState::Up || self.pen_state == PenState::Proximity)
&& self.pos_in_finish(element.pos)
{
self.finish = true;
}
self.pen_state = PenState::Down;
self.pen_pos = element.pos;
let last_pos = self.path.last().copied().unwrap_or(self.start);
self.current = constraints.constrain(element.pos - last_pos) + last_pos;
}
PenEvent::Up { element, .. } => {
if self.finish {
return ShapeBuilderProgress::Finished(vec![Shape::Polyline(
self.state_as_polyline(),
)]);
}
if self.pen_state == PenState::Down {
self.path.push(self.current);
}
self.pen_state = PenState::Up;
self.pen_pos = element.pos;
}
PenEvent::Proximity { element, .. } => {
self.pen_state = PenState::Proximity;
self.pen_pos = element.pos;
}
PenEvent::KeyPressed { keyboard_key, .. } => match keyboard_key {
KeyboardKey::Escape | KeyboardKey::CarriageReturn | KeyboardKey::Linefeed => {
return ShapeBuilderProgress::Finished(vec![Shape::Polyline(
self.state_as_polyline(),
)]);
}
_ => {}
},
PenEvent::Text { .. } => {}
PenEvent::Cancel => {
self.pen_state = PenState::Up;
self.finish = false;
}
}

ShapeBuilderProgress::InProgress
}

fn bounds(&self, style: &Style, zoom: f64) -> Option<Aabb> {
let mut polyline = self.state_as_polyline();
if !self.finish {
polyline.path.push(self.current);
}
Some(
polyline
.composed_bounds(style)
.loosened(indicators::POS_INDICATOR_RADIUS / zoom),
)
}

fn draw_styled(&self, cx: &mut piet_cairo::CairoRenderContext, style: &Style, zoom: f64) {
cx.save().unwrap();

let mut polyline = self.state_as_polyline();
if !self.finish {
polyline.path.push(self.current);
}

polyline.draw_composed(cx, style);
indicators::draw_pos_indicator(cx, PenState::Up, self.start, zoom);
if !self.finish {
if self.pos_in_finish(self.pen_pos)
&& (self.pen_state == PenState::Up || self.pen_state == PenState::Proximity)
{
indicators::draw_finish_indicator(cx, self.pen_state, self.current, zoom);
} else {
indicators::draw_pos_indicator(cx, self.pen_state, self.current, zoom);
}
}

cx.restore().unwrap();
}
}

impl PolylineBuilder {
const FINISH_TRESHOLD_DIST: f64 = 8.0;

/// The current state as a polyline.
pub fn state_as_polyline(&self) -> Polyline {
Polyline {
start: self.start,
path: self.path.clone(),
}
}

fn pos_in_finish(&self, pos: na::Vector2<f64>) -> bool {
(pos - self.path.last().copied().unwrap_or(self.start)).magnitude()
< Self::FINISH_TRESHOLD_DIST
}
}
2 changes: 2 additions & 0 deletions crates/rnote-compose/src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rnote_compose_sources = files(
'builders/penpathcurvedbuilder.rs',
'builders/penpathmodeledbuilder.rs',
'builders/penpathsimplebuilder.rs',
'builders/polylinebuilder.rs',
'builders/quadbezbuilder.rs',
'builders/quadrantcoordsystem2dbuilder.rs',
'builders/rectanglebuilder.rs',
Expand All @@ -25,6 +26,7 @@ rnote_compose_sources = files(
'shapes/ellipse.rs',
'shapes/line.rs',
'shapes/mod.rs',
'shapes/polyline.rs',
'shapes/quadbez.rs',
'shapes/rectangle.rs',
'shapes/shape.rs',
Expand Down
2 changes: 1 addition & 1 deletion crates/rnote-compose/src/penevents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ pub enum ModifierKey {
}

/// The current pen state. Used wherever there is internal state.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum PenState {
/// Up.
Up,
Expand Down
19 changes: 8 additions & 11 deletions crates/rnote-compose/src/penpath/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Shapeable for PenPath {
end: end.pos,
};

bounds.merge(&quadbez.to_kurbo().bounding_box().bounds_to_p2d_aabb());
bounds.merge(&quadbez.outline_path().bounding_box().bounds_to_p2d_aabb());
prev = *end;
}
Segment::CubBezTo { cp1, cp2, end } => {
Expand All @@ -56,7 +56,7 @@ impl Shapeable for PenPath {
end: end.pos,
};

bounds.merge(&cubbez.to_kurbo().bounding_box().bounds_to_p2d_aabb());
bounds.merge(&cubbez.outline_path().bounding_box().bounds_to_p2d_aabb());
prev = *end;
}
}
Expand All @@ -71,6 +71,10 @@ impl Shapeable for PenPath {
.flat_map(|(_, hb)| hb)
.collect()
}

fn outline_path(&self) -> kurbo::BezPath {
kurbo::BezPath::from_iter(self.to_kurbo_el_iter())
}
}

impl Transformable for PenPath {
Expand Down Expand Up @@ -183,7 +187,7 @@ impl PenPath {
};

let n_splits =
no_subsegments_for_segment_len(quadbez.to_kurbo().perimeter(0.25));
no_subsegments_for_segment_len(quadbez.outline_path().perimeter(0.25));

hitboxes.push((
i,
Expand All @@ -204,7 +208,7 @@ impl PenPath {
};

let n_splits =
no_subsegments_for_segment_len(cubbez.to_kurbo().perimeter(0.25));
no_subsegments_for_segment_len(cubbez.outline_path().perimeter(0.25));

hitboxes.push((
i,
Expand All @@ -222,13 +226,6 @@ impl PenPath {
hitboxes
}

/// Convert to [kurbo::BezPath].
pub fn to_kurbo(&self) -> kurbo::BezPath {
let elements = self.to_kurbo_el_iter();

kurbo::BezPath::from_iter(elements)
}

/// Convert to [kurbo::BezPath], flattened to the given precision.
pub fn to_kurbo_flattened(&self, tolerance: f64) -> kurbo::BezPath {
let elements = self.to_kurbo_el_iter();
Expand Down
49 changes: 20 additions & 29 deletions crates/rnote-compose/src/shapes/arrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::Line;
use crate::ext::Vector2Ext;
use crate::shapes::Shapeable;
use crate::transform::Transformable;
use kurbo::PathEl;
use kurbo::{PathEl, Shape};
use na::Rotation2;
use p2d::bounding_volume::Aabb;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -71,6 +71,10 @@ impl Shapeable for Arrow {
.map(|line| line.bounds())
.collect()
}

fn outline_path(&self) -> kurbo::BezPath {
self.to_kurbo(None)
}
}

impl Arrow {
Expand Down Expand Up @@ -109,25 +113,21 @@ impl Arrow {
.collect::<Vec<Line>>()
}

/// Convert to kurbo shapes.
pub fn to_kurbo(&self, stroke_width: Option<f64>) -> ArrowKurbo {
let main = kurbo::Line::new(self.start.to_kurbo_point(), self.tip.to_kurbo_point());
let tip_triangle = {
let tip = self.tip.to_kurbo_point();
let lline = self.compute_lline(stroke_width).to_kurbo_point();
let rline = self.compute_rline(stroke_width).to_kurbo_point();

kurbo::BezPath::from_vec(vec![
PathEl::MoveTo(lline),
PathEl::LineTo(tip),
PathEl::LineTo(rline),
])
};

ArrowKurbo {
stem: main,
tip_triangle,
}
/// Convert to kurbo shape.
pub fn to_kurbo(&self, stroke_width: Option<f64>) -> kurbo::BezPath {
let mut bez_path =
kurbo::Line::new(self.start.to_kurbo_point(), self.tip.to_kurbo_point()).to_path(0.25);
let tip = self.tip.to_kurbo_point();
let lline = self.compute_lline(stroke_width).to_kurbo_point();
let rline = self.compute_rline(stroke_width).to_kurbo_point();

bez_path.extend([
PathEl::MoveTo(lline),
PathEl::LineTo(tip),
PathEl::LineTo(rline),
]);

bez_path
}

/// Compute the `lline` of the arrow tip.
Expand Down Expand Up @@ -186,12 +186,3 @@ impl Arrow {
Self::TIP_LINES_DEFAULT_LENGTH * (1.0 + 0.18 * factor)
}
}

/// A helper struct which holds the kurbo-elements of the arrow.
#[derive(Debug, Clone, PartialEq)]
pub struct ArrowKurbo {
/// The line from `start` -> `tip`.
pub stem: kurbo::Line,
/// The line from `lline` -> `tip` -> `rline`.
pub tip_triangle: kurbo::BezPath,
}
Loading