notedeck

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

commit 342ee9404fe0c8d57e86ee92cf43cfa5f232c342
parent 3fa22479527ed7dd4a1476f2e147f2de95d50958
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 23 Feb 2026 14:13:21 -0800

nostrverse: fix editing panel layout by using vertical layout

The editing panel was inheriting the parent's horizontal layout from
ui.horizontal(), causing all widgets to render left-to-right instead
of top-to-bottom. Only the room name was visible because everything
else overflowed off-screen.

Use allocate_ui_with_layout with Layout::top_down to ensure the panel
gets a vertical layout. Also remove the Frame/ScrollArea wrapper from
render_editing_panel since it was unnecessary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
Mcrates/notedeck_nostrverse/src/lib.rs | 14+++++++++-----
Mcrates/notedeck_nostrverse/src/room_view.rs | 413++++++++++++++++++++++++++++++++++++++-----------------------------------------
2 files changed, 207 insertions(+), 220 deletions(-)

diff --git a/crates/notedeck_nostrverse/src/lib.rs b/crates/notedeck_nostrverse/src/lib.rs @@ -579,11 +579,15 @@ impl notedeck::App for NostrverseApp { // Editing panel (always visible in edit mode) if self.state.edit_mode { - ui.allocate_ui(egui::vec2(panel_width, available.y), |ui| { - if let Some(action) = render_editing_panel(ui, &mut self.state) { - self.handle_action(action, ctx); - } - }); + ui.allocate_ui_with_layout( + egui::vec2(panel_width, available.y), + egui::Layout::top_down(egui::Align::LEFT), + |ui| { + if let Some(action) = render_editing_panel(ui, &mut self.state) { + self.handle_action(action, ctx); + } + }, + ); } }); }); diff --git a/crates/notedeck_nostrverse/src/room_view.rs b/crates/notedeck_nostrverse/src/room_view.rs @@ -1,6 +1,6 @@ //! Room 3D rendering and editing UI for nostrverse via renderbud -use egui::{Color32, Pos2, Rect, Response, Sense, Stroke, Ui}; +use egui::{Color32, Pos2, Rect, Response, Sense, Ui}; use glam::Vec3; use super::room_state::{NostrverseAction, NostrverseState, RoomObject, RoomShape}; @@ -121,232 +121,215 @@ fn draw_info_overlay(painter: &egui::Painter, state: &NostrverseState, rect: Rec pub fn render_editing_panel(ui: &mut Ui, state: &mut NostrverseState) -> Option<NostrverseAction> { let mut action = None; - let panel = egui::Frame::default() - .fill(Color32::from_rgba_unmultiplied(30, 35, 45, 240)) - .inner_margin(12.0) - .outer_margin(8.0) - .corner_radius(8.0) - .stroke(Stroke::new(1.0, Color32::from_rgb(80, 90, 110))); - - panel.show(ui, |ui| { - ui.set_min_width(220.0); - - egui::ScrollArea::vertical().show(ui, |ui| { - // --- Room Properties --- - if let Some(room) = &mut state.room { - ui.strong("Room"); - ui.separator(); - - let name_changed = ui - .horizontal(|ui| { - ui.label("Name:"); - ui.text_edit_singleline(&mut room.name).changed() + // --- Room Properties --- + if let Some(room) = &mut state.room { + ui.strong("Room"); + ui.separator(); + + let name_changed = ui + .horizontal(|ui| { + ui.label("Name:"); + ui.text_edit_singleline(&mut room.name).changed() + }) + .inner; + + let mut width = room.width; + let mut height = room.height; + let mut depth = room.depth; + + let dims_changed = ui + .horizontal(|ui| { + ui.label("W:"); + let w = ui + .add( + egui::DragValue::new(&mut width) + .speed(0.5) + .range(1.0..=200.0), + ) + .changed(); + ui.label("H:"); + let h = ui + .add( + egui::DragValue::new(&mut height) + .speed(0.5) + .range(1.0..=200.0), + ) + .changed(); + ui.label("D:"); + let d = ui + .add( + egui::DragValue::new(&mut depth) + .speed(0.5) + .range(1.0..=200.0), + ) + .changed(); + w || h || d + }) + .inner; + + room.width = width; + room.height = height; + room.depth = depth; + + let shape_changed = ui + .horizontal(|ui| { + ui.label("Shape:"); + let mut changed = false; + egui::ComboBox::from_id_salt("room_shape") + .selected_text(match room.shape { + RoomShape::Rectangle => "Rectangle", + RoomShape::Circle => "Circle", + RoomShape::Custom => "Custom", }) - .inner; - - let mut width = room.width; - let mut height = room.height; - let mut depth = room.depth; - - let dims_changed = ui - .horizontal(|ui| { - ui.label("W:"); - let w = ui - .add( - egui::DragValue::new(&mut width) - .speed(0.5) - .range(1.0..=200.0), - ) + .show_ui(ui, |ui| { + changed |= ui + .selectable_value(&mut room.shape, RoomShape::Rectangle, "Rectangle") .changed(); - ui.label("H:"); - let h = ui - .add( - egui::DragValue::new(&mut height) - .speed(0.5) - .range(1.0..=200.0), - ) + changed |= ui + .selectable_value(&mut room.shape, RoomShape::Circle, "Circle") .changed(); - ui.label("D:"); - let d = ui - .add( - egui::DragValue::new(&mut depth) - .speed(0.5) - .range(1.0..=200.0), - ) - .changed(); - w || h || d - }) - .inner; - - room.width = width; - room.height = height; - room.depth = depth; - - let shape_changed = ui - .horizontal(|ui| { - ui.label("Shape:"); - let mut changed = false; - egui::ComboBox::from_id_salt("room_shape") - .selected_text(match room.shape { - RoomShape::Rectangle => "Rectangle", - RoomShape::Circle => "Circle", - RoomShape::Custom => "Custom", - }) - .show_ui(ui, |ui| { - changed |= ui - .selectable_value( - &mut room.shape, - RoomShape::Rectangle, - "Rectangle", - ) - .changed(); - changed |= ui - .selectable_value(&mut room.shape, RoomShape::Circle, "Circle") - .changed(); - }); - changed - }) - .inner; + }); + changed + }) + .inner; - if name_changed || dims_changed || shape_changed { - state.dirty = true; - } + if name_changed || dims_changed || shape_changed { + state.dirty = true; + } - ui.add_space(8.0); - } + ui.add_space(8.0); + } - // --- Object List --- - ui.strong("Objects"); - ui.separator(); - - let num_objects = state.objects.len(); - for i in 0..num_objects { - let is_selected = state - .selected_object - .as_ref() - .map(|s| s == &state.objects[i].id) - .unwrap_or(false); - - let label = format!("{} ({})", state.objects[i].name, state.objects[i].id); - if ui.selectable_label(is_selected, label).clicked() { - let id = state.objects[i].id.clone(); - state.selected_object = if is_selected { None } else { Some(id) }; - } - } + // --- Object List --- + ui.strong("Objects"); + ui.separator(); + + let num_objects = state.objects.len(); + for i in 0..num_objects { + let is_selected = state + .selected_object + .as_ref() + .map(|s| s == &state.objects[i].id) + .unwrap_or(false); + + let label = format!("{} ({})", state.objects[i].name, state.objects[i].id); + if ui.selectable_label(is_selected, label).clicked() { + let id = state.objects[i].id.clone(); + state.selected_object = if is_selected { None } else { Some(id) }; + } + } - // Add object button - ui.add_space(4.0); - if ui.button("+ Add Object").clicked() { - let new_id = format!("obj-{}", state.objects.len() + 1); - let obj = RoomObject::new(new_id.clone(), "New Object".to_string(), Vec3::ZERO); - action = Some(NostrverseAction::AddObject(obj)); - } + // Add object button + ui.add_space(4.0); + if ui.button("+ Add Object").clicked() { + let new_id = format!("obj-{}", state.objects.len() + 1); + let obj = RoomObject::new(new_id.clone(), "New Object".to_string(), Vec3::ZERO); + action = Some(NostrverseAction::AddObject(obj)); + } - ui.add_space(12.0); + ui.add_space(12.0); - // --- Object Inspector --- - if let Some(selected_id) = state.selected_object.clone() - && let Some(obj) = state.objects.iter_mut().find(|o| o.id == selected_id) - { - ui.strong("Inspector"); - ui.separator(); + // --- Object Inspector --- + if let Some(selected_id) = state.selected_object.as_ref() + && let Some(obj) = state.objects.iter_mut().find(|o| &o.id == selected_id) + { + ui.strong("Inspector"); + ui.separator(); + + ui.small(format!("ID: {}", obj.id)); + ui.add_space(4.0); + + // Editable name + let name_changed = ui + .horizontal(|ui| { + ui.label("Name:"); + ui.text_edit_singleline(&mut obj.name).changed() + }) + .inner; + + // Editable position + let mut px = obj.position.x; + let mut py = obj.position.y; + let mut pz = obj.position.z; + let pos_changed = ui + .horizontal(|ui| { + ui.label("Pos:"); + let x = ui + .add(egui::DragValue::new(&mut px).speed(0.1).prefix("x:")) + .changed(); + let y = ui + .add(egui::DragValue::new(&mut py).speed(0.1).prefix("y:")) + .changed(); + let z = ui + .add(egui::DragValue::new(&mut pz).speed(0.1).prefix("z:")) + .changed(); + x || y || z + }) + .inner; + obj.position = Vec3::new(px, py, pz); + + // Editable scale (uniform) + let mut sx = obj.scale.x; + let mut sy = obj.scale.y; + let mut sz = obj.scale.z; + let scale_changed = ui + .horizontal(|ui| { + ui.label("Scale:"); + let x = ui + .add( + egui::DragValue::new(&mut sx) + .speed(0.05) + .prefix("x:") + .range(0.01..=100.0), + ) + .changed(); + let y = ui + .add( + egui::DragValue::new(&mut sy) + .speed(0.05) + .prefix("y:") + .range(0.01..=100.0), + ) + .changed(); + let z = ui + .add( + egui::DragValue::new(&mut sz) + .speed(0.05) + .prefix("z:") + .range(0.01..=100.0), + ) + .changed(); + x || y || z + }) + .inner; + obj.scale = Vec3::new(sx, sy, sz); + + // Model URL (read-only for now) + if let Some(url) = &obj.model_url { + ui.add_space(4.0); + ui.small(format!("Model: {}", url)); + } - ui.small(format!("ID: {}", obj.id)); - ui.add_space(4.0); + if name_changed || pos_changed || scale_changed { + state.dirty = true; + } - // Editable name - let name_changed = ui - .horizontal(|ui| { - ui.label("Name:"); - ui.text_edit_singleline(&mut obj.name).changed() - }) - .inner; - - // Editable position - let mut px = obj.position.x; - let mut py = obj.position.y; - let mut pz = obj.position.z; - let pos_changed = ui - .horizontal(|ui| { - ui.label("Pos:"); - let x = ui - .add(egui::DragValue::new(&mut px).speed(0.1).prefix("x:")) - .changed(); - let y = ui - .add(egui::DragValue::new(&mut py).speed(0.1).prefix("y:")) - .changed(); - let z = ui - .add(egui::DragValue::new(&mut pz).speed(0.1).prefix("z:")) - .changed(); - x || y || z - }) - .inner; - obj.position = Vec3::new(px, py, pz); - - // Editable scale (uniform) - let mut sx = obj.scale.x; - let mut sy = obj.scale.y; - let mut sz = obj.scale.z; - let scale_changed = ui - .horizontal(|ui| { - ui.label("Scale:"); - let x = ui - .add( - egui::DragValue::new(&mut sx) - .speed(0.05) - .prefix("x:") - .range(0.01..=100.0), - ) - .changed(); - let y = ui - .add( - egui::DragValue::new(&mut sy) - .speed(0.05) - .prefix("y:") - .range(0.01..=100.0), - ) - .changed(); - let z = ui - .add( - egui::DragValue::new(&mut sz) - .speed(0.05) - .prefix("z:") - .range(0.01..=100.0), - ) - .changed(); - x || y || z - }) - .inner; - obj.scale = Vec3::new(sx, sy, sz); - - // Model URL (read-only for now) - if let Some(url) = &obj.model_url { - ui.add_space(4.0); - ui.small(format!("Model: {}", url)); - } - - if name_changed || pos_changed || scale_changed { - state.dirty = true; - } - - ui.add_space(8.0); - if ui.button("Delete Object").clicked() { - action = Some(NostrverseAction::RemoveObject(selected_id)); - } - } + ui.add_space(8.0); + if ui.button("Delete Object").clicked() { + action = Some(NostrverseAction::RemoveObject(selected_id.to_owned())); + } + } - // --- Save button --- - ui.add_space(12.0); - ui.separator(); - let save_label = if state.dirty { "Save *" } else { "Save" }; - if ui - .add_enabled(state.dirty, egui::Button::new(save_label)) - .clicked() - { - action = Some(NostrverseAction::SaveRoom); - } - }); - }); + // --- Save button --- + ui.add_space(12.0); + ui.separator(); + let save_label = if state.dirty { "Save *" } else { "Save" }; + if ui + .add_enabled(state.dirty, egui::Button::new(save_label)) + .clicked() + { + action = Some(NostrverseAction::SaveRoom); + } action }