Skip to main content

ui/
ui.rs

1//! Camera, shapes, text and UI in one small scene.
2//!
3//!   `cargo run --release --example ui`
4//!
5//! - Right or middle mouse drag: pan. Scroll: zoom toward the mouse.
6//! - Left click in the world: drop a crab there, with a sound (not when
7//!   clicking the UI).
8//! - Buttons: follow the orbiting crab, reset the camera.
9
10use ponko::*;
11
12struct Scene {
13    crab: Texture,
14    plop: Sound,
15    follow: bool,
16    dropped: usize,
17}
18
19// ANCHOR: scene
20fn scene(ctx: &mut Ctx) {
21    let t = ctx.time() as f32;
22
23    // The world: a grid of tiles, a few shapes, a crab in orbit.
24    for y in 0..20 {
25        for x in 0..20 {
26            let shade = if (x + y) % 2 == 0 { 0.16 } else { 0.19 };
27            let tile = Rect::new(x as f32 * 64.0, y as f32 * 64.0, 64.0, 64.0);
28            ctx.draw_rect(tile, Color::rgb(shade, shade + 0.03, shade));
29        }
30    }
31    let center = vec2(640.0, 640.0);
32    ctx.draw_circle(center, 180.0, Color::hex(0x3a86ff).with_alpha(0.15));
33    let orbit = center + Vec2::from_angle(t * 0.6) * 180.0;
34    ctx.draw_line(center, orbit, 3.0, Color::hex(0xffbe0b));
35    let crab = ctx.world.resource::<Scene>().crab;
36    ctx.draw_ex(
37        &crab,
38        orbit,
39        DrawParams {
40            rotation: t,
41            ..Default::default()
42        },
43    );
44
45    // A label that sticks to the crab: world position -> window pixels.
46    let above = ctx.camera.world_to_screen(orbit) + vec2(-28.0, -40.0);
47    ctx.draw_text("orbiter", above, 16.0, Color::hex(0xffbe0b));
48
49    if ctx.world.resource::<Scene>().follow {
50        ctx.camera.look_at(orbit);
51    }
52    if ctx.mouse_pressed(MouseButton::Left) && !ctx.mouse_over_ui() {
53        let at = ctx.mouse_world();
54        ctx.world.spawn((Transform2D::at(at), Sprite::new(crab)));
55        let plop = ctx.world.resource::<Scene>().plop;
56        let speed = 0.8 + ctx.random().stream(0).f32() * 0.4;
57        ctx.play_sound_ex(
58            &plop,
59            SoundParams {
60                speed,
61                ..Default::default()
62            },
63        );
64        ctx.world.resource_mut::<Scene>().dropped += 1;
65    }
66}
67// ANCHOR_END: scene
68
69// ANCHOR: hud
70fn hud(ctx: &mut Ctx) {
71    let panel = Rect::new(12.0, 12.0, 230.0, 172.0);
72    ctx.ui_panel(panel);
73    let mut at = panel.min + ctx.ui_style.padding;
74    let (zoom, dropped) = (ctx.camera.zoom, ctx.world.resource::<Scene>().dropped);
75    let lines = format!("zoom {zoom:.2}x\ncrabs dropped: {dropped}\n{:.0} FPS", ctx.fps());
76    at.y += ctx.ui_label(at, &lines).y + 8.0;
77
78    let follow = ctx.world.resource::<Scene>().follow;
79    let label = if follow { "Stop following" } else { "Follow orbiter" };
80    if ctx.ui_button(Rect::new(at.x, at.y, 210.0, 30.0), label) {
81        ctx.world.resource_mut::<Scene>().follow = !follow;
82    }
83    at.y += 38.0;
84    if ctx.ui_button(Rect::new(at.x, at.y, 210.0, 30.0), "Reset camera") {
85        ctx.camera.zoom = 1.0;
86        ctx.camera.position = Vec2::ZERO;
87        ctx.world.resource_mut::<Scene>().follow = false;
88    }
89}
90// ANCHOR_END: hud
91
92/// A short falling tone, made in code so the example needs no sound file.
93fn plop(ctx: &mut Ctx) -> Sound {
94    let rate = 44_100;
95    let samples: Vec<f32> = (0..rate / 8)
96        .map(|i| {
97            let t = i as f32 / rate as f32;
98            let freq = 900.0 - t * 4000.0;
99            (t * freq * std::f32::consts::TAU).sin() * (1.0 - t * 8.0).max(0.0) * 0.25
100        })
101        .collect();
102    ctx.create_sound(rate, &samples)
103}
104
105fn main() {
106    Game::new("Crabby Engine: camera, shapes, text, UI", 1280, 720)
107        .system(camera_controls)
108        .system(scene)
109        .system(hud)
110        .run(|ctx| {
111            let crab = ctx.load_texture("crab.png");
112            let plop = plop(ctx);
113            ctx.world.insert_resource(Scene {
114                crab,
115                plop,
116                follow: false,
117                dropped: 0,
118            });
119        });
120}