From fd25677631f6fde44425d07026b2bfe63defb39d Mon Sep 17 00:00:00 2001 From: Homer Date: Fri, 10 Jul 2026 20:33:23 +0200 Subject: [PATCH] Editor: "New Script" button on the Script inspector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage-10 editor-UX follow-up. The Script component's inspector section now ends with a name field + "➕ New Script" button (Enter in the field also submits): it writes a fresh .rhai from the tested template into assets/scripts/ via create_script_file (sanitized stem, collisions suffixed), registers it in the asset database, saves the manifest, and assigns it to the component's `source` through the normal edit path — so the assignment is one undoable SetFieldCmd. Disabled with a hint while no project is open; failures land in the Console via the log. GUI piece — needs an eye-check before promotion to main. Co-Authored-By: Claude Fable 5 --- editor/src/shell.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/editor/src/shell.rs b/editor/src/shell.rs index 453f11e..6528bae 100644 --- a/editor/src/shell.rs +++ b/editor/src/shell.rs @@ -525,6 +525,8 @@ pub struct Shell { open_project_path: String, /// Text entered in the "Groups" editor's "add group" field. new_group_name: String, + /// Text entered in the Script inspector's "New Script" name field. + new_script_name: String, /// The Console panel's command-input buffer (the terminal prompt). terminal_input: String, @@ -624,6 +626,7 @@ impl Shell { new_project_name: String::new(), open_project_path: String::new(), new_group_name: String::new(), + new_script_name: String::new(), terminal_input: String::new(), quit_requested: false, viewport_rect_px: None, @@ -1125,6 +1128,7 @@ impl Shell { extensions: &mut self.extensions, pending: &mut self.pending, rename_buf: &mut self.rename_buf, + new_script_name: &mut self.new_script_name, terminal_input: &mut self.terminal_input, rot_euler: &mut self.rot_euler, euler_for: &mut self.euler_for, @@ -2154,6 +2158,9 @@ struct ShellTabViewer<'a> { extensions: &'a mut EditorExtensions, pending: &'a mut Vec, rename_buf: &'a mut String, + /// The Script inspector's "New Script" name field. Borrowed from + /// [`Shell::new_script_name`]. + new_script_name: &'a mut String, /// The Console command-input buffer (the terminal prompt). terminal_input: &'a mut String, rot_euler: &'a mut Vec3, @@ -3364,6 +3371,14 @@ impl<'a> ShellTabViewer<'a> { edits.push((comp.name, row.info.name, new_ron)); } } + // Stage-10 UX: author a script without leaving the editor. The + // assignment goes through `edits` → SetFieldCmd like any field + // change, so it is undoable (the file itself stays — harmless). + if comp.name == "Script" { + if let Some(ron) = self.new_script_row(ui) { + edits.push((comp.name, "source", ron)); + } + } }); } @@ -3409,6 +3424,59 @@ impl<'a> ShellTabViewer<'a> { } } + /// The "New Script" row at the bottom of a `Script` component's section: + /// a name field + create button that writes a `.rhai` template into + /// `assets/scripts/`, registers it in the asset database, and returns the + /// RON for the component's `source` field — the caller routes it through + /// the normal edit path so the assignment is undoable (undo detaches the + /// script; the created file stays, which is harmless). Disabled until a + /// project is open. Errors go to the log, i.e. the Console panel. + fn new_script_row(&mut self, ui: &mut egui::Ui) -> Option { + let mut created = None; + ui.horizontal(|ui| { + let open = self.state.asset_db.is_some(); + ui.add_enabled_ui(open, |ui| { + let name = ui.add( + egui::TextEdit::singleline(self.new_script_name) + .hint_text("new_script") + .desired_width(120.0), + ); + let submitted = name.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + let clicked = ui + .small_button("➕ New Script") + .on_hover_text( + "Create a .rhai file from the template in assets/scripts/ \ + and assign it to this component", + ) + .clicked(); + if clicked || submitted { + let db = self + .state + .asset_db + .as_mut() + .expect("row is enabled only with a project open"); + match crate::assets::create_script_file(&db.assets_dir(), self.new_script_name) + { + Ok(rel) => { + let uid = db.register(&rel); + if let Err(err) = db.save() { + log::warn!("could not write asset manifest: {err}"); + } + log::info!("created {rel}"); + self.new_script_name.clear(); + created = ron::to_string(&Some(uid)).ok(); + } + Err(err) => log::warn!("could not create script: {err}"), + } + } + }); + if !open { + ui.label(egui::RichText::new("(open a project to create scripts)").weak()); + } + }); + created + } + /// Renders one reflected field as a typed widget chosen from its /// `type_name`, returning the field's new RON if the user changed it. /// Unknown types fall back to an editable RON text box, so the inspector is