notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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:
Mcrates/notedeck_nostrverse/src/room_state.rs | 3+++
Mcrates/notedeck_nostrverse/src/room_view.rs | 28+++++++++++++++++++++++++++-
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);