Skip to main content

libsurfer/
graphics.rs

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/// A point used to place graphics.
47#[derive(Serialize, Deserialize, Debug, Clone)]
48pub struct GrPoint {
49    /// Timestamp at which to place the graphic
50    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    // FIXME: This function should probably not be here, we should instead update ItemDrawingInfo to
76    // have this info
77    #[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    /// Returns the y-value of an item given a percentual value
88    #[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}