1use eframe::{
2 emath::{Align, Align2},
3 epaint::{Color32, CubicBezierShape, FontId, Shape, Stroke, Vec2},
4};
5use num::BigInt;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9 config::SurferTheme, displayed_item::DisplayedItemRef, view::DrawingContext,
10 viewport::Viewport, wave_data::WaveData,
11};
12
13#[derive(Serialize, Deserialize, Debug)]
14pub enum Direction {
15 North,
16 East,
17 South,
18 West,
19}
20
21impl Direction {
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)]
33pub enum Anchor {
34 Top,
35 Center,
36 Bottom,
37}
38
39#[derive(Serialize, Deserialize, Debug)]
40pub struct GraphicsY {
41 pub item: DisplayedItemRef,
42 pub anchor: Anchor,
43}
44
45#[derive(Serialize, Deserialize, Debug)]
47pub struct GrPoint {
48 pub x: BigInt,
50 pub y: GraphicsY,
51}
52
53#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
54pub struct GraphicId(pub usize);
55
56#[derive(Serialize, Deserialize, Debug)]
57pub enum Graphic {
58 TextArrow {
59 from: (GrPoint, Direction),
60 to: (GrPoint, Direction),
61 text: String,
62 },
63 Text {
64 pos: (GrPoint, Direction),
65 text: String,
66 },
67}
68
69impl WaveData {
70 fn get_item_y(&self, y: &GraphicsY) -> Option<f32> {
73 self.items_tree
74 .iter_visible()
75 .zip(&self.drawing_infos)
76 .find(|(node, _info)| node.item_ref == y.item)
77 .map(|(_, info)| match y.anchor {
78 Anchor::Top => info.top(),
79 Anchor::Center => info.top() + (info.bottom() - info.top()) / 2.,
80 Anchor::Bottom => info.bottom(),
81 })
82 .map(|point| point - self.top_item_draw_offset)
83 }
84
85 pub(crate) fn draw_graphics(
86 &self,
87 ctx: &mut DrawingContext,
88 size: Vec2,
89 viewport: &Viewport,
90 theme: &SurferTheme,
91 ) {
92 let color = theme.variable_dontcare;
93 let num_timestamps = self.num_timestamps().unwrap_or(1.into());
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 = viewport.pixel_from_time(&from_point.x, size.x, &num_timestamps);
102 let from_y = self.get_item_y(&from_point.y);
103
104 let to_x = viewport.pixel_from_time(&to_point.x, size.x, &num_timestamps);
105 let to_y = self.get_item_y(&to_point.y);
106
107 if let (Some(from_y), Some(to_y)) = (from_y, to_y) {
108 let from_dir = from_dir.as_vector() * 30.;
109 let to_dir_vec = to_dir.as_vector() * 30.;
110 let shape = Shape::CubicBezier(CubicBezierShape {
111 points: [
112 (ctx.to_screen)(from_x, from_y),
113 (ctx.to_screen)(from_x + from_dir.x, from_y + from_dir.y),
114 (ctx.to_screen)(to_x + to_dir_vec.x, to_y + to_dir_vec.y),
115 (ctx.to_screen)(to_x, to_y),
116 ],
117 closed: false,
118 fill: Color32::TRANSPARENT,
119 stroke: Stroke { width: 3., color }.into(),
120 });
121 ctx.painter.add(shape);
122
123 ctx.painter.text(
124 (ctx.to_screen)(to_x, to_y),
125 match to_dir {
126 Direction::North => Align2([Align::Center, Align::TOP]),
127 Direction::East => Align2([Align::LEFT, Align::Center]),
128 Direction::South => Align2([Align::Center, Align::BOTTOM]),
129 Direction::West => Align2([Align::RIGHT, Align::Center]),
130 },
131 text,
132 FontId::monospace(15.),
133 color,
134 );
135 }
136 }
137 Graphic::Text {
138 pos: (pos, dir),
139 text,
140 } => {
141 let to_x = viewport.pixel_from_time(&pos.x, size.x, &num_timestamps);
142 let to_y = self.get_item_y(&pos.y);
143 if let Some(to_y) = to_y {
144 ctx.painter.text(
145 (ctx.to_screen)(to_x, to_y),
146 match dir {
147 Direction::North => Align2([Align::Center, Align::TOP]),
148 Direction::East => Align2([Align::LEFT, Align::Center]),
149 Direction::South => Align2([Align::Center, Align::BOTTOM]),
150 Direction::West => Align2([Align::RIGHT, Align::Center]),
151 },
152 text,
153 FontId::monospace(15.),
154 color,
155 );
156 }
157 }
158 }
159 }
160 }
161}