Skip to main content

gallery/
anim.rs

1//! Animation lab tab: prop-tween scenes with autoplay.
2//!
3//! Four scenes use ordinary prop writes, and the runtime tweens each change.
4//! Runs hands-free while the tab is active; keys retarget individual scenes.
5
6use std::time::Duration;
7
8use omp_tui::{Component, IntoComponent as _, Key, Prop, Ui, components::Spinner, dom};
9
10/// One breath of the autoplay loop: long enough to watch a transition land.
11pub(crate) const AUTOPLAY_STEP: Duration = Duration::from_millis(1600);
12
13const BARS: &[(&str, &str)] =
14	&[("bar-linear", "linear"), ("bar-in", "in"), ("bar-out", "out"), ("bar-in-out", "in-out")];
15
16/// `(border/text token, panel background, status line)` per mood.
17const MOODS: &[(&str, &str, &str)] = &[
18	("ok", "#10231a", "all systems nominal"),
19	("warn", "#2b2312", "latency rising on shard 7"),
20	("err", "#2b1414", "shard 7 dropped out"),
21	("info", "#121f2e", "rebalancing replicas…"),
22];
23
24const PALETTES: &[&str] =
25	&["#0f0c29..#f5af19", "#12c2e9..#f64f59", "#134e5e..#71b280", "#41295a..#f4e2d8"];
26
27/// The animation-lab pane hosted by the gallery's `Anim` tab.
28pub(crate) fn pane() -> Box<dyn Component> {
29	let (_, mood_bg, mood_text) = MOODS[0];
30	dom! {
31		<col gap=1 pad="1 2">
32			<row gap=2>
33				<text bold fg="#f953c6..#43e97b" spin="4s">{"ANIMATION LAB"}</text>
34				{Spinner::new()}
35				<text dim>{"anim · ease · spin as plain props"}</text>
36			</row>
37			<box title="Easing race" border=round bc=muted pad="0 1">
38				<col>
39					for (id, ease) in BARS {
40						<row gap=1>
41							<text w=7 dim>{*ease}</text>
42							<col id={*id} w=12% h=1 bg=accent anim="900ms" ease={*ease}/>
43						</row>
44					}
45				</col>
46			</box>
47			<row gap=1>
48				<box id=mood grow title="Mood" border=round bleed anim="450ms" bc=ok bg={mood_bg}>
49					<text id="mood-text" fg=ok anim="450ms">{mood_text}</text>
50				</box>
51				<box id=hero grow title="Gradient morph" border=round bleed anim="900ms"
52					ease="in-out" bg={PALETTES[0]} angle=25 spin="6s">
53					<text bold>{"endpoints tween"}</text>
54					<text dim>{"angle spins forever"}</text>
55				</box>
56			</row>
57			<row gap=1>
58				<col id=sidebar w=14 anim="500ms" ease="in-out" bg="#1b2735" pad="0 1">
59					<text bold>{"sidebar"}</text>
60					<text dim>{"w tweens"}</text>
61				</col>
62				<box id=drawer grow h=4 anim="600ms" ease="in-out" border=round bc=muted
63					title="Drawer">
64					<md>{"Height tweens through **layout wakes**: every row below shifts \
65							smoothly.\n\n- retarget mid-flight: it resumes from the screen\n- \
66							first paint never animates\n- settled components request no frames"}</md>
67				</box>
68			</row>
69			<text dim>
70				{"space race · m mood · g gradient · s sidebar · d drawer · a autoplay"}
71			</text>
72		</col>
73	}
74	.into_component()
75}
76
77/// Scene state; every transition is just a prop write on retained ids.
78pub(crate) struct Lab {
79	race_wide:           bool,
80	mood:                usize,
81	palette:             usize,
82	sidebar_wide:        bool,
83	drawer_open:         bool,
84	pub(crate) autoplay: bool,
85	step:                usize,
86}
87
88impl Lab {
89	pub(crate) const fn new() -> Self {
90		Self {
91			race_wide:    false,
92			mood:         0,
93			palette:      0,
94			sidebar_wide: false,
95			drawer_open:  false,
96			autoplay:     true,
97			step:         0,
98		}
99	}
100
101	fn race(&mut self, ui: &mut Ui) {
102		self.race_wide = !self.race_wide;
103		let target = if self.race_wide { "88%" } else { "12%" };
104		for (id, _) in BARS {
105			ui.set_prop(id, Prop::W, target);
106		}
107	}
108
109	fn mood(&mut self, ui: &mut Ui) {
110		self.mood = (self.mood + 1) % MOODS.len();
111		let (token, bg, text) = MOODS[self.mood];
112		ui.set_prop("mood", Prop::Bc, token);
113		ui.set_prop("mood", Prop::Bg, bg);
114		ui.set_prop("mood-text", Prop::Fg, token);
115		ui.set_text("mood-text", text);
116	}
117
118	fn palette(&mut self, ui: &mut Ui) {
119		self.palette = (self.palette + 1) % PALETTES.len();
120		ui.set_prop("hero", Prop::Bg, PALETTES[self.palette]);
121	}
122
123	fn sidebar(&mut self, ui: &mut Ui) {
124		self.sidebar_wide = !self.sidebar_wide;
125		ui.set_prop("sidebar", Prop::W, if self.sidebar_wide { 30_u16 } else { 14 });
126	}
127
128	fn drawer(&mut self, ui: &mut Ui) {
129		self.drawer_open = !self.drawer_open;
130		ui.set_height("drawer", if self.drawer_open { 10 } else { 4 });
131	}
132
133	/// One autoplay step: cycles through the five scene toggles.
134	pub(crate) fn advance(&mut self, ui: &mut Ui) {
135		match self.step % 5 {
136			0 => self.race(ui),
137			1 => self.mood(ui),
138			2 => self.palette(ui),
139			3 => self.sidebar(ui),
140			_ => self.drawer(ui),
141		}
142		self.step += 1;
143	}
144
145	/// Routes one unclaimed key while the Anim tab is active.
146	pub(crate) fn handle_key(&mut self, key: Key, ui: &mut Ui) {
147		match key {
148			Key::Space => self.race(ui),
149			Key::Char('m') => self.mood(ui),
150			Key::Char('g') => self.palette(ui),
151			Key::Char('s') => self.sidebar(ui),
152			Key::Char('d') => self.drawer(ui),
153			Key::Char('a') => self.autoplay = !self.autoplay,
154			_ => {},
155		}
156	}
157}