1use egui::{Context, Grid, RichText, WidgetText, Window};
2use emath::{Align2, Pos2, Rect, Vec2};
3use epaint::{CornerRadius, FontId, Stroke};
4use itertools::Itertools;
5use num::BigInt;
6
7use crate::SystemState;
8use crate::{
9 config::SurferTheme,
10 displayed_item::{DisplayedItem, DisplayedMarker},
11 message::Message,
12 time::time_string,
13 view::{DrawingContext, ItemDrawingInfo},
14 viewport::Viewport,
15 wave_data::WaveData,
16};
17
18pub const DEFAULT_MARKER_NAME: &str = "Marker";
19
20impl WaveData {
21 pub fn draw_cursor(
22 &self,
23 theme: &SurferTheme,
24 ctx: &mut DrawingContext,
25 size: Vec2,
26 viewport: &Viewport,
27 ) {
28 if let Some(marker) = &self.cursor {
29 let num_timestamps = self.num_timestamps().unwrap_or(1.into());
30 let x = viewport.pixel_from_time(marker, size.x, &num_timestamps);
31
32 let stroke = Stroke {
33 color: theme.cursor.color,
34 width: theme.cursor.width,
35 };
36 ctx.painter.line_segment(
37 [
38 (ctx.to_screen)(x + 0.5, -0.5),
39 (ctx.to_screen)(x + 0.5, size.y),
40 ],
41 stroke,
42 );
43 }
44 }
45
46 pub fn draw_markers(
47 &self,
48 theme: &SurferTheme,
49 ctx: &mut DrawingContext,
50 size: Vec2,
51 viewport: &Viewport,
52 ) {
53 let num_timestamps = self.num_timestamps().unwrap_or(1.into());
54 for (idx, marker) in &self.markers {
55 let color = self
56 .items_tree
57 .iter()
58 .map(|node| self.displayed_items.get(&node.item_ref))
59 .find_map(|item| match item {
60 Some(DisplayedItem::Marker(tmp_marker)) => {
61 if *idx == tmp_marker.idx {
62 Some(tmp_marker)
63 } else {
64 None
65 }
66 }
67 _ => None,
68 })
69 .and_then(|displayed_maker| displayed_maker.color.clone())
70 .and_then(|color| theme.get_color(&color))
71 .unwrap_or(&theme.cursor.color);
72 let stroke = Stroke {
73 color: *color,
74 width: theme.cursor.width,
75 };
76 let x = viewport.pixel_from_time(marker, size.x, &num_timestamps);
77 ctx.painter.line_segment(
78 [
79 (ctx.to_screen)(x + 0.5, -0.5),
80 (ctx.to_screen)(x + 0.5, size.y),
81 ],
82 stroke,
83 );
84 }
85 }
86
87 pub fn can_add_marker(&self) -> bool {
88 self.markers.len() < 255
89 }
90
91 pub fn add_marker(&mut self, location: &BigInt, name: Option<String>, move_focus: bool) {
92 if !self.can_add_marker() {
93 return;
94 }
95
96 let idx = (0..=254)
97 .find(|idx| !self.markers.contains_key(idx))
98 .unwrap();
99
100 self.insert_item(
101 DisplayedItem::Marker(DisplayedMarker {
102 color: None,
103 background_color: None,
104 name,
105 idx,
106 }),
107 None,
108 move_focus,
109 );
110 self.markers.insert(idx, location.clone());
111 }
112
113 pub fn remove_marker(&mut self, idx: u8) {
114 if let Some(&marker_item_ref) =
115 self.displayed_items
116 .iter()
117 .find_map(|(id, item)| match item {
118 DisplayedItem::Marker(marker) if marker.idx == idx => Some(id),
119 _ => None,
120 })
121 {
122 self.remove_displayed_item(marker_item_ref);
123 }
124 }
125
126 pub fn set_marker_position(&mut self, idx: u8, location: &BigInt) {
129 if !self.markers.contains_key(&idx) {
130 self.insert_item(
131 DisplayedItem::Marker(DisplayedMarker {
132 color: None,
133 background_color: None,
134 name: None,
135 idx,
136 }),
137 None,
138 true,
139 );
140 }
141 self.markers.insert(idx, location.clone());
142 }
143
144 pub fn move_marker_to_cursor(&mut self, idx: u8) {
145 let Some(location) = self.cursor.clone() else {
146 return;
147 };
148 self.set_marker_position(idx, &location);
149 }
150
151 pub fn draw_marker_number_boxes(
152 &self,
153 ctx: &mut DrawingContext,
154 size: Vec2,
155 theme: &SurferTheme,
156 viewport: &Viewport,
157 ) {
158 let text_size = ctx.cfg.text_size;
159
160 for displayed_item in self
161 .items_tree
162 .iter_visible()
163 .map(|node| self.displayed_items.get(&node.item_ref))
164 .filter_map(|item| match item {
165 Some(DisplayedItem::Marker(marker)) => Some(marker),
166 _ => None,
167 })
168 {
169 let background_color = displayed_item
170 .color
171 .as_ref()
172 .and_then(|color| theme.get_color(color))
173 .unwrap_or(&theme.cursor.color);
174
175 let x = self.numbered_marker_location(displayed_item.idx, viewport, size.x);
176
177 let idx_string = displayed_item.idx.to_string();
178 let rect = ctx.painter.text(
180 (ctx.to_screen)(x, size.y * 0.5),
181 Align2::CENTER_CENTER,
182 idx_string.clone(),
183 FontId::proportional(text_size),
184 theme.foreground,
185 );
186
187 let min = (ctx.to_screen)(rect.min.x, 0.);
189 let max = (ctx.to_screen)(rect.max.x, size.y);
190 let min = Pos2::new(rect.min.x - 2., min.y);
191 let max = Pos2::new(rect.max.x + 2., max.y);
192
193 ctx.painter.rect_filled(
194 Rect { min, max },
195 CornerRadius::default(),
196 *background_color,
197 );
198
199 ctx.painter.text(
201 (ctx.to_screen)(x, size.y * 0.5),
202 Align2::CENTER_CENTER,
203 idx_string,
204 FontId::proportional(text_size),
205 theme.foreground,
206 );
207 }
208 }
209}
210
211impl SystemState {
212 pub fn draw_marker_window(&self, waves: &WaveData, ctx: &Context, msgs: &mut Vec<Message>) {
213 let mut open = true;
214
215 let mut markers: Vec<(u8, &BigInt, WidgetText)> = vec![];
216 if let Some(cursor) = &waves.cursor {
217 markers.push((
218 255,
219 cursor,
220 WidgetText::RichText(RichText::new("Primary").into()),
221 ));
222 }
223
224 let mut numbered_markers = waves
225 .items_tree
226 .iter()
227 .map(|node| waves.displayed_items.get(&node.item_ref))
228 .filter_map(|displayed_item| match displayed_item {
229 Some(DisplayedItem::Marker(marker)) => {
230 let text_color = self.get_item_text_color(displayed_item.unwrap());
231 Some((
232 marker.idx,
233 waves.numbered_marker_time(marker.idx),
234 marker.marker_text(text_color),
235 ))
236 }
237 _ => None,
238 })
239 .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
240 .collect_vec();
241
242 markers.append(&mut numbered_markers);
243 Window::new("Markers")
244 .collapsible(true)
245 .resizable(true)
246 .open(&mut open)
247 .show(ctx, |ui| {
248 ui.vertical_centered(|ui| {
249 Grid::new("markers")
250 .striped(true)
251 .num_columns(markers.len() + 1)
252 .spacing([5., 5.])
253 .show(ui, |ui| {
254 ui.label("");
255 for (marker_idx, _, widget_text) in &markers {
256 if *marker_idx < 255 {
257 ui.selectable_label(false, widget_text.clone())
258 .clicked()
259 .then(|| {
260 msgs.push(Message::GoToMarkerPosition(*marker_idx, 0));
261 });
262 } else {
263 ui.selectable_label(false, widget_text.clone())
264 .clicked()
265 .then(|| {
266 msgs.push(Message::GoToTime(waves.cursor.clone(), 0));
267 });
268 }
269 }
270 ui.end_row();
271 for (marker_idx, row_marker_time, row_widget_text) in &markers {
272 if *marker_idx < 255 {
273 ui.selectable_label(false, row_widget_text.clone())
274 .clicked()
275 .then(|| {
276 msgs.push(Message::GoToMarkerPosition(*marker_idx, 0));
277 });
278 } else {
279 ui.selectable_label(false, row_widget_text.clone())
280 .clicked()
281 .then(|| {
282 msgs.push(Message::GoToTime(waves.cursor.clone(), 0));
283 });
284 }
285 for (_, col_marker_time, _) in &markers {
286 ui.label(time_string(
287 &(*row_marker_time - *col_marker_time),
288 &waves.inner.metadata().timescale,
289 &self.user.wanted_timeunit,
290 &self.get_time_format(),
291 ));
292 }
293 ui.end_row();
294 }
295 });
296 ui.add_space(15.);
297 if ui.button("Close").clicked() {
298 msgs.push(Message::SetCursorWindowVisible(false));
299 }
300 });
301 });
302 if !open {
303 msgs.push(Message::SetCursorWindowVisible(false));
304 }
305 }
306
307 pub fn draw_marker_boxes(
308 &self,
309 waves: &WaveData,
310 ctx: &mut DrawingContext,
311 view_width: f32,
312 gap: f32,
313 viewport: &Viewport,
314 y_zero: f32,
315 ) {
316 let text_size = ctx.cfg.text_size;
317
318 for drawing_info in waves.drawing_infos.iter().filter_map(|item| match item {
319 ItemDrawingInfo::Marker(marker) => Some(marker),
320 _ => None,
321 }) {
322 let Some(item) = waves
323 .items_tree
324 .get_visible(drawing_info.item_list_idx)
325 .and_then(|node| waves.displayed_items.get(&node.item_ref))
326 else {
327 return;
328 };
329
330 let y_offset = drawing_info.top - y_zero;
334 let y_bottom = drawing_info.bottom - y_zero;
335
336 let background_color = item
337 .color()
338 .and_then(|color| self.user.config.theme.get_color(color))
339 .unwrap_or(&self.user.config.theme.cursor.color);
340
341 let x = waves.numbered_marker_location(drawing_info.idx, viewport, view_width);
342
343 let time = time_string(
345 waves
346 .markers
347 .get(&drawing_info.idx)
348 .unwrap_or(&BigInt::from(0)),
349 &waves.inner.metadata().timescale,
350 &self.user.wanted_timeunit,
351 &self.get_time_format(),
352 );
353
354 let text_color = *self.user.config.theme.get_best_text_color(background_color);
355
356 let galley =
358 ctx.painter
359 .layout_no_wrap(time, FontId::proportional(text_size), text_color);
360 let offset_width = galley.rect.width() * 0.5 + 2. * gap;
361
362 let min = (ctx.to_screen)(x - offset_width, y_offset - gap);
364 let max = (ctx.to_screen)(x + offset_width, y_bottom + gap);
365
366 ctx.painter.rect_filled(
367 Rect { min, max },
368 CornerRadius::default(),
369 *background_color,
370 );
371
372 ctx.painter.galley(
374 (ctx.to_screen)(
375 x - galley.rect.width() * 0.5,
376 (y_offset + y_bottom - galley.rect.height()) * 0.5,
377 ),
378 galley,
379 text_color,
380 );
381 }
382 }
383}