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:
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();
+ });
}
}
}