poetrymagnets.online

git clone https://git.bradleygannon.com/poetrymagnets.online.git
Log | Files | Refs

commit 99238e5dac05e00db4d67833d4bd5e390c1a6981
parent d65e91b1250a2db0a54ae2bdaa07dd7e25023ea7
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Sun, 15 Mar 2026 14:22:03 -0400

Add scroll and pinch zoom, clamp magnet positions to world boundaries

Diffstat:
Mfrontend/src/app.rs | 53++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 44 insertions(+), 9 deletions(-)

diff --git a/frontend/src/app.rs b/frontend/src/app.rs @@ -1,6 +1,11 @@ -use egui::{Area, Color32, CornerRadius, Frame, Id, Label, Margin, Pos2, Rect, Vec2}; +use egui::{ + Area, Color32, CornerRadius, Frame, Id, Label, Margin, Pos2, Rect, Sense, TextWrapMode, Vec2, +}; use web_sys::js_sys::Math::random; +const TOP_LEFT: Pos2 = Pos2::ZERO; +const BOTTOM_RIGHT: Pos2 = Pos2::new(2048., 2048.); + #[derive(serde::Deserialize, serde::Serialize)] struct Magnet { id: Id, @@ -11,6 +16,8 @@ struct Magnet { #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] pub struct PoetryMagnetsOnlineApp { + zoom_level: f32, + zoom_level_target: f32, pan_offset: Pos2, magnets: Vec<Magnet>, } @@ -20,10 +27,15 @@ impl Default for PoetryMagnetsOnlineApp { let mut magnets = serde_json::from_str::<Vec<Magnet>>(include_str!("../words.json")).unwrap_or_default(); for magnet in magnets.iter_mut() { - magnet.position = Pos2::new((random() * 1000.) as f32, (random() * 1000.) as f32); + magnet.position = Pos2::new( + random() as f32 * BOTTOM_RIGHT.x, + random() as f32 * BOTTOM_RIGHT.y, + ); } Self { - pan_offset: Pos2::new(0., 0.), + zoom_level: 2., + zoom_level_target: 2., + pan_offset: Pos2::ZERO, magnets, } } @@ -45,9 +57,26 @@ impl eframe::App for PoetryMagnetsOnlineApp { ui.available_rect_before_wrap(), egui::Sense::click_and_drag(), ); + if response.dragged() { self.pan_offset += response.drag_delta(); } + + let zoom_delta_raw = ui.input(|i| { + if let Some(multi_touch) = i.multi_touch() { + multi_touch.zoom_delta + } else { + 1. - i.raw_scroll_delta.y * 0.001 + } + }); + if zoom_delta_raw != 1. { + self.zoom_level_target *= zoom_delta_raw; + self.zoom_level_target = self.zoom_level_target.clamp(1., 5.); + let zoom_delta_smooth = (self.zoom_level_target - self.zoom_level) * 0.25; + self.zoom_level += zoom_delta_smooth; + self.zoom_level = self.zoom_level.clamp(1., 5.); + } + ctx.set_zoom_factor(self.zoom_level); }); for magnet in self.magnets.iter_mut() { let magnet_position_with_offset = self.pan_offset + magnet.position.to_vec2(); @@ -56,9 +85,8 @@ impl eframe::App for PoetryMagnetsOnlineApp { if !ctx.content_rect().intersects(magnet_rect_with_buffer) { continue; } - let response = Area::new(magnet.id) + Area::new(magnet.id) .current_pos(magnet_position_with_offset) - .movable(true) .constrain(false) .show(ctx, |ui| { Frame::NONE @@ -66,11 +94,18 @@ impl eframe::App for PoetryMagnetsOnlineApp { .fill(Color32::WHITE) .corner_radius(CornerRadius::same(1)) .show(ui, |ui| { - ui.add(Label::new(magnet.text.clone()).selectable(false)) + let response = ui.add( + Label::new(magnet.text.clone()) + .selectable(false) + .wrap_mode(TextWrapMode::Extend) + .sense(Sense::click_and_drag()), + ); + if response.dragged() { + magnet.position = (magnet.position + response.drag_delta()) + .clamp(TOP_LEFT, BOTTOM_RIGHT); + } }); - }) - .response; - magnet.position += response.drag_delta(); + }); } } }