1use gpui::{
2 App, Bounds, FontWeight, Hsla, PathBuilder, Pixels, Point, SharedString, TextAlign, Window,
3 point, px,
4};
5
6use super::{
7 label::PlotLabel, label::TEXT_GAP, label::TEXT_HEIGHT, label::TEXT_SIZE, label::Text,
8 origin_point,
9};
10
11pub const AXIS_GAP: f32 = 18.;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
15pub enum AxisLabelSide {
16 #[default]
18 End,
19 Start,
21}
22
23pub struct AxisText {
24 pub text: SharedString,
25 pub tick: Pixels,
26 pub color: Hsla,
27 pub font_size: Pixels,
28 pub align: TextAlign,
29}
30
31impl AxisText {
32 pub fn new(text: impl Into<SharedString>, tick: impl Into<Pixels>, color: Hsla) -> Self {
33 Self {
34 text: text.into(),
35 tick: tick.into(),
36 color,
37 font_size: TEXT_SIZE.into(),
38 align: TextAlign::Left,
39 }
40 }
41
42 pub fn font_size(mut self, font_size: impl Into<Pixels>) -> Self {
43 self.font_size = font_size.into();
44 self
45 }
46
47 pub fn align(mut self, align: TextAlign) -> Self {
48 self.align = align;
49 self
50 }
51}
52
53#[derive(Default)]
54pub struct PlotAxis {
55 x: Option<Pixels>,
56 x_label: PlotLabel,
57 x_axis: bool,
58 x_label_side: AxisLabelSide,
59 y: Option<Pixels>,
60 y_label: PlotLabel,
61 y_axis: bool,
62 y_label_side: AxisLabelSide,
63 stroke: Hsla,
64}
65
66impl PlotAxis {
67 pub fn new() -> Self {
68 Self {
69 x_axis: true,
70 ..Default::default()
71 }
72 }
73
74 pub fn x(mut self, x: impl Into<Pixels>) -> Self {
76 self.x = Some(x.into());
77 self
78 }
79
80 pub fn x_axis(mut self, x_axis: bool) -> Self {
84 self.x_axis = x_axis;
85 self
86 }
87
88 pub fn x_label(mut self, label: impl IntoIterator<Item = AxisText>) -> Self {
90 if let Some(x) = self.x {
91 let side = self.x_label_side;
92 self.x_label = label
93 .into_iter()
94 .map(|t| {
95 let y = match side {
96 AxisLabelSide::End => x + px(TEXT_GAP * 3.),
97 AxisLabelSide::Start => x - px(TEXT_GAP + TEXT_HEIGHT),
98 };
99 Text {
100 text: t.text,
101 origin: point(t.tick, y),
102 color: t.color,
103 font_size: t.font_size,
104 font_weight: FontWeight::NORMAL,
105 align: t.align,
106 }
107 })
108 .into();
109 }
110 self
111 }
112
113 pub fn x_label_side(mut self, side: AxisLabelSide) -> Self {
115 self.x_label_side = side;
116 self
117 }
118
119 pub fn y(mut self, y: impl Into<Pixels>) -> Self {
121 self.y = Some(y.into());
122 self
123 }
124
125 pub fn y_axis(mut self, y_axis: bool) -> Self {
129 self.y_axis = y_axis;
130 self
131 }
132
133 pub fn y_label(mut self, label: impl IntoIterator<Item = AxisText>) -> Self {
135 if let Some(y) = self.y {
136 let side = self.y_label_side;
137 self.y_label = label
138 .into_iter()
139 .map(|t| {
140 let x = match side {
141 AxisLabelSide::End => y + px(TEXT_GAP),
142 AxisLabelSide::Start => y - px(TEXT_GAP),
143 };
144 Text {
145 text: t.text,
146 origin: point(x, t.tick - px(TEXT_SIZE / 2.)),
147 color: t.color,
148 font_size: t.font_size,
149 font_weight: FontWeight::NORMAL,
150 align: t.align,
151 }
152 })
153 .into();
154 }
155 self
156 }
157
158 pub fn y_label_side(mut self, side: AxisLabelSide) -> Self {
160 self.y_label_side = side;
161 self
162 }
163
164 pub fn stroke(mut self, stroke: impl Into<Hsla>) -> Self {
166 self.stroke = stroke.into();
167 self
168 }
169
170 fn draw_axis(&self, start_point: Point<Pixels>, end_point: Point<Pixels>, window: &mut Window) {
171 let mut builder = PathBuilder::stroke(px(1.));
172 builder.move_to(start_point);
173 builder.line_to(end_point);
174 if let Ok(path) = builder.build() {
175 window.paint_path(path, self.stroke);
176 }
177 }
178
179 pub fn paint(&self, bounds: &Bounds<Pixels>, window: &mut Window, cx: &mut App) {
181 let origin = bounds.origin;
182
183 if let Some(x) = self.x {
185 if self.x_axis {
186 self.draw_axis(
187 origin_point(px(0.), x, origin),
188 origin_point(bounds.size.width, x, origin),
189 window,
190 );
191 }
192 }
193 self.x_label.paint(bounds, window, cx);
194
195 if let Some(y) = self.y {
197 if self.y_axis {
198 self.draw_axis(
199 origin_point(y, px(0.), origin),
200 origin_point(y, bounds.size.height, origin),
201 window,
202 );
203 }
204 }
205 self.y_label.paint(bounds, window, cx);
206 }
207}