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    }
85
86    pub(crate) fn draw_graphics(
87        &self,
88        ctx: &mut DrawingContext,
89        viewport: &Viewport,
90        theme: &SurferTheme,
91    ) {
92        let color = theme.variable_dontcare;
93        let range = self.time_range();
94        for g in self.graphics.values() {
95            match g {
96                Graphic::TextArrow {
97                    from: (from_point, from_dir),
98                    to: (to_point, to_dir),
99                    text,
100                } => {
101                    let from_x =
102                        viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
103                    let from_y = self.get_item_y(&from_point.y);
104
105                    let to_x = viewport.pixel_from_time(&to_point.x, ctx.cfg.canvas_size.x, range);
106                    let to_y = self.get_item_y(&to_point.y);
107
108                    if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
109                        let from_dir = from_dir.as_vector() * 30.;
110                        let to_dir_vec = to_dir.as_vector() * 30.;
111                        let shape = Shape::CubicBezier(CubicBezierShape {
112                            points: [
113                                (ctx.to_screen)(from_x, from_y),
114                                (ctx.to_screen)(from_x + from_dir.x, from_y + from_dir.y),
115                                (ctx.to_screen)(to_x + to_dir_vec.x, to_y + to_dir_vec.y),
116                                (ctx.to_screen)(to_x, to_y),
117                            ],
118                            closed: false,
119                            fill: Color32::TRANSPARENT,
120                            stroke: Stroke { width: 3., color }.into(),
121                        });
122                        ctx.painter.add(shape);
123
124                        ctx.painter.text(
125                            (ctx.to_screen)(to_x, to_y),
126                            match to_dir {
127                                Direction::North => Align2([Align::Center, Align::TOP]),
128                                Direction::East => Align2([Align::LEFT, Align::Center]),
129                                Direction::South => Align2([Align::Center, Align::BOTTOM]),
130                                Direction::West => Align2([Align::RIGHT, Align::Center]),
131                            },
132                            text,
133                            FontId::monospace(15.),
134                            color,
135                        );
136                    }
137                }
138                Graphic::Text {
139                    pos: (pos, dir),
140                    text,
141                } => {
142                    let to_x = viewport.pixel_from_time(&pos.x, ctx.cfg.canvas_size.x, range);
143                    let to_y = self.get_item_y(&pos.y);
144                    if let Some(to_y) = to_y {
145                        ctx.painter.text(
146                            (ctx.to_screen)(to_x, to_y),
147                            match dir {
148                                Direction::North => Align2([Align::Center, Align::TOP]),
149                                Direction::East => Align2([Align::LEFT, Align::Center]),
150                                Direction::South => Align2([Align::Center, Align::BOTTOM]),
151                                Direction::West => Align2([Align::RIGHT, Align::Center]),
152                            },
153                            text,
154                            FontId::monospace(15.),
155                            color,
156                        );
157                    }
158                }
159                Graphic::Rectangle {
160                    from: from_point,
161                    to: to_point,
162                } => {
163                    let from_x =
164                        viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
165                    let from_y = self.get_item_y(&from_point.y);
166                    let to_x =
167                        viewport.pixel_from_time(&from_point.x, ctx.cfg.canvas_size.x, range);
168                    let to_y = self.get_item_y(&to_point.y);
169
170                    if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
171                        let start_pos = (ctx.to_screen)(from_x, from_y);
172                        let end_pos = (ctx.to_screen)(to_x, to_y);
173                        let temp_rect = emath::Rect::from_two_pos(start_pos, end_pos);
174                        let stroke: Stroke = Stroke { width: 3., color };
175
176                        ctx.painter
177                            .rect_stroke(temp_rect, 0.0, stroke, egui::StrokeKind::Middle);
178                    }
179                }
180            }
181        }
182    }
183}