Skip to main content

wisp_storybook/stories/
s_text_callouts.rs

1//! Story: vector-backed callout text boxes (M-TEXT.10 / AUT-84).
2//!
3//! Five callout shapes — label box, caption pill, number badge,
4//! pointer + label, arrow + label — composed from `Graphics`
5//! primitives + `CaptionBlock` / text sprites. No new wisp types; the
6//! existing `Graphics::draw_rounded_rect` / `draw_ellipse` /
7//! `draw_line` cover the vocabulary.
8
9use glam::Vec2;
10use wisp::application::Application;
11use wisp::render::Renderer;
12use wisp::text::{CaptionBlock, TextPreset, TextTexturePipeline, WispText};
13use wisp::texture::render_texture::RenderTexture;
14use wisp::{Color, Container, Fill, Graphics, Sprite, Stage, Stroke};
15
16use crate::story::Story;
17
18pub fn story() -> Story {
19    Story {
20        id: "text-callouts",
21        category: "Text",
22        title: "Callout boxes + label + badges + arrows",
23        milestone: "M-TEXT.10",
24        writeup: include_str!("writeups/text_callouts.md"),
25        build,
26        tick: None,
27    }
28}
29
30fn build(app: &Application, stage: &mut Stage) {
31    let pipeline = TextTexturePipeline::new(app, wgpu::TextureFormat::Rgba8UnormSrgb);
32    let root = stage.root();
33
34    // ── Light backdrop sprite ──────────────────────────────────
35    let format = wgpu::TextureFormat::Rgba8Unorm;
36    let renderer = Renderer::new(app, format).expect("renderer");
37    let backdrop_rt = RenderTexture::with_format(app, 256, 256, format);
38    let _ = renderer.render_stage(
39        app,
40        backdrop_rt.view(),
41        Color::rgba(0.92, 0.93, 0.95, 1.0),
42        &Stage::new(),
43    );
44    let backdrop_bytes = backdrop_rt.read_pixels(app);
45    let backdrop_tex = wisp::Texture::from_rgba(app, 256, 256, &backdrop_bytes);
46    let mut backdrop = Sprite::from_texture(backdrop_tex).with_anchor(Vec2::splat(0.5));
47    backdrop.container.transform.scale = Vec2::splat(2.0);
48    let _ = stage.add_child(root, backdrop);
49
50    // ── 1) Caption pill (top center) ───────────────────────────
51    let pill = CaptionBlock::from_text(
52        WispText::new("Now recording")
53            .with_style(TextPreset::Caption.style().with_color(Color::WHITE)),
54    )
55    .with_width(0.7)
56    .with_padding(0.04)
57    .with_radius(0.10)
58    .with_background(Color::rgba_u8(220, 60, 80, 240));
59    let layout = pill.layout(app, &pipeline);
60    let mut cont = Container::new();
61    cont.transform.position = Vec2::new(-0.35, 0.75);
62    let id = stage.add_child(root, cont).expect("attach");
63    let _ = stage.add_child(id, layout.background);
64    let _ = stage.add_child(id, layout.text_sprite);
65
66    // ── 2) Number badge (small filled circle + number) ────────
67    let mut badge_bg = Graphics::new();
68    badge_bg.fill(Fill::Solid(Color::rgba_u8(45, 130, 220, 255)));
69    badge_bg.draw_ellipse(Vec2::new(-0.65, 0.35), Vec2::splat(0.08));
70    let _ = stage.add_child(root, badge_bg);
71
72    let n_text =
73        WispText::new("3").with_style(TextPreset::StepBadge.style().with_color(Color::WHITE));
74    let n_rt = pipeline.render(app, &n_text, 192, 192);
75    let mut n_sprite = Sprite::from_texture(n_rt.as_texture()).with_anchor(Vec2::splat(0.5));
76    n_sprite.container.transform.position = Vec2::new(-0.65, 0.35);
77    n_sprite.container.transform.scale = Vec2::new(0.12, -0.12);
78    let _ = stage.add_child(root, n_sprite);
79
80    // ── 3) Label box (rounded rect + text, center-left) ───────
81    let label = CaptionBlock::from_text(
82        WispText::new("Caption block")
83            .with_style(TextPreset::Caption.style().with_color(Color::WHITE)),
84    )
85    .with_width(0.55)
86    .with_padding(0.04)
87    .with_radius(0.04)
88    .with_background(Color::rgba_u8(20, 28, 40, 220));
89    let lab_layout = label.layout(app, &pipeline);
90    let mut lab_cont = Container::new();
91    lab_cont.transform.position = Vec2::new(-0.45, -0.05);
92    let lab_id = stage.add_child(root, lab_cont).expect("attach");
93    let _ = stage.add_child(lab_id, lab_layout.background);
94    let _ = stage.add_child(lab_id, lab_layout.text_sprite);
95
96    // ── 4) Pointer + label (line connecting label to an anchor) ─
97    let mut pointer = Graphics::new();
98    pointer.stroke(Some(Stroke::new(0.012, Color::rgba_u8(20, 28, 40, 255))));
99    pointer.fill(Fill::Solid(Color::TRANSPARENT));
100    pointer.draw_line(Vec2::new(0.10, 0.05), Vec2::new(0.55, -0.20), 0.012);
101    // Small filled circle at the target.
102    pointer.fill(Fill::Solid(Color::rgba_u8(20, 28, 40, 255)));
103    pointer.draw_ellipse(Vec2::new(0.55, -0.20), Vec2::splat(0.02));
104    let _ = stage.add_child(root, pointer);
105
106    // ── 5) Arrow + label (line + arrowhead triangle from rects) ─
107    // No general-path primitive; emulate the arrowhead with three
108    // overlapping `draw_line` calls forming a wedge.
109    let mut arrow = Graphics::new();
110    arrow.fill(Fill::Solid(Color::TRANSPARENT));
111    let arrow_color = Color::rgba_u8(45, 130, 220, 255);
112    arrow.stroke(Some(Stroke::new(0.014, arrow_color)));
113    let arrow_tip = Vec2::new(-0.20, -0.75);
114    let arrow_tail = Vec2::new(0.45, -0.45);
115    arrow.draw_line(arrow_tail, arrow_tip, 0.014);
116    // Arrowhead — two short lines fanning out from the tip.
117    arrow.draw_line(arrow_tip, arrow_tip + Vec2::new(0.10, 0.04), 0.014);
118    arrow.draw_line(arrow_tip, arrow_tip + Vec2::new(0.07, 0.10), 0.014);
119    let _ = stage.add_child(root, arrow);
120
121    // Arrow's accompanying label — a small caption right of its tail.
122    let arrow_label = WispText::new("click").with_style(
123        TextPreset::Caption
124            .style()
125            .with_color(Color::rgba_u8(45, 130, 220, 255)),
126    );
127    let al_rt = pipeline.render(app, &arrow_label, 384, 192);
128    let mut al_sprite = Sprite::from_texture(al_rt.as_texture()).with_anchor(Vec2::new(0.0, 0.5));
129    al_sprite.container.transform.position = Vec2::new(0.48, -0.42);
130    al_sprite.container.transform.scale = Vec2::new(0.45, -0.18);
131    let _ = stage.add_child(root, al_sprite);
132}