1use crate::components::editor::{DopeSheetKeyframe, DopeSheetTrack, KeyframeKind, TrackKind};
7
8#[must_use]
10pub fn sample_dope_sheet_tracks() -> Vec<DopeSheetTrack> {
11 vec![
12 DopeSheetTrack {
13 label: "Video".into(),
14 kind: TrackKind::Video,
15 keyframes: vec![
16 DopeSheetKeyframe {
17 time: 0.0,
18 kind: KeyframeKind::Hold,
19 },
20 DopeSheetKeyframe {
21 time: 2.5,
22 kind: KeyframeKind::Ease,
23 },
24 DopeSheetKeyframe {
25 time: 6.0,
26 kind: KeyframeKind::Linear,
27 },
28 ],
29 },
30 DopeSheetTrack {
31 label: "Cursor".into(),
32 kind: TrackKind::Cursor,
33 keyframes: vec![
34 DopeSheetKeyframe {
35 time: 1.2,
36 kind: KeyframeKind::Linear,
37 },
38 DopeSheetKeyframe {
39 time: 4.5,
40 kind: KeyframeKind::Linear,
41 },
42 ],
43 },
44 DopeSheetTrack {
45 label: "Audio".into(),
46 kind: TrackKind::Audio,
47 keyframes: vec![
48 DopeSheetKeyframe {
49 time: 0.0,
50 kind: KeyframeKind::Hold,
51 },
52 DopeSheetKeyframe {
53 time: 4.0,
54 kind: KeyframeKind::Hold,
55 },
56 DopeSheetKeyframe {
57 time: 7.5,
58 kind: KeyframeKind::Hold,
59 },
60 ],
61 },
62 DopeSheetTrack {
63 label: "Caption".into(),
64 kind: TrackKind::Caption,
65 keyframes: vec![DopeSheetKeyframe {
66 time: 3.0,
67 kind: KeyframeKind::Marker,
68 }],
69 },
70 ]
71}
72
73#[must_use]
76pub fn sample_dope_sheet_dense() -> Vec<DopeSheetTrack> {
77 let mut tracks = sample_dope_sheet_tracks();
78 tracks.push(DopeSheetTrack {
79 label: "Zoom".into(),
80 kind: TrackKind::Effect,
81 keyframes: (0..12)
82 .map(|i| DopeSheetKeyframe {
83 #[allow(
84 clippy::cast_precision_loss,
85 reason = "i ≤ 12 fits in f32 without loss"
86 )]
87 time: i as f32 * 0.65,
88 kind: if i % 3 == 0 {
89 KeyframeKind::Ease
90 } else {
91 KeyframeKind::Linear
92 },
93 })
94 .collect(),
95 });
96 tracks
97}
98
99#[must_use]
101pub fn sample_editor_shell(has_clip_loaded: bool) -> crate::components::editor::EditorShellView {
102 use crate::components::editor::{EditorShellView, ToolbarActionView};
103 let actions = vec![
104 ToolbarActionView {
105 id: "16-9",
106 label: "16:9",
107 icon: "▭",
108 selected: true,
109 disabled: false,
110 },
111 ToolbarActionView {
112 id: "crop",
113 label: "Crop",
114 icon: "✂",
115 selected: false,
116 disabled: false,
117 },
118 ToolbarActionView {
119 id: "annotate",
120 label: "Annotate",
121 icon: "✎",
122 selected: false,
123 disabled: !has_clip_loaded,
124 },
125 ToolbarActionView {
126 id: "trim",
127 label: "Trim",
128 icon: "⎯",
129 selected: false,
130 disabled: !has_clip_loaded,
131 },
132 ];
133 EditorShellView {
134 document_title: "Demo · auth login".into(),
135 document_subtitle: Some("Captured 2026-05-09 · 1m 24s".into()),
136 has_clip_loaded,
137 toolbar_actions: actions,
138 export_enabled: has_clip_loaded,
139 share_enabled: has_clip_loaded,
140 }
141}
142
143#[must_use]
145pub fn sample_editor_shell_export_disabled() -> crate::components::editor::EditorShellView {
146 let mut v = sample_editor_shell(true);
147 v.export_enabled = false;
148 v
149}
150
151#[must_use]
154pub fn sample_editor_drop_zone(drag_active: bool) -> crate::components::editor::EditorDropZoneView {
155 use crate::components::editor::{CanvasBackendView, DropZoneActionView, EditorDropZoneView};
156 EditorDropZoneView {
157 backend: CanvasBackendView::CssFallback,
158 headline: "Drop a clip to start editing",
159 subtext: "or press ⌘⇧2 to record one now",
160 actions: vec![
161 DropZoneActionView {
162 id: "record",
163 label: "Record screen",
164 description: "Open the tray recorder",
165 icon: "●",
166 },
167 DropZoneActionView {
168 id: "library",
169 label: "Open library",
170 description: "Pick a recent recording",
171 icon: "▦",
172 },
173 DropZoneActionView {
174 id: "file",
175 label: "Import file",
176 description: "Choose an MP4 / MOV",
177 icon: "📁",
178 },
179 ],
180 recent_clips: sample_recent_clips(),
181 drag_active,
182 }
183}
184
185fn sample_recent_clips() -> Vec<crate::components::editor::RecentClipView> {
186 use crate::components::editor::RecentClipView;
187 vec![
188 RecentClipView {
189 id: "rec-01",
190 title: "Demo · auth login",
191 duration_label: "1m 24s",
192 thumbnail_css: "linear-gradient(135deg,#1e3a8a,#7c3aed)",
193 },
194 RecentClipView {
195 id: "rec-02",
196 title: "Standup recap",
197 duration_label: "4m 02s",
198 thumbnail_css: "linear-gradient(135deg,#0f766e,#facc15)",
199 },
200 RecentClipView {
201 id: "rec-03",
202 title: "Bug repro · payments",
203 duration_label: "0m 38s",
204 thumbnail_css: "linear-gradient(135deg,#7f1d1d,#f97316)",
205 },
206 ]
207}
208
209#[must_use]
211pub fn sample_editor_drop_zone_no_recent() -> crate::components::editor::EditorDropZoneView {
212 let mut v = sample_editor_drop_zone(false);
213 v.recent_clips.clear();
214 v
215}
216
217#[must_use]
219pub fn sample_inspector_style_tab() -> crate::components::editor::InspectorPanelView {
220 use crate::components::editor::{
221 InspectorPanelView, InspectorTab, PropertyControlView, PropertyRowView, PropertySectionView,
222 };
223 let appearance = PropertySectionView {
224 title: "APPEARANCE",
225 rows: vec![
226 PropertyRowView {
227 label: "Background",
228 value: Some("Studio dark"),
229 disabled: false,
230 control: PropertyControlView::SelectPill {
231 current_label: "Studio dark",
232 },
233 },
234 PropertyRowView {
235 label: "Corner radius",
236 value: Some("12 px"),
237 disabled: false,
238 control: PropertyControlView::SliderPercent { percent: 35 },
239 },
240 PropertyRowView {
241 label: "Drop shadow",
242 value: None,
243 disabled: false,
244 control: PropertyControlView::Toggle { on: true },
245 },
246 ],
247 };
248 let motion = PropertySectionView {
249 title: "MOTION",
250 rows: vec![
251 PropertyRowView {
252 label: "Auto-zoom",
253 value: Some("2.0×"),
254 disabled: false,
255 control: PropertyControlView::SliderPercent { percent: 50 },
256 },
257 PropertyRowView {
258 label: "Smooth pan",
259 value: None,
260 disabled: false,
261 control: PropertyControlView::Toggle { on: true },
262 },
263 ],
264 };
265 InspectorPanelView {
266 tabs: vec![
267 InspectorTab::Style,
268 InspectorTab::Cursor,
269 InspectorTab::Audio,
270 InspectorTab::Captions,
271 InspectorTab::Ai,
272 ],
273 active: InspectorTab::Style,
274 sections: vec![appearance, motion],
275 }
276}
277
278#[must_use]
280pub fn sample_inspector_cursor_tab() -> crate::components::editor::InspectorPanelView {
281 use crate::components::editor::{
282 InspectorTab, PropertyControlView, PropertyRowView, PropertySectionView,
283 };
284 let mut v = sample_inspector_style_tab();
285 v.active = InspectorTab::Cursor;
286 v.sections = vec![
287 PropertySectionView {
288 title: "APPEARANCE",
289 rows: vec![
290 PropertyRowView {
291 label: "Size",
292 value: Some("120 %"),
293 disabled: false,
294 control: PropertyControlView::SliderPercent { percent: 60 },
295 },
296 PropertyRowView {
297 label: "Color",
298 value: None,
299 disabled: false,
300 control: PropertyControlView::ColorSwatches {
301 swatches: vec!["#fafafa", "#facc15", "#38bdf8", "#a78bfa", "#f97316"],
302 selected: 2,
303 },
304 },
305 PropertyRowView {
306 label: "Halo",
307 value: None,
308 disabled: false,
309 control: PropertyControlView::Toggle { on: true },
310 },
311 ],
312 },
313 PropertySectionView {
314 title: "CLICK EFFECT",
315 rows: vec![PropertyRowView {
316 label: "Effect",
317 value: Some("Ring"),
318 disabled: false,
319 control: PropertyControlView::SelectPill {
320 current_label: "Ring",
321 },
322 }],
323 },
324 ];
325 v
326}
327
328#[must_use]
330pub fn sample_inspector_disabled_section() -> crate::components::editor::InspectorPanelView {
331 let mut v = sample_inspector_style_tab();
332 for s in &mut v.sections {
333 for r in &mut s.rows {
334 r.disabled = true;
335 }
336 }
337 v
338}
339
340#[must_use]
342pub fn sample_timeline_skeleton() -> crate::components::editor::TimelineView {
343 use crate::components::editor::{TimelineTrackView, TimelineView};
344 TimelineView {
345 playhead_label: "00:03",
346 duration_label: "01:24",
347 is_playing: false,
348 tracks: vec![
349 TimelineTrackView {
350 id: "video",
351 label: "Video",
352 placeholder: Some("drop a clip to fill"),
353 disabled: false,
354 selected: false,
355 },
356 TimelineTrackView {
357 id: "auto-zoom",
358 label: "Auto-zoom",
359 placeholder: Some("detected from cursor"),
360 disabled: false,
361 selected: false,
362 },
363 TimelineTrackView {
364 id: "audio",
365 label: "Audio",
366 placeholder: Some("mic + system"),
367 disabled: false,
368 selected: false,
369 },
370 ],
371 }
372}
373
374#[must_use]
376pub fn sample_timeline_skeleton_playing() -> crate::components::editor::TimelineView {
377 let mut v = sample_timeline_skeleton();
378 v.is_playing = true;
379 if let Some(t) = v.tracks.first_mut() {
380 t.selected = true;
381 }
382 v
383}
384
385#[must_use]
387pub fn sample_timeline_skeleton_empty() -> crate::components::editor::TimelineView {
388 let mut v = sample_timeline_skeleton();
389 for t in &mut v.tracks {
390 t.placeholder = None;
391 }
392 v
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn dope_sheet_tracks_have_four_rows() {
401 assert_eq!(sample_dope_sheet_tracks().len(), 4);
402 }
403
404 #[test]
405 fn dope_sheet_dense_adds_a_zoom_track() {
406 let tracks = sample_dope_sheet_dense();
407 assert_eq!(tracks.len(), 5);
408 assert_eq!(tracks.last().unwrap().keyframes.len(), 12);
409 }
410}