1use std::{
2 f64::consts::TAU,
3 iter::repeat,
4};
5use adic::AdicInteger;
6
7use crate::{AdicShapeError, DisplayShape};
8use super::element::{
9 AdicEl,
10 PathDInstruction,
11 CircleEl, PathEl, TextEl,
12};
13
14
15type Coordinate = (f64, f64);
16
17#[derive(Debug, Clone)]
18#[doc = ""]
32#[doc = "<style>"]
33#[doc = include_str!("../../img/rustdoc.css")]
34#[doc = "</style>"]
35#[doc = ""]
36#[doc = include_str!("../../img/clock-shape-example.svg")]
37#[doc = ""]
38pub struct ClockShape {
39 p: u32,
41 options: ClockShapeOptions,
43 position: ClockPosition,
45 clock_hands: Vec<ClockHand>,
47}
48
49
50#[derive(Debug, Clone, Copy)]
51pub struct ClockShapeOptions {
53 pub clock_movement: ClockMovement,
55 pub show_hand_radii: bool,
57 pub viewbox_width: u32,
59 pub viewbox_height: u32,
61}
62
63impl Default for ClockShapeOptions {
64 fn default() -> Self {
65 ClockShapeOptions {
66 clock_movement: ClockMovement::Sweeping,
67 show_hand_radii: true,
68 viewbox_width: 100,
69 viewbox_height: 100,
70 }
71 }
72}
73
74
75#[derive(Debug, Clone, Copy)]
76pub struct ClockPosition {
78 pub center: Coordinate,
80 pub radius: f64,
82}
83
84#[derive(Debug, Clone, Copy)]
85pub enum ClockMovement {
87 Ticking,
89 Sweeping,
91}
92
93
94impl ClockShape {
95
96 pub fn new(
101 adic_data: &impl AdicInteger,
102 num_digits: usize,
103 options: ClockShapeOptions,
104 ) -> Result<Self, AdicShapeError> {
105
106 let p = adic_data.p();
107 let mut hands = Vec::with_capacity(num_digits);
108 let mut offset = 0.0;
109 for (i, d) in adic_data.digits().copied().chain(repeat(0)).take(num_digits).enumerate() {
110 hands.push(ClockHand{
111 order: i.try_into()?,
112 tick: d,
113 offset,
114 });
115 offset = match options.clock_movement {
116 ClockMovement::Ticking => 0.0,
117 ClockMovement::Sweeping => {
118 (f64::from(d) + offset) / f64::from(p)
119 }
120 };
121 }
122
123 let width = f64::from(options.viewbox_width);
124 let height = f64::from(options.viewbox_height);
125 Ok(Self {
126 p,
127 options,
128 position: ClockPosition {
129 center: (0.5*width, 0.5*height),
130 radius: 0.4*width,
131 },
132 clock_hands: hands,
133 })
134
135 }
136
137 pub fn p(&self) -> u32 {
139 self.p
140 }
141
142 pub fn center(&self) -> (f64, f64) {
144 self.position.center
145 }
146
147 pub fn hands(&self) -> &Vec<ClockHand> {
149 &self.clock_hands
150 }
151
152 pub fn num_hands(&self) -> usize {
154 self.hands().len()
155 }
156
157 pub fn hand_positions(&self) -> Vec<ClockHandPosition> {
159 self.clock_hands.iter().map(|hand| self.calc_hand_position(hand)).collect()
160 }
161
162 pub fn tick_positions(&self) -> Vec<Coordinate> {
164 (0..self.p).map(|tick| {
165 let tick_unit_coord = unit_coord(self.p, f64::from(tick));
166 let x = self.position.center.0 + self.position.radius * tick_unit_coord.0;
167 let y = self.position.center.1 + self.position.radius * tick_unit_coord.1;
168 (x, y)
169 }).collect()
170 }
171
172 fn calc_hand_position(
173 &self,
174 hand: &ClockHand,
175 ) -> ClockHandPosition {
176
177 let frac_radius = f64::from(hand.order + 1) / f64::from(u32::try_from(self.num_hands() + 1).unwrap());
179 let radius = frac_radius * self.position.radius;
180
181 let tick_amount = f64::from(hand.tick) + hand.offset;
182 let unit = unit_coord(self.p, tick_amount);
183 let x = self.position.center.0 + radius * unit.0;
184 let y = self.position.center.1 + radius * unit.1;
185
186 ClockHandPosition {
187 radius,
188 head_position: (x, y),
189 }
190
191 }
192
193 fn clock_face_instructions(&self) -> impl Iterator<Item=AdicEl> {
194
195 let (cx, cy) = self.position.center;
196 let face_radius = self.position.radius;
197
198 let mut face_views = vec![
199 AdicEl::Circle(CircleEl{
200 class: Some("clock-circle".to_string()),
201 cx, cy, r: face_radius,
202 })
203 ];
204
205 if self.options.show_hand_radii {
206 self.hand_positions().iter().map(|pos| pos.radius).for_each(|r| {
207 face_views.push(
208 AdicEl::Circle(CircleEl{
209 class: Some("clock-sub-circle".to_string()),
210 cx, cy, r,
211 })
212 );
213 });
214 }
215
216 face_views.into_iter()
217
218 }
219
220 fn clock_hand_instructions(&self) -> impl Iterator<Item=AdicEl> {
221
222 let (cx, cy) = self.position.center;
229 self.hand_positions().into_iter().flat_map(move |hand_pos| {
230 let target = hand_pos.head_position;
231 let hand_data = vec![
232 PathDInstruction::Move((cx, cy)),
233 PathDInstruction::Line(target),
234 ];
235 [
236 AdicEl::Path(PathEl{
237 class: Some("clock-hand-path".to_string()),
238 d: hand_data,
239 }),
240 AdicEl::Circle(CircleEl{
241 class: Some("clock-head-circle".to_string()),
242 cx: target.0, cy: target.1, r: 0.5,
243 })
244 ]
245 })
246
247 }
248
249 fn clock_mark_instructions(&self) -> impl Iterator<Item=AdicEl> {
250
251 let (cx, cy) = self.position.center;
252 self.tick_positions().into_iter().map(move |tick_pos| {
253 let source = (cx + (tick_pos.0 - cx) * 0.95, cy + (tick_pos.1 - cy) * 0.95);
254 let target = tick_pos;
255 let mark_instructions = vec![
256 PathDInstruction::Move(source),
257 PathDInstruction::Line(target),
258 ];
259 AdicEl::Path(PathEl{
260 class: Some("clock-hand-path".to_string()),
261 d: mark_instructions
262 })
263 })
264
265 }
266
267 fn clock_label_instructions(&self) -> impl Iterator<Item=AdicEl> {
268
269 let label_font_size = 4.;
270 let magic_font_multiplier = 1.;
272 let label_size = label_font_size * magic_font_multiplier;
273 let label_style = format!("position: fixed; font-size: {label_size}pt;");
274
275 let (cx, cy) = self.position.center;
276 self.tick_positions().into_iter().enumerate().map(move |(tick, tick_pos)| {
277 let adjusted = (
278 (tick_pos.0 - cx) * 0.1,
279 (tick_pos.1 - cy) * 0.1
280 );
281 AdicEl::Text(TextEl{
283 content: tick.to_string(),
284 class: Some("tick-label".to_string()),
285 style: Some(label_style.clone()),
286 x: tick_pos.0, y: tick_pos.1,
287 dx: adjusted.0, dy: adjusted.1,
288 })
289 })
290
291 }
292
293}
294
295impl DisplayShape for ClockShape {
296
297 fn adic_els(&self) -> impl Iterator<Item=AdicEl> {
299
300 let clock_face_circle = self.clock_face_instructions();
302 let clock_hand_paths = self.clock_hand_instructions();
303 let clock_marks = self.clock_mark_instructions();
304 let clock_labels = self.clock_label_instructions();
305
306 clock_face_circle
307 .chain(clock_hand_paths)
308 .chain(clock_marks)
309 .chain(clock_labels)
310
311 }
312
313 fn default_class(&self) -> String {
314 "adic-clock".to_string()
315 }
316
317 fn viewbox_width(&self) -> u32 {
318 self.options.viewbox_width
319 }
320 fn viewbox_height(&self) -> u32 {
321 self.options.viewbox_height
322 }
323}
324
325
326#[derive(Debug, Clone)]
327pub struct ClockHand {
329 pub order: i32,
331 pub tick: u32,
333 pub offset: f64,
335}
336
337#[derive(Debug, Clone)]
338pub struct ClockHandPosition {
340 pub radius: f64,
342 pub head_position: Coordinate,
344}
345
346
347
348fn unit_coord(p: u32, tick_amount: f64) -> Coordinate {
349 let arc_fraction = tick_amount / f64::from(p);
350 let x = (TAU * arc_fraction).sin();
351 let y = - (TAU * arc_fraction).cos();
352 (x, y)
353}
354
355
356
357#[cfg(test)]
358mod test {
359 use adic::uadic;
360 use super::{ClockMovement, ClockShape, ClockShapeOptions};
361
362 use super::super::test_util::assert_diff_lt;
363
364 #[test]
365 fn correct_numbers() {
366
367 let adic_data = uadic!(5, [3, 2, 4, 1, 4, 1, 2]);
368 let shape_options = ClockShapeOptions::default();
369
370 let clock_shape = ClockShape::new(&adic_data, 6, shape_options).unwrap();
371
372 assert_eq!(5, clock_shape.p);
373 assert_eq!(6, clock_shape.num_hands());
374
375 let hands = clock_shape.clock_hands;
376 assert_eq!(0, hands[0].order);
377 assert_eq!(3, hands[0].tick);
378 assert_eq!(1, hands[1].order);
379 assert_eq!(2, hands[1].tick);
380 assert_eq!(2, hands[2].order);
381 assert_eq!(4, hands[2].tick);
382 assert_eq!(3, hands[3].order);
383 assert_eq!(1, hands[3].tick);
384 assert_eq!(4, hands[4].order);
385 assert_eq!(4, hands[4].tick);
386 assert_eq!(5, hands[5].order);
387 assert_eq!(1, hands[5].tick);
388
389 }
390
391 #[test]
392 fn correct_bounds() {
393
394 let adic_data = uadic!(5, [3, 2, 4, 1, 4, 1, 2]);
395 let shape_options = ClockShapeOptions {
396 clock_movement: ClockMovement::Ticking,
397 ..Default::default()
398 };
399 let clock_shape = ClockShape::new(&adic_data, 6, shape_options).unwrap();
400 let hand_pos = clock_shape.hand_positions();
401 assert!(0. <= hand_pos.iter().map(|hand| hand.head_position.0).min_by(f64::total_cmp).unwrap());
402 assert!(100. >= hand_pos.iter().map(|hand| hand.head_position.0).max_by(f64::total_cmp).unwrap());
403 assert!(0. <= hand_pos.iter().map(|hand| hand.head_position.1).min_by(f64::total_cmp).unwrap());
404 assert!(100. >= hand_pos.iter().map(|hand| hand.head_position.1).max_by(f64::total_cmp).unwrap());
405 assert!(0. <= hand_pos.iter().map(|hand| hand.radius).min_by(f64::total_cmp).unwrap());
406 assert!(50. >= hand_pos.iter().map(|hand| hand.radius).max_by(f64::total_cmp).unwrap());
407
408 }
409
410 #[test]
411 fn correct_offsets() {
412
413 let adic_data = uadic!(5, [3, 2, 4, 1, 4, 1, 2]);
414 let shape_options = ClockShapeOptions {
415 clock_movement: ClockMovement::Ticking,
416 ..Default::default()
417 };
418 let clock_shape = ClockShape::new(&adic_data, 6, shape_options).unwrap();
419
420 for hand in clock_shape.clock_hands {
421 assert_diff_lt!(0., hand.offset, 0.1);
422 }
423
424 let shape_options = ClockShapeOptions {
425 clock_movement: ClockMovement::Sweeping,
426 ..Default::default()
427 };
428 let clock_shape = ClockShape::new(&adic_data, 6, shape_options).unwrap();
429
430 let hands = clock_shape.clock_hands;
431 assert_diff_lt!(0., hands[0].offset, 0.1);
432 assert_diff_lt!(0.6, hands[1].offset, 0.1);
433 assert_diff_lt!(0.52, hands[2].offset, 0.1);
434 assert_diff_lt!(0.90, hands[3].offset, 0.1);
435 assert_diff_lt!(0.38, hands[4].offset, 0.1);
436 assert_diff_lt!(0.88, hands[5].offset, 0.1);
437
438
439 }
440
441}