wisp_storybook/stories/s_editor_background.rs
1//! Story: editor background framing (ED.18 / M-EDIT).
2//!
3//! The cinematic "framed screen" look: the recording sits on a colored
4//! backdrop, lifted off it by a uniform padding and clipped to a
5//! rounded-corner window. This mirrors the editor's real compose — a
6//! full-NDC gradient `Graphics` backdrop drawn behind a rounded-rect-clipped
7//! "screen" — exactly the structure `EditorPreview::set_background` builds in
8//! `wisp::RecordingScene` (backdrop Phase 1, clipped screen Phase 2).
9//!
10//! Single-bind-group clip + graphics (no blur), so it runs on every CI OS —
11//! NOT in `LAVAPIPE_INCOMPATIBLE`.
12
13use wisp::application::Application;
14use wisp::math::Rect;
15use wisp::{Color, Container, Fill, Graphics, MaskShape, Node, Stage, Stroke};
16
17pub fn story() -> crate::story::Story {
18 crate::story::Story {
19 id: "editor-background-framing",
20 category: "Editor",
21 title: "Background framing — backdrop · padding · rounded corners",
22 milestone: "ED.18",
23 writeup: include_str!("writeups/editor_background.md"),
24 build,
25 tick: None,
26 }
27}
28
29fn build(_app: &Application, stage: &mut Stage) {
30 // Backdrop: the default "Aurora" warm→cool diagonal gradient, filling the
31 // whole canvas. In the real compose this is the non-clipped Phase-1 node.
32 let mut backdrop = Graphics::new();
33 let theta = 135.0_f32.to_radians();
34 let dir = glam::Vec2::new(theta.cos(), theta.sin());
35 backdrop.fill(Fill::LinearGradient {
36 start: -dir,
37 end: dir,
38 color_a: Color::rgb_u8(255, 138, 128),
39 color_b: Color::rgb_u8(40, 53, 147),
40 });
41 backdrop.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
42 let _ = stage.add_child(stage.root(), backdrop);
43
44 // Drop shadow (ED.18): a dark rounded-rect the shape of the frame window,
45 // offset down-right, drawn behind the screen so the offset sliver reads as
46 // a shadow lifting the screen off the backdrop. Phase-1 (unclipped),
47 // composited after the backdrop and under the screen.
48 let mut shadow = Graphics::new();
49 shadow.fill(Fill::Solid(Color::rgba(0.0, 0.0, 0.0, 0.45)));
50 shadow.draw_rounded_rect(Rect::new(-0.84 + 0.03, -0.78 - 0.05, 1.68, 1.56), 0.06);
51 let _ = stage.add_child(stage.root(), shadow);
52
53 // The framed screen: a rounded-rect window, inset by the padding. In the
54 // real compose this is the clipped screen sprite (dispatched Phase 2, so
55 // it composites over the backdrop). Defaults (padding 64, corner 14 on a
56 // 1920-wide canvas) → window ≈ [-0.93, 0.93] × [-0.88, 0.88], radius
57 // ≈ 0.015; widened a touch here so the inset reads at thumbnail size.
58 let mut window = Container::new();
59 window.clip = Some(MaskShape::rounded_rect(
60 Rect::new(-0.84, -0.78, 1.68, 1.56),
61 0.06,
62 ));
63 let window_id = stage
64 .add_child(stage.root(), Node::Container(window))
65 .expect("window container");
66
67 // Light "screen" content so the colored backdrop margin + rounded corners
68 // read clearly (the storybook backdrop-visibility convention: light
69 // content over a colored frame). A pale vertical gradient with a single
70 // accent bar stands in for a screenshot.
71 let mut screen = Graphics::new();
72 screen.fill(Fill::LinearGradient {
73 start: glam::Vec2::new(0.0, 1.0),
74 end: glam::Vec2::new(0.0, -1.0),
75 color_a: Color::rgb_u8(248, 250, 252),
76 color_b: Color::rgb_u8(214, 222, 232),
77 });
78 screen.draw_rect(Rect::new(-1.0, -1.0, 2.0, 2.0));
79 // Accent "title bar" so the screen reads as content, not a flat panel.
80 screen.fill(Fill::Solid(Color::rgb_u8(60, 110, 220)));
81 screen.draw_rect(Rect::new(-0.84, 0.58, 1.68, 0.2));
82 let _ = stage.add_child(window_id, screen);
83
84 // Inset border (ED.18): a rounded-rect stroke tracing the frame window,
85 // over the screen. A full-NDC clip forces Phase-2 dispatch so it composites
86 // on top (mirrors RecordingScene::set_frame_border).
87 let mut border = Graphics::new();
88 border.container.clip = Some(MaskShape::rect(Rect::new(-1.0, -1.0, 2.0, 2.0)));
89 border.fill(Fill::Solid(Color::rgba(0.0, 0.0, 0.0, 0.0)));
90 border.stroke(Some(Stroke::new(0.012, Color::rgba(1.0, 1.0, 1.0, 0.85))));
91 border.draw_rounded_rect(Rect::new(-0.84, -0.78, 1.68, 1.56), 0.06);
92 let _ = stage.add_child(stage.root(), border);
93}