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((255, cursor, WidgetText::RichText(RichText::new("Primary"))));
218 }
219
220 let mut numbered_markers = waves
221 .items_tree
222 .iter()
223 .map(|node| waves.displayed_items.get(&node.item_ref))
224 .filter_map(|displayed_item| match displayed_item {
225 Some(DisplayedItem::Marker(marker)) => {
226 let text_color = self.get_item_text_color(displayed_item.unwrap());
227 Some((
228 marker.idx,
229 waves.numbered_marker_time(marker.idx),
230 marker.marker_text(text_color),
231 ))
232 }
233 _ => None,
234 })
235 .sorted_by(|a, b| Ord::cmp(&a.0, &b.0))
236 .collect_vec();
237
238 markers.append(&mut numbered_markers);
239 Window::new("Markers")
240 .collapsible(true)
241 .resizable(true)
242 .open(&mut open)
243 .show(ctx, |ui| {
244 ui.vertical_centered(|ui| {
245 Grid::new("markers")
246 .striped(true)
247 .num_columns(markers.len() + 1)
248 .spacing([5., 5.])
249 .show(ui, |ui| {
250 ui.label("");
251 for (marker_idx, _, widget_text) in &markers {
252 if *marker_idx < 255 {
253 ui.selectable_label(false, widget_text.clone())
254 .clicked()
255 .then(|| {
256 msgs.push(Message::GoToMarkerPosition(*marker_idx, 0));
257 });
258 } else {
259 ui.selectable_label(false, widget_text.clone())
260 .clicked()
261 .then(|| {
262 msgs.push(Message::GoToTime(waves.cursor.clone(), 0));
263 });
264 }
265 }
266 ui.end_row();
267 for (marker_idx, row_marker_time, row_widget_text) in &markers {
268 if *marker_idx < 255 {
269 ui.selectable_label(false, row_widget_text.clone())
270 .clicked()
271 .then(|| {
272 msgs.push(Message::GoToMarkerPosition(*marker_idx, 0));
273 });
274 } else {
275 ui.selectable_label(false, row_widget_text.clone())
276 .clicked()
277 .then(|| {
278 msgs.push(Message::GoToTime(waves.cursor.clone(), 0));
279 });
280 }
281 for (_, col_marker_time, _) in &markers {
282 ui.label(time_string(
283 &(*row_marker_time - *col_marker_time),
284 &waves.inner.metadata().timescale,
285 &self.user.wanted_timeunit,
286 &self.get_time_format(),
287 ));
288 }
289 ui.end_row();
290 }
291 });
292 ui.add_space(15.);
293 if ui.button("Close").clicked() {
294 msgs.push(Message::SetCursorWindowVisible(false));
295 }
296 });
297 });
298 if !open {
299 msgs.push(Message::SetCursorWindowVisible(false));
300 }
301 }
302
303 pub fn draw_marker_boxes(
304 &self,
305 waves: &WaveData,
306 ctx: &mut DrawingContext,
307 view_width: f32,
308 gap: f32,
309 viewport: &Viewport,
310 y_zero: f32,
311 ) {
312 let text_size = ctx.cfg.text_size;
313
314 for drawing_info in waves.drawing_infos.iter().filter_map(|item| match item {
315 ItemDrawingInfo::Marker(marker) => Some(marker),
316 _ => None,
317 }) {
318 let Some(item) = waves
319 .items_tree
320 .get_visible(drawing_info.item_list_idx)
321 .and_then(|node| waves.displayed_items.get(&node.item_ref))
322 else {
323 return;
324 };
325
326 let y_offset = drawing_info.top - y_zero;
330 let y_bottom = drawing_info.bottom - y_zero;
331
332 let background_color = item
333 .color()
334 .and_then(|color| self.user.config.theme.get_color(color))
335 .unwrap_or(&self.user.config.theme.cursor.color);
336
337 let x = waves.numbered_marker_location(drawing_info.idx, viewport, view_width);
338
339 let time = time_string(
341 waves
342 .markers
343 .get(&drawing_info.idx)
344 .unwrap_or(&BigInt::from(0)),
345 &waves.inner.metadata().timescale,
346 &self.user.wanted_timeunit,
347 &self.get_time_format(),
348 );
349
350 let text_color = *self.user.config.theme.get_best_text_color(background_color);
351
352 let galley =
354 ctx.painter
355 .layout_no_wrap(time, FontId::proportional(text_size), text_color);
356 let offset_width = galley.rect.width() * 0.5 + 2. * gap;
357
358 let min = (ctx.to_screen)(x - offset_width, y_offset - gap);
360 let max = (ctx.to_screen)(x + offset_width, y_bottom + gap);
361
362 ctx.painter.rect_filled(
363 Rect { min, max },
364 CornerRadius::default(),
365 *background_color,
366 );
367
368 ctx.painter.galley(
370 (ctx.to_screen)(
371 x - galley.rect.width() * 0.5,
372 (y_offset + y_bottom - galley.rect.height()) * 0.5,
373 ),
374 galley,
375 text_color,
376 );
377 }
378 }
379}