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