1use ecolor::Color32;
2use emath::{Align, Align2, Vec2};
3use epaint::{CubicBezierShape, FontId, Shape, Stroke};
4use num::BigInt;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 config::SurferTheme, displayed_item::DisplayedItemRef, view::DrawingContext,
9 viewport::Viewport, wave_data::WaveData,
10};
11
12#[derive(Serialize, Deserialize, Debug)]
13pub enum Direction {
14 North,
15 East,
16 South,
17 West,
18}
19
20impl Direction {
21 #[must_use]
22 pub fn as_vector(&self) -> Vec2 {
23 match self {
24 Direction::North => Vec2::new(0., -1.),
25 Direction::East => Vec2::new(-1., 0.),
26 Direction::South => Vec2::new(0., 1.),
27 Direction::West => Vec2::new(1., 0.),
28 }
29 }
30}
31
32#[derive(Serialize, Deserialize, Debug, Clone)]
33pub enum Anchor {
34 Top,
35 Center,
36 Bottom,
37 Percentual(f32),
38}
39
40#[derive(Serialize, Deserialize, Debug, Clone)]
41pub struct GraphicsY {
42 pub item: DisplayedItemRef,
43 pub anchor: Anchor,
44}
45
46#[derive(Serialize, Deserialize, Debug, Clone)]
48pub struct GrPoint {
49 pub x: BigInt,
51 pub y: GraphicsY,
52}
53
54#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
55pub struct GraphicId(pub usize);
56
57#[derive(Serialize, Deserialize, Debug)]
58pub enum Graphic {
59 TextArrow {
60 from: (GrPoint, Direction),
61 to: (GrPoint, Direction),
62 text: String,
63 },
64 Text {
65 pos: (GrPoint, Direction),
66 text: String,
67 },
68 Rectangle {
69 from: GrPoint,
70 to: GrPoint,
71 },
72}
73
74impl WaveData {
75 #[must_use]
78 pub fn get_item_y(&self, y: &GraphicsY) -> Option<f32> {
79 self.items_tree
80 .iter_visible()
81 .zip(&self.drawing_infos)
82 .find(|(node, _info)| node.item_ref == y.item)
83 .map(|(_, info)| info.get_y_from_anchor(&y.anchor))
84 }
85
86 pub(crate) fn draw_graphics(
87 &self,
88 ctx: &mut DrawingContext,
89 viewport: &Viewport,
90 theme: &SurferTheme,
91 ) {
92 let color = theme.variable_dontcare;
93 let range = self.time_range();
94 for g in self.graphics.values() {
95 match g {
96 Graphic::TextArrow {
97 from: (from_point, from_dir),
98 to: (to_point, to_dir),
99 text,
100 } => {
101 let from_x =
102 viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
103 let from_y = self.get_item_y(&from_point.y);
104
105 let to_x = viewport.pixel_from_time(&to_point.x, ctx.cfg.canvas_size.x, range);
106 let to_y = self.get_item_y(&to_point.y);
107
108 if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
109 let from_dir = from_dir.as_vector() * 30.;
110 let to_dir_vec = to_dir.as_vector() * 30.;
111 let shape = Shape::CubicBezier(CubicBezierShape {
112 points: [
113 (ctx.to_screen)(from_x, from_y),
114 (ctx.to_screen)(from_x + from_dir.x, from_y + from_dir.y),
115 (ctx.to_screen)(to_x + to_dir_vec.x, to_y + to_dir_vec.y),
116 (ctx.to_screen)(to_x, to_y),
117 ],
118 closed: false,
119 fill: Color32::TRANSPARENT,
120 stroke: Stroke { width: 3., color }.into(),
121 });
122 ctx.painter.add(shape);
123
124 ctx.painter.text(
125 (ctx.to_screen)(to_x, to_y),
126 match to_dir {
127 Direction::North => Align2([Align::Center, Align::TOP]),
128 Direction::East => Align2([Align::LEFT, Align::Center]),
129 Direction::South => Align2([Align::Center, Align::BOTTOM]),
130 Direction::West => Align2([Align::RIGHT, Align::Center]),
131 },
132 text,
133 FontId::monospace(15.),
134 color,
135 );
136 }
137 }
138 Graphic::Text {
139 pos: (pos, dir),
140 text,
141 } => {
142 let to_x = viewport.pixel_from_time(&pos.x, ctx.cfg.canvas_size.x, range);
143 let to_y = self.get_item_y(&pos.y);
144 if let Some(to_y) = to_y {
145 ctx.painter.text(
146 (ctx.to_screen)(to_x, to_y),
147 match dir {
148 Direction::North => Align2([Align::Center, Align::TOP]),
149 Direction::East => Align2([Align::LEFT, Align::Center]),
150 Direction::South => Align2([Align::Center, Align::BOTTOM]),
151 Direction::West => Align2([Align::RIGHT, Align::Center]),
152 },
153 text,
154 FontId::monospace(15.),
155 color,
156 );
157 }
158 }
159 Graphic::Rectangle {
160 from: from_point,
161 to: to_point,
162 } => {
163 let from_x =
164 viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
165 let from_y = self.get_item_y(&from_point.y);
166 let to_x =
167 viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
168 let to_y = self.get_item_y(&to_point.y);
169
170 if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
171 let start_pos = (ctx.to_screen)(from_x, from_y);
172 let end_pos = (ctx.to_screen)(to_x, to_y);
173 let temp_rect = emath::Rect::from_two_pos(start_pos, end_pos);
174 let stroke: Stroke = Stroke { width: 3., color };
175
176 ctx.painter
177 .rect_stroke(temp_rect, 0.0, stroke, egui::StrokeKind::Middle);
178 }
179 }
180 }
181 }
182 }
183}