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

feat(editor): include database, placing .inc templates from the node bank

This commit is contained in:
2026-10-02 23:57:35 +02:00
parent a612b0c96c
commit 968594c3e9
20 changed files with 1106 additions and 168 deletions

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/application.h"
#include "editor/editorSettings.hpp"
#include "editor/editorModelSets.hpp"
#include "editor/editorIncludeInfo.hpp"
#include "utilities/Globals.h"
#include "simulation/simulation.h"
#include "simulation/simulationtime.h"
@@ -530,7 +531,7 @@ void editor_mode::handle_brush_mouse_hold(int Action, int Button)
// picked only when a placement is due, so a random pick from a set isn't drawn every tick
const std::string *src = ui()->get_active_node_template();
if (!src)
if (!src || src->starts_with(editor_includes::directive_mark)) // scenery templates are placed one at a time
return;
std::string name = "editor_";
@@ -672,6 +673,15 @@ void editor_mode::undo_last()
EditorSnapshot snap = m_history.back();
m_history.pop_back();
if (snap.instance != 0)
{
// include of a scenery template was placed or removed; either is taken back by flipping its removal
toggle_include(snap.instance);
g_redo.push_back(std::move(snap));
m_instance = 0;
return;
}
if (snap.action == EditorSnapshot::Action::Delete)
{
// undo delete -> recreate model
@@ -754,6 +764,14 @@ void editor_mode::redo_last()
EditorSnapshot snap = g_redo.back();
g_redo.pop_back();
if (snap.instance != 0)
{
toggle_include(snap.instance);
m_history.push_back(std::move(snap));
m_instance = 0;
return;
}
// handle delete redo (re-delete) separately
if (snap.action == EditorSnapshot::Action::Delete)
{
@@ -906,6 +924,10 @@ bool editor_mode::update()
m_dragging = false;
ui()->set_node(nullptr);
}
if (m_instance != 0 && false == scene::Layers.removable(m_instance))
{
m_instance = 0;
}
simulation::Region->update_sounds();
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
@@ -1578,6 +1600,82 @@ void editor_mode::save()
WriteLog("Editor: " + result.message, logtype::generic);
}
void editor_mode::toggle_include(scene::instance_handle const Instance)
{
if (false == scene::Layers.tracked(Instance))
return;
auto const &included = scene::Layers.instance(Instance);
if (included.dead)
{
// its directive is gone from the scenery file, and the place it was at with it
ui()->set_status("Include of \"" + *included.file + "\" was removed from the saved scenery, this can't be taken back.", true);
return;
}
scene::Layers.removed(Instance, false == included.removed);
ui()->set_status("Include of \"" + *included.file + "\" " + (included.removed ? "removed." : "restored."));
}
void editor_mode::place_include(std::string const &File, int const RotationMode, float const FixedRotation)
{
if (scene::Layers.empty())
{
// the directive has to go to a scenery file, and without the sources tracked there's no telling which
ui()->set_status("Scenery templates can be placed only in a scenery opened for editing (-edit).", true);
WriteLog("Editor: scenery templates can be placed only in a scenery opened for editing (-edit)", logtype::generic);
return;
}
include_info info;
std::string issue;
auto const parameters = editor_includes::parameter_count(File);
if (false == editor_includes::load(File, info, issue) || false == editor_includes::complete(info, parameters, &issue))
{
ui()->set_status("Template \"" + File + "\" can't be placed: " + issue, true);
return;
}
// the directive is written at the end of the file of the active layer, and has to make sense with the placement in effect there
auto const layer = scene::Layers.resolve(scene::Layers.active());
if (std::string reason; false == scene::Layers.valid(layer) || scene::Layers.layer(layer).removed || false == scene::Layers.writable(layer, &reason))
{
ui()->set_status("Template \"" + File + "\" can't be placed: " + (reason.empty() ? "there's no active layer to put it in" : "the active layer can't be saved, " + reason), true);
return;
}
auto const context = scene::Layers.layer(layer).context_end;
if (context.rotation != glm::vec3{0.f} || context.scale != glm::vec3{1.f})
{
// a template can pass its parameters to an origin directive, which isn't affected by these, or straight to
// the nodes, which are. there's no single set of parameter values which puts both at the cursor
ui()->set_status("Template \"" + File + "\" can't be placed: the file of the active layer ends with a rotation or a scale in effect. Pick another layer.", true);
return;
}
glm::dvec3 const location = Camera.Pos + clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
std::optional<float> yaw;
if (RotationMode == functions_panel::RANDOM)
yaw = static_cast<float>(LocalRandom(0.0, 360.0));
else if (RotationMode == functions_panel::FIXED)
yaw = FixedRotation;
auto const directive = editor_includes::directive(File, info, parameters, context.to_local(location), yaw);
auto const instance = scene::Layers.place(layer, File, directive);
if (instance == 0)
{
ui()->set_status("Template \"" + File + "\" can't be placed in the active layer.", true);
return;
}
// show the models of the template right away. the rest of it comes into play when the saved scenery is loaded
auto const preview = simulation::State.preview_include(directive, context, layer, instance);
EditorSnapshot snap;
snap.instance = instance;
snap.node_name = File;
m_history.push_back(std::move(snap));
g_redo.clear();
ui()->set_status(
"\"" + info.name + "\" placed in layer \"" + scene::Layers.layer(layer).name + "\": " + std::to_string(preview.first) + " model(s) shown"
+ (preview.second > 0 ? ", " + std::to_string(preview.second) + " other statement(s) of the template take effect once the scenery is saved and loaded again." : "."));
}
void editor_mode::export_scenery()
{
// hiding a layer clears the visibility flag of its nodes, and the flag is a part of exported node data.
@@ -2196,6 +2294,7 @@ void editor_mode::exit()
// drop selection so a stale/dangling node pointer isn't used on the next editor session
m_node = nullptr;
m_instance = 0;
m_gizmo_using = false;
ui()->set_node(nullptr);
@@ -2291,6 +2390,19 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
break;
case GLFW_KEY_DELETE:
if (is_press(Action) && m_instance != 0)
{
// include of a scenery template. what it shows goes out of sight, the directive is erased on save
scene::Layers.removed(m_instance, true);
EditorSnapshot snap;
snap.instance = m_instance;
snap.node_name = *scene::Layers.instance(m_instance).file;
m_history.push_back(std::move(snap));
g_redo.clear();
ui()->set_status("Include of \"" + *scene::Layers.instance(m_instance).file + "\" removed, Ctrl+Z brings it back. Its directive is erased when the scenery is saved.");
m_instance = 0;
break;
}
if (is_press(Action))
{
TAnimModel *model = dynamic_cast<TAnimModel *>(m_node);
@@ -2414,13 +2526,25 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
return;
m_node = nullptr;
m_instance = 0;
// in a scenery opened for editing some nodes are off limits: the ones in locked layers,
// and the ones whose definitions can't be rewritten on save
if (std::string reason; node && false == scene::Layers.editable(node, &reason))
// in a scenery opened for editing selection has its limits. nodes of locked layers are off limits.
// nodes defined by a scenery template can't be rewritten one by one on save, so a click on one
// selects the include of the template as a whole
if (node && mode == nodebank_panel::MODIFY)
{
ui()->set_status("\"" + (node->name().empty() ? std::string{"(unnamed node)"} : node->name()) + "\" can't be edited: " + reason, true);
node = nullptr;
std::string reason;
if (node->from_template() && scene::Layers.removable(node->m_instance, &reason))
{
m_instance = node->m_instance;
ui()->set_status("Include of \"" + *scene::Layers.instance(m_instance).file + "\" selected. Del removes it from the scenery.");
node = nullptr;
}
else if (node->from_template() || false == scene::Layers.editable(node, &reason))
{
ui()->set_status("\"" + (node->name().empty() ? std::string{"(unnamed node)"} : node->name()) + "\" can't be edited: " + reason, true);
node = nullptr;
}
}
// ignore picks that are beyond allowed placement distance
@@ -2451,6 +2575,13 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
if (!src)
return;
if (src->starts_with(editor_includes::directive_mark))
{
// a scenery template rather than a single node
place_include(src->substr(editor_includes::directive_mark.size()), rotation_mode, fixed_rotation_value);
return;
}
std::string name = "editor_";
glm::dvec3 mouseOffset = clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
TAnimModel *cloned = simulation::State.create_model(*src, name, Camera.Pos + mouseOffset);

View File

@@ -97,6 +97,7 @@ class editor_mode : public application_mode
glm::vec3 scale{1.0f, 1.0f, 1.0f};
UID uuid; // node UUID for reference, used as fallback lookup for deleted/recreated nodes
scene::layer_handle layer{null_handle}; // scenery layer of the node, so a recreated node returns to it
scene::instance_handle instance{0}; // include of a scenery template which was placed or removed, instead of a node
};
void push_snapshot(scene::basic_node *node, EditorSnapshot::Action Action = EditorSnapshot::Action::Move, std::string const &Serialized = std::string());
@@ -127,6 +128,7 @@ class editor_mode : public application_mode
double fTime50Hz{0.0}; // bufor czasu dla komunikacji z PoKeys
scene::basic_editor m_editor;
scene::basic_node *m_node{nullptr}; // currently selected scene node
scene::instance_handle m_instance{0}; // currently selected include of a scenery template; these are handled as a whole
bool m_takesnapshot{true}; // helper, hints whether snapshot of selected node(s) should be taken before modification
bool m_dragging = false;
glm::dvec3 oldPos; // world position of the last brush placement
@@ -167,6 +169,10 @@ class editor_mode : public application_mode
void commit_terrain();
// writes the changes to the files of scenery opened for editing (Ctrl+S)
void save();
// places include of specified scenery template at the cursor, in the active layer
void place_include(std::string const &File, int RotationMode, float FixedRotation);
// undo and redo of placing or removing an include: brings removed include back, or removes it
void toggle_include(scene::instance_handle Instance);
// exports the scenery in legacy (text) format, with layers hidden in the editor left out of the picture
void export_scenery();
// raises/lowers terrain under the cursor while the left mouse button is held in sculpt mode

View File

@@ -136,7 +136,7 @@ void editor_ui::render_menu_contents()
{
ImGui::MenuItem(STR_C("Toolset"), nullptr, &m_nodebankpanel.is_open);
ImGui::MenuItem(STR_C("Layers"), nullptr, &m_layerspanel.is_open);
ImGui::MenuItem(STR_C("Include descriptions"), nullptr, &m_includespanel.is_open);
ImGui::MenuItem(STR_C("Include database"), nullptr, &m_includespanel.is_open);
ImGui::EndMenu();
}
}

View File

@@ -68,7 +68,7 @@ class editor_ui : public ui_layer
functions_panel m_functionspanel{"Functions", true}; // not a window of its own, its settings are drawn in the toolset tabs
nodebank_panel m_nodebankpanel{"Toolset", true}; // main editor window: gizmo, node properties, edit modes and the node bank
layers_panel m_layerspanel{"Layers", true};
includes_panel m_includespanel{"Include descriptions", false};
includes_panel m_includespanel{"Include database", false};
std::function<void()> m_save;
std::function<void()> m_export;
brush_object_list m_brushobjects;

View File

@@ -589,6 +589,38 @@ void nodebank_panel::render()
++idx;
}
}
// scenery templates. placing one adds an include directive to a scenery file, so they take scenery opened for editing
if (Global.editor_session && ImGui::CollapsingHeader("Scenery templates (.inc)"))
{
if (false == EditorIncludes.scanned())
{
EditorIncludes.scan();
}
auto const &templates{EditorIncludes.entries()};
std::string const *category{nullptr};
auto listed{0};
for (auto const entryidx : EditorIncludes.ready())
{
auto const &entry{templates[entryidx]};
if (false == searchfilter.empty() && false == contains(entry.name, searchfilter) && false == contains(entry.file, searchfilter) && false == contains(entry.category, searchfilter))
{
continue;
}
if (false == entry.category.empty() && (category == nullptr || *category != entry.category))
{
ImGui::TextDisabled(" %s", entry.category.c_str());
}
category = &entry.category;
auto const label{(entry.category.empty() ? " " : " ") + entry.name + " (" + Bezogonkow(entry.file) + ")##inc" + std::to_string(entryidx)};
if (list_item(label.c_str(), entry.statement == m_selectedtemplate))
m_selectedtemplate = entry.statement;
++listed;
}
if (listed == 0)
{
ImGui::TextDisabled(EditorIncludes.ready().empty() ? " (none yet, describe the templates in the Include database window)" : " (no match)");
}
}
ImGui::ListBoxFooter();
}
ImGui::PopItemWidth();
@@ -667,7 +699,7 @@ void nodebank_panel::manual_list(char const *Id, std::vector<std::string> &List,
}
if (ImGui::Button("Add selected"))
{
if (m_selectedtemplate && false == m_selectedtemplate->empty())
if (node_selected())
List.push_back(*m_selectedtemplate);
}
if (ImGui::IsItemHovered())
@@ -882,7 +914,7 @@ void nodebank_panel::render_sets_window()
auto const setid{edited->id};
if (ImGui::Button("Add selected"))
{
if (m_selectedtemplate && false == m_selectedtemplate->empty())
if (node_selected())
EditorModelSets.add(setid, {*m_selectedtemplate});
}
if (ImGui::IsItemHovered())
@@ -962,6 +994,12 @@ const std::string *nodebank_panel::get_active_template()
return m_selectedtemplate.get();
}
bool nodebank_panel::node_selected() const
{
// scenery templates are placed with include directives, the tools which scatter nodes have no use for them
return m_selectedtemplate && false == m_selectedtemplate->empty() && false == m_selectedtemplate->starts_with(editor_includes::directive_mark);
}
std::vector<std::string> nodebank_panel::group_names() const
{
std::vector<std::string> names;
@@ -1504,6 +1542,7 @@ void includes_panel::open(std::string const &File)
{
m_file = File;
m_statuserror = false;
m_parameters = 0;
std::string error;
m_loaded = editor_includes::load(File, m_info, error);
if (false == m_loaded)
@@ -1512,13 +1551,217 @@ void includes_panel::open(std::string const &File)
m_statuserror = true;
return;
}
auto const described{false == m_info.name.empty() || false == m_info.parameters.empty()};
// every parameter the template uses gets an entry, described or not
auto const count{editor_includes::parameter_count(File)};
for (auto id = 1; id <= count; ++id)
m_parameters = editor_includes::parameter_count(File);
for (auto id = 1; id <= m_parameters; ++id)
{
m_info.parameter(id);
}
m_status = "The template uses " + std::to_string(count) + " parameter(s)";
m_status = "The template uses " + std::to_string(m_parameters) + " parameter(s).";
if (false == described)
{
// starting point for a new description
editor_includes::suggest(File, m_info);
m_status += " It has no description yet; the roles are a suggestion based on how the template uses the parameters.";
}
}
void includes_panel::render_list()
{
if (false == EditorIncludes.scanned())
{
EditorIncludes.scan();
}
auto const &entries{EditorIncludes.entries()};
ImGui::PushItemWidth(ImGui::GetFontSize() * 12.0f);
auto changed{ImGui::InputTextWithHint("##filter", "Search templates", m_filter, IM_ARRAYSIZE(m_filter))};
ImGui::PopItemWidth();
ImGui::SameLine();
changed |= ImGui::Checkbox("Described", &m_describedonly);
if (false == scene::Layers.templates().empty())
{
// known only for scenery opened for editing
ImGui::SameLine();
changed |= ImGui::Checkbox("Used by the scenery", &m_usedonly);
}
ImGui::SameLine();
if (ImGui::Button("Rescan"))
{
EditorIncludes.scan();
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Looks through the scenery directory again, for the templates added or changed outside of the editor");
}
if (changed || m_listrevision != EditorIncludes.revision())
{
m_listrevision = EditorIncludes.revision();
m_listed.clear();
m_described = 0;
std::string const filter{m_filter};
auto const &used{scene::Layers.templates()};
for (std::size_t idx = 0; idx < entries.size(); ++idx)
{
auto const &entry{entries[idx]};
m_described += (entry.described ? 1 : 0);
if ((m_describedonly && false == entry.described) || (m_usedonly && false == used.empty() && used.count(entry.file) == 0))
{
continue;
}
if (false == filter.empty() && false == contains(entry.file, filter) && false == contains(entry.name, filter) && false == contains(entry.category, filter))
{
continue;
}
m_listed.emplace_back(idx);
}
}
ImGui::TextDisabled("%d template(s) in the scenery directory, %d described, %d ready for the node bank", static_cast<int>(entries.size()), m_described,
static_cast<int>(EditorIncludes.ready().size()));
ImGui::BeginChild("##templates", ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 9.0f), true);
ImGuiListClipper clipper(static_cast<int>(m_listed.size()));
while (clipper.Step())
{
for (auto row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
auto const &entry{entries[m_listed[row]]};
auto const marker{entry.complete ? "[ready] " : entry.described ? "[incomplete] " : ""};
auto const label{marker + Bezogonkow(entry.file) + (entry.name.empty() ? "" : " - " + entry.name) + "##" + std::to_string(m_listed[row])};
if (false == entry.described)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
}
auto const clicked{list_item(label.c_str(), entry.file == m_file)};
if (false == entry.described)
{
ImGui::PopStyleColor();
}
if (false == entry.complete && false == entry.issue.empty() && ImGui::IsItemHovered())
{
ImGui::SetTooltip("Not in the node bank: %s", Bezogonkow(entry.issue).c_str());
}
if (clicked)
{
open(entry.file);
}
}
}
if (m_listed.empty())
{
ImGui::TextDisabled(entries.empty() ? "(no .inc files in the scenery directory)" : "(no match)");
}
ImGui::EndChild();
}
void includes_panel::render_description()
{
ImGui::Separator();
ImGui::Text("%s", Bezogonkow(m_file).c_str());
input_text("Name", m_info.name);
input_text("Category", m_info.category);
input_text("Description", m_info.description);
ImGui::Columns(4, "##parameters", false);
ImGui::SetColumnWidth(0, ImGui::CalcTextSize("(p000) ").x);
ImGui::SetColumnWidth(1, ImGui::CalcTextSize("texture ").x + ImGui::GetFrameHeight() * 2.0f);
ImGui::TextDisabled("param");
ImGui::NextColumn();
ImGui::TextDisabled("role");
ImGui::NextColumn();
ImGui::TextDisabled("label");
ImGui::NextColumn();
ImGui::TextDisabled("default");
ImGui::NextColumn();
for (auto &parameter : m_info.parameters)
{
ImGui::PushID(parameter.id);
ImGui::AlignTextToFramePadding();
// parameters which keep the template out of the node bank stand out
if (parameter.id <= m_parameters && parameter.value.empty() && false == editor_includes::automatic(parameter.role))
{
ImGui::TextColored(status_errorcolor, "(p%d)", parameter.id);
}
else
{
ImGui::Text("(p%d)", parameter.id);
}
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
if (ImGui::BeginCombo("##role", parameter.role.c_str()))
{
for (auto const &role : editor_includes::roles)
{
if (ImGui::Selectable(role.c_str(), role == parameter.role))
{
parameter.role = role;
}
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
input_text("##label", parameter.label);
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
input_text("##default", parameter.value);
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::Columns(1);
// what the node bank is going to make of the description
ImGui::PushTextWrapPos(0.0f);
if (std::string issue; editor_includes::complete(m_info, m_parameters, &issue))
{
ImGui::TextUnformatted("Complete: once saved, the template is offered by the node bank (Insert tab, Scenery templates).");
}
else
{
ImGui::TextColored(status_errorcolor, "Not for the node bank yet: %s.", issue.c_str());
}
ImGui::PopTextWrapPos();
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("A template placed from the node bank gets the cursor location in the pos.* parameters, the rotation set in the\n"
"Insert tab in rot.y, and a unique name (the default value followed by a random suffix) in the name parameter.\n"
"The other parameters receive their default values.");
}
if (ImGui::Button("Suggest roles"))
{
editor_includes::suggest(m_file, m_info);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Sets roles of the parameters still marked as free, where the way the template uses them tells what they are");
}
ImGui::SameLine();
if (ImGui::Button("Add parameter"))
{
m_info.parameter(m_info.parameters.empty() ? 1 : m_info.parameters.back().id + 1);
}
ImGui::SameLine();
if (ImGui::Button("Save to file"))
{
std::string error;
m_statuserror = false == editor_includes::save(m_file, m_info, error);
m_status = m_statuserror ? error : "Description saved in \"" + m_file + "\"";
if (false == m_statuserror)
{
EditorIncludes.update(m_file);
}
}
ImGui::SameLine();
if (ImGui::Button("Reload"))
{
open(m_file);
}
}
void includes_panel::render()
@@ -1528,115 +1771,27 @@ void includes_panel::render()
return;
}
ImGui::SetNextWindowPos(ImVec2S(560, 110), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2S(520, 440), ImGuiCond_FirstUseEver);
// next to the layers window where the screen is wide enough for both
auto const position{ImVec2S(970, 60)};
auto const windowsize{ImVec2S(600, 600)};
ImGui::SetNextWindowPos(position.x + windowsize.x <= ImGui::GetIO().DisplaySize.x ? position : ImVec2S(560, 110), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(windowsize, ImGuiCond_FirstUseEver);
auto const panelname{(title.empty() ? m_name : title) + "###" + m_name};
if (ImGui::Begin(panelname.c_str(), &is_open, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse))
{
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
ImGui::TextWrapped("Describes .inc templates and their parameters. The description is stored in the template file, as //$e comment lines.");
ImGui::TextWrapped("The .inc templates of the scenery directory. Describe a template and its parameters to add it to the node bank; "
"the description is stored in the template file, as //$e comment lines.");
if (false == Global.editor_session)
{
ImGui::TextWrapped("The node bank offers the templates in a scenery opened for editing (-edit).");
}
ImGui::PopStyleColor();
// templates the loaded scenery makes use of; known only for scenery opened for editing
auto const &templates{scene::Layers.templates()};
if (false == templates.empty())
{
ImGui::BeginChild("##templates", ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 6.0f), true);
for (auto const &file : templates)
{
if (ImGui::Selectable(Bezogonkow(file).c_str(), file == m_file))
{
open(file);
}
}
ImGui::EndChild();
}
ImGui::PushItemWidth(-ImGui::CalcTextSize(" Open ").x - ImGui::GetStyle().ItemSpacing.x * 2.0f);
auto const entered{ImGui::InputTextWithHint("##file", "path of an .inc file in the scenery directory", m_filename, IM_ARRAYSIZE(m_filename), ImGuiInputTextFlags_EnterReturnsTrue)};
ImGui::PopItemWidth();
ImGui::SameLine();
if ((ImGui::Button("Open") || entered) && m_filename[0] != '\0')
{
std::string file{ToLower(m_filename)};
replace_slashes(file);
open(file);
}
render_list();
if (m_loaded)
{
ImGui::Separator();
ImGui::Text("%s", Bezogonkow(m_file).c_str());
input_text("Name", m_info.name);
input_text("Category", m_info.category);
input_text("Description", m_info.description);
ImGui::Columns(4, "##parameters", false);
ImGui::SetColumnWidth(0, ImGui::CalcTextSize("(p000) ").x);
ImGui::SetColumnWidth(1, ImGui::CalcTextSize("texture ").x + ImGui::GetFrameHeight() * 2.0f);
ImGui::TextDisabled("param");
ImGui::NextColumn();
ImGui::TextDisabled("role");
ImGui::NextColumn();
ImGui::TextDisabled("label");
ImGui::NextColumn();
ImGui::TextDisabled("default");
ImGui::NextColumn();
for (auto &parameter : m_info.parameters)
{
ImGui::PushID(parameter.id);
ImGui::AlignTextToFramePadding();
ImGui::Text("(p%d)", parameter.id);
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
if (ImGui::BeginCombo("##role", parameter.role.c_str()))
{
for (auto const &role : editor_includes::roles)
{
if (ImGui::Selectable(role.c_str(), role == parameter.role))
{
parameter.role = role;
}
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
input_text("##label", parameter.label);
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PushItemWidth(-1.0f);
input_text("##default", parameter.value);
ImGui::PopItemWidth();
ImGui::NextColumn();
ImGui::PopID();
}
ImGui::Columns(1);
if (ImGui::Button("Suggest roles"))
{
editor_includes::suggest(m_file, m_info);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("Sets roles of the parameters still marked as free, where the way the template uses them tells what they are");
}
ImGui::SameLine();
if (ImGui::Button("Add parameter"))
{
m_info.parameter(m_info.parameters.empty() ? 1 : m_info.parameters.back().id + 1);
}
ImGui::SameLine();
if (ImGui::Button("Save to file"))
{
std::string error;
m_statuserror = false == editor_includes::save(m_file, m_info, error);
m_status = m_statuserror ? error : "Description saved in \"" + m_file + "\"";
}
ImGui::SameLine();
if (ImGui::Button("Reload"))
{
open(m_file);
}
render_description();
}
if (false == m_status.empty())
{

View File

@@ -136,6 +136,8 @@ class nodebank_panel : public ui_panel
// methods:
std::string generate_node_label(std::string Input) const;
void render_sets_window();
// true if the node bank selection is a definition of a node, which the hand-made lists and the model sets can take
bool node_selected() const;
// members:
std::vector<std::pair<std::string, std::shared_ptr<std::string>>> m_nodebank;
char m_nodesearch[128];
@@ -178,7 +180,8 @@ class layers_panel : public ui_panel
std::string m_popuperror;
};
// editor of the descriptions of *.inc scenery templates, see editorIncludeInfo.hpp
// database of the *.inc scenery templates: the templates found in the scenery directory, with the editor of their
// descriptions (see editorIncludeInfo.hpp). templates whose descriptions are complete are offered by the node bank
class includes_panel : public ui_panel
{
@@ -190,10 +193,20 @@ class includes_panel : public ui_panel
private:
// methods
void open(std::string const &File);
void render_list();
void render_description();
// members
// template list
char m_filter[128]{};
bool m_describedonly{false};
bool m_usedonly{false};
std::vector<std::size_t> m_listed; // entries of the template bank matching the filter
int m_listrevision{-1}; // revision of the template bank the list was made for
int m_described{0};
// description editor
std::string m_file; // template being edited, relative to the scenery directory
char m_filename[256]{}; // name of a template typed in by hand
include_info m_info;
int m_parameters{0}; // number of parameters the template uses
bool m_loaded{false};
std::string m_status;
bool m_statuserror{false};