1use crate::{theme, ticker::Ticker};
3use piet_common::{
4 kurbo::{Line, Point, Rect, Size},
5 Color, Error as PietError, Piet, PietTextLayout, RenderContext, Text, TextAttribute,
6 TextLayout, TextLayoutBuilder,
7};
8use std::{fmt, ops::Deref};
9
10const DEFAULT_LABEL_FONT_SIZE: f64 = 16.;
11
12#[derive(Debug, Copy, Clone, PartialEq)]
17pub enum LabelPosition {
18 Before,
20 After,
22}
23
24#[derive(Debug, Clone, Copy)]
35pub enum Direction {
36 Horizontal,
37 Vertical,
38}
39
40#[derive(Clone)]
47pub struct Axis<T> {
48 direction: Direction,
50 label_pos: LabelPosition,
52 ticker: T,
54
55 label_font_size: f64,
59
60 is_layout_valid: bool,
62 axis_len: f64,
64 label_layouts: Vec<Label>,
70 labels_to_draw: Vec<usize>,
72}
73
74impl<T: fmt::Debug> fmt::Debug for Axis<T> {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 f.debug_struct("Axis")
77 .field("direction", &self.direction)
78 .field("label_pos", &self.label_pos)
79 .field("is_layout_valid", &self.is_layout_valid)
80 .field("axis_len", &self.axis_len)
81 .field("ticker", &self.ticker)
82 .field("label_layouts", &self.label_layouts)
83 .field("labels_to_draw", &self.labels_to_draw)
84 .finish()
85 }
86}
87
88impl<T: Ticker> Axis<T> {
89 pub fn new(direction: Direction, label_pos: LabelPosition, ticker: T) -> Self {
91 Self {
92 direction,
93 label_pos,
94 ticker,
95 label_font_size: DEFAULT_LABEL_FONT_SIZE,
96
97 is_layout_valid: false,
98 axis_len: 0.,
99 label_layouts: vec![],
100 labels_to_draw: vec![],
101 }
102 }
103
104 pub fn ticker(&self) -> &T {
105 &self.ticker
106 }
107
108 pub fn set_ticker(&mut self, new_ticker: T) {
109 self.ticker = new_ticker;
110 self.is_layout_valid = false;
111 }
112
113 pub fn size(&self) -> Size {
114 self.assert_layout();
115
116 let max_label_size = self.labels_to_draw().fold(Size::ZERO, |mut size, layout| {
118 let run_size = layout.rect().size();
119 if run_size.width > size.width {
120 size.width = run_size.width;
121 }
122 if run_size.height > size.height {
123 size.height = run_size.height;
124 }
125 size
126 });
127
128 match self.direction {
129 Direction::Horizontal => {
130 Size::new(self.axis_len, max_label_size.height + theme::MARGIN)
131 }
132 Direction::Vertical => Size::new(max_label_size.width + theme::MARGIN, self.axis_len),
133 }
134 }
135
136 pub fn layout(&mut self, axis_len: f64, rc: &mut Piet) -> Result<(), PietError> {
138 self.is_layout_valid = true;
139 self.axis_len = axis_len;
140 self.ticker.layout(axis_len);
141 self.build_label_layouts(rc)?;
142 self.fit_labels();
143 Ok(())
144 }
145
146 pub fn draw(&self, rc: &mut Piet) {
148 let Size { width, height } = self.size();
149
150 for tick in self.ticker.ticks() {
152 let tick_line = match (self.direction, self.label_pos) {
153 (Direction::Vertical, LabelPosition::Before) => {
154 Line::new((width - 5., tick.pos), (width, tick.pos))
156 }
157 (Direction::Vertical, LabelPosition::After) => {
158 Line::new((0., tick.pos), (5., tick.pos))
160 }
161 (Direction::Horizontal, LabelPosition::Before) => {
162 Line::new((tick.pos, height - 5.), (tick.pos, height))
164 }
165 (Direction::Horizontal, LabelPosition::After) => {
166 Line::new((tick.pos, 0.), (tick.pos, 5.))
168 }
169 };
170 rc.stroke(tick_line, &Color::grey8(80), 1.);
171 }
172
173 let axis_line = match (self.direction, self.label_pos) {
175 (Direction::Horizontal, LabelPosition::Before) => {
176 Line::new((-1., height), (width + 1., height))
177 }
178 (Direction::Horizontal, LabelPosition::After) => Line::new((-1., 0.), (width + 1., 0.)),
179 (Direction::Vertical, LabelPosition::Before) => {
180 Line::new((width, -1.), (width, height + 1.))
181 }
182 (Direction::Vertical, LabelPosition::After) => Line::new((0., -1.), (0., height + 1.)),
183 };
184 rc.stroke(axis_line, &Color::BLACK, 2.);
185
186 for label in self.labels_to_draw() {
188 rc.draw_text(&label.layout, label.pos);
189 }
190 }
191
192 fn build_label_layouts(&mut self, rc: &mut Piet) -> Result<(), PietError> {
193 self.assert_layout();
194 self.label_layouts.clear();
195
196 if self.ticker.len() == 0 {
197 return Ok(());
199 }
200
201 let text = rc.text();
202 let mut largest = Size::ZERO;
205 for tick in self.ticker.ticks() {
206 let layout = text
207 .new_text_layout(tick.label)
208 .default_attribute(TextAttribute::FontSize(self.label_font_size))
209 .build()?;
210 let size = layout.size();
211 if size.width > largest.width {
212 largest.width = size.width;
213 }
214 if size.height > largest.height {
215 largest.height = size.height;
216 }
217 self.label_layouts.push(Label {
218 layout,
219 pos: Point::ZERO,
220 });
221 }
222
223 for (pos, label) in self
225 .ticker
226 .ticks()
227 .map(|tick| tick.pos)
228 .zip(self.label_layouts.iter_mut())
229 {
230 let size = label.layout.size();
231
232 let pos = match self.direction {
233 Direction::Horizontal => {
234 let x = pos - size.width * 0.5;
235 let y = match self.label_pos {
236 LabelPosition::Before => 0.,
238 LabelPosition::After => theme::MARGIN,
239 };
240 Point { x, y }
241 }
242 Direction::Vertical => {
243 let x = match self.label_pos {
244 LabelPosition::Before => largest.width - size.width,
246 LabelPosition::After => theme::MARGIN,
248 };
249 let y = pos - size.height * 0.5;
250 Point { x, y }
251 }
252 };
253 label.pos = pos;
254 }
255 Ok(())
256 }
257
258 fn fit_labels(&mut self) {
261 let mut step = 1;
264 while step <= (self.label_layouts.len() + 1) / 2 {
267 self.labels_to_draw.clear();
268 self.labels_to_draw
271 .extend((0..self.label_layouts.len()).step_by(step));
272 if !self.do_layouts_overlap() {
273 return;
274 }
275 step += 1;
276 }
277 println!("can't layout anything");
279 self.labels_to_draw.clear();
280 }
281
282 fn labels_to_draw(&self) -> impl Iterator<Item = &'_ Label> {
284 self.labels_to_draw
285 .iter()
286 .copied()
287 .map(|idx| &self.label_layouts[idx])
288 }
289
290 fn do_layouts_overlap(&self) -> bool {
297 #[cfg(debug_assertions)]
298 self.assert_layout();
299 let mut prev_rect: Option<Rect> = None;
305
306 for label in self.labels_to_draw() {
307 let rect = label.rect();
308 if let Some(prev_rect) = prev_rect {
309 if !prev_rect.intersect(rect).is_empty() {
310 return true;
311 }
312 }
313 prev_rect = Some(rect);
314 }
315 false
316 }
317
318 fn assert_layout(&self) {
319 if !self.is_layout_valid {
320 panic!("layout not called");
321 }
322 }
323}
324
325#[derive(Clone)]
327struct Label {
328 pos: Point,
329 layout: PietTextLayout,
330}
331
332impl Label {
333 pub fn rect(&self) -> Rect {
334 Rect::from_origin_size(self.pos, self.layout.size())
335 }
336}
337
338impl Deref for Label {
339 type Target = PietTextLayout;
340 fn deref(&self) -> &Self::Target {
341 &self.layout
342 }
343}
344
345impl fmt::Debug for Label {
346 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
347 f.debug_struct("Label")
348 .field("text", &self.layout.text())
349 .field("area", &self.rect())
350 .finish()
351 }
352}