commit a88f583cfe4d1a0cc50ba937606963feca4f6388
parent 566efef3c45414a100c684ab82e4e11d94847d65
Author: William Casarin <jb55@jb55.com>
Date: Tue, 24 Feb 2026 12:05:51 -0800
nostrverse: add rotation snap when grid snap is enabled
Snaps rotation to configurable angle increments (default 15°) during
both drag-rotate and inspector Rot Y editing. Adds a "Rot snap" control
visible when grid snap is on.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat:
2 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/crates/notedeck_nostrverse/src/room_state.rs b/crates/notedeck_nostrverse/src/room_state.rs
@@ -263,6 +263,8 @@ pub struct NostrverseState {
pub rotate_mode: bool,
/// Whether the current drag is a rotation drag (started on an object in rotate mode)
pub rotate_drag: bool,
+ /// Rotation snap increment in degrees (used when grid snap is enabled)
+ pub rotation_snap: f32,
/// Cached serialized scene text (avoids re-serializing every frame)
pub cached_scene_text: String,
}
@@ -283,6 +285,7 @@ impl NostrverseState {
grid_snap_enabled: false,
rotate_mode: false,
rotate_drag: false,
+ rotation_snap: 15.0,
cached_scene_text: String::new(),
}
}
diff --git a/crates/notedeck_nostrverse/src/room_view.rs b/crates/notedeck_nostrverse/src/room_view.rs
@@ -286,6 +286,15 @@ pub fn show_room_view(
let delta_x = response.drag_delta().x;
let angle = delta_x * ROTATE_SENSITIVITY;
let new_rotation = Quat::from_rotation_y(angle) * obj.rotation;
+ // Snap to angle increments when grid snap is enabled
+ let new_rotation = if state.grid_snap_enabled {
+ let (_, y, _) = new_rotation.to_euler(glam::EulerRot::YXZ);
+ let snap_rad = state.rotation_snap.to_radians();
+ let snapped_y = (y / snap_rad).round() * snap_rad;
+ Quat::from_rotation_y(snapped_y)
+ } else {
+ new_rotation
+ };
action = Some(NostrverseAction::RotateObject {
id: sel_id,
rotation: new_rotation,
@@ -732,14 +741,20 @@ pub fn render_editing_panel(ui: &mut Ui, state: &mut NostrverseState) -> Option<
// Editable Y rotation (degrees)
let (_, angle_y, _) = obj.rotation.to_euler(glam::EulerRot::YXZ);
let mut deg = angle_y.to_degrees();
+ let snap = state.grid_snap_enabled;
+ let snap_deg = state.rotation_snap;
let rot_changed = ui
.horizontal(|ui| {
ui.label("Rot Y:");
- ui.add(egui::DragValue::new(&mut deg).speed(1.0).suffix("°"))
+ let speed = if snap { snap_deg } else { 1.0 };
+ ui.add(egui::DragValue::new(&mut deg).speed(speed).suffix("°"))
.changed()
})
.inner;
if rot_changed {
+ if snap {
+ deg = (deg / snap_deg).round() * snap_deg;
+ }
obj.rotation = Quat::from_rotation_y(deg.to_radians());
}
@@ -777,6 +792,17 @@ pub fn render_editing_panel(ui: &mut Ui, state: &mut NostrverseState) -> Option<
);
}
});
+ if state.grid_snap_enabled {
+ ui.horizontal(|ui| {
+ ui.label(" Rot snap:");
+ ui.add(
+ egui::DragValue::new(&mut state.rotation_snap)
+ .speed(1.0)
+ .range(1.0..=90.0)
+ .suffix("°"),
+ );
+ });
+ }
// --- Save button ---
ui.add_space(12.0);