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:
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
}