Skip to content

Commit

Permalink
draw fuller arrow
Browse files Browse the repository at this point in the history
This changes the arrow from being 3 thin lines to a more fuller arrow.
  • Loading branch information
bobvanderlinden committed Dec 28, 2024
1 parent 9b3f6f0 commit 11339b1
Showing 1 changed file with 40 additions and 33 deletions.
73 changes: 40 additions & 33 deletions src/tools/arrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use femtovg::{FontId, Path};
use relm4::gtk::gdk::{Key, ModifierType};

use crate::{
math::Vec2D,
math::{Angle, Vec2D},
sketch_board::{MouseEventMsg, MouseEventType},
style::Style,
};
Expand Down Expand Up @@ -101,30 +101,6 @@ impl Tool for ArrowTool {
}
}

impl Arrow {
fn get_arrow_head_points(&self) -> (Vec2D, Vec2D) {
let end = match self.end {
Some(e) => e,
None => return (Vec2D::zero(), Vec2D::zero()), // exit if no end
};

// borrowed from: https://math.stackexchange.com/questions/1314006/drawing-an-arrow
let delta = self.start - end;
let l1 = delta.norm();
const L2: f32 = 30.0;
const PHI: f32 = PI / 6.0;
let (sin_phi, cos_phi) = PHI.sin_cos();

let x3 = end.x + L2 / l1 * (delta.x * cos_phi + delta.y * sin_phi);
let y3 = end.y + L2 / l1 * (delta.y * cos_phi - delta.x * sin_phi);

let x4 = end.x + L2 / l1 * (delta.x * cos_phi - delta.y * sin_phi);
let y4 = end.y + L2 / l1 * (delta.y * cos_phi + delta.x * sin_phi);

(Vec2D::new(x3, y3), Vec2D::new(x4, y4))
}
}

impl Drawable for Arrow {
fn draw(
&self,
Expand All @@ -135,19 +111,50 @@ impl Drawable for Arrow {
Some(e) => e,
None => return Ok(()), // exit if no end
};
let (p1, p2) = self.get_arrow_head_points();

canvas.save();

let mut path = Path::new();
path.move_to(self.start.x, self.start.y);
path.line_to(end.x, end.y);
let offset = end - self.start;
let length = offset.norm();
let direction = offset * (1.0 / length);

// Glossary:
// Head: the point of the head at the end of the arrow.
// Tail: the straight line from the start to the head of the arrow.
// Side: the sloped side of the arrow head.
// Midpoint: where the tail ends and the head begins.

// We rotate the canvas so that we can draw the arrow on the x-axis.
// start will be the (0,0)
// end will be (length, 0)
canvas.translate(self.start.x, self.start.y);
canvas.rotate(direction.angle().radians);

path.move_to(p1.x, p1.y);
path.line_to(end.x, end.y);
path.line_to(p2.x, p2.y);
let tail_width = 8.0;
let head_size = 50.0; // The length of the (sloped) side of the arrow head.
let midpoint_offset = 10.0; // 0 will place the midpoint right below. A negative value will result in a diamond shape head. A positive value will result in sharper sides.
let head_angle = Angle::from_degrees(60.0); // The angle of the point of the arrow head.

let tail_half_width = tail_width / 2.0;
let head_half_angle = head_angle * 0.5;
let head_left = Vec2D::new(length, 0.0) - Vec2D::from_angle(head_half_angle) * head_size;
let midpoint_x = head_left.x + midpoint_offset;

let mut path = Path::new();
path.move_to(midpoint_x, tail_half_width);
path.line_to(head_left.x, -head_left.y);
path.line_to(length, 0.0);
path.line_to(head_left.x, head_left.y);
path.line_to(midpoint_x, -tail_half_width);
if midpoint_x > 0.0 {
// If the midpoint is placed _before_ the start, there is only a head and no tail.
// We can skip the beginning of the tail.
path.line_to(0.0, -tail_half_width);
path.line_to(0.0, tail_half_width);
}
path.close();

canvas.stroke_path(&path, &self.style.into());
canvas.fill_path(&path, &self.style.into());

canvas.restore();
Ok(())
Expand Down

0 comments on commit 11339b1

Please sign in to comment.