#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum StrokeStyle {
#[default]
Solid,
Dashed,
Dotted,
DashDot,
DashDotDot,
}
#[derive(Debug, Clone)]
pub struct StrokeSettings {
pub width: f32,
pub style: StrokeStyle,
pub color: egui::Color32,
}
impl Default for StrokeSettings {
fn default() -> Self {
Self { width: 4.0, style: StrokeStyle::Solid, color: egui::Color32::RED }
}
}
pub const PRESET_COLORS: [egui::Color32; 7] = [
egui::Color32::RED,
egui::Color32::from_rgb(255, 165, 0), egui::Color32::YELLOW,
egui::Color32::GREEN,
egui::Color32::from_rgb(0, 120, 255), egui::Color32::BLACK,
egui::Color32::WHITE,
];
pub const PRESET_WIDTHS: [f32; 6] = [2.0, 4.0, 8.0, 12.0, 16.0, 24.0];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SequenceStyle {
#[default]
Number, Letter, Roman, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ShapeType {
#[default]
Rectangle,
Ellipse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FillMode {
#[default]
Outline, Filled, }
#[derive(Debug, Clone)]
pub struct Arrow {
pub start: egui::Pos2,
pub end: egui::Pos2,
pub settings: StrokeSettings,
pub selected: bool,
}
impl Arrow {
pub fn new(start: egui::Pos2, end: egui::Pos2, settings: &StrokeSettings) -> Self {
Self { start, end, settings: settings.clone(), selected: false }
}
pub fn snap_angle(&mut self) {
let dx = self.end.x - self.start.x;
let dy = self.end.y - self.start.y;
let angle = dy.atan2(dx);
let snapped = (angle / (std::f32::consts::PI / 4.0)).round() * (std::f32::consts::PI / 4.0);
let length = (dx * dx + dy * dy).sqrt();
self.end = egui::pos2(
self.start.x + length * snapped.cos(),
self.start.y + length * snapped.sin(),
);
}
pub fn direction(&self) -> egui::Vec2 {
(self.end - self.start).normalized()
}
pub fn length(&self) -> f32 {
self.start.distance(self.end)
}
}
#[derive(Debug, Clone)]
pub struct SequenceMarker {
pub pos: egui::Pos2,
pub number: u32,
pub style: SequenceStyle,
pub radius: f32,
pub color: egui::Color32,
}
impl SequenceMarker {
pub fn new(pos: egui::Pos2, number: u32, style: SequenceStyle, color: egui::Color32) -> Self {
Self { pos, number, style, radius: 12.0, color }
}
pub fn label(&self) -> String {
match self.style {
SequenceStyle::Number => self.number.to_string(),
SequenceStyle::Letter => {
let c = (b'A' + ((self.number - 1) % 26) as u8) as char;
c.to_string()
}
SequenceStyle::Roman => to_roman(self.number),
}
}
}
fn to_roman(n: u32) -> String {
let numerals = [
(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I"),
];
let mut result = String::new();
let mut n = n;
for (value, numeral) in numerals {
while n >= value {
result.push_str(numeral);
n -= value;
}
}
result
}
#[derive(Debug, Clone)]
pub struct Shape {
pub rect: egui::Rect,
pub shape_type: ShapeType,
pub fill_mode: FillMode,
pub settings: StrokeSettings,
pub selected: bool,
}
impl Shape {
pub fn new(
rect: egui::Rect,
shape_type: ShapeType,
fill_mode: FillMode,
settings: &StrokeSettings,
) -> Self {
Self { rect, shape_type, fill_mode, settings: settings.clone(), selected: false }
}
pub fn from_corners(
start: egui::Pos2,
end: egui::Pos2,
shape_type: ShapeType,
fill_mode: FillMode,
settings: &StrokeSettings,
) -> Self {
let rect = egui::Rect::from_two_pos(start, end);
Self::new(rect, shape_type, fill_mode, settings)
}
}
#[derive(Debug, Clone)]
pub struct Polyline {
pub points: Vec<egui::Pos2>,
pub settings: StrokeSettings,
pub closed: bool,
pub selected: bool,
}
impl Polyline {
pub fn new(settings: &StrokeSettings) -> Self {
Self { points: Vec::new(), settings: settings.clone(), closed: false, selected: false }
}
pub fn add_point(&mut self, pos: egui::Pos2) {
self.points.push(pos);
}
pub fn update_last(&mut self, pos: egui::Pos2) {
if let Some(last) = self.points.last_mut() {
*last = pos;
}
}
pub fn is_valid(&self) -> bool {
self.points.len() >= 2
}
pub fn close(&mut self) {
self.closed = true;
}
pub fn length(&self) -> f32 {
self.points.windows(2).map(|w| w[0].distance(w[1])).sum::<f32>()
+ if self.closed && self.points.len() >= 2 {
self.points.last().unwrap().distance(self.points[0])
} else {
0.0
}
}
}
#[derive(Debug, Clone)]
pub struct Highlighter {
pub rect: egui::Rect,
pub color: egui::Color32,
}
impl Default for Highlighter {
fn default() -> Self {
Self {
rect: egui::Rect::NOTHING,
color: egui::Color32::from_rgba_unmultiplied(255, 255, 0, 100), }
}
}
impl Highlighter {
pub fn new(rect: egui::Rect, color: egui::Color32) -> Self {
Self { rect, color }
}
pub fn from_corners(start: egui::Pos2, end: egui::Pos2, color: egui::Color32) -> Self {
Self { rect: egui::Rect::from_two_pos(start, end), color }
}
pub fn yellow(rect: egui::Rect) -> Self {
Self { rect, color: egui::Color32::from_rgba_unmultiplied(255, 255, 0, 100) }
}
pub fn with_opacity(rect: egui::Rect, base_color: egui::Color32, opacity: u8) -> Self {
Self {
rect,
color: egui::Color32::from_rgba_unmultiplied(
base_color.r(),
base_color.g(),
base_color.b(),
opacity,
),
}
}
pub fn is_valid(&self) -> bool {
self.rect.width() > 2.0 && self.rect.height() > 2.0
}
}
pub fn highlight_colors() -> [egui::Color32; 5] {
[
egui::Color32::from_rgba_unmultiplied(255, 255, 0, 100), egui::Color32::from_rgba_unmultiplied(0, 255, 0, 100), egui::Color32::from_rgba_unmultiplied(255, 0, 255, 100), egui::Color32::from_rgba_unmultiplied(0, 255, 255, 100), egui::Color32::from_rgba_unmultiplied(255, 165, 0, 100), ]
}
#[derive(Debug, Clone)]
pub struct Stroke {
pub points: Vec<egui::Pos2>,
pub settings: StrokeSettings,
}
#[derive(Debug, Clone, Default)]
pub struct Annotations {
pub strokes: Vec<Stroke>,
pub current_stroke: Option<Stroke>,
pub arrows: Vec<Arrow>,
pub current_arrow: Option<Arrow>,
pub shapes: Vec<Shape>,
pub current_shape: Option<Shape>,
pub polylines: Vec<Polyline>,
pub current_polyline: Option<Polyline>,
pub highlighters: Vec<Highlighter>,
pub current_highlighter: Option<Highlighter>,
pub markers: Vec<SequenceMarker>,
pub next_sequence: u32,
pub sequence_style: SequenceStyle,
}
impl Annotations {
pub fn new() -> Self {
Self { next_sequence: 1, ..Self::default() }
}
pub fn start_stroke(&mut self, pos: egui::Pos2, settings: &StrokeSettings) {
self.current_stroke = Some(Stroke { points: vec![pos], settings: settings.clone() });
}
pub fn add_point(&mut self, pos: egui::Pos2) {
if let Some(ref mut stroke) = self.current_stroke {
stroke.points.push(pos);
}
}
pub fn finish_stroke(&mut self) {
if let Some(stroke) = self.current_stroke.take() {
if stroke.points.len() > 1 {
self.strokes.push(stroke);
}
}
}
pub fn is_drawing(&self) -> bool {
self.current_stroke.is_some()
}
pub fn start_arrow(&mut self, pos: egui::Pos2, settings: &StrokeSettings) {
self.current_arrow = Some(Arrow::new(pos, pos, settings));
}
pub fn update_arrow(&mut self, pos: egui::Pos2, snap: bool) {
if let Some(ref mut arrow) = self.current_arrow {
arrow.end = pos;
if snap {
arrow.snap_angle();
}
}
}
pub fn finish_arrow(&mut self) {
if let Some(arrow) = self.current_arrow.take() {
if arrow.length() > 5.0 {
self.arrows.push(arrow);
}
}
}
pub fn is_drawing_arrow(&self) -> bool {
self.current_arrow.is_some()
}
pub fn start_shape(
&mut self,
pos: egui::Pos2,
shape_type: ShapeType,
fill_mode: FillMode,
settings: &StrokeSettings,
) {
let rect = egui::Rect::from_two_pos(pos, pos);
self.current_shape = Some(Shape::new(rect, shape_type, fill_mode, settings));
}
pub fn update_shape(&mut self, pos: egui::Pos2) {
if let Some(ref mut shape) = self.current_shape {
let start = shape.rect.min;
shape.rect = egui::Rect::from_two_pos(start, pos);
}
}
pub fn finish_shape(&mut self) {
if let Some(shape) = self.current_shape.take() {
if shape.rect.width() > 5.0 && shape.rect.height() > 5.0 {
self.shapes.push(shape);
}
}
}
pub fn is_drawing_shape(&self) -> bool {
self.current_shape.is_some()
}
pub fn add_marker(&mut self, pos: egui::Pos2, color: egui::Color32) {
let marker = SequenceMarker::new(pos, self.next_sequence, self.sequence_style, color);
self.markers.push(marker);
self.next_sequence += 1;
}
pub fn set_sequence_style(&mut self, style: SequenceStyle) {
self.sequence_style = style;
}
pub fn increment_sequence(&mut self) {
self.next_sequence = self.next_sequence.saturating_add(1);
}
pub fn decrement_sequence(&mut self) {
self.next_sequence = self.next_sequence.saturating_sub(1).max(1);
}
pub fn reset_sequence(&mut self) {
self.next_sequence = 1;
}
pub fn start_polyline(&mut self, pos: egui::Pos2, settings: &StrokeSettings) {
let mut polyline = Polyline::new(settings);
polyline.add_point(pos);
polyline.add_point(pos); self.current_polyline = Some(polyline);
}
pub fn add_polyline_point(&mut self, pos: egui::Pos2) {
if let Some(ref mut polyline) = self.current_polyline {
polyline.add_point(pos);
}
}
pub fn update_polyline_preview(&mut self, pos: egui::Pos2) {
if let Some(ref mut polyline) = self.current_polyline {
polyline.update_last(pos);
}
}
pub fn finish_polyline(&mut self) {
if let Some(mut polyline) = self.current_polyline.take() {
polyline.points.pop();
if polyline.is_valid() {
self.polylines.push(polyline);
}
}
}
pub fn close_polyline(&mut self) {
if let Some(mut polyline) = self.current_polyline.take() {
polyline.points.pop();
if polyline.points.len() >= 3 {
polyline.close();
self.polylines.push(polyline);
}
}
}
pub fn is_drawing_polyline(&self) -> bool {
self.current_polyline.is_some()
}
pub fn start_highlighter(&mut self, pos: egui::Pos2, color: egui::Color32) {
self.current_highlighter = Some(Highlighter::from_corners(pos, pos, color));
}
pub fn update_highlighter(&mut self, pos: egui::Pos2) {
if let Some(ref mut highlighter) = self.current_highlighter {
let start = highlighter.rect.min;
highlighter.rect = egui::Rect::from_two_pos(start, pos);
}
}
pub fn finish_highlighter(&mut self) {
if let Some(highlighter) = self.current_highlighter.take() {
if highlighter.is_valid() {
self.highlighters.push(highlighter);
}
}
}
pub fn is_drawing_highlighter(&self) -> bool {
self.current_highlighter.is_some()
}
pub fn is_any_drawing(&self) -> bool {
self.current_stroke.is_some()
|| self.current_arrow.is_some()
|| self.current_shape.is_some()
|| self.current_polyline.is_some()
|| self.current_highlighter.is_some()
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.strokes.clear();
self.current_stroke = None;
self.arrows.clear();
self.current_arrow = None;
self.shapes.clear();
self.current_shape = None;
self.polylines.clear();
self.current_polyline = None;
self.highlighters.clear();
self.current_highlighter = None;
self.markers.clear();
self.next_sequence = 1;
}
pub fn is_empty(&self) -> bool {
self.strokes.is_empty()
&& self.arrows.is_empty()
&& self.shapes.is_empty()
&& self.polylines.is_empty()
&& self.highlighters.is_empty()
&& self.markers.is_empty()
}
pub fn snapshot(&self) -> AnnotationsSnapshot {
AnnotationsSnapshot {
strokes: self.strokes.clone(),
arrows: self.arrows.clone(),
shapes: self.shapes.clone(),
polylines: self.polylines.clone(),
highlighters: self.highlighters.clone(),
markers: self.markers.clone(),
next_sequence: self.next_sequence,
sequence_style: self.sequence_style,
}
}
pub fn restore(&mut self, snapshot: AnnotationsSnapshot) {
self.strokes = snapshot.strokes;
self.arrows = snapshot.arrows;
self.shapes = snapshot.shapes;
self.polylines = snapshot.polylines;
self.highlighters = snapshot.highlighters;
self.markers = snapshot.markers;
self.next_sequence = snapshot.next_sequence;
self.sequence_style = snapshot.sequence_style;
self.current_stroke = None;
self.current_arrow = None;
self.current_shape = None;
self.current_polyline = None;
self.current_highlighter = None;
}
}
#[derive(Debug, Clone, Default)]
pub struct AnnotationsSnapshot {
pub strokes: Vec<Stroke>,
pub arrows: Vec<Arrow>,
pub shapes: Vec<Shape>,
pub polylines: Vec<Polyline>,
pub highlighters: Vec<Highlighter>,
pub markers: Vec<SequenceMarker>,
pub next_sequence: u32,
pub sequence_style: SequenceStyle,
}