mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 03:59:19 +02:00
feat(editor): user-defined model sets, random insert from set, tabbed editor panel
This commit is contained in:
@@ -167,6 +167,7 @@ set(SOURCES
|
||||
"application/driveruipanels.cpp"
|
||||
"input/editorkeyboardinput.cpp"
|
||||
"editor/editorSettings.cpp"
|
||||
"editor/editorModelSets.cpp"
|
||||
"editor/editorTerrain.cpp"
|
||||
"editor/editorTerrainStreamer.cpp"
|
||||
"application/editormode.cpp"
|
||||
|
||||
@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "application/application.h"
|
||||
#include "editor/editorSettings.hpp"
|
||||
#include "editor/editorModelSets.hpp"
|
||||
#include "utilities/Globals.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
@@ -164,19 +165,6 @@ namespace
|
||||
return std::abs(sum) * 0.5;
|
||||
}
|
||||
|
||||
// short readable name of a node template (model file and texture), like the node bank shows it
|
||||
std::string template_label(std::string const &Template)
|
||||
{
|
||||
cParser tokenizer(Template);
|
||||
tokenizer.getTokens(9, false); // node, ranges, name, type, position, rotation
|
||||
auto model{tokenizer.getToken<std::string>(false)};
|
||||
auto texture{tokenizer.getToken<std::string>(false)};
|
||||
replace_slashes(model);
|
||||
erase_extension(model);
|
||||
replace_slashes(texture);
|
||||
return texture == "none" || texture.empty() ? model : model + " (" + texture + ")";
|
||||
}
|
||||
|
||||
// world triangles bucketed on a uniform XZ grid, for many ground height queries over one area
|
||||
class triangle_grid
|
||||
{
|
||||
@@ -293,6 +281,9 @@ void editor_mode::editormode_input::poll()
|
||||
|
||||
editor_mode::editor_mode() {
|
||||
m_userinterface = std::make_shared<editor_ui>();
|
||||
// the area fill settings live in the node bank window, in the tab of the fill mode
|
||||
ui()->set_fill_options([this]() { render_area_fill(); });
|
||||
ui()->set_gizmo_options([this]() { render_gizmo_options(); });
|
||||
}
|
||||
|
||||
editor_ui *editor_mode::ui() const
|
||||
@@ -303,6 +294,7 @@ editor_ui *editor_mode::ui() const
|
||||
bool editor_mode::init()
|
||||
{
|
||||
EditorSettings.load();
|
||||
EditorModelSets.load();
|
||||
Camera.Init({0, 15, 0}, {glm::radians(-30.0), glm::radians(180.0), 0}, nullptr);
|
||||
return m_input.init();
|
||||
}
|
||||
@@ -473,17 +465,18 @@ void editor_mode::handle_brush_mouse_hold(int Action, int Button)
|
||||
if (!mouseHold || !viewport_click())
|
||||
return;
|
||||
|
||||
// spacing is measured in world space: the mouse offset is camera-relative and the camera may move mid-stroke
|
||||
glm::dvec3 const newPos = Camera.Pos + clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
|
||||
if (m_brush_has_last && glm::distance(newPos, oldPos) < ui()->getSpacing())
|
||||
return;
|
||||
|
||||
// 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)
|
||||
return;
|
||||
|
||||
std::string name = "editor_";
|
||||
|
||||
// spacing is measured in world space: the mouse offset is camera-relative and the camera may move mid-stroke
|
||||
glm::dvec3 const newPos = Camera.Pos + clamp_mouse_offset_to_max(GfxRenderer->Mouse_Position());
|
||||
if (m_brush_has_last && glm::distance(newPos, oldPos) < ui()->getSpacing())
|
||||
return;
|
||||
|
||||
TAnimModel *cloned = simulation::State.create_model(*src, name, newPos);
|
||||
oldPos = newPos;
|
||||
m_brush_has_last = true;
|
||||
@@ -819,9 +812,6 @@ bool editor_mode::update()
|
||||
#endif
|
||||
m_userinterface->update();
|
||||
|
||||
// update brush settings visibility depending on panel mode
|
||||
ui()->toggleBrushSettings(ui()->mode() == nodebank_panel::BRUSH);
|
||||
|
||||
if (mouseHold)
|
||||
{
|
||||
// process continuous brush placement
|
||||
@@ -892,12 +882,9 @@ bool editor_mode::update()
|
||||
// --- ImGuizmo: in-viewport transform gizmo for the selected node ---
|
||||
render_gizmo();
|
||||
|
||||
// --- area fill: outline overlay and settings, while the mode is active ---
|
||||
// --- area fill: outline overlay while the mode is active (its settings are drawn in the node bank window) ---
|
||||
if (ui()->mode() == nodebank_panel::FILL)
|
||||
{
|
||||
draw_area_fill_outline();
|
||||
render_area_fill();
|
||||
}
|
||||
|
||||
// --- ImGui: Editor Settings & History windows ---
|
||||
if(m_settings_open)
|
||||
@@ -914,31 +901,39 @@ void editor_mode::render_settings()
|
||||
{
|
||||
ImGui::Begin("Editor Settings", &m_settings_open, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
|
||||
ImGui::TextUnformatted("Camera movement");
|
||||
|
||||
const char *schemes[] = {"WSAD (new)", "Arrows (legacy)"};
|
||||
int current = EditorSettings.movement() == editorSettings::movement_scheme::legacy ? 1 : 0;
|
||||
if (ImGui::Combo("##movement_scheme", ¤t, schemes, IM_ARRAYSIZE(schemes)))
|
||||
if (ImGui::BeginTabBar("##editorsettings"))
|
||||
{
|
||||
EditorSettings.movement(current == 1 ? editorSettings::movement_scheme::legacy
|
||||
: editorSettings::movement_scheme::wsad);
|
||||
m_input.keyboard.apply_scheme();
|
||||
EditorSettings.save();
|
||||
if (ImGui::BeginTabItem("General"))
|
||||
{
|
||||
ImGui::TextUnformatted("Camera movement");
|
||||
|
||||
const char *schemes[] = {"WSAD (new)", "Arrows (legacy)"};
|
||||
int current = EditorSettings.movement() == editorSettings::movement_scheme::legacy ? 1 : 0;
|
||||
if (ImGui::Combo("##movement_scheme", ¤t, schemes, IM_ARRAYSIZE(schemes)))
|
||||
{
|
||||
EditorSettings.movement(current == 1 ? editorSettings::movement_scheme::legacy
|
||||
: editorSettings::movement_scheme::wsad);
|
||||
m_input.keyboard.apply_scheme();
|
||||
EditorSettings.save();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Transform gizmo (ImGuizmo)", &m_gizmo_enabled);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Terrain"))
|
||||
{
|
||||
render_terrain_ui();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Transform gizmo (ImGuizmo)", &m_gizmo_enabled);
|
||||
|
||||
render_terrain_ui();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void editor_mode::render_terrain_ui()
|
||||
{
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Terrain");
|
||||
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
ImGui::InputInt("Grid cells", &m_terrain_cells);
|
||||
m_terrain_cells = std::clamp(m_terrain_cells, 1, 512);
|
||||
@@ -1406,10 +1401,11 @@ void editor_mode::run_area_fill()
|
||||
|
||||
// model package
|
||||
std::vector<std::string> package;
|
||||
if (m_fill_package == 0)
|
||||
if (m_fill_source.kind == model_set_ref::source::manual)
|
||||
package = m_fill_custom;
|
||||
else
|
||||
package = ui()->nodebank_group_templates(static_cast<std::size_t>(m_fill_package - 1));
|
||||
for (auto const *entry : ui()->nodebank().set_entries(m_fill_source))
|
||||
package.push_back(*entry);
|
||||
package.erase(std::remove_if(package.begin(), package.end(), [](std::string const &Template) { return Template.empty(); }), package.end());
|
||||
if (package.empty())
|
||||
{
|
||||
@@ -1621,9 +1617,7 @@ void editor_mode::draw_area_fill_outline() const
|
||||
|
||||
void editor_mode::render_area_fill()
|
||||
{
|
||||
ImGui::Begin("Area fill", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
|
||||
ImGui::TextUnformatted("LMB: add outline point, Backspace: remove last point");
|
||||
ImGui::TextDisabled("LMB: add outline point Backspace: remove last point");
|
||||
double const area = polygon_area_xz(m_fill_points);
|
||||
ImGui::Text("Points: %zu Area: %.0f m2 (%.2f ha)", m_fill_points.size(), area, area / 10000.0);
|
||||
if (ImGui::Button("Remove last point") && !m_fill_points.empty())
|
||||
@@ -1634,58 +1628,13 @@ void editor_mode::render_area_fill()
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// model package: a node bank group, or a custom set assembled from node bank templates
|
||||
auto const groups = ui()->nodebank_groups();
|
||||
m_fill_package = std::clamp(m_fill_package, 0, static_cast<int>(groups.size()));
|
||||
std::string const current = m_fill_package == 0 ? std::string("Custom set") : groups[m_fill_package - 1];
|
||||
ImGui::SetNextItemWidth(250.0f);
|
||||
if (ImGui::BeginCombo("Model package", current.c_str()))
|
||||
{
|
||||
if (ImGui::Selectable("Custom set", m_fill_package == 0))
|
||||
m_fill_package = 0;
|
||||
for (int i = 0; i < static_cast<int>(groups.size()); ++i)
|
||||
{
|
||||
auto const label = groups[i] + "##fillgroup" + std::to_string(i);
|
||||
if (ImGui::Selectable(label.c_str(), m_fill_package == i + 1))
|
||||
m_fill_package = i + 1;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (m_fill_package == 0)
|
||||
{
|
||||
ImGui::BeginChild("fill_custom_set", ImVec2(350.0f, 110.0f), true);
|
||||
for (int i = 0; i < static_cast<int>(m_fill_custom.size()); ++i)
|
||||
{
|
||||
auto const label = template_label(m_fill_custom[i]) + "##fillcustom" + std::to_string(i);
|
||||
if (ImGui::Selectable(label.c_str(), m_fill_custom_idx == i))
|
||||
m_fill_custom_idx = i;
|
||||
}
|
||||
ImGui::EndChild();
|
||||
if (ImGui::Button("Add selected template"))
|
||||
{
|
||||
// the node bank selection, not a random pick from the brush set
|
||||
auto const *src = ui()->get_active_node_template(true);
|
||||
if (src && !src->empty())
|
||||
m_fill_custom.push_back(*src);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remove") && m_fill_custom_idx >= 0 && m_fill_custom_idx < static_cast<int>(m_fill_custom.size()))
|
||||
{
|
||||
m_fill_custom.erase(m_fill_custom.begin() + m_fill_custom_idx);
|
||||
m_fill_custom_idx = -1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear set"))
|
||||
{
|
||||
m_fill_custom.clear();
|
||||
m_fill_custom_idx = -1;
|
||||
}
|
||||
}
|
||||
// model package: a hand-assembled list, a user-defined set or a node bank group
|
||||
auto &bank = ui()->nodebank();
|
||||
bank.set_combo("Model set", m_fill_source, "Custom list");
|
||||
if (m_fill_source.kind == model_set_ref::source::manual)
|
||||
bank.manual_list("fillset", m_fill_custom, m_fill_custom_idx);
|
||||
else
|
||||
{
|
||||
ImGui::Text("Templates in group: %zu", ui()->nodebank_group_templates(static_cast<std::size_t>(m_fill_package - 1)).size());
|
||||
}
|
||||
ImGui::TextDisabled("%zu templates in set", bank.set_entries(m_fill_source).size());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
@@ -1697,7 +1646,9 @@ void editor_mode::render_area_fill()
|
||||
m_fill_min_spacing = std::max(0.0f, m_fill_min_spacing);
|
||||
ImGui::SetNextItemWidth(200.0f);
|
||||
ImGui::DragFloatRange2("Scale", &m_fill_scale_min, &m_fill_scale_max, 0.01f, 0.1f, 5.0f, "min %.2f", "max %.2f");
|
||||
ImGui::Checkbox("Random rotation (off: Functions panel setting)", &m_fill_random_rotation);
|
||||
ImGui::Checkbox("Random rotation", &m_fill_random_rotation);
|
||||
if (!m_fill_random_rotation)
|
||||
ui()->render_rotation_controls();
|
||||
ImGui::Checkbox("Large models count as ground (terrain tiles)", &m_fill_models_as_ground);
|
||||
|
||||
ImGui::Text("Estimated objects: %.0f", std::round(area / 10000.0 * m_fill_density));
|
||||
@@ -1711,21 +1662,20 @@ void editor_mode::render_area_fill()
|
||||
|
||||
if (!m_fill_status.empty())
|
||||
ImGui::TextUnformatted(m_fill_status.c_str());
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void editor_mode::render_gizmo()
|
||||
void editor_mode::render_gizmo_options()
|
||||
{
|
||||
// the transform gizmo is suppressed while editing terrain, so the brush/chunk tool owns the mouse
|
||||
if (!m_gizmo_enabled || m_terrain_sculpt || m_chunk_edit)
|
||||
ImGui::Checkbox("Enabled", &m_gizmo_enabled);
|
||||
if (!m_gizmo_enabled)
|
||||
return;
|
||||
if (m_terrain_sculpt || m_chunk_edit)
|
||||
{
|
||||
m_gizmo_using = false;
|
||||
ImGui::TextDisabled("Suspended while editing terrain");
|
||||
return;
|
||||
}
|
||||
|
||||
// compact control window: lets the user pick the transform mode without keyboard shortcuts
|
||||
ImGui::Begin("Gizmo", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing);
|
||||
// lets the user pick the transform mode without keyboard shortcuts
|
||||
int op = static_cast<int>(m_gizmo_op);
|
||||
ImGui::RadioButton("Translate (Q)", &op, static_cast<int>(gizmo_operation::translate));
|
||||
ImGui::SameLine();
|
||||
@@ -1745,7 +1695,16 @@ void editor_mode::render_gizmo()
|
||||
}
|
||||
if (!m_node)
|
||||
ImGui::TextDisabled("No node selected");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void editor_mode::render_gizmo()
|
||||
{
|
||||
// the transform gizmo is suppressed while editing terrain, so the brush/chunk tool owns the mouse
|
||||
if (!m_gizmo_enabled || m_terrain_sculpt || m_chunk_edit)
|
||||
{
|
||||
m_gizmo_using = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_node)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#pragma once
|
||||
|
||||
#include "application/applicationmode.h"
|
||||
#include "application/editoruipanels.h"
|
||||
#include "input/editormouseinput.h"
|
||||
#include "input/editorkeyboardinput.h"
|
||||
#include "vehicle/Camera.h"
|
||||
@@ -211,7 +212,7 @@ class editor_mode : public application_mode
|
||||
std::vector<TAnimModel *> m_fill_last; // instances created by the most recent fill, for "Undo last fill"
|
||||
std::vector<std::string> m_fill_custom; // user-assembled package of node templates
|
||||
int m_fill_custom_idx{-1};
|
||||
int m_fill_package{0}; // 0 = custom set, N = nodebank group N-1
|
||||
model_set_ref m_fill_source; // manual = m_fill_custom, otherwise a user set or node bank group
|
||||
float m_fill_density{200.0f}; // objects per hectare
|
||||
float m_fill_min_spacing{2.0f}; // minimal distance between placed objects (m)
|
||||
bool m_fill_random_rotation{true}; // random yaw; otherwise the Functions panel rotation settings apply
|
||||
@@ -223,6 +224,8 @@ class editor_mode : public application_mode
|
||||
// ImGuizmo-based transform gizmo for the selected node
|
||||
enum class gizmo_operation { translate, rotate, scale };
|
||||
void render_gizmo();
|
||||
// gizmo settings, drawn in the toolset window
|
||||
void render_gizmo_options();
|
||||
bool m_gizmo_enabled{true}; // master switch for the in-viewport gizmo
|
||||
bool m_gizmo_using{false}; // tracks an ongoing drag, so a single undo snapshot is taken per drag
|
||||
bool m_gizmo_local{false}; // manipulate in the object's local space instead of world space
|
||||
|
||||
@@ -21,10 +21,10 @@ editor_ui::editor_ui()
|
||||
clear_panels();
|
||||
// bind the panels with ui object. maybe not the best place for this but, eh
|
||||
|
||||
add_external_panel(&m_itempropertiespanel);
|
||||
add_external_panel(&m_nodebankpanel);
|
||||
add_external_panel(&m_functionspanel);
|
||||
add_external_panel(&m_brushobjects);
|
||||
|
||||
m_nodebankpanel.mode_options = [this](nodebank_panel::edit_mode const Mode) { render_mode_options(Mode); };
|
||||
m_nodebankpanel.header_sections = [this]() { render_header_sections(); };
|
||||
}
|
||||
|
||||
// updates state of UI elements
|
||||
@@ -43,26 +43,67 @@ void editor_ui::update()
|
||||
ui_layer::update();
|
||||
m_itempropertiespanel.update(m_node);
|
||||
m_functionspanel.update(m_node);
|
||||
|
||||
auto ptr = get_active_node_template(true);
|
||||
if (ptr)
|
||||
m_brushobjects.update(*ptr);
|
||||
}
|
||||
|
||||
void editor_ui::toggleBrushSettings(bool isVisible)
|
||||
void editor_ui::render_mode_options(nodebank_panel::edit_mode const Mode)
|
||||
{
|
||||
if (m_brushobjects.is_open != isVisible)
|
||||
m_brushobjects.is_open = isVisible;
|
||||
switch (Mode)
|
||||
{
|
||||
case nodebank_panel::MODIFY:
|
||||
ImGui::TextDisabled("LMB: select node F: focus End: drop to ground Del: delete");
|
||||
break;
|
||||
case nodebank_panel::COPY:
|
||||
ImGui::TextDisabled("LMB: copy the clicked model to the node bank");
|
||||
break;
|
||||
case nodebank_panel::ADD:
|
||||
{
|
||||
ImGui::TextDisabled("LMB: insert a model at the cursor");
|
||||
ImGui::Checkbox("Random model from set", &m_insertrandom);
|
||||
if (m_insertrandom)
|
||||
{
|
||||
m_nodebankpanel.set_combo("Set##insert", m_insertset, nullptr);
|
||||
auto const count{m_nodebankpanel.set_entries(m_insertset).size()};
|
||||
ImGui::TextDisabled("%zu templates in set%s", count, count == 0 ? ", the node bank selection is used" : "");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::TextDisabled("Inserts the template selected in the node bank");
|
||||
}
|
||||
m_functionspanel.render_controls();
|
||||
break;
|
||||
}
|
||||
case nodebank_panel::BRUSH:
|
||||
ImGui::TextDisabled("Hold LMB: paint models along the cursor path");
|
||||
m_brushobjects.render_options(m_nodebankpanel);
|
||||
m_functionspanel.render_controls();
|
||||
break;
|
||||
case nodebank_panel::FILL:
|
||||
if (m_filloptions)
|
||||
m_filloptions();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> editor_ui::nodebank_groups() const
|
||||
void editor_ui::render_header_sections()
|
||||
{
|
||||
return m_nodebankpanel.group_names();
|
||||
if (ImGui::CollapsingHeader("Gizmo", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
if (m_gizmooptions)
|
||||
m_gizmooptions();
|
||||
}
|
||||
if (ImGui::CollapsingHeader("Node properties", ImGuiTreeNodeFlags_DefaultOpen))
|
||||
{
|
||||
ImGui::Indent();
|
||||
m_itempropertiespanel.render_body();
|
||||
ImGui::Unindent();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> editor_ui::nodebank_group_templates(std::size_t const Group) const
|
||||
void editor_ui::render_rotation_controls()
|
||||
{
|
||||
return m_nodebankpanel.group_templates(Group);
|
||||
m_functionspanel.render_controls();
|
||||
}
|
||||
|
||||
void editor_ui::set_node(scene::basic_node *Node)
|
||||
@@ -77,9 +118,23 @@ void editor_ui::add_node_template(const std::string &desc)
|
||||
|
||||
std::string const *editor_ui::get_active_node_template(bool bypassRandom)
|
||||
{
|
||||
if (!bypassRandom && m_brushobjects.is_open && m_brushobjects.useRandom && m_brushobjects.Objects.size() > 0)
|
||||
if (!bypassRandom)
|
||||
{
|
||||
return m_brushobjects.GetRandomObject();
|
||||
// random pick from the active tool's set; an empty set falls back to the node bank selection
|
||||
std::string const *pick{nullptr};
|
||||
if (m_nodebankpanel.mode == nodebank_panel::BRUSH && m_brushobjects.useRandom)
|
||||
{
|
||||
if (m_brushobjects.source.kind == model_set_ref::source::manual)
|
||||
pick = m_brushobjects.Objects.empty() ? nullptr : m_brushobjects.GetRandomObject();
|
||||
else
|
||||
pick = m_nodebankpanel.random_template(m_brushobjects.source);
|
||||
}
|
||||
else if (m_nodebankpanel.mode == nodebank_panel::ADD && m_insertrandom)
|
||||
{
|
||||
pick = m_nodebankpanel.random_template(m_insertset);
|
||||
}
|
||||
if (pick != nullptr)
|
||||
return pick;
|
||||
}
|
||||
return m_nodebankpanel.get_active_template();
|
||||
}
|
||||
|
||||
@@ -36,15 +36,36 @@ class editor_ui : public ui_layer
|
||||
const std::string *get_active_node_template(bool bypassRandom = false);
|
||||
nodebank_panel::edit_mode mode();
|
||||
float getSpacing();
|
||||
void toggleBrushSettings(bool isVisible);
|
||||
std::vector<std::string> nodebank_groups() const;
|
||||
std::vector<std::string> nodebank_group_templates(std::size_t const Group) const;
|
||||
// node bank and model set access (set selection widgets, set contents)
|
||||
nodebank_panel &nodebank()
|
||||
{
|
||||
return m_nodebankpanel;
|
||||
}
|
||||
// rotation settings widgets, for tools drawn by the editor mode
|
||||
void render_rotation_controls();
|
||||
// draws the area fill settings in its node bank tab (the fill state lives in the editor mode)
|
||||
void set_fill_options(std::function<void()> Renderer)
|
||||
{
|
||||
m_filloptions = std::move(Renderer);
|
||||
}
|
||||
// draws the gizmo settings in the toolset window (the gizmo state lives in the editor mode)
|
||||
void set_gizmo_options(std::function<void()> Renderer)
|
||||
{
|
||||
m_gizmooptions = std::move(Renderer);
|
||||
}
|
||||
|
||||
private:
|
||||
// methods
|
||||
void render_mode_options(nodebank_panel::edit_mode const Mode);
|
||||
void render_header_sections();
|
||||
// members
|
||||
itemproperties_panel m_itempropertiespanel{"Node Properties", true};
|
||||
functions_panel m_functionspanel{"Functions", true};
|
||||
nodebank_panel m_nodebankpanel{"Node Bank", true};
|
||||
brush_object_list m_brushobjects{"Brush properties", false};
|
||||
itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window
|
||||
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
|
||||
brush_object_list m_brushobjects;
|
||||
bool m_insertrandom{false}; // insert mode picks a random template from m_insertset
|
||||
model_set_ref m_insertset;
|
||||
std::function<void()> m_filloptions;
|
||||
std::function<void()> m_gizmooptions;
|
||||
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
|
||||
};
|
||||
|
||||
@@ -19,6 +19,21 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "world/MemCell.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "editor/editorModelSets.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
if (Selected)
|
||||
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetStyleColorVec4(ImGuiCol_HeaderActive));
|
||||
auto const clicked{ImGui::Selectable(Label, Selected)};
|
||||
if (Selected)
|
||||
ImGui::PopStyleColor();
|
||||
return clicked;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void itemproperties_panel::update(scene::basic_node *Node)
|
||||
{
|
||||
@@ -285,19 +300,24 @@ void itemproperties_panel::render()
|
||||
auto const panelname{(title.empty() ? m_name : title) + "###" + m_name};
|
||||
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
|
||||
{
|
||||
// header section
|
||||
for (auto const &line : text_lines)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
|
||||
}
|
||||
// transform editor (position/rotation/scale) — TAnimModel only
|
||||
render_transform_editor();
|
||||
// group section
|
||||
render_group();
|
||||
render_body();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void itemproperties_panel::render_body()
|
||||
{
|
||||
// header section
|
||||
for (auto const &line : text_lines)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
|
||||
}
|
||||
// transform editor (position/rotation/scale) — TAnimModel only
|
||||
render_transform_editor();
|
||||
// group section
|
||||
render_group();
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -375,27 +395,6 @@ bool itemproperties_panel::render_group()
|
||||
return true;
|
||||
}
|
||||
|
||||
brush_object_list::brush_object_list(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen)
|
||||
{
|
||||
size_min = {50, 100};
|
||||
size_max = {1000, 500};
|
||||
}
|
||||
|
||||
bool brush_object_list::VectorGetter(void *data, int idx, const char **out_text)
|
||||
{
|
||||
auto *vec = static_cast<std::vector<std::string> *>(data);
|
||||
|
||||
if (idx < 0 || idx >= vec->size())
|
||||
return false;
|
||||
|
||||
*out_text = (*vec)[idx].c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
void brush_object_list::update(std::string nodeTemplate)
|
||||
{
|
||||
Template = nodeTemplate;
|
||||
}
|
||||
std::string *brush_object_list::GetRandomObject()
|
||||
{
|
||||
static std::string empty; // fallback
|
||||
@@ -408,39 +407,25 @@ std::string *brush_object_list::GetRandomObject()
|
||||
return &Objects[dist(Global.local_random_engine)];
|
||||
}
|
||||
|
||||
void brush_object_list::render()
|
||||
void brush_object_list::render_options(nodebank_panel &Bank)
|
||||
{
|
||||
if (false == is_open)
|
||||
ImGui::SliderFloat("Spacing", &spacing, 0.1f, 20.0f, "%.1f m");
|
||||
ImGui::Checkbox("Random model from set", &useRandom);
|
||||
if (false == useRandom)
|
||||
{
|
||||
ImGui::TextDisabled("Paints the template selected in the node bank");
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse | (size.x > 0 ? ImGuiWindowFlags_NoResize : 0);
|
||||
if (ImGui::Begin("Brush random set", nullptr, flags))
|
||||
Bank.set_combo("Preset", source, "Manual list");
|
||||
if (source.kind == model_set_ref::source::manual)
|
||||
{
|
||||
ImGui::SliderFloat("Spacing", &spacing, 0.1f, 20.0f, "%.1f m");
|
||||
ImGui::Checkbox("Enable brush random from set", &useRandom);
|
||||
if (useRandom)
|
||||
{
|
||||
ImGui::Text("Set of objects to choose from:");
|
||||
ImGui::ListBox("", &idx, VectorGetter, (void *)&Objects, Objects.size(), 6);
|
||||
if (ImGui::Button("Add"))
|
||||
{
|
||||
if (!Template.empty())
|
||||
Objects.push_back(Template);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remove") && idx >= 0 && idx < Objects.size())
|
||||
{
|
||||
Objects.erase(Objects.begin() + idx);
|
||||
}
|
||||
if (ImGui::Button("Remove all"))
|
||||
{
|
||||
Objects.clear();
|
||||
}
|
||||
}
|
||||
Bank.manual_list("brushset", Objects, idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const count{Bank.set_entries(source).size()};
|
||||
ImGui::TextDisabled("%zu templates in set%s", count, count == 0 ? ", the node bank selection is used" : "");
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
nodebank_panel::nodebank_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen)
|
||||
@@ -518,6 +503,11 @@ void nodebank_panel::render()
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2S(size.x, size.y));
|
||||
}
|
||||
else
|
||||
{
|
||||
// mode settings are drawn above the list, so start with a window tall enough for both
|
||||
ImGui::SetNextWindowSize(ImVec2S(440, 640), ImGuiCond_FirstUseEver);
|
||||
}
|
||||
if (size_min.x > 0)
|
||||
{
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2S(size_min.x, size_min.y), ImVec2S(size_max.x, size_max.y));
|
||||
@@ -526,30 +516,47 @@ void nodebank_panel::render()
|
||||
|
||||
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
|
||||
{
|
||||
|
||||
ImGui::RadioButton("Modify node", (int *)&mode, MODIFY);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Insert from bank", (int *)&mode, ADD);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Brush mode", (int *)&mode, BRUSH);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Copy to bank", (int *)&mode, COPY);
|
||||
ImGui::SameLine();
|
||||
ImGui::RadioButton("Area fill", (int *)&mode, FILL);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reload Nodebank"))
|
||||
if (ImGui::Button("Reload node bank"))
|
||||
{
|
||||
nodebank_reload();
|
||||
}
|
||||
|
||||
if (mode == BRUSH)
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(m_setsopen ? "Close model sets" : "Model sets..."))
|
||||
{
|
||||
// ImGui::SliderFloat("Spacing", &spacing, 0.1f, 20.0f, "%.1f m");
|
||||
m_setsopen = !m_setsopen;
|
||||
}
|
||||
|
||||
if (header_sections)
|
||||
{
|
||||
header_sections();
|
||||
}
|
||||
|
||||
// edit modes as tabs, each with its own settings
|
||||
std::pair<char const *, edit_mode> const modes[] = {{"Select", MODIFY}, {"Insert", ADD}, {"Brush", BRUSH}, {"Area fill", FILL}, {"Copy to bank", COPY}};
|
||||
if (ImGui::BeginTabBar("##editmodes", ImGuiTabBarFlags_FittingPolicyResizeDown))
|
||||
{
|
||||
for (auto const &tab : modes)
|
||||
{
|
||||
if (false == ImGui::BeginTabItem(tab.first))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
mode = tab.second;
|
||||
if (mode_options)
|
||||
{
|
||||
mode_options(mode);
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushItemWidth(-1);
|
||||
ImGui::InputTextWithHint("Search", "Search node bank", m_nodesearch, IM_ARRAYSIZE(m_nodesearch));
|
||||
if (ImGui::ListBoxHeader("##nodebank", ImVec2(-1, -1)))
|
||||
// the list takes the rest of the window, but keeps a usable height when the sections above are expanded (the window scrolls then)
|
||||
auto const listheight{std::max(ImGui::GetContentRegionAvail().y, ImGui::GetTextLineHeightWithSpacing() * 10.0f)};
|
||||
if (ImGui::ListBoxHeader("##nodebank", ImVec2(-1, listheight)))
|
||||
{
|
||||
auto idx{0};
|
||||
auto isvisible{false};
|
||||
@@ -572,16 +579,366 @@ void nodebank_panel::render()
|
||||
continue;
|
||||
}
|
||||
auto const label{" " + entry.first + "##" + std::to_string(idx)};
|
||||
if (ImGui::Selectable(label.c_str(), entry.second == m_selectedtemplate))
|
||||
if (list_item(label.c_str(), entry.second == m_selectedtemplate))
|
||||
m_selectedtemplate = entry.second;
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
ImGui::ListBoxFooter();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
|
||||
if (m_setsopen)
|
||||
{
|
||||
render_sets_window();
|
||||
}
|
||||
}
|
||||
|
||||
bool nodebank_panel::set_combo(char const *Label, model_set_ref &Ref, char const *Manuallabel)
|
||||
{
|
||||
auto changed{false};
|
||||
auto const preview{set_name(Ref, Manuallabel)};
|
||||
if (false == ImGui::BeginCombo(Label, preview.c_str()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Manuallabel != nullptr)
|
||||
{
|
||||
if (ImGui::Selectable(Manuallabel, Ref.kind == model_set_ref::source::manual))
|
||||
{
|
||||
Ref = model_set_ref{};
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled("User sets");
|
||||
auto const &sets{EditorModelSets.sets()};
|
||||
if (sets.empty())
|
||||
{
|
||||
ImGui::TextDisabled(" (none yet, see Model sets...)");
|
||||
}
|
||||
for (auto const &set : sets)
|
||||
{
|
||||
auto const label{" " + set.name + " (" + std::to_string(set.templates.size()) + ")##userset" + std::to_string(set.id)};
|
||||
if (ImGui::Selectable(label.c_str(), Ref.kind == model_set_ref::source::user && Ref.id == set.id))
|
||||
{
|
||||
Ref = {model_set_ref::source::user, set.id};
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::TextDisabled("Node bank groups");
|
||||
auto const groups{group_names()};
|
||||
for (int idx = 0; idx < static_cast<int>(groups.size()); ++idx)
|
||||
{
|
||||
auto const label{" " + groups[idx] + "##bankgroup" + std::to_string(idx)};
|
||||
if (ImGui::Selectable(label.c_str(), Ref.kind == model_set_ref::source::nodebank && Ref.id == idx))
|
||||
{
|
||||
Ref = {model_set_ref::source::nodebank, idx};
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
return changed;
|
||||
}
|
||||
|
||||
void nodebank_panel::manual_list(char const *Id, std::vector<std::string> &List, int &Selected)
|
||||
{
|
||||
ImGui::PushID(Id);
|
||||
if (ImGui::ListBoxHeader("##list", ImVec2(-1, ImGui::GetTextLineHeightWithSpacing() * 6.5f)))
|
||||
{
|
||||
for (int idx = 0; idx < static_cast<int>(List.size()); ++idx)
|
||||
{
|
||||
auto const label{generate_node_label(List[idx]) + "##" + std::to_string(idx)};
|
||||
if (list_item(label.c_str(), Selected == idx))
|
||||
Selected = idx;
|
||||
}
|
||||
if (List.empty())
|
||||
{
|
||||
ImGui::TextDisabled("(empty)");
|
||||
}
|
||||
ImGui::ListBoxFooter();
|
||||
}
|
||||
if (ImGui::Button("Add selected"))
|
||||
{
|
||||
if (m_selectedtemplate && false == m_selectedtemplate->empty())
|
||||
List.push_back(*m_selectedtemplate);
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Adds the template selected in the node bank list");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remove") && Selected >= 0 && Selected < static_cast<int>(List.size()))
|
||||
{
|
||||
List.erase(List.begin() + Selected);
|
||||
Selected = -1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear"))
|
||||
{
|
||||
List.clear();
|
||||
Selected = -1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Save as set") && false == List.empty())
|
||||
{
|
||||
open_sets_window(EditorModelSets.create("New set", List));
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
std::vector<std::string const *> nodebank_panel::set_entries(model_set_ref const &Ref) const
|
||||
{
|
||||
std::vector<std::string const *> entries;
|
||||
if (Ref.kind == model_set_ref::source::user)
|
||||
{
|
||||
if (auto const *set = EditorModelSets.find(Ref.id))
|
||||
{
|
||||
for (auto const &entry : set->templates)
|
||||
entries.push_back(&entry);
|
||||
}
|
||||
}
|
||||
else if (Ref.kind == model_set_ref::source::nodebank && Ref.id >= 0)
|
||||
{
|
||||
// same grouping as group_templates(), without copying the templates
|
||||
int group{0};
|
||||
bool started{false};
|
||||
for (auto const &entry : m_nodebank)
|
||||
{
|
||||
if (entry.second->empty())
|
||||
{
|
||||
if (started)
|
||||
++group;
|
||||
started = true;
|
||||
continue;
|
||||
}
|
||||
started = true;
|
||||
if (group == Ref.id)
|
||||
entries.push_back(entry.second.get());
|
||||
else if (group > Ref.id)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::string const *nodebank_panel::random_template(model_set_ref const &Ref) const
|
||||
{
|
||||
auto const entries{set_entries(Ref)};
|
||||
if (entries.empty())
|
||||
return nullptr;
|
||||
|
||||
std::uniform_int_distribution<std::size_t> dist(0, entries.size() - 1);
|
||||
return entries[dist(Global.local_random_engine)];
|
||||
}
|
||||
|
||||
std::string nodebank_panel::set_name(model_set_ref const &Ref, char const *Manuallabel) const
|
||||
{
|
||||
switch (Ref.kind)
|
||||
{
|
||||
case model_set_ref::source::user:
|
||||
{
|
||||
auto const *set = EditorModelSets.find(Ref.id);
|
||||
return set != nullptr ? set->name : "(deleted set)";
|
||||
}
|
||||
case model_set_ref::source::nodebank:
|
||||
{
|
||||
auto const groups{group_names()};
|
||||
return Ref.id >= 0 && Ref.id < static_cast<int>(groups.size()) ? "Node bank: " + groups[Ref.id] : "(missing node bank group)";
|
||||
}
|
||||
default:
|
||||
return Manuallabel != nullptr ? Manuallabel : "(none)";
|
||||
}
|
||||
}
|
||||
|
||||
void nodebank_panel::open_sets_window(int const Setid)
|
||||
{
|
||||
m_setsopen = true;
|
||||
if (Setid != 0)
|
||||
{
|
||||
m_setsselected = Setid;
|
||||
m_setsentry = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void nodebank_panel::render_sets_window()
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2S(600, 380), ImGuiCond_FirstUseEver);
|
||||
if (false == ImGui::Begin("Model sets", &m_setsopen, ImGuiWindowFlags_NoCollapse))
|
||||
{
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
auto const &sets{EditorModelSets.sets()};
|
||||
if (EditorModelSets.find(m_setsselected) == nullptr)
|
||||
{
|
||||
m_setsselected = sets.empty() ? 0 : sets.front().id;
|
||||
m_setsentry = -1;
|
||||
}
|
||||
|
||||
// left side: the sets
|
||||
auto const buttonsheight{ImGui::GetFrameHeightWithSpacing()};
|
||||
ImGui::BeginGroup();
|
||||
ImGui::BeginChild("##setlist", ImVec2(190 * Global.ui_scale, -buttonsheight), true);
|
||||
for (auto const &set : sets)
|
||||
{
|
||||
auto const label{set.name + " (" + std::to_string(set.templates.size()) + ")##set" + std::to_string(set.id)};
|
||||
if (list_item(label.c_str(), set.id == m_setsselected))
|
||||
{
|
||||
m_setsselected = set.id;
|
||||
m_setsentry = -1;
|
||||
}
|
||||
}
|
||||
if (sets.empty())
|
||||
{
|
||||
ImGui::TextDisabled("No sets yet");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
if (ImGui::Button("New"))
|
||||
{
|
||||
open_sets_window(EditorModelSets.create("New set"));
|
||||
}
|
||||
auto const *edited{EditorModelSets.find(m_setsselected)};
|
||||
if (edited != nullptr)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Duplicate"))
|
||||
{
|
||||
open_sets_window(EditorModelSets.create(edited->name + " copy", edited->templates));
|
||||
edited = EditorModelSets.find(m_setsselected);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete"))
|
||||
{
|
||||
ImGui::OpenPopup("##deleteset");
|
||||
}
|
||||
if (ImGui::BeginPopup("##deleteset"))
|
||||
{
|
||||
ImGui::Text("Delete set \"%s\"?", edited->name.c_str());
|
||||
if (ImGui::Button("Delete##confirm"))
|
||||
{
|
||||
EditorModelSets.remove(edited->id);
|
||||
edited = nullptr;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel"))
|
||||
{
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// right side: content of the selected set
|
||||
ImGui::BeginGroup();
|
||||
if (edited == nullptr)
|
||||
{
|
||||
ImGui::TextDisabled("Create a set with \"New\", or use \"Save as set\" on a brush or area fill list.");
|
||||
ImGui::TextDisabled("Sets can be chosen in Insert (random model), Brush and Area fill.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_setsnameid != edited->id)
|
||||
{
|
||||
// selection changed, load its name into the edit buffer
|
||||
std::snprintf(m_setsname, sizeof(m_setsname), "%s", edited->name.c_str());
|
||||
m_setsnameid = edited->id;
|
||||
}
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Rename").x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x);
|
||||
auto const entered{ImGui::InputText("##setname", m_setsname, IM_ARRAYSIZE(m_setsname), ImGuiInputTextFlags_EnterReturnsTrue)};
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Rename") || entered)
|
||||
{
|
||||
EditorModelSets.rename(edited->id, m_setsname);
|
||||
m_setsnameid = 0; // reload, the name could have been adjusted to stay unique
|
||||
}
|
||||
|
||||
ImGui::BeginChild("##setentries", ImVec2(0, -buttonsheight * 2), true);
|
||||
for (int idx = 0; idx < static_cast<int>(edited->labels.size()); ++idx)
|
||||
{
|
||||
auto const label{edited->labels[idx] + "##entry" + std::to_string(idx)};
|
||||
if (list_item(label.c_str(), idx == m_setsentry))
|
||||
m_setsentry = idx;
|
||||
}
|
||||
if (edited->templates.empty())
|
||||
{
|
||||
ImGui::TextDisabled("(empty) select a template in the node bank and press \"Add selected\"");
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
auto const setid{edited->id};
|
||||
if (ImGui::Button("Add selected"))
|
||||
{
|
||||
if (m_selectedtemplate && false == m_selectedtemplate->empty())
|
||||
EditorModelSets.add(setid, {*m_selectedtemplate});
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
ImGui::SetTooltip("Adds the template selected in the node bank list");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remove") && m_setsentry >= 0)
|
||||
{
|
||||
EditorModelSets.erase(setid, static_cast<std::size_t>(m_setsentry));
|
||||
m_setsentry = -1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Clear"))
|
||||
{
|
||||
ImGui::OpenPopup("##clearset");
|
||||
}
|
||||
if (ImGui::BeginPopup("##clearset"))
|
||||
{
|
||||
ImGui::TextUnformatted("Remove all templates from this set?");
|
||||
if (ImGui::Button("Clear##confirm"))
|
||||
{
|
||||
EditorModelSets.clear(setid);
|
||||
m_setsentry = -1;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel"))
|
||||
{
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// whole node bank group at once
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Add group").x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x);
|
||||
auto const groups{group_names()};
|
||||
m_setsgroup.id = groups.empty() ? -1 : std::clamp(m_setsgroup.id, 0, static_cast<int>(groups.size()) - 1);
|
||||
if (ImGui::BeginCombo("##addgroup", m_setsgroup.id >= 0 ? groups[m_setsgroup.id].c_str() : "(no node bank groups)"))
|
||||
{
|
||||
for (int idx = 0; idx < static_cast<int>(groups.size()); ++idx)
|
||||
{
|
||||
auto const label{groups[idx] + "##addgroup" + std::to_string(idx)};
|
||||
if (ImGui::Selectable(label.c_str(), idx == m_setsgroup.id))
|
||||
m_setsgroup.id = idx;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Add group"))
|
||||
{
|
||||
std::vector<std::string> templates;
|
||||
for (auto const *entry : set_entries(m_setsgroup))
|
||||
templates.push_back(*entry);
|
||||
EditorModelSets.add(setid, templates);
|
||||
}
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void nodebank_panel::add_template(const std::string &desc)
|
||||
@@ -691,15 +1048,7 @@ void functions_panel::render()
|
||||
if (true == ImGui::Begin(panelname.c_str(), nullptr, flags))
|
||||
{
|
||||
// header section
|
||||
|
||||
ImGui::RadioButton("Random rotation", (int *)&rot_mode, RANDOM);
|
||||
ImGui::RadioButton("Fixed rotation", (int *)&rot_mode, FIXED);
|
||||
if (rot_mode == FIXED)
|
||||
{
|
||||
// ImGui::Checkbox("Get rotation from last object", &rot_from_last);
|
||||
ImGui::SliderFloat("Rotation Value", &rot_value, 0.0f, 360.0f, "%.1f");
|
||||
};
|
||||
ImGui::RadioButton("Default rotation", (int *)&rot_mode, DEFAULT);
|
||||
render_controls();
|
||||
for (auto const &line : text_lines)
|
||||
{
|
||||
ImGui::TextColored(ImVec4(line.color.r, line.color.g, line.color.b, line.color.a), line.data.c_str());
|
||||
@@ -707,3 +1056,19 @@ void functions_panel::render()
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void functions_panel::render_controls()
|
||||
{
|
||||
// order matches rotation_mode
|
||||
char const *const modes[] = {"Random", "Fixed", "Default (from template)"};
|
||||
auto current{static_cast<int>(rot_mode)};
|
||||
if (ImGui::Combo("Rotation", ¤t, modes, IM_ARRAYSIZE(modes)))
|
||||
{
|
||||
rot_mode = static_cast<rotation_mode>(current);
|
||||
}
|
||||
if (rot_mode == FIXED)
|
||||
{
|
||||
// ImGui::Checkbox("Get rotation from last object", &rot_from_last);
|
||||
ImGui::SliderFloat("Rotation value", &rot_value, 0.0f, 360.0f, "%.1f deg");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "application/uilayer.h"
|
||||
#include "utilities/Classes.h"
|
||||
|
||||
#include <functional>
|
||||
/*
|
||||
// helper, associated bool is set when the primary value was changed and expects processing at the observer's leisure
|
||||
template<typename Type_>
|
||||
@@ -37,6 +39,8 @@ class itemproperties_panel : public ui_panel
|
||||
// are still treated as read-only.
|
||||
void update(scene::basic_node *Node);
|
||||
void render() override;
|
||||
// panel content without the window, also drawn inside the toolset window
|
||||
void render_body();
|
||||
|
||||
private:
|
||||
// methods
|
||||
@@ -53,22 +57,35 @@ class itemproperties_panel : public ui_panel
|
||||
std::vector<text_line> m_grouplines;
|
||||
};
|
||||
|
||||
class brush_object_list : public ui_panel
|
||||
// reference to a set of node templates, used by the random insert, the brush and the area fill
|
||||
struct model_set_ref
|
||||
{
|
||||
enum class source
|
||||
{
|
||||
manual, // list assembled by hand in the tool itself (or no set, where the tool has no such list)
|
||||
user, // user-defined set (see EditorModelSets), id == set id
|
||||
nodebank // node bank group, id == group index (as returned by nodebank_panel::group_names())
|
||||
};
|
||||
source kind{source::manual};
|
||||
int id{-1};
|
||||
};
|
||||
|
||||
class nodebank_panel;
|
||||
|
||||
// brush settings, drawn inside the node bank window when the brush mode is active
|
||||
class brush_object_list
|
||||
{
|
||||
private:
|
||||
int idx;
|
||||
static bool VectorGetter(void *data, int idx, const char **out_text);
|
||||
std::string Template;
|
||||
int idx{-1};
|
||||
|
||||
public:
|
||||
brush_object_list(std::string const &Name, bool const Isopen);
|
||||
void render() override;
|
||||
void update(std::string nodeTemplate);
|
||||
void render_options(nodebank_panel &Bank);
|
||||
|
||||
// class use
|
||||
std::vector<std::string> Objects;
|
||||
std::vector<std::string> Objects; // manual list, used when source is set to manual
|
||||
std::string *GetRandomObject();
|
||||
bool useRandom = {false};
|
||||
model_set_ref source;
|
||||
float spacing{1.0f};
|
||||
};
|
||||
|
||||
@@ -96,13 +113,39 @@ class nodebank_panel : public ui_panel
|
||||
// node templates of the group with specified index (as returned by group_names())
|
||||
std::vector<std::string> group_templates(std::size_t const Group) const;
|
||||
|
||||
// model sets (user-defined sets and node bank groups)
|
||||
// combo selecting a set. Manuallabel names the tool's own hand-made list; nullptr hides that choice. returns: true on change
|
||||
bool set_combo(char const *Label, model_set_ref &Ref, char const *Manuallabel);
|
||||
// list of templates assembled by hand, with buttons to add the node bank selection, remove entries and save it as a user set
|
||||
void manual_list(char const *Id, std::vector<std::string> &List, int &Selected);
|
||||
// node templates of referenced set; empty for the manual list or a set which no longer exists
|
||||
std::vector<std::string const *> set_entries(model_set_ref const &Ref) const;
|
||||
// random template from referenced set, or nullptr if the set is empty
|
||||
std::string const *random_template(model_set_ref const &Ref) const;
|
||||
std::string set_name(model_set_ref const &Ref, char const *Manuallabel) const;
|
||||
// shows the set manager window, optionally with specified user set selected
|
||||
void open_sets_window(int const Setid = 0);
|
||||
|
||||
// draws settings of the active edit mode inside its tab
|
||||
std::function<void(edit_mode)> mode_options;
|
||||
// draws sections attached above the mode tabs (gizmo, node properties)
|
||||
std::function<void()> header_sections;
|
||||
|
||||
private:
|
||||
// methods:
|
||||
std::string generate_node_label(std::string Input) const;
|
||||
void render_sets_window();
|
||||
// members:
|
||||
std::vector<std::pair<std::string, std::shared_ptr<std::string>>> m_nodebank;
|
||||
char m_nodesearch[128];
|
||||
std::shared_ptr<std::string> m_selectedtemplate;
|
||||
// set manager window
|
||||
bool m_setsopen{false};
|
||||
int m_setsselected{0}; // id of the user set being edited
|
||||
int m_setsentry{-1}; // selected template of the edited set
|
||||
int m_setsnameid{0}; // id of the set whose name is held in the edit buffer
|
||||
char m_setsname[128]{};
|
||||
model_set_ref m_setsgroup{model_set_ref::source::nodebank, 0}; // node bank group to append to the edited set
|
||||
};
|
||||
|
||||
class functions_panel : public ui_panel
|
||||
@@ -124,6 +167,8 @@ class functions_panel : public ui_panel
|
||||
|
||||
void update(scene::basic_node const *Node);
|
||||
void render() override;
|
||||
// rotation settings widgets, also drawn inside the node bank window
|
||||
void render_controls();
|
||||
|
||||
private:
|
||||
// methods
|
||||
|
||||
@@ -130,67 +130,89 @@ bool ui_layer::mouse_button_callback(int button, int action, int mods)
|
||||
return m_imguiio->WantCaptureMouse;
|
||||
}
|
||||
|
||||
static ImVec4 imvec_lerp(const ImVec4 &a, const ImVec4 &b, float t)
|
||||
{
|
||||
return ImVec4(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t, a.w + (b.w - a.w) * t);
|
||||
}
|
||||
|
||||
void ui_layer::imgui_style()
|
||||
{
|
||||
// palette shared with the MaSzyna starter: flat dark panels, thin borders, green accents
|
||||
auto const rgb = [](int const Color, float const Alpha = 1.0f) {
|
||||
return ImVec4(((Color >> 16) & 0xff) / 255.0f, ((Color >> 8) & 0xff) / 255.0f, (Color & 0xff) / 255.0f, Alpha);
|
||||
};
|
||||
int constexpr panel{0x1e2327}; // window background
|
||||
int constexpr base{0x15171b}; // darker background, between panels
|
||||
int constexpr header{0x2a3036}; // section headers, hovered items
|
||||
int constexpr border{0x3a424a};
|
||||
int constexpr field{0x121517}; // input fields, combos
|
||||
int constexpr accent{0x177f00}; // selection, active elements
|
||||
int constexpr accentbright{0x41c400}; // marks: checkmarks, active tab underline
|
||||
int constexpr accentdim{0x164b0e};
|
||||
|
||||
ImVec4 *colors = ImGui::GetStyle().Colors;
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.04f, 0.04f, 0.04f, Global.UIBgOpacity); // ui.bg.opacity from config file
|
||||
colors[ImGuiCol_Text] = rgb(0xffffff);
|
||||
colors[ImGuiCol_TextDisabled] = rgb(0xa0a0a2);
|
||||
colors[ImGuiCol_WindowBg] = rgb(panel, Global.UIBgOpacity); // ui.bg.opacity from config file
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.31f, 0.34f, 0.31f, 0.50f);
|
||||
colors[ImGuiCol_PopupBg] = rgb(base, 0.98f);
|
||||
colors[ImGuiCol_Border] = rgb(border);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.21f, 0.28f, 0.17f, 0.54f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.40f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.64f, 0.23f, 0.67f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.03f, 0.03f, 0.03f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.21f, 0.28f, 0.17f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.01f, 0.01f, 0.01f, 0.53f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.29f, 0.29f, 0.29f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.37f, 0.53f, 0.25f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.42f, 0.64f, 0.23f, 0.40f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.37f, 0.54f, 0.19f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.42f, 0.64f, 0.23f, 0.31f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.80f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.42f, 0.64f, 0.23f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.29f, 0.41f, 0.18f, 0.78f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.29f, 0.41f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.42f, 0.64f, 0.23f, 0.25f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.42f, 0.64f, 0.23f, 0.67f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.42f, 0.64f, 0.23f, 0.95f);
|
||||
colors[ImGuiCol_FrameBg] = rgb(field);
|
||||
colors[ImGuiCol_FrameBgHovered] = rgb(header);
|
||||
colors[ImGuiCol_FrameBgActive] = rgb(border);
|
||||
colors[ImGuiCol_TitleBg] = rgb(base);
|
||||
colors[ImGuiCol_TitleBgActive] = rgb(header);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = rgb(base, 0.75f);
|
||||
colors[ImGuiCol_MenuBarBg] = rgb(panel);
|
||||
colors[ImGuiCol_ScrollbarBg] = rgb(base, 0.60f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = rgb(border);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = rgb(0x5a636c);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = rgb(0x858585);
|
||||
colors[ImGuiCol_CheckMark] = rgb(accentbright);
|
||||
colors[ImGuiCol_SliderGrab] = rgb(accent);
|
||||
colors[ImGuiCol_SliderGrabActive] = rgb(accentbright);
|
||||
colors[ImGuiCol_Button] = rgb(header);
|
||||
colors[ImGuiCol_ButtonHovered] = rgb(border);
|
||||
colors[ImGuiCol_ButtonActive] = rgb(accent);
|
||||
// shared by section headers and selected list items
|
||||
colors[ImGuiCol_Header] = rgb(header);
|
||||
colors[ImGuiCol_HeaderHovered] = rgb(accentdim);
|
||||
colors[ImGuiCol_HeaderActive] = rgb(accent);
|
||||
colors[ImGuiCol_Separator] = rgb(border);
|
||||
colors[ImGuiCol_SeparatorHovered] = rgb(accent);
|
||||
colors[ImGuiCol_SeparatorActive] = rgb(accentbright);
|
||||
colors[ImGuiCol_ResizeGrip] = rgb(border, 0.50f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = rgb(accent);
|
||||
colors[ImGuiCol_ResizeGripActive] = rgb(accentbright);
|
||||
colors[ImGuiCol_Tab] = rgb(header);
|
||||
colors[ImGuiCol_TabHovered] = rgb(border);
|
||||
colors[ImGuiCol_TabActive] = rgb(accent); // also the line under the tab bar
|
||||
colors[ImGuiCol_TabUnfocused] = rgb(header);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = rgb(accentdim);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
|
||||
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.42f, 0.64f, 0.23f, 0.35f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
|
||||
colors[ImGuiCol_TextSelectedBg] = rgb(accent, 0.60f);
|
||||
colors[ImGuiCol_DragDropTarget] = rgb(accentbright, 0.90f);
|
||||
colors[ImGuiCol_NavHighlight] = rgb(accentbright);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = rgb(base, 0.60f);
|
||||
|
||||
colors[ImGuiCol_Separator] = colors[ImGuiCol_Border];
|
||||
colors[ImGuiCol_Tab] = imvec_lerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f);
|
||||
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered];
|
||||
colors[ImGuiCol_TabActive] = imvec_lerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f);
|
||||
colors[ImGuiCol_TabUnfocused] = imvec_lerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f);
|
||||
colors[ImGuiCol_TabUnfocusedActive] = imvec_lerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f);
|
||||
// flat, square elements with thin borders
|
||||
auto &style = ImGui::GetStyle();
|
||||
style.WindowRounding = 0.0f;
|
||||
style.ChildRounding = 0.0f;
|
||||
style.FrameRounding = 0.0f;
|
||||
style.PopupRounding = 0.0f;
|
||||
style.ScrollbarRounding = 0.0f;
|
||||
style.GrabRounding = 0.0f;
|
||||
style.TabRounding = 0.0f;
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.ChildBorderSize = 1.0f;
|
||||
style.PopupBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.TabBorderSize = 0.0f;
|
||||
|
||||
ImGui::GetStyle().ScaleAllSizes(Global.ui_scale);
|
||||
style.ScaleAllSizes(Global.ui_scale);
|
||||
}
|
||||
|
||||
bool ui_layer::init(GLFWwindow *Window)
|
||||
|
||||
235
editor/editorModelSets.cpp
Normal file
235
editor/editorModelSets.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorModelSets.hpp"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/parser.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
editorModelSets EditorModelSets;
|
||||
|
||||
// file layout:
|
||||
// set <name>
|
||||
// <node template, single line>
|
||||
// ...
|
||||
// endset
|
||||
namespace
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fs::path sets_path()
|
||||
{
|
||||
fs::path p = user_config_path("eu07_editor_sets.txt");
|
||||
return p.empty() ? fs::path("eu07_editor_sets.txt") : p;
|
||||
}
|
||||
|
||||
std::string trimmed(std::string Text)
|
||||
{
|
||||
auto const first = Text.find_first_not_of(" \t");
|
||||
if (first == std::string::npos)
|
||||
return {};
|
||||
auto const last = Text.find_last_not_of(" \t");
|
||||
return Text.substr(first, last - first + 1);
|
||||
}
|
||||
|
||||
// the file stores one template per line, so line breaks inside a template (e.g. from export_as_text) become plain separators
|
||||
std::string single_line(std::string Text)
|
||||
{
|
||||
std::replace_if(Text.begin(), Text.end(), [](char const Character) { return Character == '\r' || Character == '\n' || Character == '\t'; }, ' ');
|
||||
return trimmed(Text);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool editorModelSets::load()
|
||||
{
|
||||
m_sets.clear();
|
||||
|
||||
const fs::path path = sets_path();
|
||||
std::error_code ec;
|
||||
if (!fs::exists(path, ec))
|
||||
return false;
|
||||
|
||||
std::ifstream stream(path, std::ios_base::in | std::ios_base::binary);
|
||||
if (!stream.is_open())
|
||||
return false;
|
||||
|
||||
editor_model_set *current{nullptr};
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
line = single_line(line);
|
||||
if (line.empty() || line.rfind("//", 0) == 0)
|
||||
continue;
|
||||
|
||||
// a set header also closes a set with missing "endset"; templates always start with "node"
|
||||
if (line.rfind("set", 0) == 0 && (line.size() == 3 || line[3] == ' '))
|
||||
{
|
||||
editor_model_set set;
|
||||
set.id = m_nextid++;
|
||||
set.name = unique_name(line.substr(3));
|
||||
m_sets.push_back(std::move(set));
|
||||
current = &m_sets.back();
|
||||
continue;
|
||||
}
|
||||
if (line == "endset")
|
||||
{
|
||||
current = nullptr;
|
||||
continue;
|
||||
}
|
||||
if (current == nullptr)
|
||||
continue;
|
||||
current->labels.push_back(label(line));
|
||||
current->templates.push_back(std::move(line));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editorModelSets::save() const
|
||||
{
|
||||
const fs::path path = sets_path();
|
||||
std::error_code ec;
|
||||
fs::create_directories(path.parent_path(), ec);
|
||||
|
||||
std::ofstream stream(path, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary);
|
||||
if (!stream.is_open())
|
||||
{
|
||||
ErrorLog("failed to save editor model sets");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream << "// MaSzyna editor: user-defined model sets\n";
|
||||
for (auto const &set : m_sets)
|
||||
{
|
||||
stream << "\nset " << set.name << "\n";
|
||||
for (auto const &entry : set.templates)
|
||||
stream << entry << "\n";
|
||||
stream << "endset\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
editor_model_set const *editorModelSets::find(int const Id) const
|
||||
{
|
||||
auto const lookup = std::find_if(m_sets.begin(), m_sets.end(), [Id](auto const &Set) { return Set.id == Id; });
|
||||
return lookup != m_sets.end() ? &(*lookup) : nullptr;
|
||||
}
|
||||
|
||||
editor_model_set *editorModelSets::find_(int const Id)
|
||||
{
|
||||
auto const lookup = std::find_if(m_sets.begin(), m_sets.end(), [Id](auto const &Set) { return Set.id == Id; });
|
||||
return lookup != m_sets.end() ? &(*lookup) : nullptr;
|
||||
}
|
||||
|
||||
int editorModelSets::create(std::string const &Name, std::vector<std::string> const &Templates)
|
||||
{
|
||||
editor_model_set set;
|
||||
set.id = m_nextid++;
|
||||
set.name = unique_name(Name);
|
||||
for (auto const &entry : Templates)
|
||||
{
|
||||
auto line = single_line(entry);
|
||||
if (line.empty())
|
||||
continue;
|
||||
set.labels.push_back(label(line));
|
||||
set.templates.push_back(std::move(line));
|
||||
}
|
||||
m_sets.push_back(std::move(set));
|
||||
save();
|
||||
return m_sets.back().id;
|
||||
}
|
||||
|
||||
void editorModelSets::remove(int const Id)
|
||||
{
|
||||
auto const lookup = std::find_if(m_sets.begin(), m_sets.end(), [Id](auto const &Set) { return Set.id == Id; });
|
||||
if (lookup == m_sets.end())
|
||||
return;
|
||||
m_sets.erase(lookup);
|
||||
save();
|
||||
}
|
||||
|
||||
void editorModelSets::rename(int const Id, std::string const &Name)
|
||||
{
|
||||
auto *set = find_(Id);
|
||||
if (set == nullptr)
|
||||
return;
|
||||
auto const name = unique_name(Name, Id);
|
||||
if (name == set->name)
|
||||
return;
|
||||
set->name = name;
|
||||
save();
|
||||
}
|
||||
|
||||
void editorModelSets::add(int const Id, std::vector<std::string> const &Templates)
|
||||
{
|
||||
auto *set = find_(Id);
|
||||
if (set == nullptr)
|
||||
return;
|
||||
auto const count{set->templates.size()};
|
||||
for (auto const &entry : Templates)
|
||||
{
|
||||
auto line = single_line(entry);
|
||||
if (line.empty())
|
||||
continue;
|
||||
set->labels.push_back(label(line));
|
||||
set->templates.push_back(std::move(line));
|
||||
}
|
||||
if (set->templates.size() != count)
|
||||
save();
|
||||
}
|
||||
|
||||
void editorModelSets::erase(int const Id, std::size_t const Index)
|
||||
{
|
||||
auto *set = find_(Id);
|
||||
if (set == nullptr || Index >= set->templates.size())
|
||||
return;
|
||||
set->templates.erase(set->templates.begin() + Index);
|
||||
set->labels.erase(set->labels.begin() + Index);
|
||||
save();
|
||||
}
|
||||
|
||||
void editorModelSets::clear(int const Id)
|
||||
{
|
||||
auto *set = find_(Id);
|
||||
if (set == nullptr || set->templates.empty())
|
||||
return;
|
||||
set->templates.clear();
|
||||
set->labels.clear();
|
||||
save();
|
||||
}
|
||||
|
||||
std::string editorModelSets::unique_name(std::string const &Base, int const IgnoreId) const
|
||||
{
|
||||
auto base = single_line(Base);
|
||||
if (base.empty())
|
||||
base = "New set";
|
||||
auto const taken = [&](std::string const &Name) {
|
||||
return std::any_of(m_sets.begin(), m_sets.end(), [&](auto const &Set) { return Set.id != IgnoreId && Set.name == Name; });
|
||||
};
|
||||
auto name = base;
|
||||
for (int suffix = 2; taken(name); ++suffix)
|
||||
name = base + " " + std::to_string(suffix);
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string editorModelSets::label(std::string const &Template)
|
||||
{
|
||||
cParser tokenizer(Template);
|
||||
tokenizer.getTokens(9, false); // node, ranges, name, type, position, rotation
|
||||
auto model{tokenizer.getToken<std::string>(false)};
|
||||
auto texture{tokenizer.getToken<std::string>(false)};
|
||||
replace_slashes(model);
|
||||
erase_extension(model);
|
||||
replace_slashes(texture);
|
||||
return texture == "none" || texture.empty() ? model : model + " (" + texture + ")";
|
||||
}
|
||||
56
editor/editorModelSets.hpp
Normal file
56
editor/editorModelSets.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// user-defined sets of node templates (e.g. "deciduous trees", "bushes"), used by the editor brush,
|
||||
// random insert and area fill. stored next to the user's ini files
|
||||
struct editor_model_set
|
||||
{
|
||||
int id{0}; // runtime identifier, stable while the editor runs (renames don't break references)
|
||||
std::string name;
|
||||
std::vector<std::string> templates; // node definitions, one per entry, same format as nodebank entries
|
||||
std::vector<std::string> labels; // short readable names of the templates, cached for the UI
|
||||
};
|
||||
|
||||
class editorModelSets
|
||||
{
|
||||
public:
|
||||
bool load();
|
||||
bool save() const;
|
||||
|
||||
std::vector<editor_model_set> const &sets() const
|
||||
{
|
||||
return m_sets;
|
||||
}
|
||||
editor_model_set const *find(int Id) const;
|
||||
// all modifying methods save the sets right away
|
||||
// returns: id of the new set
|
||||
int create(std::string const &Name, std::vector<std::string> const &Templates = {});
|
||||
void remove(int Id);
|
||||
void rename(int Id, std::string const &Name);
|
||||
void add(int Id, std::vector<std::string> const &Templates);
|
||||
void erase(int Id, std::size_t Index);
|
||||
void clear(int Id);
|
||||
// name not used by any other set, built from specified base
|
||||
std::string unique_name(std::string const &Base, int IgnoreId = 0) const;
|
||||
// short readable name of a node template (model file and texture)
|
||||
static std::string label(std::string const &Template);
|
||||
|
||||
private:
|
||||
editor_model_set *find_(int Id);
|
||||
|
||||
std::vector<editor_model_set> m_sets;
|
||||
int m_nextid{1};
|
||||
};
|
||||
|
||||
extern editorModelSets EditorModelSets;
|
||||
Reference in New Issue
Block a user