| 1 | //! Gallery: one state change caught at fixed moments. The gallery is static, |
| 2 | //! so each entry starts the motions at a base instant and paints a fixed |
| 3 | //! offset after it; nothing reads a clock while painting. |
| 4 | |
| 5 | use std::sync::OnceLock; |
| 6 | use std::time::{Duration, Instant}; |
| 7 | |
| 8 | use ratatui::{buffer::Buffer, layout::Rect}; |
| 9 | |
| 10 | use super::Entry; |
| 11 | use crate::{MotionDemo, MotionMode, MotionPolicy, MotionSet, Paint, Spinner, Theme}; |
| 12 | |
| 13 | /// The instant every entry measures from. Read once, when the entries are |
| 14 | /// built, because an `Instant` cannot be made without the clock; only |
| 15 | /// differences from it are ever used. |
| 16 | fn base() -> Instant { |
| 17 | static BASE: OnceLock<Instant> = OnceLock::new(); |
| 18 | *BASE.get_or_init(Instant::now) |
| 19 | } |
| 20 | |
| 21 | /// Paint the demo `after` its state change to done, under `mode`. |
| 22 | fn frame(area: Rect, buf: &mut Buffer, theme: &Theme, mode: MotionMode, after: Duration) { |
| 23 | let (t0, policy) = (base(), MotionPolicy::new(mode, theme)); |
| 24 | let mut motions = MotionSet::new(); |
| 25 | MotionDemo::start(&mut motions, true, t0, policy); |
| 26 | MotionDemo::new(&motions, t0 + after, policy, true).paint(area, buf, theme); |
| 27 | } |
| 28 | |
| 29 | fn working(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 30 | let policy = MotionPolicy::new(MotionMode::Full, theme); |
| 31 | MotionDemo::new(&MotionSet::new(), base(), policy, false).paint(area, buf, theme); |
| 32 | } |
| 33 | |
| 34 | fn just_started(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 35 | frame(area, buf, theme, MotionMode::Full, Duration::ZERO); |
| 36 | } |
| 37 | |
| 38 | fn mid_flight(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 39 | frame( |
| 40 | area, |
| 41 | buf, |
| 42 | theme, |
| 43 | MotionMode::Full, |
| 44 | Duration::from_millis(100), |
| 45 | ); |
| 46 | } |
| 47 | |
| 48 | fn settled(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 49 | frame( |
| 50 | area, |
| 51 | buf, |
| 52 | theme, |
| 53 | MotionMode::Full, |
| 54 | Duration::from_millis(500), |
| 55 | ); |
| 56 | } |
| 57 | |
| 58 | /// Reduced motion, 100 ms in: already the settled frame. |
| 59 | fn reduced(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 60 | frame( |
| 61 | area, |
| 62 | buf, |
| 63 | theme, |
| 64 | MotionMode::Reduced, |
| 65 | Duration::from_millis(100), |
| 66 | ); |
| 67 | } |
| 68 | |
| 69 | /// The spinner under each mode, 3 s into the same work: what `Reduced` and |
| 70 | /// `Still` hold still (kept from the first draft of this package). |
| 71 | fn modes(area: Rect, buf: &mut Buffer, theme: &Theme) { |
| 72 | for (i, (mode, label)) in [ |
| 73 | (MotionMode::Full, "Working"), |
| 74 | (MotionMode::Reduced, "Reduced motion"), |
| 75 | (MotionMode::Still, "Still"), |
| 76 | ] |
| 77 | .into_iter() |
| 78 | .enumerate() |
| 79 | { |
| 80 | let row = Rect::new(area.x, area.y.saturating_add(i as u16), area.width, 1); |
| 81 | Spinner::new(label, Duration::from_secs(3), mode).paint(row.intersection(area), buf, theme); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | pub(crate) fn entries() -> Vec<Entry> { |
| 86 | base(); |
| 87 | [ |
| 88 | ("motion-modes", modes as fn(Rect, &mut Buffer, &Theme)), |
| 89 | ("motion-working", working), |
| 90 | ("motion-started", just_started), |
| 91 | ("motion-mid-flight", mid_flight), |
| 92 | ("motion-settled", settled), |
| 93 | ("motion-reduced", reduced), |
| 94 | ] |
| 95 | .into_iter() |
| 96 | .map(|(name, draw)| Entry { |
| 97 | name, |
| 98 | width: 40, |
| 99 | height: 3, |
| 100 | draw, |
| 101 | }) |
| 102 | .collect() |
| 103 | } |
| 104 |