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 .map(|point| point - self.top_item_draw_offset)
85 }
86
87 #[must_use]
89 pub fn get_item_y_scale(&self, item: DisplayedItemRef, y: f32) -> Option<f32> {
90 let y = y + self.top_item_draw_offset;
91 self.items_tree
92 .iter_visible()
93 .zip(&self.drawing_infos)
94 .find(|(node, _info)| node.item_ref == item)
95 .map(|(_, info)| (y - info.top()) / (info.height()))
96 }
97
98 pub(crate) fn draw_graphics(
99 &self,
100 ctx: &mut DrawingContext,
101 viewport: &Viewport,
102 theme: &SurferTheme,
103 ) {
104 let color = theme.variable_dontcare;
105 let max_timestamp = self.safe_max_timestamp();
106 let time_offset = self.time_offset();
107 for g in self.graphics.values() {
108 match g {
109 Graphic::TextArrow {
110 from: (from_point, from_dir),
111 to: (to_point, to_dir),
112 text,
113 } => {
114 let from_x = viewport.pixel_from_time(
115 &from_point.x,
116 ctx.cfg.canvas_size.x,
117 &max_timestamp,
118 time_offset,
119 );
120 let from_y = self.get_item_y(&from_point.y);
121
122 let to_x = viewport.pixel_from_time(
123 &to_point.x,
124 ctx.cfg.canvas_size.x,
125 &max_timestamp,
126 time_offset,
127 );
128 let to_y = self.get_item_y(&to_point.y);
129
130 if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
131 let from_dir = from_dir.as_vector() * 30.;
132 let to_dir_vec = to_dir.as_vector() * 30.;
133 let shape = Shape::CubicBezier(CubicBezierShape {
134 points: [
135 (ctx.to_screen)(from_x, from_y),
136 (ctx.to_screen)(from_x + from_dir.x, from_y + from_dir.y),
137 (ctx.to_screen)(to_x + to_dir_vec.x, to_y + to_dir_vec.y),
138 (ctx.to_screen)(to_x, to_y),
139 ],
140 closed: false,
141 fill: Color32::TRANSPARENT,
142 stroke: Stroke { width: 3., color }.into(),
143 });
144 ctx.painter.add(shape);
145
146 ctx.painter.text(
147 (ctx.to_screen)(to_x, to_y),
148 match to_dir {
149 Direction::North => Align2([Align::Center, Align::TOP]),
150 Direction::East => Align2([Align::LEFT, Align::Center]),
151 Direction::South => Align2([Align::Center, Align::BOTTOM]),
152 Direction::West => Align2([Align::RIGHT, Align::Center]),
153 },
154 text,
155 FontId::monospace(15.),
156 color,
157 );
158 }
159 }
160 Graphic::Text {
161 pos: (pos, dir),
162 text,
163 } => {
164 let to_x = viewport.pixel_from_time(
165 &pos.x,
166 ctx.cfg.canvas_size.x,
167 &max_timestamp,
168 time_offset,
169 );
170 let to_y = self.get_item_y(&pos.y);
171 if let Some(to_y) = to_y {
172 ctx.painter.text(
173 (ctx.to_screen)(to_x, to_y),
174 match dir {
175 Direction::North => Align2([Align::Center, Align::TOP]),
176 Direction::East => Align2([Align::LEFT, Align::Center]),
177 Direction::South => Align2([Align::Center, Align::BOTTOM]),
178 Direction::West => Align2([Align::RIGHT, Align::Center]),
179 },
180 text,
181 FontId::monospace(15.),
182 color,
183 );
184 }
185 }
186 Graphic::Rectangle {
187 from: from_point,
188 to: to_point,
189 } => {
190 let from_x = viewport.pixel_from_time(
191 &from_point.x,
192 ctx.cfg.canvas_size.x,
193 &max_timestamp,
194 time_offset,
195 );
196 let from_y = self.get_item_y(&from_point.y);
197 let to_x = viewport.pixel_from_time(
198 &from_point.x,
199 ctx.cfg.canvas_size.x,
200 &max_timestamp,
201 time_offset,
202 );
203 let to_y = self.get_item_y(&to_point.y);
204
205 if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
206 let start_pos = (ctx.to_screen)(from_x, from_y);
207 let end_pos = (ctx.to_screen)(to_x, to_y);
208 let temp_rect = emath::Rect::from_two_pos(start_pos, end_pos);
209 let stroke: Stroke = Stroke { width: 3., color };
210
211 ctx.painter
212 .rect_stroke(temp_rect, 0.0, stroke, egui::StrokeKind::Middle);
213 }
214 }
215 }
216 }
217 }
218}