17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 05:09:20 +02:00

feat(editor): nodes placed with the brush and the area fill go without names; name of a model instance set in the node properties and written to its definition on save; redo of a deletion finds nodes which have no name

This commit is contained in:
2026-10-06 21:58:57 +02:00
parent 90c5420396
commit c4a22eabd5
9 changed files with 166 additions and 11 deletions

View File

@@ -757,17 +757,14 @@ void editor_mode::handle_brush_mouse_hold(int Action, int Button)
if (!src || src->starts_with(editor_includes::directive_mark)) // scenery templates are placed one at a time
return;
std::string name = "editor_";
TAnimModel *cloned = simulation::State.create_model(*src, name, placement_on_ground(newPos));
// NOTE: no name for what the brush puts in, as with thousands of nodes the names take their share of the time
// the scenery needs to load. a node which needs one, to be referred to by an event, gets it in the node properties
TAnimModel *cloned = simulation::State.create_model(*src, std::string{}, placement_on_ground(newPos));
oldPos = newPos;
m_brush_has_last = true;
if (!cloned)
return;
std::string new_name = "editor_" + cloned->uuid.to_string();
cloned->m_name = new_name;
apply_rotation_for_new_node(cloned, rotation_mode, fixed_rotation_value);
std::string as_text;
@@ -924,6 +921,7 @@ void editor_mode::undo_last()
redoSnap.serialized = snap.serialized;
redoSnap.position = snap.position;
redoSnap.node_ptr = nullptr;
redoSnap.uuid = snap.uuid;
redoSnap.layer = snap.layer;
g_redo.push_back(std::move(redoSnap));
@@ -936,6 +934,8 @@ void editor_mode::undo_last()
m_node = created;
m_node->uuid = snap.uuid; // restore original UUID for better tracking (not strictly necessary)
add_to_hierarchy(created);
// a node without a name can be found for the redo only this way
g_redo.back().node_ptr = created;
ui()->set_node(m_node);
}
return;
@@ -1028,7 +1028,8 @@ void editor_mode::redo_last()
hist.layer = snap.layer;
m_history.push_back(std::move(hist));
scene::basic_node *target = simulation::Instances.find(snap.node_name);
// NOTE: located the way other steps do it, as the name alone doesn't lead to a node which has none
scene::basic_node *target = find_node_by_any(snap.node_ptr, snap.uuid.to_string(), snap.node_name);
if (target)
{
if (auto *model = dynamic_cast<TAnimModel *>(target))
@@ -2722,10 +2723,10 @@ void editor_mode::run_area_fill()
continue;
auto const &src = package[package.size() > 1 ? std::min(package.size() - 1, static_cast<std::size_t>(LocalRandom(0.0, static_cast<double>(package.size())))) : 0];
TAnimModel *cloned = simulation::State.create_model(src, "editor_", location);
// NOTE: no names here either, same as with the brush
TAnimModel *cloned = simulation::State.create_model(src, std::string{}, location);
if (!cloned)
continue;
cloned->m_name = "editor_" + cloned->uuid.to_string();
apply_rotation_for_new_node(cloned, rotation_mode, fixed_rotation_value);
if (scalemin != 1.0f || scalemax != 1.0f)
cloned->Scale(static_cast<float>(LocalRandom(scalemin, scalemax)));

View File

@@ -20,10 +20,29 @@ http://mozilla.org/MPL/2.0/.
#include "world/MemCell.h"
#include "application/editoruilayer.h"
#include "rendering/renderer.h"
#include "simulation/simulation.h"
#include "editor/editorModelSets.hpp"
namespace
{
// tells why provided text can't be the name of a node, empty text if it can. the scenery parser has to get it back as a single token
std::string node_name_issue(std::string const &Name)
{
if (Name.find_first_of(" \t\r\n;\"") != std::string::npos)
{
return "a name can't hold spaces, semicolons or quotes";
}
if (Name.find("//") != std::string::npos || Name.find("/*") != std::string::npos)
{
return "a name can't hold a comment mark";
}
if (Name == "include")
{
return "this word has a meaning of its own in scenery files";
}
return {};
}
// list entry; the selected one is drawn with the accent colour, like selections in the starter
bool list_item(char const *Label, bool const Selected)
{
@@ -322,12 +341,75 @@ void itemproperties_panel::render_body()
{
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
}
// name of the node — TAnimModel only
render_name_editor();
// transform editor (position/rotation/scale) — TAnimModel only
render_transform_editor();
// group section
render_group();
}
// Name of a picked TAnimModel. The editor places decorations without names, as each name takes its share of the time
// the scenery needs to load; this is where an instance gets one, for the events to refer to it.
void itemproperties_panel::render_name_editor()
{
if (m_node == nullptr) { return; }
if (typeid(*m_node) != typeid(TAnimModel)) { return; }
auto *picked = static_cast<TAnimModel *>(m_node);
// the name of an instance defined by a template, or by a file which can't be rewritten, wouldn't make it to the scenery files
if (false == scene::Layers.editable(picked)) { return; }
if (m_namednode != m_node || false == m_nameactive)
{
// the field shows the name of the node unless it's being typed in
if (m_namednode != m_node)
{
m_nameissue.clear();
}
m_namednode = m_node;
std::snprintf(m_namebuffer, sizeof(m_namebuffer), "%s", picked->name().c_str());
}
auto const entered{ImGui::InputTextWithHint("name", "(none)", m_namebuffer, IM_ARRAYSIZE(m_namebuffer), ImGuiInputTextFlags_EnterReturnsTrue)};
m_nameactive = ImGui::IsItemActive();
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Name the events can refer to the node by.\nNodes without a name load quicker, leave it empty unless something needs it");
}
if (entered || ImGui::IsItemDeactivatedAfterEdit())
{
// names are lower case, like everything else the scenery parser hands over
std::string name{m_namebuffer};
for (auto &character : name)
{
if (character >= 'A' && character <= 'Z')
{
character = static_cast<char>(character - 'A' + 'a');
}
}
if (name == "none")
{
name.clear();
}
if (name != picked->name())
{
m_nameissue = node_name_issue(name);
if (m_nameissue.empty() && false == simulation::State.rename_model(picked, name))
{
m_nameissue = "another model instance goes by this name";
}
}
else
{
m_nameissue.clear();
}
m_nameactive = false;
}
if (false == m_nameissue.empty())
{
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", m_nameissue.c_str());
}
}
// In-place editor for position (double precision), rotation (degrees, 0-360),
// and uniform scale (per-axis float, 1.000) of a picked TAnimModel.
// Other node subclasses don't expose these knobs through the same API, so the

View File

@@ -69,11 +69,17 @@ class itemproperties_panel : public ui_panel
// renders DragFloat3/DragScalarN widgets for position, rotation and scale
// of the currently bound TAnimModel; no-op for other node subclasses.
void render_transform_editor();
// renders the field with the name of the currently bound TAnimModel, which gives the instance another name; no-op for other node subclasses
void render_name_editor();
// renders widgets for the parameters of the bound include
void render_include();
// members
scene::basic_node *m_node{nullptr}; // scene node bound to the panel
scene::basic_node *m_namednode{nullptr}; // scene node whose name is held by the edit buffer
char m_namebuffer[128]{}; // name of the node, as shown and typed in
bool m_nameactive{false}; // the name is being typed in
std::string m_nameissue; // why the name typed in wasn't taken
include_selection *m_include{nullptr}; // include of a scenery template bound to the panel
scene::group_handle m_grouphandle{null_handle}; // scene group bound to the panel
std::string m_groupprefix;