From 68b931c5213c667d79ebc952acf34432a291a934 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 11 Aug 2023 23:42:01 -0400 Subject: [PATCH 01/50] Galaxy: add EditorAPI privileged classes - Avoids needing to write additional setters unused by game code - EditorAPI classes defined and used only in editor module --- src/galaxy/StarSystem.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/galaxy/StarSystem.h b/src/galaxy/StarSystem.h index 5f2d255677e..93ded4fcab5 100644 --- a/src/galaxy/StarSystem.h +++ b/src/galaxy/StarSystem.h @@ -30,6 +30,7 @@ class StarSystem : public RefCounted { friend class SystemBody; friend class GalaxyObjectCache; class GeneratorAPI; // Complete definition below + class EditorAPI; // Defined in editor module enum ExplorationState { eUNEXPLORED = 0, @@ -165,9 +166,10 @@ class StarSystem : public RefCounted { class StarSystem::GeneratorAPI : public StarSystem { private: friend class GalaxyGenerator; - GeneratorAPI(const SystemPath &path, RefCountedPtr galaxy, StarSystemCache *cache, Random &rand); public: + GeneratorAPI(const SystemPath &path, RefCountedPtr galaxy, StarSystemCache *cache, Random &rand); + bool HasCustomBodies() const { return m_hasCustomBodies; } void SetCustom(bool isCustom, bool hasCustomBodies) From 14af1da9db36b4492e5d493e438e05b5adc0b532 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 11 Aug 2023 23:44:34 -0400 Subject: [PATCH 02/50] EditorDraw: add additional utilities - ColorEdit3 specialization for Color4ub - Reusable undo stack debug window - EnumStrings-based EditEnum combo-box with undo support --- src/editor/EditorDraw.cpp | 77 +++++++++++++++++++++++++++++++- src/editor/EditorDraw.h | 11 +++++ src/editor/ModelViewer.cpp | 10 ----- src/editor/ModelViewerWidget.cpp | 18 ++------ 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index fbf50ddf419..5e37a09af0d 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -2,10 +2,17 @@ // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "EditorDraw.h" -#include "UndoSystem.h" + +#include "Color.h" +#include "EnumStrings.h" + +#include "UndoStepType.h" +#include "editor/UndoSystem.h" #include "imgui/imgui.h" +#include "fmt/format.h" + using namespace Editor; ImRect Draw::RectCut(ImRect &orig, float amount, RectSide side) @@ -92,6 +99,45 @@ void Draw::EndLayout() ImGui::Spacing(); } +void Draw::ShowUndoDebugWindow(UndoSystem *undo, bool *p_open) +{ + if (!ImGui::Begin("Undo Stack", p_open, 0)) { + ImGui::End(); + return; + } + + ImGui::Text("Undo Depth: %ld", undo->GetEntryDepth()); + ImGui::Separator(); + + size_t numEntries = undo->GetNumEntries(); + size_t currentIdx = undo->GetCurrentEntry(); + size_t selectedIdx = currentIdx; + + if (ImGui::Selectable("", currentIdx == 0)) + selectedIdx = 0; + + for (size_t idx = 0; idx < numEntries; idx++) + { + const UndoEntry *entry = undo->GetEntry(idx); + + bool isSelected = currentIdx == idx + 1; + std::string label = fmt::format("{}##{}", entry->GetName(), idx); + + if (ImGui::Selectable(label.c_str(), isSelected)) + selectedIdx = idx + 1; + } + + ImGui::End(); + + // If we selected an earlier history entry, undo to that point + for (; currentIdx > selectedIdx; --currentIdx) + undo->Undo(); + + // If we selected a later history entry, redo to that point + for (; currentIdx < selectedIdx; ++currentIdx) + undo->Redo(); +} + bool Draw::UndoHelper(std::string_view label, UndoSystem *undo) { if (ImGui::IsItemDeactivated()) { @@ -135,6 +181,27 @@ bool Draw::ComboUndoHelper(std::string_view label, const char *preview, UndoSyst return ComboUndoHelper(label, label.data(), preview, undo); } +void Draw::EditEnum(std::string_view label, const char *name, const char *ns, int *val, size_t val_max, UndoSystem *undo) +{ + size_t selected = size_t(*val); + const char *preview = EnumStrings::GetString(ns, selected); + if (!preview) + preview = ""; + + if (ComboUndoHelper(label, name, preview, undo)) { + if (ImGui::IsWindowAppearing()) + AddUndoSingleValue(undo, val); + + for (size_t idx = 0; idx <= val_max; ++idx) { + const char *name = EnumStrings::GetString(ns, idx); + if (name && ImGui::Selectable(name, selected == idx)) + *val = int(idx); + } + + ImGui::EndCombo(); + } +} + bool Draw::MenuButton(const char *label) { ImVec2 screenPos = ImGui::GetCursorScreenPos(); @@ -167,3 +234,11 @@ bool Draw::ToggleButton(const char *label, bool *value, ImVec4 activeColor) return changed; } + +bool Draw::ColorEdit3(const char *label, Color *color) +{ + Color4f _c = color->ToColor4f(); + bool changed = ImGui::ColorEdit3(label, &_c[0]); + *color = Color(_c); + return changed; +} diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index f177be8a478..644211136c6 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -11,6 +11,8 @@ #include +struct Color4ub; + namespace Editor { class UndoSystem; } @@ -42,6 +44,9 @@ namespace Editor::Draw { // End a horizontal layout block void EndLayout(); + // Show a window to debug the state of the passed undo system + void ShowUndoDebugWindow(UndoSystem *undo, bool *p_open = nullptr); + // Manage pushing/popping an UndoEntry after an input widget that provides IsItemActivated() and IsItemDeactivated() // Note: this helper relies on the widget *not* changing the underlying value the frame IsItemActivated() is true bool UndoHelper(std::string_view label, UndoSystem *undo); @@ -53,12 +58,18 @@ namespace Editor::Draw { // The above, but defaulting the label to the entryName bool ComboUndoHelper(std::string_view label, const char *preview, UndoSystem *undo); + // Show an edit dialog box to chose a value from an enumeration + void EditEnum(std::string_view label, const char *name, const char *ns, int *val, size_t val_max, UndoSystem *undo); + // Simple button that summons a popup menu underneath it bool MenuButton(const char *label); // Simple on/off toggle button with a text label bool ToggleButton(const char *label, bool *value, ImVec4 activeColor); + // Color edit button + bool ColorEdit3(const char *label, Color4ub *color); + } inline bool operator==(const ImVec2 &a, const ImVec2 &b) diff --git a/src/editor/ModelViewer.cpp b/src/editor/ModelViewer.cpp index 47535e4a4d9..a424864d607 100644 --- a/src/editor/ModelViewer.cpp +++ b/src/editor/ModelViewer.cpp @@ -33,16 +33,6 @@ namespace { static constexpr const char *LOG_WND_NAME = "Log"; } -namespace ImGui { - bool ColorEdit3(const char *label, Color &color) - { - Color4f _c = color.ToColor4f(); - bool changed = ColorEdit3(label, &_c[0]); - color = Color(_c); - return changed; - } -} // namespace ImGui - ModelViewer::ModelViewer(EditorApp *app, LuaManager *lm) : m_app(app), m_input(app->GetInput()), diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index 575588ac05d..f95536082be 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -49,18 +49,6 @@ namespace { } } -namespace ImGui { - - static bool ColorEdit3(const char *label, Color &color) - { - Color4f _c = color.ToColor4f(); - bool changed = ColorEdit3(label, &_c[0]); - color = Color(_c); - return changed; - } - -} - // ─── Setup ─────────────────────────────────────────────────────────────────── ModelViewerWidget::ModelViewerWidget(EditorApp *app) : @@ -762,9 +750,9 @@ void ModelViewerWidget::DrawMenus() SetRandomColors(); bool valuesChanged = false; - valuesChanged |= ImGui::ColorEdit3("##Color 1", m_colors[0]); - valuesChanged |= ImGui::ColorEdit3("##Color 2", m_colors[1]); - valuesChanged |= ImGui::ColorEdit3("##Color 3", m_colors[2]); + valuesChanged |= Draw::ColorEdit3("##Color 1", &m_colors[0]); + valuesChanged |= Draw::ColorEdit3("##Color 2", &m_colors[1]); + valuesChanged |= Draw::ColorEdit3("##Color 3", &m_colors[2]); if (valuesChanged) m_model->SetColors(m_colors); From 40af1b8e55fc965f9ba170e6b53d55c22dee4646 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 11 Aug 2023 23:56:24 -0400 Subject: [PATCH 03/50] Add visual editor for custom system definitions Still in the early stages, can load and display custom system hierarchy and properties. Can consistently round-trip most auto-generated system definitions, though requires more work for modified systems. --- src/editor/CMakeLists.txt | 1 + src/editor/EditorApp.cpp | 20 +- src/editor/system/SystemEditor.cpp | 689 +++++++++++++++++++++++++++++ src/editor/system/SystemEditor.h | 66 +++ 4 files changed, 774 insertions(+), 2 deletions(-) create mode 100644 src/editor/system/SystemEditor.cpp create mode 100644 src/editor/system/SystemEditor.h diff --git a/src/editor/CMakeLists.txt b/src/editor/CMakeLists.txt index 729b15c668d..f068d729720 100644 --- a/src/editor/CMakeLists.txt +++ b/src/editor/CMakeLists.txt @@ -2,6 +2,7 @@ list(APPEND EDITOR_SRC_FOLDERS ${CMAKE_CURRENT_SOURCE_DIR} mfd/ + system/ ) # Creates variables EDITOR_CXX_FILES and EDITOR_HXX_FILES diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index de704e200f5..14636e0a285 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -9,6 +9,8 @@ #include "Lang.h" #include "ModManager.h" #include "ModelViewer.h" +#include "SDL_keycode.h" +#include "EnumStrings.h" #include "argh/argh.h" #include "core/IniConfig.h" @@ -17,7 +19,7 @@ #include "lua/Lua.h" #include "graphics/opengl/RendererGL.h" -#include "SDL_keycode.h" +#include "system/SystemEditor.h" using namespace Editor; @@ -53,6 +55,20 @@ void EditorApp::Initialize(argh::parser &cmdline) SetAppName("ModelViewer"); return; } + + if (cmdline("--system")) { + std::string systemPath; + cmdline("--system") >> systemPath; + + RefCountedPtr systemEditor(new SystemEditor(this)); + + if (!systemPath.empty()) + systemEditor->LoadSystem(systemPath); + + QueueLifecycle(systemEditor); + SetAppName("SystemEditor"); + return; + } } void EditorApp::AddLoadingTask(TaskSet::Handle handle) @@ -78,8 +94,8 @@ void EditorApp::OnStartup() cfg.Read(FileSystem::userFiles, "editor.ini"); cfg.Save(); // write defaults if the file doesn't exist + EnumStrings::Init(); Lua::Init(); - ModManager::Init(); ModManager::LoadMods(&cfg); diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp new file mode 100644 index 00000000000..37477200fab --- /dev/null +++ b/src/editor/system/SystemEditor.cpp @@ -0,0 +1,689 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "SystemEditor.h" + +#include "EnumStrings.h" +#include "FileSystem.h" +#include "editor/EditorApp.h" +#include "editor/EditorDraw.h" +#include "galaxy/AtmosphereParameters.h" +#include "galaxy/CustomSystem.h" +#include "galaxy/Economy.h" +#include "galaxy/Galaxy.h" +#include "galaxy/GalaxyGenerator.h" +#include "galaxy/Polit.h" +#include "galaxy/StarSystemGenerator.h" +#include "editor/UndoSystem.h" +#include "editor/UndoStepType.h" +#include "lua/Lua.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_stdlib.h" + +#include + +using namespace Editor; + +namespace { + static constexpr const char *OUTLINE_WND_ID = "Outline"; + static constexpr const char *PROPERTIES_WND_ID = "Properties"; + static constexpr const char *VIEWPORT_WND_ID = "Viewport"; + + bool InvalidSystemNameChar(char c) + { + return !( + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')); + } +} + +namespace ImGui { + bool InputFixed(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1, const char *format = "%.4f", ImGuiInputTextFlags flags = 0) + { + double val_d = val->ToDouble(); + bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); + if (changed) + *val = fixed::FromDouble(val_d); + + return changed; + } + + bool InputFixedAU(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1) + { + return InputFixed(str, val, step, step_fast, "%.4f AU"); + } + + bool InputFixedDegrees(const char *str, fixed *val, double step = 1.0, double step_fast = 10.0, const char *format = "%.3f°", ImGuiInputTextFlags flags = 0) + { + double val_d = RAD2DEG(val->ToDouble()); + bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); + if (changed) + *val = fixed::FromDouble(DEG2RAD(val_d)); + + return changed; + } +}; + +class SystemBody::EditorAPI { +public: + // Return a list of star types in the system; expects to be passed the root body + static std::string GetStarTypes(SystemBody *body) + { + std::string types = ""; + + if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) { + types = types + "'" + EnumStrings::GetString("BodyType", body->GetType()) + "', "; + } + + for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { + types = types + GetStarTypes(body->m_children[ii]); + } + + return types; + } + + // NOTE: duplicated from StarSystem.cpp + static std::string ExportToLua(FILE *f, SystemBody *body) + { + const int multiplier = 10000; + + // strip characters that will not work in Lua + std::string code_name = body->GetName(); + std::transform(code_name.begin(), code_name.end(), code_name.begin(), ::tolower); + code_name.erase(remove_if(code_name.begin(), code_name.end(), InvalidSystemNameChar), code_name.end()); + + // find the body type index so we can lookup the name + const char *pBodyTypeName = EnumStrings::GetString("BodyType", body->GetType()); + + if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) { + fprintf(f, + "local %s = CustomSystemBody:new(\"%s\", '%s')\n" + "\t:latitude(math.deg2rad(%.1f))\n" + "\t:longitude(math.deg2rad(%.1f))\n", + + code_name.c_str(), + body->GetName().c_str(), pBodyTypeName, + body->m_inclination.ToDouble() * 180 / M_PI, + body->m_orbitalOffset.ToDouble() * 180 / M_PI); + } else { + fprintf(f, + "local %s = CustomSystemBody:new(\"%s\", '%s')\n" + "\t:radius(f(%d,%d))\n" + "\t:mass(f(%d,%d))\n", + code_name.c_str(), + body->GetName().c_str(), pBodyTypeName, + int(round(body->GetRadiusAsFixed().ToDouble() * multiplier)), multiplier, + int(round(body->GetMassAsFixed().ToDouble() * multiplier)), multiplier); + + if (body->GetAspectRatio() != 1.0) { + fprintf(f, + "\t:equatorial_to_polar_radius(f(%d,%d))\n", + int(round(body->GetAspectRatio() * multiplier)), multiplier); + } + + if (body->GetType() != SystemBody::TYPE_GRAVPOINT) { + fprintf(f, + "\t:seed(%u)\n" + "\t:temp(%d)\n" + "\t:semi_major_axis(f(%d,%d))\n" + "\t:eccentricity(f(%d,%d))\n" + "\t:rotation_period(f(%d,%d))\n" + "\t:axial_tilt(fixed.deg2rad(f(%d,%d)))\n" + "\t:rotational_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" + "\t:orbital_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" + "\t:orbital_offset(fixed.deg2rad(f(%d,%d)))\n", + body->GetSeed(), body->GetAverageTemp(), + int(round(body->GetOrbit().GetSemiMajorAxis() / AU * multiplier)), multiplier, + int(round(body->GetOrbit().GetEccentricity() * multiplier)), multiplier, + int(round(body->m_rotationPeriod.ToDouble() * multiplier)), multiplier, + int(round(RAD2DEG(body->GetAxialTilt()) * multiplier)), multiplier, + int(round(body->m_rotationalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, + int(round(body->m_orbitalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, + int(round(body->m_orbitalOffset.ToDouble() * multiplier * 180 / M_PI)), multiplier); + + if (body->GetInclinationAsFixed() != fixed(0, 0)) { + fprintf(f, + "\t:inclination(math.deg2rad(%f))\n", + RAD2DEG(body->GetInclinationAsFixed().ToDouble())); + } + + } + + if (body->GetType() == SystemBody::TYPE_PLANET_TERRESTRIAL) + fprintf(f, + "\t:metallicity(f(%d,%d))\n" + "\t:volcanicity(f(%d,%d))\n" + "\t:atmos_density(f(%d,%d))\n" + "\t:atmos_oxidizing(f(%d,%d))\n" + "\t:ocean_cover(f(%d,%d))\n" + "\t:ice_cover(f(%d,%d))\n" + "\t:life(f(%d,%d))\n", + int(round(body->GetMetallicity() * multiplier)), multiplier, + int(round(body->GetVolcanicity() * multiplier)), multiplier, + int(round(body->GetVolatileGas() * multiplier)), multiplier, + int(round(body->GetAtmosOxidizing() * multiplier)), multiplier, + int(round(body->GetVolatileLiquid() * multiplier)), multiplier, + int(round(body->GetVolatileIces() * multiplier)), multiplier, + int(round(body->GetLife() * multiplier)), multiplier); + } + + fprintf(f, "\n"); + + std::string code_list = code_name; + if (body->m_children.size() > 0) { + code_list = code_list + ",\n\t{\n"; + for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { + code_list = code_list + "\t" + ExportToLua(f, body->m_children[ii]) + ",\n"; + } + code_list = code_list + "\t}"; + } + + return code_list; + } + + static void UpdateBodyOrbit(SystemBody *body) + { + body->m_orbMin = body->m_semiMajorAxis - body->m_eccentricity * body->m_semiMajorAxis; + body->m_orbMax = 2 * body->m_semiMajorAxis - body->m_orbMin; + + if (body->m_parent) + UpdateOrbitAroundParent(body, body->m_parent); + } + + static void UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent) + { + if (parent->GetType() == SystemBody::TYPE_GRAVPOINT) // generalize Kepler's law to multiple stars + body->m_orbit.SetShapeAroundBarycentre(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->GetMass(), body->m_eccentricity.ToDouble()); + else + body->m_orbit.SetShapeAroundPrimary(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->m_eccentricity.ToDouble()); + + body->m_orbit.SetPhase(body->m_orbitalPhaseAtStart.ToDouble()); + + double latitude = body->m_inclination.ToDouble(); + double longitude = body->m_orbitalOffset.ToDouble(); + body->m_orbit.SetPlane(matrix3x3d::RotateY(longitude) * matrix3x3d::RotateX(-0.5 * M_PI + latitude)); + } + + static void EditOrbitalParameters(SystemBody *body, UndoSystem *undo) + { + ImGui::SeparatorText("Orbital Parameters"); + + bool orbitChanged = false; + + orbitChanged |= ImGui::InputFixedAU("Semi-Major Axis", &body->m_semiMajorAxis); + if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) + AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, [=](){ UpdateBodyOrbit(body); }); + + orbitChanged |= ImGui::InputFixed("Eccentricity", &body->m_eccentricity); + if (Draw::UndoHelper("Edit Eccentricity", undo)) + AddUndoSingleValueClosure(undo, &body->m_eccentricity, [=](){ UpdateBodyOrbit(body); }); + + ImGui::BeginDisabled(); + ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); + ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); + ImGui::EndDisabled(); + + orbitChanged |= ImGui::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); + if (Draw::UndoHelper("Edit Axial Tilt", undo)) + AddUndoSingleValue(undo, &body->m_axialTilt); + + orbitChanged |= ImGui::InputFixedDegrees("Inclination", &body->m_inclination); + if (Draw::UndoHelper("Edit Inclination", undo)) + AddUndoSingleValue(undo, &body->m_inclination); + + orbitChanged |= ImGui::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); + if (Draw::UndoHelper("Edit Orbital Offset", undo)) + AddUndoSingleValue(undo, &body->m_orbitalOffset); + + orbitChanged |= ImGui::InputFixedDegrees("Orbital Phase at Start", &body->m_orbitalPhaseAtStart); + if (Draw::UndoHelper("Edit Orbital Phase at Start", undo)) + AddUndoSingleValue(undo, &body->m_orbitalPhaseAtStart); + + orbitChanged |= ImGui::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); + if (Draw::UndoHelper("Edit Rotational Phase at Start", undo)) + AddUndoSingleValue(undo, &body->m_rotationalPhaseAtStart); + + orbitChanged |= ImGui::InputFixed("Rotation Period (Days)", &body->m_rotationPeriod, 1.0, 10.0); + if (Draw::UndoHelper("Edit Rotation Period", undo)) + AddUndoSingleValue(undo, &body->m_rotationPeriod); + + if (orbitChanged) + UpdateBodyOrbit(body); + } + + static void EditEconomicProperties(SystemBody *body, UndoSystem *undo) + { + ImGui::SeparatorText("Economic Parameters"); + + ImGui::InputFixed("Population", &body->m_population); + if (Draw::UndoHelper("Edit Population", undo)) + AddUndoSingleValue(undo, &body->m_population); + + ImGui::InputFixed("Agricultural Activity", &body->m_agricultural); + if (Draw::UndoHelper("Edit Agricultural Activity", undo)) + AddUndoSingleValue(undo, &body->m_agricultural); + } + + static void EditStarportProperties(SystemBody *body, UndoSystem *undo) + { + if (body->GetType() == TYPE_STARPORT_SURFACE) { + ImGui::SeparatorText("Surface Parameters"); + + ImGui::InputFixedDegrees("Latitude", &body->m_inclination); + if (Draw::UndoHelper("Edit Latitude", undo)) + AddUndoSingleValue(undo, &body->m_inclination); + + ImGui::InputFixedDegrees("Longitude", &body->m_orbitalOffset); + if (Draw::UndoHelper("Edit Longitude", undo)) + AddUndoSingleValue(undo, &body->m_orbitalOffset); + } else { + EditOrbitalParameters(body, undo); + } + + EditEconomicProperties(body, undo); + } + + static void EditProperties(SystemBody *body, UndoSystem *undo) + { + bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; + + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5); + + ImGui::InputText("Name", &body->m_name); + if (Draw::UndoHelper("Edit Name", undo)) + AddUndoSingleValue(undo, &body->m_name); + + Draw::EditEnum("Edit Body Type", "Body Type", "BodyType", reinterpret_cast(&body->m_type), BodyType::TYPE_MAX, undo); + + ImGui::InputInt("Seed", reinterpret_cast(&body->m_seed)); + if (Draw::UndoHelper("Edit Seed", undo)) + AddUndoSingleValue(undo, &body->m_seed); + + if (body->GetSuperType() < SUPERTYPE_STARPORT) { + ImGui::InputFixed(isStar ? "Radius (sol)" : "Radius (earth)", &body->m_radius); + if (Draw::UndoHelper("Edit Radius", undo)) + AddUndoSingleValue(undo, &body->m_radius); + + ImGui::InputFixed("Aspect Ratio", &body->m_aspectRatio); + if (Draw::UndoHelper("Edit Aspect Ratio", undo)) + AddUndoSingleValue(undo, &body->m_aspectRatio); + + ImGui::InputFixed(isStar ? "Mass (sol)" : "Mass (earth)", &body->m_mass); + if (Draw::UndoHelper("Edit Mass", undo)) + AddUndoSingleValue(undo, &body->m_mass); + + ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10); + if (Draw::UndoHelper("Edit Temperature", undo)) + AddUndoSingleValue(undo, &body->m_averageTemp); + + } else { + EditStarportProperties(body, undo); + + ImGui::PopItemWidth(); + return; + } + + // TODO: orbital parameters not needed for root body + + EditOrbitalParameters(body, undo); + + if (isStar) { + ImGui::PopItemWidth(); + return; + } + + ImGui::SeparatorText("Surface Parameters"); + + ImGui::InputFixed("Metallicity", &body->m_metallicity); + if (Draw::UndoHelper("Edit Metallicity", undo)) + AddUndoSingleValue(undo, &body->m_metallicity); + + ImGui::InputFixed("Volcanicity", &body->m_volcanicity); + if (Draw::UndoHelper("Edit Volcanicity", undo)) + AddUndoSingleValue(undo, &body->m_volcanicity); + + ImGui::InputFixed("Atmosphere Density", &body->m_volatileGas); + if (Draw::UndoHelper("Edit Atmosphere Density", undo)) + AddUndoSingleValue(undo, &body->m_volatileGas); + + ImGui::InputFixed("Atmosphere Oxidizing", &body->m_atmosOxidizing); + if (Draw::UndoHelper("Edit Atmosphere Oxidizing", undo)) + AddUndoSingleValue(undo, &body->m_atmosOxidizing); + + ImGui::InputFixed("Ocean Coverage", &body->m_volatileLiquid); + if (Draw::UndoHelper("Edit Ocean Coverage", undo)) + AddUndoSingleValue(undo, &body->m_volatileLiquid); + + ImGui::InputFixed("Ice Coverage", &body->m_volatileIces); + if (Draw::UndoHelper("Edit Ice Coverage", undo)) + AddUndoSingleValue(undo, &body->m_volatileIces); + + // TODO: unused by other code + // ImGui::InputFixed("Human Activity", &body->m_humanActivity); + // if (Draw::UndoHelper("Edit Human Activity", undo)) + // AddUndoSingleValue(undo, &body->m_humanActivity); + + ImGui::InputFixed("Life", &body->m_life); + if (Draw::UndoHelper("Edit Life", undo)) + AddUndoSingleValue(undo, &body->m_life); + + EditEconomicProperties(body, undo); + + ImGui::PopItemWidth(); + } +}; + +class StarSystem::EditorAPI { +public: + static void ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) + { + fprintf(f, "-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details\n"); + fprintf(f, "-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt\n\n"); + + SystemBody *rootBody = system->GetRootBody().Get(); + + std::string stars_in_system = SystemBody::EditorAPI::GetStarTypes(rootBody); + + const char *govType = EnumStrings::GetString("PolitGovType", system->GetSysPolit().govType); + + fprintf(f, "local system = CustomSystem:new('%s', { %s })\n\t:govtype('%s')\n\t:short_desc('%s')\n\t:long_desc([[%s]])\n\n", + system->GetName().c_str(), stars_in_system.c_str(), govType, system->GetShortDescription().c_str(), system->GetLongDescription().c_str()); + + fprintf(f, "system:bodies(%s)\n\n", SystemBody::EditorAPI::ExportToLua(f, rootBody).c_str()); + + SystemPath pa = system->GetPath(); + RefCountedPtr sec = galaxy->GetSector(pa); + + fprintf(f, "system:add_to_sector(%d,%d,%d,v(%.4f,%.4f,%.4f))\n", + pa.sectorX, pa.sectorY, pa.sectorZ, + sec->m_systems[pa.systemIndex].GetPosition().x / Sector::SIZE, + sec->m_systems[pa.systemIndex].GetPosition().y / Sector::SIZE, + sec->m_systems[pa.systemIndex].GetPosition().z / Sector::SIZE); + } + + static void EditProperties(StarSystem *system, UndoSystem *undo) + { + ImGui::InputText("Name", &system->m_name); + if (Draw::UndoHelper("Edit System Name", undo)) + AddUndoSingleValue(undo, &system->m_name); + + // TODO: other names + + ImGui::InputText("Short Description", &system->m_shortDesc); + if (Draw::UndoHelper("Edit System Short Description", undo)) + AddUndoSingleValue(undo, &system->m_shortDesc); + + ImGui::InputTextMultiline("Long Description", &system->m_longDesc, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); + if (Draw::UndoHelper("Edit System Long Description", undo)) + AddUndoSingleValue(undo, &system->m_longDesc); + + ImGui::SeparatorText("Generation Parameters"); + + ImGui::InputInt("Seed", reinterpret_cast(&system->m_seed)); + if (Draw::UndoHelper("Edit Seed", undo)) + AddUndoSingleValue(undo, &system->m_seed); + + bool explored = system->m_explored == ExplorationState::eEXPLORED_AT_START; + ImGui::Checkbox("Explored", &explored); + if (Draw::UndoHelper("Edit System Explored", undo)) + AddUndoSingleValue(undo, &system->m_explored, explored ? eEXPLORED_AT_START : eUNEXPLORED); + + ImGui::SeparatorText("Economic Parameters"); + + // TODO: faction + + Draw::EditEnum("Edit System Government", "Government", "PolitGovType", + reinterpret_cast(&system->m_polit.govType), Polit::GovType::GOV_MAX - 1, undo); + + ImGui::InputFixed("Lawlessness", &system->m_polit.lawlessness); + if (Draw::UndoHelper("Edit System Lawlessness", undo)) + AddUndoSingleValue(undo, &system->m_polit.lawlessness); + } +}; + +SystemEditor::SystemEditor(EditorApp *app) : + m_app(app), + m_undo(new UndoSystem()), + m_selectedBody(nullptr) +{ + GalacticEconomy::Init(); + + m_galaxy = GalaxyGenerator::Create(); + m_systemLoader.reset(new CustomSystemsDatabase(m_galaxy.Get(), "systems")); + + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; +} + +SystemEditor::~SystemEditor() +{ +} + +bool SystemEditor::LoadSystem(const std::string &filepath) +{ + const CustomSystem *csys = m_systemLoader->LoadSystem(filepath); + if (!csys) + return false; + + SystemPath path = {csys->sectorX, csys->sectorY, csys->sectorZ, csys->systemIndex}; + Uint32 _init[6] = { csys->systemIndex, Uint32(csys->sectorX), Uint32(csys->sectorY), Uint32(csys->sectorZ), UNIVERSE_SEED, Uint32(csys->seed) }; + Random rng(_init, 6); + + RefCountedPtr system(new StarSystem::GeneratorAPI(path, m_galaxy, nullptr, rng)); + auto generator = std::make_unique(); + + if (!generator->ApplyToSystem(rng, system, csys)) { + Log::Error("System is fully random, cannot load from file"); + return false; + } + + // FIXME: need to run StarSystemPopulateGenerator here to finish filling out system + + if (!system->GetRootBody()) { + Log::Error("Custom system doesn't have a root body"); + return false; + } + + m_system = system; + m_filepath = filepath; + + return true; +} + +void SystemEditor::WriteSystem(const std::string &filepath) +{ + Log::Info("Writing to path: {}/{}", FileSystem::GetDataDir(), filepath); + // FIXME: need better file-saving interface for the user + FILE *f = FileSystem::FileSourceFS(FileSystem::GetDataDir()).OpenWriteStream(filepath, FileSystem::FileSourceFS::WRITE_TEXT); + + if (!f) + return; + + StarSystem::EditorAPI::ExportToLua(f, m_system.Get(), m_galaxy.Get()); + + fclose(f); +} + +void SystemEditor::Start() +{ +} + +void SystemEditor::End() +{ +} + +// ─── Update Loop ───────────────────────────────────────────────────────────── + +void SystemEditor::HandleInput() +{ + +} + +void SystemEditor::Update(float deltaTime) +{ + ImGuiID editorID = ImGui::GetID("System Editor"); + if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, editorID, ImGuiInputFlags_RouteGlobal)) { + GetUndo()->Redo(); + } else if (ImGui::Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, editorID, ImGuiInputFlags_RouteGlobal)) { + GetUndo()->Undo(); + } + + DrawInterface(); + + if (ImGui::IsKeyPressed(ImGuiKey_F1)) { + WriteSystem(m_filepath); + } +} + +// ─── Interface Rendering ───────────────────────────────────────────────────── + +void SystemEditor::SetupLayout(ImGuiID dockspaceID) +{ + ImGuiID nodeID = ImGui::DockBuilderAddNode(dockspaceID); + + ImGui::DockBuilderSetNodePos(nodeID, ImGui::GetWindowPos()); + ImGui::DockBuilderSetNodeSize(nodeID, ImGui::GetWindowSize()); + + ImGuiID leftSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Left, 0.25, nullptr, &nodeID); + ImGuiID rightSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Right, 0.25 / (1.0 - 0.25), nullptr, &nodeID); + // ImGuiID bottom = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Down, 0.2, nullptr, &nodeID); + + ImGui::DockBuilderDockWindow(OUTLINE_WND_ID, leftSide); + ImGui::DockBuilderDockWindow(PROPERTIES_WND_ID, rightSide); + ImGui::DockBuilderDockWindow(VIEWPORT_WND_ID, nodeID); + + ImGui::DockBuilderFinish(dockspaceID); +} + +void SystemEditor::DrawInterface() +{ + Draw::ShowUndoDebugWindow(GetUndo()); + + static bool isFirstRun = true; + + Draw::BeginHostWindow("HostWindow", nullptr, ImGuiWindowFlags_NoSavedSettings); + + ImGuiID dockspaceID = ImGui::GetID("DockSpace"); + + if (isFirstRun) + SetupLayout(dockspaceID); + + ImGui::DockSpace(dockspaceID); + + if (ImGui::Begin(OUTLINE_WND_ID)) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); + DrawOutliner(); + ImGui::PopFont(); + } + ImGui::End(); + + if (ImGui::Begin(PROPERTIES_WND_ID)) { + if (m_selectedBody) + DrawBodyProperties(); + else + DrawSystemProperties(); + } + ImGui::End(); + + ImGui::End(); + + if (isFirstRun) + isFirstRun = false; +} + +void SystemEditor::DrawOutliner() +{ + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + std::string label = fmt::format("System: {}", m_system->GetName()); + if (ImGui::Selectable(label.c_str(), !m_selectedBody)) { + m_selectedBody = nullptr; + } + + ImGui::PopFont(); + + ImGui::Spacing(); + + if (ImGui::BeginChild("OutlinerList")) { + std::vector> m_systemStack { + { m_system->GetRootBody().Get(), 0 } + }; + + if (!DrawBodyNode(m_system->GetRootBody().Get())) { + ImGui::EndChild(); + return; + } + + while (!m_systemStack.empty()) { + auto &pair = m_systemStack.back(); + + if (pair.second == pair.first->GetNumChildren()) { + m_systemStack.pop_back(); + ImGui::TreePop(); + continue; + } + + SystemBody *body = pair.first->GetChildren()[pair.second++]; + if (DrawBodyNode(body)) + m_systemStack.push_back({ body, 0 }); + } + } + ImGui::EndChild(); +} + +bool SystemEditor::DrawBodyNode(SystemBody *body) +{ + ImGuiTreeNodeFlags flags = + ImGuiTreeNodeFlags_DefaultOpen | + ImGuiTreeNodeFlags_OpenOnDoubleClick | + ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_SpanFullWidth; + + if (body->GetNumChildren() == 0) + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + + if (body == m_selectedBody) + flags |= ImGuiTreeNodeFlags_Selected; + + bool open = ImGui::TreeNodeEx(body->GetName().c_str(), flags); + + if (ImGui::IsItemActivated()) { + m_selectedBody = body; + } + + // TODO: custom rendering on body entry, e.g. icon / contents etc. + + return open && body->GetNumChildren(); +} + +void SystemEditor::DrawBodyProperties() +{ + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + ImGui::Text("Body: %s (%d)", m_selectedBody->GetName().c_str(), m_selectedBody->GetPath().bodyIndex); + ImGui::PopFont(); + + ImGui::Spacing(); + + SystemBody::EditorAPI::EditProperties(m_selectedBody, GetUndo()); +} + +void SystemEditor::DrawSystemProperties() +{ + if (!m_system) { + ImGui::Text("No loaded system"); + return; + } + + SystemPath path = m_system->GetPath(); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + ImGui::Text("%s (%d, %d, %d : %d)", + m_system->GetName().c_str(), + path.sectorX, path.sectorY, path.sectorZ, path.systemIndex); + ImGui::PopFont(); + + ImGui::Spacing(); + + StarSystem::EditorAPI::EditProperties(m_system.Get(), GetUndo()); +} diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h new file mode 100644 index 00000000000..fc1fa3e1662 --- /dev/null +++ b/src/editor/system/SystemEditor.h @@ -0,0 +1,66 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "Input.h" +#include "core/Application.h" +#include "pigui/PiGui.h" +#include "RefCounted.h" +#include "editor/UndoSystem.h" + +#include + +class Galaxy; +class StarSystem; +class SystemBody; +class CustomSystemsDatabase; + +namespace Editor { + +class EditorApp; + +class SystemEditor : public Application::Lifecycle { +public: + SystemEditor(EditorApp *app); + ~SystemEditor(); + + bool LoadSystem(const std::string &filepath); + void WriteSystem(const std::string &filepath); + +protected: + void Start() override; + void Update(float deltaTime) override; + void End() override; + + void HandleInput(); + +private: + void SetupLayout(ImGuiID dockspaceID); + void DrawInterface(); + + bool DrawBodyNode(SystemBody *body); + void DrawOutliner(); + + void DrawBodyProperties(); + void DrawSystemProperties(); + + void DrawUndoDebug(); + + UndoSystem *GetUndo() { return m_undo.get(); } + +private: + EditorApp *m_app; + + RefCountedPtr m_galaxy; + RefCountedPtr m_system; + std::unique_ptr m_systemLoader; + + std::unique_ptr m_undo; + + std::string m_filepath; + + SystemBody *m_selectedBody; +}; + +} // namespace Editor From 1d7428e325b3dfcaa2cf9a7165d86a339755f0f5 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 14 Aug 2023 18:07:28 -0400 Subject: [PATCH 04/50] Random system names, fix identifier leading digits --- src/editor/system/SystemEditor.cpp | 40 +++++++++++++++++++++++++++--- src/editor/system/SystemEditor.h | 2 ++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 37477200fab..f4bb1d6dcfe 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -12,6 +12,7 @@ #include "galaxy/Economy.h" #include "galaxy/Galaxy.h" #include "galaxy/GalaxyGenerator.h" +#include "galaxy/NameGenerator.h" #include "galaxy/Polit.h" #include "galaxy/StarSystemGenerator.h" #include "editor/UndoSystem.h" @@ -94,6 +95,10 @@ class SystemBody::EditorAPI { std::transform(code_name.begin(), code_name.end(), code_name.begin(), ::tolower); code_name.erase(remove_if(code_name.begin(), code_name.end(), InvalidSystemNameChar), code_name.end()); + // Ensure we prepend a character to numbers to avoid generating an invalid identifier + if (isdigit(code_name.front())) + code_name = "body_" + code_name; + // find the body type index so we can lookup the name const char *pBodyTypeName = EnumStrings::GetString("BodyType", body->GetType()); @@ -403,12 +408,29 @@ class StarSystem::EditorAPI { sec->m_systems[pa.systemIndex].GetPosition().z / Sector::SIZE); } - static void EditProperties(StarSystem *system, UndoSystem *undo) + static void EditName(StarSystem *system, Random &rng, UndoSystem *undo) { - ImGui::InputText("Name", &system->m_name); + float buttonSize = ImGui::GetFrameHeight(); + ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - buttonSize - ImGui::GetStyle().ItemSpacing.x); + + ImGui::InputText("##Name", &system->m_name); + if (Draw::UndoHelper("Edit System Name", undo)) + AddUndoSingleValue(undo, &system->m_name); + + ImGui::SameLine(); + if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { + system->m_name.clear(); + NameGenerator::GetSystemName(*&system->m_name, rng); + } if (Draw::UndoHelper("Edit System Name", undo)) AddUndoSingleValue(undo, &system->m_name); + ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextUnformatted("Name"); + } + + static void EditProperties(StarSystem *system, UndoSystem *undo) + { // TODO: other names ImGui::InputText("Short Description", &system->m_shortDesc); @@ -471,14 +493,21 @@ bool SystemEditor::LoadSystem(const std::string &filepath) Random rng(_init, 6); RefCountedPtr system(new StarSystem::GeneratorAPI(path, m_galaxy, nullptr, rng)); - auto generator = std::make_unique(); + auto customStage = std::make_unique(); - if (!generator->ApplyToSystem(rng, system, csys)) { + if (!customStage->ApplyToSystem(rng, system, csys)) { Log::Error("System is fully random, cannot load from file"); return false; } // FIXME: need to run StarSystemPopulateGenerator here to finish filling out system + // Setting up faction affinity etc. requires running full gamut of generator stages + + // auto populateStage = std::make_unique(); + // GalaxyGenerator::StarSystemConfig config; + // config.isCustomOnly = true; + + // populateStage->Apply(rng, m_galaxy, system, &config); if (!system->GetRootBody()) { Log::Error("Custom system doesn't have a root body"); @@ -685,5 +714,8 @@ void SystemEditor::DrawSystemProperties() ImGui::Spacing(); + Random rng (Uint32(m_app->GetTime() * 4.0) ^ m_system->GetSeed()); + StarSystem::EditorAPI::EditName(m_system.Get(), rng, GetUndo()); + StarSystem::EditorAPI::EditProperties(m_system.Get(), GetUndo()); } diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index fc1fa3e1662..091383a4c05 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -45,6 +45,8 @@ class SystemEditor : public Application::Lifecycle { void DrawBodyProperties(); void DrawSystemProperties(); + void EditName(const char *undo_label, std::string *name); + void DrawUndoDebug(); UndoSystem *GetUndo() { return m_undo.get(); } From 6f79d4972bfe5d67f717f3c2192a43c92f1dc6e9 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 16 Aug 2023 00:19:09 -0400 Subject: [PATCH 05/50] Add Editor::Draw::BeginHorizontalBar - Simple wrapper around toggling horizontal layout type --- src/editor/EditorDraw.cpp | 12 ++++++++++++ src/editor/EditorDraw.h | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 5e37a09af0d..25f55e0d2a6 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -99,6 +99,18 @@ void Draw::EndLayout() ImGui::Spacing(); } +void Draw::BeginHorizontalBar() +{ + ImGui::BeginGroup(); + ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Horizontal; +} + +void Draw::EndHorizontalBar() +{ + ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Vertical; + ImGui::EndGroup(); +} + void Draw::ShowUndoDebugWindow(UndoSystem *undo, bool *p_open) { if (!ImGui::Begin("Undo Stack", p_open, 0)) { diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 644211136c6..8ade52c6214 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -44,6 +44,12 @@ namespace Editor::Draw { // End a horizontal layout block void EndLayout(); + // Setup horizontal layout for a button bar + void BeginHorizontalBar(); + + // End a horizontal layout block + void EndHorizontalBar(); + // Show a window to debug the state of the passed undo system void ShowUndoDebugWindow(UndoSystem *undo, bool *p_open = nullptr); From 4a7bf4d27e71ba54f737783e7847fbd3c7d6558d Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 16 Aug 2023 00:22:12 -0400 Subject: [PATCH 06/50] Canonize the UndoStep::Swap() pattern Most undo operations on plain data can be implemented as std::swap with extra trimmings. Allow undo steps to implement their operations in terms of either separate Undo/Redo functions or a single Swap function. --- src/editor/UndoStepType.h | 40 +++++++++++++-------------------------- src/editor/UndoSystem.h | 7 +++++-- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/editor/UndoStepType.h b/src/editor/UndoStepType.h index b5d4920fa1f..d3ada5b228b 100644 --- a/src/editor/UndoStepType.h +++ b/src/editor/UndoStepType.h @@ -38,8 +38,7 @@ namespace Editor { std::swap(m_state, *m_dataRef); } - void Undo() override { std::swap(*m_dataRef, m_state); } - void Redo() override { std::swap(*m_dataRef, m_state); } + void Swap() override { std::swap(*m_dataRef, m_state); } // Implement HasChanged as !(a == b) to reduce the number of operator overloads required bool HasChanged() const override { return !(*m_dataRef == m_state); } @@ -65,17 +64,10 @@ namespace Editor { m_state(newValue), m_onUpdate(std::move(updateClosure)) { - std::swap(*m_dataRef, m_state); - m_onUpdate(); + Swap(); } - void Undo() override - { - std::swap(*m_dataRef, m_state); - m_onUpdate(); - } - - void Redo() override + void Swap() override { std::swap(*m_dataRef, m_state); m_onUpdate(); @@ -141,22 +133,19 @@ namespace Editor { m_dataRef(data), m_state(newValue) { - swap(); + Swap(); } - void Undo() override { swap(); } - void Redo() override { swap(); } - - bool HasChanged() const override { return !((m_dataRef->*GetterFn)() == m_state); } - - private: // two-way swap with opaque setter/getter functions - void swap() { + void Swap() override { ValueType t = (m_dataRef->*GetterFn)(); std::swap(t, m_state); (m_dataRef->*SetterFn)(std::move(t)); } + bool HasChanged() const override { return !((m_dataRef->*GetterFn)() == m_state); } + + private: Obj *m_dataRef; ValueType m_state; }; @@ -177,17 +166,11 @@ namespace Editor { m_state(newValue), m_update(std::move(updateClosure)) { - swap(); + Swap(); } - void Undo() override { swap(); } - void Redo() override { swap(); } - - bool HasChanged() const override { return !((m_dataRef->*GetterFn)() == m_state); } - - private: // two-way swap with opaque setter/getter functions and update closure - void swap() { + void Swap() { ValueType t = (m_dataRef->*GetterFn)(); std::swap(t, m_state); (m_dataRef->*SetterFn)(std::move(t)); @@ -195,6 +178,9 @@ namespace Editor { m_update(); } + bool HasChanged() const override { return !((m_dataRef->*GetterFn)() == m_state); } + + private: Obj *m_dataRef; ValueType m_state; ClosureType m_update; diff --git a/src/editor/UndoSystem.h b/src/editor/UndoSystem.h index 6ab2994177b..752aabb4177 100644 --- a/src/editor/UndoSystem.h +++ b/src/editor/UndoSystem.h @@ -34,10 +34,13 @@ class UndoStep { virtual ~UndoStep() = default; // Execute an undo step (replace application state with stored state) - virtual void Undo() = 0; + virtual void Undo() { Swap(); } // Execute a redo step (replace stored state with application state) - virtual void Redo() = 0; + virtual void Redo() { Swap(); } + + // Helper method to allow state changes in a single function + virtual void Swap() {}; // Optimization step: entries for which none of the steps represent a // change in state will not be added to the undo stack From c76457c9847bff673be8f62b45d56de3a7d2c85c Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 16 Aug 2023 00:24:32 -0400 Subject: [PATCH 07/50] SystemEditor: add/remove bodies, cleanup - Split *EditorAPI classes into separate file - Add new file for Undo helper classes - Implement creation and deletion of non-root SystemBodies. - Further work required to set sane/sensible defaults for new body and handle making a body the new root. --- src/editor/system/GalaxyEditAPI.cpp | 471 +++++++++++++++++++++++ src/editor/system/GalaxyEditAPI.h | 44 +++ src/editor/system/SystemBodyUndo.h | 81 ++++ src/editor/system/SystemEditor.cpp | 483 +++--------------------- src/editor/system/SystemEditor.h | 4 +- src/editor/system/SystemEditorHelpers.h | 37 ++ 6 files changed, 685 insertions(+), 435 deletions(-) create mode 100644 src/editor/system/GalaxyEditAPI.cpp create mode 100644 src/editor/system/GalaxyEditAPI.h create mode 100644 src/editor/system/SystemBodyUndo.h create mode 100644 src/editor/system/SystemEditorHelpers.h diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp new file mode 100644 index 00000000000..f20434677db --- /dev/null +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -0,0 +1,471 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "GalaxyEditAPI.h" + +#include "SystemEditorHelpers.h" + +#include "editor/UndoStepType.h" +#include "editor/EditorDraw.h" + +#include "EnumStrings.h" +#include "galaxy/Sector.h" +#include "galaxy/Galaxy.h" +#include "galaxy/NameGenerator.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_stdlib.h" + +using namespace Editor; + +namespace { + bool InvalidSystemNameChar(char c) + { + return !( + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')); + } +} + +void StarSystem::EditorAPI::ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) +{ + fprintf(f, "-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details\n"); + fprintf(f, "-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt\n\n"); + + SystemBody *rootBody = system->GetRootBody().Get(); + + std::string stars_in_system = SystemBody::EditorAPI::GetStarTypes(rootBody); + + const char *govType = EnumStrings::GetString("PolitGovType", system->GetSysPolit().govType); + + fprintf(f, "local system = CustomSystem:new('%s', { %s })\n\t:govtype('%s')\n\t:short_desc('%s')\n\t:long_desc([[%s]])\n\n", + system->GetName().c_str(), stars_in_system.c_str(), govType, system->GetShortDescription().c_str(), system->GetLongDescription().c_str()); + + fprintf(f, "system:bodies(%s)\n\n", SystemBody::EditorAPI::ExportToLua(f, rootBody).c_str()); + + SystemPath pa = system->GetPath(); + RefCountedPtr sec = galaxy->GetSector(pa); + + fprintf(f, "system:add_to_sector(%d,%d,%d,v(%.4f,%.4f,%.4f))\n", + pa.sectorX, pa.sectorY, pa.sectorZ, + sec->m_systems[pa.systemIndex].GetPosition().x / Sector::SIZE, + sec->m_systems[pa.systemIndex].GetPosition().y / Sector::SIZE, + sec->m_systems[pa.systemIndex].GetPosition().z / Sector::SIZE); +} + +SystemBody *StarSystem::EditorAPI::NewBody(StarSystem *system) +{ + return system->NewBody(); +} + +void StarSystem::EditorAPI::AddBody(StarSystem *system, SystemBody *body, size_t idx) +{ + if (idx == size_t(-1)) + idx = system->m_bodies.size(); + + auto iter = system->m_bodies.begin() + idx; + system->m_bodies.emplace(iter, body); +} + +void StarSystem::EditorAPI::RemoveBody(StarSystem *system, SystemBody *body) +{ + auto iter = std::find(system->m_bodies.begin(), system->m_bodies.end(), body); + if (iter != system->m_bodies.end()) + system->m_bodies.erase(iter); +} + +void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem *undo) +{ + float buttonSize = ImGui::GetFrameHeight(); + ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - buttonSize - ImGui::GetStyle().ItemSpacing.x); + + ImGui::InputText("##Name", &system->m_name); + if (Draw::UndoHelper("Edit System Name", undo)) + AddUndoSingleValue(undo, &system->m_name); + + ImGui::SameLine(); + if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { + system->m_name.clear(); + NameGenerator::GetSystemName(*&system->m_name, rng); + } + if (Draw::UndoHelper("Edit System Name", undo)) + AddUndoSingleValue(undo, &system->m_name); + + ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextUnformatted("Name"); +} + +void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) +{ + // TODO: other names + + ImGui::InputText("Short Description", &system->m_shortDesc); + if (Draw::UndoHelper("Edit System Short Description", undo)) + AddUndoSingleValue(undo, &system->m_shortDesc); + + ImGui::InputTextMultiline("Long Description", &system->m_longDesc, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); + if (Draw::UndoHelper("Edit System Long Description", undo)) + AddUndoSingleValue(undo, &system->m_longDesc); + + ImGui::SeparatorText("Generation Parameters"); + + ImGui::InputInt("Seed", reinterpret_cast(&system->m_seed)); + if (Draw::UndoHelper("Edit Seed", undo)) + AddUndoSingleValue(undo, &system->m_seed); + + bool explored = system->m_explored == ExplorationState::eEXPLORED_AT_START; + ImGui::Checkbox("Explored", &explored); + if (Draw::UndoHelper("Edit System Explored", undo)) + AddUndoSingleValue(undo, &system->m_explored, explored ? eEXPLORED_AT_START : eUNEXPLORED); + + ImGui::SeparatorText("Economic Parameters"); + + // TODO: faction + + Draw::EditEnum("Edit System Government", "Government", "PolitGovType", + reinterpret_cast(&system->m_polit.govType), Polit::GovType::GOV_MAX - 1, undo); + + ImGui::InputFixed("Lawlessness", &system->m_polit.lawlessness); + if (Draw::UndoHelper("Edit System Lawlessness", undo)) + AddUndoSingleValue(undo, &system->m_polit.lawlessness); +} + +// ─── SystemBody::EditorAPI ─────────────────────────────────────────────────── + +// Return a list of star types in the system; expects to be passed the root body +std::string SystemBody::EditorAPI::GetStarTypes(SystemBody *body) +{ + std::string types = ""; + + if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) { + types = types + "'" + EnumStrings::GetString("BodyType", body->GetType()) + "', "; + } + + for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { + types = types + GetStarTypes(body->m_children[ii]); + } + + return types; +} + +// NOTE: duplicated from StarSystem.cpp +std::string SystemBody::EditorAPI::ExportToLua(FILE *f, SystemBody *body) +{ + const int multiplier = 10000; + + // strip characters that will not work in Lua + std::string code_name = body->GetName(); + std::transform(code_name.begin(), code_name.end(), code_name.begin(), ::tolower); + code_name.erase(remove_if(code_name.begin(), code_name.end(), InvalidSystemNameChar), code_name.end()); + + // Ensure we prepend a character to numbers to avoid generating an invalid identifier + if (isdigit(code_name.front())) + code_name = "body_" + code_name; + + // find the body type index so we can lookup the name + const char *pBodyTypeName = EnumStrings::GetString("BodyType", body->GetType()); + + if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) { + fprintf(f, + "local %s = CustomSystemBody:new(\"%s\", '%s')\n" + "\t:seed(%d)" + "\t:latitude(math.deg2rad(%.1f))\n" + "\t:longitude(math.deg2rad(%.1f))\n", + + code_name.c_str(), + body->GetName().c_str(), pBodyTypeName, + body->m_seed, + body->m_inclination.ToDouble() * 180 / M_PI, + body->m_orbitalOffset.ToDouble() * 180 / M_PI); + } else { + fprintf(f, + "local %s = CustomSystemBody:new(\"%s\", '%s')\n" + "\t:radius(f(%d,%d))\n" + "\t:mass(f(%d,%d))\n", + code_name.c_str(), + body->GetName().c_str(), pBodyTypeName, + int(round(body->GetRadiusAsFixed().ToDouble() * multiplier)), multiplier, + int(round(body->GetMassAsFixed().ToDouble() * multiplier)), multiplier); + + if (body->GetAspectRatio() != 1.0) { + fprintf(f, + "\t:equatorial_to_polar_radius(f(%d,%d))\n", + int(round(body->GetAspectRatio() * multiplier)), multiplier); + } + + if (body->GetType() != SystemBody::TYPE_GRAVPOINT) { + fprintf(f, + "\t:seed(%u)\n" + "\t:temp(%d)\n" + "\t:semi_major_axis(f(%d,%d))\n" + "\t:eccentricity(f(%d,%d))\n" + "\t:rotation_period(f(%d,%d))\n" + "\t:axial_tilt(fixed.deg2rad(f(%d,%d)))\n" + "\t:rotational_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" + "\t:orbital_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" + "\t:orbital_offset(fixed.deg2rad(f(%d,%d)))\n", + body->GetSeed(), body->GetAverageTemp(), + int(round(body->GetOrbit().GetSemiMajorAxis() / AU * multiplier)), multiplier, + int(round(body->GetOrbit().GetEccentricity() * multiplier)), multiplier, + int(round(body->m_rotationPeriod.ToDouble() * multiplier)), multiplier, + int(round(RAD2DEG(body->GetAxialTilt()) * multiplier)), multiplier, + int(round(body->m_rotationalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, + int(round(body->m_orbitalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, + int(round(body->m_orbitalOffset.ToDouble() * multiplier * 180 / M_PI)), multiplier); + + if (body->GetInclinationAsFixed() != fixed(0, 0)) { + fprintf(f, + "\t:inclination(math.deg2rad(%f))\n", + RAD2DEG(body->GetInclinationAsFixed().ToDouble())); + } + + } + + if (body->GetType() == SystemBody::TYPE_PLANET_TERRESTRIAL) + fprintf(f, + "\t:metallicity(f(%d,%d))\n" + "\t:volcanicity(f(%d,%d))\n" + "\t:atmos_density(f(%d,%d))\n" + "\t:atmos_oxidizing(f(%d,%d))\n" + "\t:ocean_cover(f(%d,%d))\n" + "\t:ice_cover(f(%d,%d))\n" + "\t:life(f(%d,%d))\n", + int(round(body->GetMetallicity() * multiplier)), multiplier, + int(round(body->GetVolcanicity() * multiplier)), multiplier, + int(round(body->GetVolatileGas() * multiplier)), multiplier, + int(round(body->GetAtmosOxidizing() * multiplier)), multiplier, + int(round(body->GetVolatileLiquid() * multiplier)), multiplier, + int(round(body->GetVolatileIces() * multiplier)), multiplier, + int(round(body->GetLife() * multiplier)), multiplier); + } + + fprintf(f, "\n"); + + std::string code_list = code_name; + if (body->m_children.size() > 0) { + code_list = code_list + ",\n\t{\n"; + for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { + code_list = code_list + "\t" + ExportToLua(f, body->m_children[ii]) + ",\n"; + } + code_list = code_list + "\t}"; + } + + return code_list; +} + + +void SystemBody::EditorAPI::AddChild(SystemBody *parent, SystemBody *child, size_t idx) +{ + if (idx == size_t(-1)) + idx = parent->m_children.size(); + + auto iter = parent->m_children.begin() + idx; + parent->m_children.emplace(iter, child); + + child->m_parent = parent; +} + +SystemBody *SystemBody::EditorAPI::RemoveChild(SystemBody *parent, size_t idx) +{ + if (idx == size_t(-1)) + idx = parent->m_children.size() - 1; + + SystemBody *outBody = parent->m_children[idx]; + parent->m_children.erase(parent->m_children.begin() + idx); + + outBody->m_parent = nullptr; + + return outBody; +} + +void SystemBody::EditorAPI::UpdateBodyOrbit(SystemBody *body) +{ + body->m_orbMin = body->m_semiMajorAxis - body->m_eccentricity * body->m_semiMajorAxis; + body->m_orbMax = 2 * body->m_semiMajorAxis - body->m_orbMin; + + if (body->m_parent) + UpdateOrbitAroundParent(body, body->m_parent); +} + +void SystemBody::EditorAPI::UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent) +{ + if (parent->GetType() == SystemBody::TYPE_GRAVPOINT) // generalize Kepler's law to multiple stars + body->m_orbit.SetShapeAroundBarycentre(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->GetMass(), body->m_eccentricity.ToDouble()); + else + body->m_orbit.SetShapeAroundPrimary(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->m_eccentricity.ToDouble()); + + body->m_orbit.SetPhase(body->m_orbitalPhaseAtStart.ToDouble()); + + double latitude = body->m_inclination.ToDouble(); + double longitude = body->m_orbitalOffset.ToDouble(); + body->m_orbit.SetPlane(matrix3x3d::RotateY(longitude) * matrix3x3d::RotateX(-0.5 * M_PI + latitude)); +} + +void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem *undo) +{ + ImGui::SeparatorText("Orbital Parameters"); + + bool orbitChanged = false; + + orbitChanged |= ImGui::InputFixedAU("Semi-Major Axis", &body->m_semiMajorAxis); + if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) + AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, [=](){ UpdateBodyOrbit(body); }); + + orbitChanged |= ImGui::InputFixed("Eccentricity", &body->m_eccentricity); + if (Draw::UndoHelper("Edit Eccentricity", undo)) + AddUndoSingleValueClosure(undo, &body->m_eccentricity, [=](){ UpdateBodyOrbit(body); }); + + ImGui::BeginDisabled(); + ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); + ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); + ImGui::EndDisabled(); + + orbitChanged |= ImGui::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); + if (Draw::UndoHelper("Edit Axial Tilt", undo)) + AddUndoSingleValue(undo, &body->m_axialTilt); + + orbitChanged |= ImGui::InputFixedDegrees("Inclination", &body->m_inclination); + if (Draw::UndoHelper("Edit Inclination", undo)) + AddUndoSingleValue(undo, &body->m_inclination); + + orbitChanged |= ImGui::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); + if (Draw::UndoHelper("Edit Orbital Offset", undo)) + AddUndoSingleValue(undo, &body->m_orbitalOffset); + + orbitChanged |= ImGui::InputFixedDegrees("Orbital Phase at Start", &body->m_orbitalPhaseAtStart); + if (Draw::UndoHelper("Edit Orbital Phase at Start", undo)) + AddUndoSingleValue(undo, &body->m_orbitalPhaseAtStart); + + orbitChanged |= ImGui::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); + if (Draw::UndoHelper("Edit Rotational Phase at Start", undo)) + AddUndoSingleValue(undo, &body->m_rotationalPhaseAtStart); + + orbitChanged |= ImGui::InputFixed("Rotation Period (Days)", &body->m_rotationPeriod, 1.0, 10.0); + if (Draw::UndoHelper("Edit Rotation Period", undo)) + AddUndoSingleValue(undo, &body->m_rotationPeriod); + + if (orbitChanged) + UpdateBodyOrbit(body); +} + +void SystemBody::EditorAPI::EditEconomicProperties(SystemBody *body, UndoSystem *undo) +{ + ImGui::SeparatorText("Economic Parameters"); + + ImGui::InputFixed("Population", &body->m_population); + if (Draw::UndoHelper("Edit Population", undo)) + AddUndoSingleValue(undo, &body->m_population); + + ImGui::InputFixed("Agricultural Activity", &body->m_agricultural); + if (Draw::UndoHelper("Edit Agricultural Activity", undo)) + AddUndoSingleValue(undo, &body->m_agricultural); +} + +void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem *undo) +{ + if (body->GetType() == TYPE_STARPORT_SURFACE) { + ImGui::SeparatorText("Surface Parameters"); + + ImGui::InputFixedDegrees("Latitude", &body->m_inclination); + if (Draw::UndoHelper("Edit Latitude", undo)) + AddUndoSingleValue(undo, &body->m_inclination); + + ImGui::InputFixedDegrees("Longitude", &body->m_orbitalOffset); + if (Draw::UndoHelper("Edit Longitude", undo)) + AddUndoSingleValue(undo, &body->m_orbitalOffset); + } else { + EditOrbitalParameters(body, undo); + } + + EditEconomicProperties(body, undo); +} + +void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) +{ + bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; + + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5); + + ImGui::InputText("Name", &body->m_name); + if (Draw::UndoHelper("Edit Name", undo)) + AddUndoSingleValue(undo, &body->m_name); + + Draw::EditEnum("Edit Body Type", "Body Type", "BodyType", reinterpret_cast(&body->m_type), BodyType::TYPE_MAX, undo); + + ImGui::InputInt("Seed", reinterpret_cast(&body->m_seed)); + if (Draw::UndoHelper("Edit Seed", undo)) + AddUndoSingleValue(undo, &body->m_seed); + + if (body->GetSuperType() < SUPERTYPE_STARPORT) { + ImGui::InputFixed(isStar ? "Radius (sol)" : "Radius (earth)", &body->m_radius); + if (Draw::UndoHelper("Edit Radius", undo)) + AddUndoSingleValue(undo, &body->m_radius); + + ImGui::InputFixed("Aspect Ratio", &body->m_aspectRatio); + if (Draw::UndoHelper("Edit Aspect Ratio", undo)) + AddUndoSingleValue(undo, &body->m_aspectRatio); + + ImGui::InputFixed(isStar ? "Mass (sol)" : "Mass (earth)", &body->m_mass); + if (Draw::UndoHelper("Edit Mass", undo)) + AddUndoSingleValue(undo, &body->m_mass); + + ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10); + if (Draw::UndoHelper("Edit Temperature", undo)) + AddUndoSingleValue(undo, &body->m_averageTemp); + + } else { + EditStarportProperties(body, undo); + + ImGui::PopItemWidth(); + return; + } + + // TODO: orbital parameters not needed for root body + + EditOrbitalParameters(body, undo); + + if (isStar) { + ImGui::PopItemWidth(); + return; + } + + ImGui::SeparatorText("Surface Parameters"); + + ImGui::InputFixed("Metallicity", &body->m_metallicity); + if (Draw::UndoHelper("Edit Metallicity", undo)) + AddUndoSingleValue(undo, &body->m_metallicity); + + ImGui::InputFixed("Volcanicity", &body->m_volcanicity); + if (Draw::UndoHelper("Edit Volcanicity", undo)) + AddUndoSingleValue(undo, &body->m_volcanicity); + + ImGui::InputFixed("Atmosphere Density", &body->m_volatileGas); + if (Draw::UndoHelper("Edit Atmosphere Density", undo)) + AddUndoSingleValue(undo, &body->m_volatileGas); + + ImGui::InputFixed("Atmosphere Oxidizing", &body->m_atmosOxidizing); + if (Draw::UndoHelper("Edit Atmosphere Oxidizing", undo)) + AddUndoSingleValue(undo, &body->m_atmosOxidizing); + + ImGui::InputFixed("Ocean Coverage", &body->m_volatileLiquid); + if (Draw::UndoHelper("Edit Ocean Coverage", undo)) + AddUndoSingleValue(undo, &body->m_volatileLiquid); + + ImGui::InputFixed("Ice Coverage", &body->m_volatileIces); + if (Draw::UndoHelper("Edit Ice Coverage", undo)) + AddUndoSingleValue(undo, &body->m_volatileIces); + + // TODO: unused by other code + // ImGui::InputFixed("Human Activity", &body->m_humanActivity); + // if (Draw::UndoHelper("Edit Human Activity", undo)) + // AddUndoSingleValue(undo, &body->m_humanActivity); + + ImGui::InputFixed("Life", &body->m_life); + if (Draw::UndoHelper("Edit Life", undo)) + AddUndoSingleValue(undo, &body->m_life); + + EditEconomicProperties(body, undo); + + ImGui::PopItemWidth(); +} diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h new file mode 100644 index 00000000000..e60f18d0c06 --- /dev/null +++ b/src/editor/system/GalaxyEditAPI.h @@ -0,0 +1,44 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "galaxy/StarSystem.h" + +namespace Editor { + class UndoSystem; +} + +class StarSystem::EditorAPI { +public: + static void ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy); + + static SystemBody* NewBody(StarSystem *system); + + static void AddBody(StarSystem *system, SystemBody *body, size_t idx = -1); + static void RemoveBody(StarSystem *system, SystemBody *body); + + static void ReorderBodyIndex(StarSystem *system); + + static void EditName(StarSystem *system, Random &rng, Editor::UndoSystem *undo); + static void EditProperties(StarSystem *system, Editor::UndoSystem *undo); +}; + +class SystemBody::EditorAPI { +public: + // Return a list of star types in the system; expects to be passed the root body + static std::string GetStarTypes(SystemBody *body); + // NOTE: duplicated from StarSystem.cpp + static std::string ExportToLua(FILE *f, SystemBody *body); + + static void AddChild(SystemBody *parent, SystemBody *child, size_t idx = -1); + static SystemBody *RemoveChild(SystemBody *parent, size_t idx = -1); + + static void UpdateBodyOrbit(SystemBody *body); + static void UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent); + + static void EditOrbitalParameters(SystemBody *body, Editor::UndoSystem *undo); + static void EditEconomicProperties(SystemBody *body, Editor::UndoSystem *undo); + static void EditStarportProperties(SystemBody *body, Editor::UndoSystem *undo); + static void EditProperties(SystemBody *body, Editor::UndoSystem *undo); +}; diff --git a/src/editor/system/SystemBodyUndo.h b/src/editor/system/SystemBodyUndo.h new file mode 100644 index 00000000000..6405a37bd78 --- /dev/null +++ b/src/editor/system/SystemBodyUndo.h @@ -0,0 +1,81 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "GalaxyEditAPI.h" + +#include "editor/UndoSystem.h" +#include "galaxy/StarSystem.h" +#include "galaxy/SystemBody.h" + +namespace Editor::SystemEditorHelpers { + + class UndoManageStarSystemBody : public UndoStep { + public: + UndoManageStarSystemBody(StarSystem *system, SystemBody *add, SystemBody *rem = nullptr, bool apply = false) : + m_system(system), + m_addBody(add), + m_remBody(rem) + { + if (apply) + Swap(); + } + + void Swap() override { + if (m_addBody) + StarSystem::EditorAPI::AddBody(m_system, m_addBody.Get()); + if (m_remBody) + StarSystem::EditorAPI::RemoveBody(m_system, m_remBody.Get()); + std::swap(m_addBody, m_remBody); + } + + private: + StarSystem *m_system; + RefCountedPtr m_addBody; + RefCountedPtr m_remBody; + }; + + // UndoStep helper to handle adding or deleting a child SystemBody from a parent + class UndoAddRemoveChildBody : public UndoStep { + public: + UndoAddRemoveChildBody(SystemBody *parent, SystemBody *add, size_t idx) : + m_parent(parent), + m_add(add), + m_idx(idx) + { + Swap(); + } + + UndoAddRemoveChildBody(SystemBody *parent, SystemBody *add) : + m_parent(parent), + m_add(add), + m_idx(-1) + { + Swap(); + } + + UndoAddRemoveChildBody(SystemBody *parent, size_t idx) : + m_parent(parent), + m_add(nullptr), + m_idx(idx) + { + Swap(); + } + + void Swap() override { + if (m_add) { + SystemBody::EditorAPI::AddChild(m_parent, m_add.Get(), m_idx); + m_add.Reset(); + } else { + m_add.Reset(SystemBody::EditorAPI::RemoveChild(m_parent, m_idx)); + } + } + + private: + SystemBody *m_parent; + RefCountedPtr m_add; + size_t m_idx; + }; + +} // namespace Editor diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index f4bb1d6dcfe..6dfac560e6f 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -3,24 +3,25 @@ #include "SystemEditor.h" +#include "GalaxyEditAPI.h" +#include "SystemEditorHelpers.h" + #include "EnumStrings.h" #include "FileSystem.h" + #include "editor/EditorApp.h" #include "editor/EditorDraw.h" -#include "galaxy/AtmosphereParameters.h" -#include "galaxy/CustomSystem.h" -#include "galaxy/Economy.h" +#include "editor/UndoSystem.h" +#include "editor/UndoStepType.h" + #include "galaxy/Galaxy.h" #include "galaxy/GalaxyGenerator.h" -#include "galaxy/NameGenerator.h" -#include "galaxy/Polit.h" #include "galaxy/StarSystemGenerator.h" -#include "editor/UndoSystem.h" -#include "editor/UndoStepType.h" + #include "lua/Lua.h" #include "imgui/imgui.h" -#include "imgui/imgui_stdlib.h" +#include "system/SystemBodyUndo.h" #include @@ -31,438 +32,24 @@ namespace { static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; - bool InvalidSystemNameChar(char c) - { - return !( - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9')); - } } -namespace ImGui { - bool InputFixed(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1, const char *format = "%.4f", ImGuiInputTextFlags flags = 0) - { - double val_d = val->ToDouble(); - bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); - if (changed) - *val = fixed::FromDouble(val_d); - - return changed; - } - - bool InputFixedAU(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1) - { - return InputFixed(str, val, step, step_fast, "%.4f AU"); - } - - bool InputFixedDegrees(const char *str, fixed *val, double step = 1.0, double step_fast = 10.0, const char *format = "%.3f°", ImGuiInputTextFlags flags = 0) - { - double val_d = RAD2DEG(val->ToDouble()); - bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); - if (changed) - *val = fixed::FromDouble(DEG2RAD(val_d)); - - return changed; - } -}; - -class SystemBody::EditorAPI { +class SystemEditor::UndoSetSelection : public UndoStep { public: - // Return a list of star types in the system; expects to be passed the root body - static std::string GetStarTypes(SystemBody *body) + UndoSetSelection(SystemEditor *editor, SystemBody *newSelection) : + m_editor(editor), + m_selection(newSelection) { - std::string types = ""; - - if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) { - types = types + "'" + EnumStrings::GetString("BodyType", body->GetType()) + "', "; - } - - for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { - types = types + GetStarTypes(body->m_children[ii]); - } - - return types; + Swap(); } - // NOTE: duplicated from StarSystem.cpp - static std::string ExportToLua(FILE *f, SystemBody *body) - { - const int multiplier = 10000; - - // strip characters that will not work in Lua - std::string code_name = body->GetName(); - std::transform(code_name.begin(), code_name.end(), code_name.begin(), ::tolower); - code_name.erase(remove_if(code_name.begin(), code_name.end(), InvalidSystemNameChar), code_name.end()); - - // Ensure we prepend a character to numbers to avoid generating an invalid identifier - if (isdigit(code_name.front())) - code_name = "body_" + code_name; - - // find the body type index so we can lookup the name - const char *pBodyTypeName = EnumStrings::GetString("BodyType", body->GetType()); - - if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) { - fprintf(f, - "local %s = CustomSystemBody:new(\"%s\", '%s')\n" - "\t:latitude(math.deg2rad(%.1f))\n" - "\t:longitude(math.deg2rad(%.1f))\n", - - code_name.c_str(), - body->GetName().c_str(), pBodyTypeName, - body->m_inclination.ToDouble() * 180 / M_PI, - body->m_orbitalOffset.ToDouble() * 180 / M_PI); - } else { - fprintf(f, - "local %s = CustomSystemBody:new(\"%s\", '%s')\n" - "\t:radius(f(%d,%d))\n" - "\t:mass(f(%d,%d))\n", - code_name.c_str(), - body->GetName().c_str(), pBodyTypeName, - int(round(body->GetRadiusAsFixed().ToDouble() * multiplier)), multiplier, - int(round(body->GetMassAsFixed().ToDouble() * multiplier)), multiplier); - - if (body->GetAspectRatio() != 1.0) { - fprintf(f, - "\t:equatorial_to_polar_radius(f(%d,%d))\n", - int(round(body->GetAspectRatio() * multiplier)), multiplier); - } - - if (body->GetType() != SystemBody::TYPE_GRAVPOINT) { - fprintf(f, - "\t:seed(%u)\n" - "\t:temp(%d)\n" - "\t:semi_major_axis(f(%d,%d))\n" - "\t:eccentricity(f(%d,%d))\n" - "\t:rotation_period(f(%d,%d))\n" - "\t:axial_tilt(fixed.deg2rad(f(%d,%d)))\n" - "\t:rotational_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" - "\t:orbital_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" - "\t:orbital_offset(fixed.deg2rad(f(%d,%d)))\n", - body->GetSeed(), body->GetAverageTemp(), - int(round(body->GetOrbit().GetSemiMajorAxis() / AU * multiplier)), multiplier, - int(round(body->GetOrbit().GetEccentricity() * multiplier)), multiplier, - int(round(body->m_rotationPeriod.ToDouble() * multiplier)), multiplier, - int(round(RAD2DEG(body->GetAxialTilt()) * multiplier)), multiplier, - int(round(body->m_rotationalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, - int(round(body->m_orbitalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, - int(round(body->m_orbitalOffset.ToDouble() * multiplier * 180 / M_PI)), multiplier); - - if (body->GetInclinationAsFixed() != fixed(0, 0)) { - fprintf(f, - "\t:inclination(math.deg2rad(%f))\n", - RAD2DEG(body->GetInclinationAsFixed().ToDouble())); - } - - } - - if (body->GetType() == SystemBody::TYPE_PLANET_TERRESTRIAL) - fprintf(f, - "\t:metallicity(f(%d,%d))\n" - "\t:volcanicity(f(%d,%d))\n" - "\t:atmos_density(f(%d,%d))\n" - "\t:atmos_oxidizing(f(%d,%d))\n" - "\t:ocean_cover(f(%d,%d))\n" - "\t:ice_cover(f(%d,%d))\n" - "\t:life(f(%d,%d))\n", - int(round(body->GetMetallicity() * multiplier)), multiplier, - int(round(body->GetVolcanicity() * multiplier)), multiplier, - int(round(body->GetVolatileGas() * multiplier)), multiplier, - int(round(body->GetAtmosOxidizing() * multiplier)), multiplier, - int(round(body->GetVolatileLiquid() * multiplier)), multiplier, - int(round(body->GetVolatileIces() * multiplier)), multiplier, - int(round(body->GetLife() * multiplier)), multiplier); - } - - fprintf(f, "\n"); - - std::string code_list = code_name; - if (body->m_children.size() > 0) { - code_list = code_list + ",\n\t{\n"; - for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { - code_list = code_list + "\t" + ExportToLua(f, body->m_children[ii]) + ",\n"; - } - code_list = code_list + "\t}"; - } - - return code_list; - } - - static void UpdateBodyOrbit(SystemBody *body) - { - body->m_orbMin = body->m_semiMajorAxis - body->m_eccentricity * body->m_semiMajorAxis; - body->m_orbMax = 2 * body->m_semiMajorAxis - body->m_orbMin; - - if (body->m_parent) - UpdateOrbitAroundParent(body, body->m_parent); - } - - static void UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent) - { - if (parent->GetType() == SystemBody::TYPE_GRAVPOINT) // generalize Kepler's law to multiple stars - body->m_orbit.SetShapeAroundBarycentre(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->GetMass(), body->m_eccentricity.ToDouble()); - else - body->m_orbit.SetShapeAroundPrimary(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->m_eccentricity.ToDouble()); - - body->m_orbit.SetPhase(body->m_orbitalPhaseAtStart.ToDouble()); - - double latitude = body->m_inclination.ToDouble(); - double longitude = body->m_orbitalOffset.ToDouble(); - body->m_orbit.SetPlane(matrix3x3d::RotateY(longitude) * matrix3x3d::RotateX(-0.5 * M_PI + latitude)); + void Swap() override { + std::swap(m_editor->m_selectedBody, m_selection); } - static void EditOrbitalParameters(SystemBody *body, UndoSystem *undo) - { - ImGui::SeparatorText("Orbital Parameters"); - - bool orbitChanged = false; - - orbitChanged |= ImGui::InputFixedAU("Semi-Major Axis", &body->m_semiMajorAxis); - if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) - AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, [=](){ UpdateBodyOrbit(body); }); - - orbitChanged |= ImGui::InputFixed("Eccentricity", &body->m_eccentricity); - if (Draw::UndoHelper("Edit Eccentricity", undo)) - AddUndoSingleValueClosure(undo, &body->m_eccentricity, [=](){ UpdateBodyOrbit(body); }); - - ImGui::BeginDisabled(); - ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); - ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); - ImGui::EndDisabled(); - - orbitChanged |= ImGui::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); - if (Draw::UndoHelper("Edit Axial Tilt", undo)) - AddUndoSingleValue(undo, &body->m_axialTilt); - - orbitChanged |= ImGui::InputFixedDegrees("Inclination", &body->m_inclination); - if (Draw::UndoHelper("Edit Inclination", undo)) - AddUndoSingleValue(undo, &body->m_inclination); - - orbitChanged |= ImGui::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); - if (Draw::UndoHelper("Edit Orbital Offset", undo)) - AddUndoSingleValue(undo, &body->m_orbitalOffset); - - orbitChanged |= ImGui::InputFixedDegrees("Orbital Phase at Start", &body->m_orbitalPhaseAtStart); - if (Draw::UndoHelper("Edit Orbital Phase at Start", undo)) - AddUndoSingleValue(undo, &body->m_orbitalPhaseAtStart); - - orbitChanged |= ImGui::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); - if (Draw::UndoHelper("Edit Rotational Phase at Start", undo)) - AddUndoSingleValue(undo, &body->m_rotationalPhaseAtStart); - - orbitChanged |= ImGui::InputFixed("Rotation Period (Days)", &body->m_rotationPeriod, 1.0, 10.0); - if (Draw::UndoHelper("Edit Rotation Period", undo)) - AddUndoSingleValue(undo, &body->m_rotationPeriod); - - if (orbitChanged) - UpdateBodyOrbit(body); - } - - static void EditEconomicProperties(SystemBody *body, UndoSystem *undo) - { - ImGui::SeparatorText("Economic Parameters"); - - ImGui::InputFixed("Population", &body->m_population); - if (Draw::UndoHelper("Edit Population", undo)) - AddUndoSingleValue(undo, &body->m_population); - - ImGui::InputFixed("Agricultural Activity", &body->m_agricultural); - if (Draw::UndoHelper("Edit Agricultural Activity", undo)) - AddUndoSingleValue(undo, &body->m_agricultural); - } - - static void EditStarportProperties(SystemBody *body, UndoSystem *undo) - { - if (body->GetType() == TYPE_STARPORT_SURFACE) { - ImGui::SeparatorText("Surface Parameters"); - - ImGui::InputFixedDegrees("Latitude", &body->m_inclination); - if (Draw::UndoHelper("Edit Latitude", undo)) - AddUndoSingleValue(undo, &body->m_inclination); - - ImGui::InputFixedDegrees("Longitude", &body->m_orbitalOffset); - if (Draw::UndoHelper("Edit Longitude", undo)) - AddUndoSingleValue(undo, &body->m_orbitalOffset); - } else { - EditOrbitalParameters(body, undo); - } - - EditEconomicProperties(body, undo); - } - - static void EditProperties(SystemBody *body, UndoSystem *undo) - { - bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; - - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5); - - ImGui::InputText("Name", &body->m_name); - if (Draw::UndoHelper("Edit Name", undo)) - AddUndoSingleValue(undo, &body->m_name); - - Draw::EditEnum("Edit Body Type", "Body Type", "BodyType", reinterpret_cast(&body->m_type), BodyType::TYPE_MAX, undo); - - ImGui::InputInt("Seed", reinterpret_cast(&body->m_seed)); - if (Draw::UndoHelper("Edit Seed", undo)) - AddUndoSingleValue(undo, &body->m_seed); - - if (body->GetSuperType() < SUPERTYPE_STARPORT) { - ImGui::InputFixed(isStar ? "Radius (sol)" : "Radius (earth)", &body->m_radius); - if (Draw::UndoHelper("Edit Radius", undo)) - AddUndoSingleValue(undo, &body->m_radius); - - ImGui::InputFixed("Aspect Ratio", &body->m_aspectRatio); - if (Draw::UndoHelper("Edit Aspect Ratio", undo)) - AddUndoSingleValue(undo, &body->m_aspectRatio); - - ImGui::InputFixed(isStar ? "Mass (sol)" : "Mass (earth)", &body->m_mass); - if (Draw::UndoHelper("Edit Mass", undo)) - AddUndoSingleValue(undo, &body->m_mass); - - ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10); - if (Draw::UndoHelper("Edit Temperature", undo)) - AddUndoSingleValue(undo, &body->m_averageTemp); - - } else { - EditStarportProperties(body, undo); - - ImGui::PopItemWidth(); - return; - } - - // TODO: orbital parameters not needed for root body - - EditOrbitalParameters(body, undo); - - if (isStar) { - ImGui::PopItemWidth(); - return; - } - - ImGui::SeparatorText("Surface Parameters"); - - ImGui::InputFixed("Metallicity", &body->m_metallicity); - if (Draw::UndoHelper("Edit Metallicity", undo)) - AddUndoSingleValue(undo, &body->m_metallicity); - - ImGui::InputFixed("Volcanicity", &body->m_volcanicity); - if (Draw::UndoHelper("Edit Volcanicity", undo)) - AddUndoSingleValue(undo, &body->m_volcanicity); - - ImGui::InputFixed("Atmosphere Density", &body->m_volatileGas); - if (Draw::UndoHelper("Edit Atmosphere Density", undo)) - AddUndoSingleValue(undo, &body->m_volatileGas); - - ImGui::InputFixed("Atmosphere Oxidizing", &body->m_atmosOxidizing); - if (Draw::UndoHelper("Edit Atmosphere Oxidizing", undo)) - AddUndoSingleValue(undo, &body->m_atmosOxidizing); - - ImGui::InputFixed("Ocean Coverage", &body->m_volatileLiquid); - if (Draw::UndoHelper("Edit Ocean Coverage", undo)) - AddUndoSingleValue(undo, &body->m_volatileLiquid); - - ImGui::InputFixed("Ice Coverage", &body->m_volatileIces); - if (Draw::UndoHelper("Edit Ice Coverage", undo)) - AddUndoSingleValue(undo, &body->m_volatileIces); - - // TODO: unused by other code - // ImGui::InputFixed("Human Activity", &body->m_humanActivity); - // if (Draw::UndoHelper("Edit Human Activity", undo)) - // AddUndoSingleValue(undo, &body->m_humanActivity); - - ImGui::InputFixed("Life", &body->m_life); - if (Draw::UndoHelper("Edit Life", undo)) - AddUndoSingleValue(undo, &body->m_life); - - EditEconomicProperties(body, undo); - - ImGui::PopItemWidth(); - } -}; - -class StarSystem::EditorAPI { -public: - static void ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) - { - fprintf(f, "-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details\n"); - fprintf(f, "-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt\n\n"); - - SystemBody *rootBody = system->GetRootBody().Get(); - - std::string stars_in_system = SystemBody::EditorAPI::GetStarTypes(rootBody); - - const char *govType = EnumStrings::GetString("PolitGovType", system->GetSysPolit().govType); - - fprintf(f, "local system = CustomSystem:new('%s', { %s })\n\t:govtype('%s')\n\t:short_desc('%s')\n\t:long_desc([[%s]])\n\n", - system->GetName().c_str(), stars_in_system.c_str(), govType, system->GetShortDescription().c_str(), system->GetLongDescription().c_str()); - - fprintf(f, "system:bodies(%s)\n\n", SystemBody::EditorAPI::ExportToLua(f, rootBody).c_str()); - - SystemPath pa = system->GetPath(); - RefCountedPtr sec = galaxy->GetSector(pa); - - fprintf(f, "system:add_to_sector(%d,%d,%d,v(%.4f,%.4f,%.4f))\n", - pa.sectorX, pa.sectorY, pa.sectorZ, - sec->m_systems[pa.systemIndex].GetPosition().x / Sector::SIZE, - sec->m_systems[pa.systemIndex].GetPosition().y / Sector::SIZE, - sec->m_systems[pa.systemIndex].GetPosition().z / Sector::SIZE); - } - - static void EditName(StarSystem *system, Random &rng, UndoSystem *undo) - { - float buttonSize = ImGui::GetFrameHeight(); - ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - buttonSize - ImGui::GetStyle().ItemSpacing.x); - - ImGui::InputText("##Name", &system->m_name); - if (Draw::UndoHelper("Edit System Name", undo)) - AddUndoSingleValue(undo, &system->m_name); - - ImGui::SameLine(); - if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { - system->m_name.clear(); - NameGenerator::GetSystemName(*&system->m_name, rng); - } - if (Draw::UndoHelper("Edit System Name", undo)) - AddUndoSingleValue(undo, &system->m_name); - - ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextUnformatted("Name"); - } - - static void EditProperties(StarSystem *system, UndoSystem *undo) - { - // TODO: other names - - ImGui::InputText("Short Description", &system->m_shortDesc); - if (Draw::UndoHelper("Edit System Short Description", undo)) - AddUndoSingleValue(undo, &system->m_shortDesc); - - ImGui::InputTextMultiline("Long Description", &system->m_longDesc, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); - if (Draw::UndoHelper("Edit System Long Description", undo)) - AddUndoSingleValue(undo, &system->m_longDesc); - - ImGui::SeparatorText("Generation Parameters"); - - ImGui::InputInt("Seed", reinterpret_cast(&system->m_seed)); - if (Draw::UndoHelper("Edit Seed", undo)) - AddUndoSingleValue(undo, &system->m_seed); - - bool explored = system->m_explored == ExplorationState::eEXPLORED_AT_START; - ImGui::Checkbox("Explored", &explored); - if (Draw::UndoHelper("Edit System Explored", undo)) - AddUndoSingleValue(undo, &system->m_explored, explored ? eEXPLORED_AT_START : eUNEXPLORED); - - ImGui::SeparatorText("Economic Parameters"); - - // TODO: faction - - Draw::EditEnum("Edit System Government", "Government", "PolitGovType", - reinterpret_cast(&system->m_polit.govType), Polit::GovType::GOV_MAX - 1, undo); - - ImGui::InputFixed("Lawlessness", &system->m_polit.lawlessness); - if (Draw::UndoHelper("Edit System Lawlessness", undo)) - AddUndoSingleValue(undo, &system->m_polit.lawlessness); - } +private: + SystemEditor *m_editor; + SystemBody *m_selection; }; SystemEditor::SystemEditor(EditorApp *app) : @@ -489,8 +76,8 @@ bool SystemEditor::LoadSystem(const std::string &filepath) return false; SystemPath path = {csys->sectorX, csys->sectorY, csys->sectorZ, csys->systemIndex}; - Uint32 _init[6] = { csys->systemIndex, Uint32(csys->sectorX), Uint32(csys->sectorY), Uint32(csys->sectorZ), UNIVERSE_SEED, Uint32(csys->seed) }; - Random rng(_init, 6); + Uint32 _init[5] = { Uint32(csys->seed), Uint32(csys->sectorX), Uint32(csys->sectorY), Uint32(csys->sectorZ), UNIVERSE_SEED }; + Random rng(_init, 5); RefCountedPtr system(new StarSystem::GeneratorAPI(path, m_galaxy, nullptr, rng)); auto customStage = std::make_unique(); @@ -634,6 +221,34 @@ void SystemEditor::DrawOutliner() ImGui::Spacing(); + Draw::BeginHorizontalBar(); + + if (ImGui::Button("A##AddBody")) { + SystemBody *parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); + SystemBody *body = StarSystem::EditorAPI::NewBody(m_system.Get()); + + GetUndo()->BeginEntry("Add Body"); + GetUndo()->AddUndoStep(m_system.Get(), body); + GetUndo()->AddUndoStep(parent, body); + GetUndo()->AddUndoStep(this, body); + GetUndo()->EndEntry(); + } + + bool canDeleteBody = m_selectedBody && m_selectedBody != m_system->GetRootBody().Get(); + if (canDeleteBody && ImGui::Button("D##DeleteBody")) { + SystemBody *parent = m_selectedBody->GetParent(); + auto iter = std::find(parent->GetChildren().begin(), parent->GetChildren().end(), m_selectedBody); + size_t idx = std::distance(parent->GetChildren().begin(), iter); + + GetUndo()->BeginEntry("Delete Body"); + GetUndo()->AddUndoStep(parent, idx); + GetUndo()->AddUndoStep(m_system.Get(), nullptr, m_selectedBody, true); + GetUndo()->AddUndoStep(this, nullptr); + GetUndo()->EndEntry(); + } + + Draw::EndHorizontalBar(); + if (ImGui::BeginChild("OutlinerList")) { std::vector> m_systemStack { { m_system->GetRootBody().Get(), 0 } diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 091383a4c05..b309b583256 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -7,7 +7,6 @@ #include "core/Application.h" #include "pigui/PiGui.h" #include "RefCounted.h" -#include "editor/UndoSystem.h" #include @@ -19,6 +18,7 @@ class CustomSystemsDatabase; namespace Editor { class EditorApp; +class UndoSystem; class SystemEditor : public Application::Lifecycle { public: @@ -52,6 +52,8 @@ class SystemEditor : public Application::Lifecycle { UndoSystem *GetUndo() { return m_undo.get(); } private: + class UndoSetSelection; + EditorApp *m_app; RefCountedPtr m_galaxy; diff --git a/src/editor/system/SystemEditorHelpers.h b/src/editor/system/SystemEditorHelpers.h new file mode 100644 index 00000000000..b769b922083 --- /dev/null +++ b/src/editor/system/SystemEditorHelpers.h @@ -0,0 +1,37 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "imgui/imgui.h" +#include "imgui/imgui_stdlib.h" + +#include "fixed.h" +#include "libs.h" + +namespace ImGui { + inline bool InputFixed(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1, const char *format = "%.4f", ImGuiInputTextFlags flags = 0) + { + double val_d = val->ToDouble(); + bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); + if (changed) + *val = fixed::FromDouble(val_d); + + return changed; + } + + inline bool InputFixedAU(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1) + { + return InputFixed(str, val, step, step_fast, "%.4f AU"); + } + + inline bool InputFixedDegrees(const char *str, fixed *val, double step = 1.0, double step_fast = 10.0, const char *format = "%.3f°", ImGuiInputTextFlags flags = 0) + { + double val_d = RAD2DEG(val->ToDouble()); + bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); + if (changed) + *val = fixed::FromDouble(DEG2RAD(val_d)); + + return changed; + } +}; From b123d11f9d0050a3b47656a117860e927d17ff32 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 17 Aug 2023 20:47:09 -0400 Subject: [PATCH 08/50] Add tree node drag-drop reordering helper func - Handles dropping a node before, after, or inside of a tree node - Generic enough to support multiple datatypes --- src/editor/EditorDraw.cpp | 82 +++++++++++++++++++++++++++++++++++++++ src/editor/EditorDraw.h | 10 +++++ 2 files changed, 92 insertions(+) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 25f55e0d2a6..0edc85ad52b 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -254,3 +254,85 @@ bool Draw::ColorEdit3(const char *label, Color *color) *color = Color(_c); return changed; } + +Draw::DragDropTarget Draw::HierarchyDragDrop(const char *type, ImGuiID targetID, void *data, void *outData, size_t dataSize) +{ + ImGuiContext &g = *ImGui::GetCurrentContext(); + + ImU32 col_highlight = ImGui::GetColorU32(ImGuiCol_ButtonHovered); + ImU32 col_trans = ImGui::GetColorU32(ImGuiCol_ButtonHovered, 0.f); + + Draw::DragDropTarget ret = DragDropTarget::DROP_NONE; + + ImGui::PushID(targetID); + + if (ImGui::BeginDragDropSource()) { + ImGui::SetDragDropPayload(type, data, dataSize); + ImGui::EndDragDropSource(); + } + + ImVec2 min = ImGui::GetItemRectMin(); + ImVec2 max = ImGui::GetItemRectMax(); + float halfHeight = ImGui::GetItemRectSize().y * 0.4f; + float text_offset = g.FontSize + ImGui::GetStyle().FramePadding.x * 2.f; + float inner_x = ImGui::GetCursorScreenPos().x + text_offset; + + ImGuiID beforeTarget = ImGui::GetID("##drop_before"); + ImGuiID afterTarget = ImGui::GetID("##drop_after"); + ImGuiID innerTarget = ImGui::GetID("##drop-in"); + + ImRect beforeRect(min.x, min.y, max.x, min.y + halfHeight); + ImRect afterRect(min.x, max.y - halfHeight, max.x, max.y); + ImRect innerRect(inner_x, min.y, max.x, max.y); + + if (ImGui::BeginDragDropTargetCustom(beforeRect, beforeTarget)) { + const ImGuiPayload *payload = ImGui::AcceptDragDropPayload(type, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect); + if (payload && payload->Preview) { + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(beforeRect.Min, beforeRect.Max, col_highlight, col_highlight, col_trans, col_trans); + } + + if (payload && payload->Delivery) { + assert(size_t(payload->DataSize) == dataSize); + memcpy(outData, payload->Data, payload->DataSize); + + ret = DragDropTarget::DROP_BEFORE; + } + + ImGui::EndDragDropTarget(); + } + + if (ImGui::BeginDragDropTargetCustom(afterRect, afterTarget)) { + const ImGuiPayload *payload = ImGui::AcceptDragDropPayload(type, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect); + if (payload && payload->Preview) { + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(afterRect.Min, afterRect.Max, col_trans, col_trans, col_highlight, col_highlight); + } + + if (payload && payload->Delivery) { + assert(size_t(payload->DataSize) == dataSize); + memcpy(outData, payload->Data, payload->DataSize); + + ret = DragDropTarget::DROP_AFTER; + } + + ImGui::EndDragDropTarget(); + } + + if (ImGui::BeginDragDropTargetCustom(innerRect, innerTarget)) { + const ImGuiPayload *payload = ImGui::AcceptDragDropPayload(type, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect); + if (payload && payload->Preview) { + ImGui::GetWindowDrawList()->AddRectFilledMultiColor(innerRect.Min, innerRect.Max, col_trans, col_highlight, col_highlight, col_trans); + } + + if (payload && payload->Delivery) { + assert(size_t(payload->DataSize) == dataSize); + memcpy(outData, payload->Data, payload->DataSize); + + ret = DragDropTarget::DROP_CHILD; + } + + ImGui::EndDragDropTarget(); + } + + ImGui::PopID(); + return ret; +} diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 8ade52c6214..ce9df7a3959 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -76,6 +76,16 @@ namespace Editor::Draw { // Color edit button bool ColorEdit3(const char *label, Color4ub *color); + enum DragDropTarget { + DROP_NONE = 0, + DROP_BEFORE = 1, + DROP_CHILD = 2, + DROP_AFTER = 3 + }; + + // Begin tri-mode drag-drop handling on a + DragDropTarget HierarchyDragDrop(const char *type, ImGuiID targetID, void *data, void *outData, size_t dataSize); + } inline bool operator==(const ImVec2 &a, const ImVec2 &b) From 8b7f43a241d6508e80e19f65c76b3ca2ee1b0704 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 17 Aug 2023 20:47:42 -0400 Subject: [PATCH 09/50] Add drag/drop and automatic body index updates --- src/editor/system/GalaxyEditAPI.cpp | 41 +++++++++++++ src/editor/system/GalaxyEditAPI.h | 1 + src/editor/system/SystemBodyUndo.h | 39 ++++++++++--- src/editor/system/SystemEditor.cpp | 91 ++++++++++++++++++++++++----- src/editor/system/SystemEditor.h | 11 +++- 5 files changed, 162 insertions(+), 21 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index f20434677db..fb94aaca87e 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -75,6 +75,34 @@ void StarSystem::EditorAPI::RemoveBody(StarSystem *system, SystemBody *body) system->m_bodies.erase(iter); } +void StarSystem::EditorAPI::ReorderBodyIndex(StarSystem *system) +{ + size_t index = 0; + system->GetRootBody()->m_path.bodyIndex = index++; + + std::vector> orderStack { + { system->GetRootBody().Get(), 0 } + }; + + while (!orderStack.empty()) { + auto &pair = orderStack.back(); + + if (pair.second >= pair.first->GetNumChildren()) { + orderStack.pop_back(); + continue; + } + + SystemBody *body = pair.first->GetChildren()[pair.second++]; + orderStack.push_back({ body, 0 }); + + body->m_path.bodyIndex = index ++; + } + + std::sort(system->m_bodies.begin(), system->m_bodies.end(), [](auto a, auto b) { + return a->m_path.bodyIndex < b->m_path.bodyIndex; + }); +} + void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem *undo) { float buttonSize = ImGui::GetFrameHeight(); @@ -279,6 +307,19 @@ SystemBody *SystemBody::EditorAPI::RemoveChild(SystemBody *parent, size_t idx) return outBody; } +size_t SystemBody::EditorAPI::GetIndexInParent(SystemBody *body) +{ + SystemBody *parent = body->GetParent(); + if (!parent) + return size_t(-1); + + auto iter = std::find(parent->GetChildren().begin(), parent->GetChildren().end(), body); + if (iter == parent->GetChildren().end()) + return size_t(-1); + + return std::distance(parent->GetChildren().begin(), iter); +} + void SystemBody::EditorAPI::UpdateBodyOrbit(SystemBody *body) { body->m_orbMin = body->m_semiMajorAxis - body->m_eccentricity * body->m_semiMajorAxis; diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index e60f18d0c06..87775679036 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -33,6 +33,7 @@ class SystemBody::EditorAPI { static void AddChild(SystemBody *parent, SystemBody *child, size_t idx = -1); static SystemBody *RemoveChild(SystemBody *parent, size_t idx = -1); + static size_t GetIndexInParent(SystemBody *body); static void UpdateBodyOrbit(SystemBody *body); static void UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent); diff --git a/src/editor/system/SystemBodyUndo.h b/src/editor/system/SystemBodyUndo.h index 6405a37bd78..caed7ba7bdf 100644 --- a/src/editor/system/SystemBodyUndo.h +++ b/src/editor/system/SystemBodyUndo.h @@ -9,11 +9,11 @@ #include "galaxy/StarSystem.h" #include "galaxy/SystemBody.h" -namespace Editor::SystemEditorHelpers { +namespace Editor::SystemEditorUndo { - class UndoManageStarSystemBody : public UndoStep { + class ManageStarSystemBody : public UndoStep { public: - UndoManageStarSystemBody(StarSystem *system, SystemBody *add, SystemBody *rem = nullptr, bool apply = false) : + ManageStarSystemBody(StarSystem *system, SystemBody *add, SystemBody *rem = nullptr, bool apply = false) : m_system(system), m_addBody(add), m_remBody(rem) @@ -36,10 +36,35 @@ namespace Editor::SystemEditorHelpers { RefCountedPtr m_remBody; }; + // Helper to reorder body indexes at the end of an undo / redo operation by adding two undo steps + class ReorderStarSystemBodies : public UndoStep { + public: + ReorderStarSystemBodies(StarSystem *system, bool onRedo = false) : + m_system(system), + m_onRedo(onRedo) + { + Redo(); + } + + void Undo() override { + if (!m_onRedo) + StarSystem::EditorAPI::ReorderBodyIndex(m_system); + } + + void Redo() override { + if (m_onRedo) + StarSystem::EditorAPI::ReorderBodyIndex(m_system); + } + + private: + StarSystem *m_system; + bool m_onRedo; + }; + // UndoStep helper to handle adding or deleting a child SystemBody from a parent - class UndoAddRemoveChildBody : public UndoStep { + class AddRemoveChildBody : public UndoStep { public: - UndoAddRemoveChildBody(SystemBody *parent, SystemBody *add, size_t idx) : + AddRemoveChildBody(SystemBody *parent, SystemBody *add, size_t idx) : m_parent(parent), m_add(add), m_idx(idx) @@ -47,7 +72,7 @@ namespace Editor::SystemEditorHelpers { Swap(); } - UndoAddRemoveChildBody(SystemBody *parent, SystemBody *add) : + AddRemoveChildBody(SystemBody *parent, SystemBody *add) : m_parent(parent), m_add(add), m_idx(-1) @@ -55,7 +80,7 @@ namespace Editor::SystemEditorHelpers { Swap(); } - UndoAddRemoveChildBody(SystemBody *parent, size_t idx) : + AddRemoveChildBody(SystemBody *parent, size_t idx) : m_parent(parent), m_add(nullptr), m_idx(idx) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 6dfac560e6f..e85ac2eabff 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -55,7 +55,8 @@ class SystemEditor::UndoSetSelection : public UndoStep { SystemEditor::SystemEditor(EditorApp *app) : m_app(app), m_undo(new UndoSystem()), - m_selectedBody(nullptr) + m_selectedBody(nullptr), + m_pendingReparent() { GalacticEconomy::Init(); @@ -147,6 +148,19 @@ void SystemEditor::Update(float deltaTime) DrawInterface(); + if (m_pendingReparent.body) { + size_t parentIdx = SystemBody::EditorAPI::GetIndexInParent(m_pendingReparent.body); + + GetUndo()->BeginEntry("Reorder Body"); + GetUndo()->AddUndoStep(m_system.Get()); + GetUndo()->AddUndoStep(m_pendingReparent.body->GetParent(), parentIdx); + GetUndo()->AddUndoStep(m_pendingReparent.parent, m_pendingReparent.body, m_pendingReparent.idx); + GetUndo()->AddUndoStep(m_system.Get(), true); + GetUndo()->EndEntry(); + + m_pendingReparent = {}; + } + if (ImGui::IsKeyPressed(ImGuiKey_F1)) { WriteSystem(m_filepath); } @@ -212,6 +226,8 @@ void SystemEditor::DrawOutliner() { ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + std::string name = m_system.Valid() ? m_system->GetName() : ""; + std::string label = fmt::format("System: {}", m_system->GetName()); if (ImGui::Selectable(label.c_str(), !m_selectedBody)) { m_selectedBody = nullptr; @@ -219,6 +235,10 @@ void SystemEditor::DrawOutliner() ImGui::PopFont(); + if (!m_system) { + return; + } + ImGui::Spacing(); Draw::BeginHorizontalBar(); @@ -228,21 +248,41 @@ void SystemEditor::DrawOutliner() SystemBody *body = StarSystem::EditorAPI::NewBody(m_system.Get()); GetUndo()->BeginEntry("Add Body"); - GetUndo()->AddUndoStep(m_system.Get(), body); - GetUndo()->AddUndoStep(parent, body); + GetUndo()->AddUndoStep(m_system.Get()); + GetUndo()->AddUndoStep(m_system.Get(), body); + GetUndo()->AddUndoStep(parent, body); + GetUndo()->AddUndoStep(m_system.Get(), true); GetUndo()->AddUndoStep(this, body); GetUndo()->EndEntry(); } bool canDeleteBody = m_selectedBody && m_selectedBody != m_system->GetRootBody().Get(); if (canDeleteBody && ImGui::Button("D##DeleteBody")) { - SystemBody *parent = m_selectedBody->GetParent(); - auto iter = std::find(parent->GetChildren().begin(), parent->GetChildren().end(), m_selectedBody); - size_t idx = std::distance(parent->GetChildren().begin(), iter); + + std::vector toDelete { m_selectedBody }; + size_t sliceBegin = 0; + + while (sliceBegin < toDelete.size()) { + size_t sliceEnd = toDelete.size(); + for (size_t idx = sliceBegin; idx < sliceEnd; idx++) { + if (toDelete[idx]->HasChildren()) + for (auto &child : toDelete[idx]->GetChildren()) + toDelete.push_back(child); + } + sliceBegin = sliceEnd; + } GetUndo()->BeginEntry("Delete Body"); - GetUndo()->AddUndoStep(parent, idx); - GetUndo()->AddUndoStep(m_system.Get(), nullptr, m_selectedBody, true); + GetUndo()->AddUndoStep(m_system.Get()); + + for (auto &child : reverse_container(toDelete)) { + SystemBody *parent = child->GetParent(); + size_t idx = SystemBody::EditorAPI::GetIndexInParent(child); + GetUndo()->AddUndoStep(parent, idx); + GetUndo()->AddUndoStep(m_system.Get(), nullptr, child, true); + } + + GetUndo()->AddUndoStep(m_system.Get(), true); GetUndo()->AddUndoStep(this, nullptr); GetUndo()->EndEntry(); } @@ -254,7 +294,7 @@ void SystemEditor::DrawOutliner() { m_system->GetRootBody().Get(), 0 } }; - if (!DrawBodyNode(m_system->GetRootBody().Get())) { + if (!DrawBodyNode(m_system->GetRootBody().Get(), true)) { ImGui::EndChild(); return; } @@ -262,27 +302,48 @@ void SystemEditor::DrawOutliner() while (!m_systemStack.empty()) { auto &pair = m_systemStack.back(); - if (pair.second == pair.first->GetNumChildren()) { + if (pair.second >= pair.first->GetNumChildren()) { m_systemStack.pop_back(); ImGui::TreePop(); continue; } SystemBody *body = pair.first->GetChildren()[pair.second++]; - if (DrawBodyNode(body)) + if (DrawBodyNode(body, false)) m_systemStack.push_back({ body, 0 }); } } ImGui::EndChild(); } -bool SystemEditor::DrawBodyNode(SystemBody *body) +void SystemEditor::HandleOutlinerDragDrop(SystemBody *refBody) +{ + // Handle drag-drop re-order/re-parent + SystemBody *dropBody = nullptr; + Draw::DragDropTarget dropTarget = Draw::HierarchyDragDrop("SystemBody", ImGui::GetID(refBody), &refBody, &dropBody, sizeof(SystemBody *)); + + if (dropTarget != Draw::DragDropTarget::DROP_NONE && refBody != dropBody) { + size_t targetIdx = SystemBody::EditorAPI::GetIndexInParent(refBody); + + m_pendingReparent.body = dropBody; + m_pendingReparent.parent = dropTarget == Draw::DROP_CHILD ? refBody : refBody->GetParent(); + m_pendingReparent.idx = 0; + + if (dropTarget == Draw::DROP_BEFORE) + m_pendingReparent.idx = targetIdx; + else if (dropTarget == Draw::DROP_AFTER) + m_pendingReparent.idx = targetIdx + 1; + } +} + +bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_SpanFullWidth; + ImGuiTreeNodeFlags_SpanFullWidth | + ImGuiTreeNodeFlags_FramePadding; if (body->GetNumChildren() == 0) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -292,6 +353,10 @@ bool SystemEditor::DrawBodyNode(SystemBody *body) bool open = ImGui::TreeNodeEx(body->GetName().c_str(), flags); + if (!isRoot) { + HandleOutlinerDragDrop(body); + } + if (ImGui::IsItemActivated()) { m_selectedBody = body; } diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index b309b583256..2e140bca79c 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -39,7 +39,8 @@ class SystemEditor : public Application::Lifecycle { void SetupLayout(ImGuiID dockspaceID); void DrawInterface(); - bool DrawBodyNode(SystemBody *body); + bool DrawBodyNode(SystemBody *body, bool isRoot); + void HandleOutlinerDragDrop(SystemBody *refBody); void DrawOutliner(); void DrawBodyProperties(); @@ -65,6 +66,14 @@ class SystemEditor : public Application::Lifecycle { std::string m_filepath; SystemBody *m_selectedBody; + + struct ReparentRequest { + SystemBody *parent = nullptr; + SystemBody *body = nullptr; + size_t idx = 0; + }; + + ReparentRequest m_pendingReparent; }; } // namespace Editor From 1aa1d9b8eef812ee625c24c9a93172a2185456f9 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 19 Aug 2023 04:35:39 -0400 Subject: [PATCH 10/50] SystemEditor: add body icons --- src/editor/EditorIcons.h | 4 ++-- src/editor/system/SystemEditor.cpp | 28 +++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h index b43b2a01ab1..3bbbe0314b7 100644 --- a/src/editor/EditorIcons.h +++ b/src/editor/EditorIcons.h @@ -3,14 +3,14 @@ #pragma once -#define EICON_GRAVPOINT "\uF01F" #define EICON_SUN "\uF023" #define EICON_ASTEROID "\uF024" +#define EICON_GRAVPOINT "\uF025" #define EICON_ROCKY_PLANET "\uF033" #define EICON_MOON "\uF043" #define EICON_GAS_GIANT "\uF053" #define EICON_SPACE_STATION "\uF063" -#define EICON_SURFACE_STATION "\uF0F2" +#define EICON_SURFACE_STATION "\uF073" #define EICON_PAUSE "\uF055" #define EICON_PLAY "\uF056" diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index e85ac2eabff..424521eb978 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -3,6 +3,7 @@ #include "SystemEditor.h" +#include "EditorIcons.h" #include "GalaxyEditAPI.h" #include "SystemEditorHelpers.h" @@ -32,6 +33,25 @@ namespace { static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; + const char *GetBodyIcon(SystemBody *body) { + if (body->GetType() == SystemBody::TYPE_GRAVPOINT) + return EICON_GRAVPOINT; + if (body->GetType() == SystemBody::TYPE_STARPORT_ORBITAL) + return EICON_SPACE_STATION; + if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) + return EICON_SURFACE_STATION; + if (body->GetType() == SystemBody::TYPE_PLANET_ASTEROID) + return EICON_ASTEROID; + if (body->GetSuperType() == SystemBody::SUPERTYPE_ROCKY_PLANET) + return (!body->GetParent() || body->GetParent()->GetSuperType() < SystemBody::SUPERTYPE_ROCKY_PLANET) ? + EICON_ROCKY_PLANET : EICON_MOON; + if (body->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT) + return EICON_GAS_GIANT; + if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) + return EICON_SUN; + + return "?"; + } } class SystemEditor::UndoSetSelection : public UndoStep { @@ -289,12 +309,15 @@ void SystemEditor::DrawOutliner() Draw::EndHorizontalBar(); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); if (ImGui::BeginChild("OutlinerList")) { std::vector> m_systemStack { { m_system->GetRootBody().Get(), 0 } }; if (!DrawBodyNode(m_system->GetRootBody().Get(), true)) { + ImGui::PopFont(); ImGui::EndChild(); return; } @@ -314,6 +337,7 @@ void SystemEditor::DrawOutliner() } } ImGui::EndChild(); + ImGui::PopFont(); } void SystemEditor::HandleOutlinerDragDrop(SystemBody *refBody) @@ -351,7 +375,9 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) if (body == m_selectedBody) flags |= ImGuiTreeNodeFlags_Selected; - bool open = ImGui::TreeNodeEx(body->GetName().c_str(), flags); + ImGuiID bodyId = ImGui::GetID(body); + std::string name = fmt::format("{} {}###{:x}", GetBodyIcon(body), body->GetName(), bodyId); + bool open = ImGui::TreeNodeEx(name.c_str(), flags); if (!isRoot) { HandleOutlinerDragDrop(body); From a6d9bbd48ce6e40ad8c356bf61a275e773d68299 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 22 Aug 2023 18:50:27 -0400 Subject: [PATCH 11/50] Editor: pass closure by value Allows using the same pre-created closure multiple times. --- src/editor/UndoStepType.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/UndoStepType.h b/src/editor/UndoStepType.h index d3ada5b228b..841e4d7e5b0 100644 --- a/src/editor/UndoStepType.h +++ b/src/editor/UndoStepType.h @@ -97,13 +97,13 @@ namespace Editor { } template - inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, UpdateClosure &&closure) + inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, UpdateClosure closure) { s->AddUndoStep>(dataRef, std::move(closure)); } template - inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, const T &newValue, UpdateClosure &&closure) + inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, const T &newValue, UpdateClosure closure) { s->AddUndoStep>(dataRef, newValue, std::move(closure)); } From 8b55c7aaa09fe27c1dcec125e65a69160ab7ba1b Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 22 Aug 2023 18:50:38 -0400 Subject: [PATCH 12/50] SystemEditor: Update body orbits on undo --- src/editor/system/GalaxyEditAPI.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index fb94aaca87e..3145effbb2a 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -348,14 +348,15 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * ImGui::SeparatorText("Orbital Parameters"); bool orbitChanged = false; + auto updateBodyOrbit = [=](){ UpdateBodyOrbit(body); }; orbitChanged |= ImGui::InputFixedAU("Semi-Major Axis", &body->m_semiMajorAxis); if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) - AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, [=](){ UpdateBodyOrbit(body); }); + AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, updateBodyOrbit); orbitChanged |= ImGui::InputFixed("Eccentricity", &body->m_eccentricity); if (Draw::UndoHelper("Edit Eccentricity", undo)) - AddUndoSingleValueClosure(undo, &body->m_eccentricity, [=](){ UpdateBodyOrbit(body); }); + AddUndoSingleValueClosure(undo, &body->m_eccentricity, updateBodyOrbit); ImGui::BeginDisabled(); ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); @@ -364,27 +365,27 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * orbitChanged |= ImGui::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); if (Draw::UndoHelper("Edit Axial Tilt", undo)) - AddUndoSingleValue(undo, &body->m_axialTilt); + AddUndoSingleValueClosure(undo, &body->m_axialTilt, updateBodyOrbit); orbitChanged |= ImGui::InputFixedDegrees("Inclination", &body->m_inclination); if (Draw::UndoHelper("Edit Inclination", undo)) - AddUndoSingleValue(undo, &body->m_inclination); + AddUndoSingleValueClosure(undo, &body->m_inclination, updateBodyOrbit); orbitChanged |= ImGui::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); if (Draw::UndoHelper("Edit Orbital Offset", undo)) - AddUndoSingleValue(undo, &body->m_orbitalOffset); + AddUndoSingleValueClosure(undo, &body->m_orbitalOffset, updateBodyOrbit); orbitChanged |= ImGui::InputFixedDegrees("Orbital Phase at Start", &body->m_orbitalPhaseAtStart); if (Draw::UndoHelper("Edit Orbital Phase at Start", undo)) - AddUndoSingleValue(undo, &body->m_orbitalPhaseAtStart); + AddUndoSingleValueClosure(undo, &body->m_orbitalPhaseAtStart, updateBodyOrbit); orbitChanged |= ImGui::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); if (Draw::UndoHelper("Edit Rotational Phase at Start", undo)) - AddUndoSingleValue(undo, &body->m_rotationalPhaseAtStart); + AddUndoSingleValueClosure(undo, &body->m_rotationalPhaseAtStart, updateBodyOrbit); orbitChanged |= ImGui::InputFixed("Rotation Period (Days)", &body->m_rotationPeriod, 1.0, 10.0); if (Draw::UndoHelper("Edit Rotation Period", undo)) - AddUndoSingleValue(undo, &body->m_rotationPeriod); + AddUndoSingleValueClosure(undo, &body->m_rotationPeriod, updateBodyOrbit); if (orbitChanged) UpdateBodyOrbit(body); From f2294cd093a90b0d26b94c7900fec1058236f573 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 22 Aug 2023 18:51:42 -0400 Subject: [PATCH 13/50] SystemEditor: integrate SystemMapViewport Icon rendering etc. is only an initial prototype, but it has achieved minimum viable functionality. --- src/editor/system/SystemEditor.cpp | 61 +++++---- src/editor/system/SystemEditor.h | 20 ++- src/editor/system/SystemEditorViewport.cpp | 146 +++++++++++++++++++++ src/editor/system/SystemEditorViewport.h | 44 +++++++ 4 files changed, 247 insertions(+), 24 deletions(-) create mode 100644 src/editor/system/SystemEditorViewport.cpp create mode 100644 src/editor/system/SystemEditorViewport.h diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 424521eb978..9711b064372 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -18,11 +18,12 @@ #include "galaxy/Galaxy.h" #include "galaxy/GalaxyGenerator.h" #include "galaxy/StarSystemGenerator.h" - #include "lua/Lua.h" +#include "pigui/PiGui.h" #include "imgui/imgui.h" #include "system/SystemBodyUndo.h" +#include "system/SystemEditorViewport.h" #include @@ -32,26 +33,26 @@ namespace { static constexpr const char *OUTLINE_WND_ID = "Outline"; static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; +} - const char *GetBodyIcon(SystemBody *body) { - if (body->GetType() == SystemBody::TYPE_GRAVPOINT) - return EICON_GRAVPOINT; - if (body->GetType() == SystemBody::TYPE_STARPORT_ORBITAL) - return EICON_SPACE_STATION; - if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) - return EICON_SURFACE_STATION; - if (body->GetType() == SystemBody::TYPE_PLANET_ASTEROID) - return EICON_ASTEROID; - if (body->GetSuperType() == SystemBody::SUPERTYPE_ROCKY_PLANET) - return (!body->GetParent() || body->GetParent()->GetSuperType() < SystemBody::SUPERTYPE_ROCKY_PLANET) ? - EICON_ROCKY_PLANET : EICON_MOON; - if (body->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT) - return EICON_GAS_GIANT; - if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) - return EICON_SUN; - - return "?"; - } +const char *Editor::GetBodyIcon(const SystemBody *body) { + if (body->GetType() == SystemBody::TYPE_GRAVPOINT) + return EICON_GRAVPOINT; + if (body->GetType() == SystemBody::TYPE_STARPORT_ORBITAL) + return EICON_SPACE_STATION; + if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) + return EICON_SURFACE_STATION; + if (body->GetType() == SystemBody::TYPE_PLANET_ASTEROID) + return EICON_ASTEROID; + if (body->GetSuperType() == SystemBody::SUPERTYPE_ROCKY_PLANET) + return (!body->GetParent() || body->GetParent()->GetSuperType() < SystemBody::SUPERTYPE_ROCKY_PLANET) ? + EICON_ROCKY_PLANET : EICON_MOON; + if (body->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT) + return EICON_GAS_GIANT; + if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) + return EICON_SUN; + + return "?"; } class SystemEditor::UndoSetSelection : public UndoStep { @@ -83,6 +84,8 @@ SystemEditor::SystemEditor(EditorApp *app) : m_galaxy = GalaxyGenerator::Create(); m_systemLoader.reset(new CustomSystemsDatabase(m_galaxy.Get(), "systems")); + m_viewport.reset(new SystemEditorViewport(m_app, this)); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } @@ -125,6 +128,8 @@ bool SystemEditor::LoadSystem(const std::string &filepath) m_system = system; m_filepath = filepath; + m_viewport->SetSystem(system); + return true; } @@ -142,6 +147,15 @@ void SystemEditor::WriteSystem(const std::string &filepath) fclose(f); } +// Here to avoid needing to drag in the Galaxy header in SystemEditor.h +RefCountedPtr SystemEditor::GetGalaxy() { return m_galaxy; } + +void SystemEditor::SetSelectedBody(SystemBody *body) +{ + // note: using const_cast here to work with Projectables which store a const pointer + m_selectedBody = body; +} + void SystemEditor::Start() { } @@ -195,8 +209,8 @@ void SystemEditor::SetupLayout(ImGuiID dockspaceID) ImGui::DockBuilderSetNodePos(nodeID, ImGui::GetWindowPos()); ImGui::DockBuilderSetNodeSize(nodeID, ImGui::GetWindowSize()); - ImGuiID leftSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Left, 0.25, nullptr, &nodeID); - ImGuiID rightSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Right, 0.25 / (1.0 - 0.25), nullptr, &nodeID); + ImGuiID leftSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Left, 0.2, nullptr, &nodeID); + ImGuiID rightSide = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Right, 0.2 / (1.0 - 0.2), nullptr, &nodeID); // ImGuiID bottom = ImGui::DockBuilderSplitNode(nodeID, ImGuiDir_Down, 0.2, nullptr, &nodeID); ImGui::DockBuilderDockWindow(OUTLINE_WND_ID, leftSide); @@ -209,6 +223,7 @@ void SystemEditor::SetupLayout(ImGuiID dockspaceID) void SystemEditor::DrawInterface() { Draw::ShowUndoDebugWindow(GetUndo()); + ImGui::ShowMetricsWindow(); static bool isFirstRun = true; @@ -236,6 +251,8 @@ void SystemEditor::DrawInterface() } ImGui::End(); + m_viewport->Update(m_app->DeltaTime()); + ImGui::End(); if (isFirstRun) diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 2e140bca79c..4fce91fcc45 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -4,12 +4,15 @@ #pragma once #include "Input.h" -#include "core/Application.h" -#include "pigui/PiGui.h" +#include "Random.h" #include "RefCounted.h" +#include "core/Application.h" #include +// Forward declaration +typedef unsigned int ImGuiID; + class Galaxy; class StarSystem; class SystemBody; @@ -19,6 +22,9 @@ namespace Editor { class EditorApp; class UndoSystem; +class SystemEditorViewport; + +const char *GetBodyIcon(const SystemBody *body); class SystemEditor : public Application::Lifecycle { public: @@ -28,6 +34,12 @@ class SystemEditor : public Application::Lifecycle { bool LoadSystem(const std::string &filepath); void WriteSystem(const std::string &filepath); + Random &GetRng() { return m_random; } + RefCountedPtr GetGalaxy(); + + void SetSelectedBody(SystemBody *body); + SystemBody *GetSystemBody(); + protected: void Start() override; void Update(float deltaTime) override; @@ -61,6 +73,10 @@ class SystemEditor : public Application::Lifecycle { RefCountedPtr m_system; std::unique_ptr m_systemLoader; + std::unique_ptr m_viewport; + + Random m_random; + std::unique_ptr m_undo; std::string m_filepath; diff --git a/src/editor/system/SystemEditorViewport.cpp b/src/editor/system/SystemEditorViewport.cpp new file mode 100644 index 00000000000..3cca0f20eed --- /dev/null +++ b/src/editor/system/SystemEditorViewport.cpp @@ -0,0 +1,146 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "SystemEditorViewport.h" + +#include "EditorIcons.h" +#include "SystemEditor.h" + +#include "Background.h" +#include "SystemView.h" +#include "galaxy/Galaxy.h" +#include "galaxy/StarSystem.h" + +#include "editor/EditorApp.h" +#include "editor/ViewportWindow.h" +#include "pigui/PiGui.h" +#include "system/SystemEditor.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" + +using namespace Editor; + +SystemEditorViewport::SystemEditorViewport(EditorApp *app, SystemEditor *editor) : + ViewportWindow(app), + m_app(app), + m_editor(editor) +{ + m_map.reset(new SystemMapViewport(m_app)); + + m_map->svColor[SystemMapViewport::GRID] = Color(0x24242BFF); + m_map->svColor[SystemMapViewport::GRID_LEG] = Color(0x787878FF); + m_map->svColor[SystemMapViewport::SYSTEMBODY] = Color(0xB5BCE3FF).Shade(0.5); + m_map->svColor[SystemMapViewport::SYSTEMBODY_ORBIT] = Color(0x5ACC0AFF); +} + +SystemEditorViewport::~SystemEditorViewport() +{ +} + +void SystemEditorViewport::SetSystem(RefCountedPtr system) +{ + m_map->SetReferenceTime(0.0); // Jan 1 3200 + m_map->SetCurrentSystem(system); + + m_background.reset(new Background::Container(m_app->GetRenderer(), m_editor->GetRng())); + m_background->SetDrawFlags(Background::Container::DRAW_SKYBOX); + m_map->SetBackground(m_background.get()); +} + +bool SystemEditorViewport::OnCloseRequested() +{ + return false; +} + +void SystemEditorViewport::OnUpdate(float deltaTime) +{ + m_map->Update(deltaTime); +} + +void SystemEditorViewport::OnRender(Graphics::Renderer *r) +{ + m_map->Draw3D(); +} + +void SystemEditorViewport::OnHandleInput(bool clicked, bool released, ImVec2 mousePos) +{ + m_map->HandleInput(m_app->DeltaTime()); +} + +void SystemEditorViewport::OnDraw() +{ + ImDrawListSplitter split {}; + + split.Split(ImGui::GetWindowDrawList(), 2); + split.SetCurrentChannel(ImGui::GetWindowDrawList(), 0); + + split.SetCurrentChannel(ImGui::GetWindowDrawList(), 1); + + ImVec2 windowTL = ImGui::GetWindowPos(); + ImVec2 windowBR = ImVec2(windowTL.x + ImGui::GetWindowSize().x, windowTL.y + ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.f); + ImGui::GetWindowDrawList()->PushClipRect(windowTL, windowBR); + ImGui::GetWindowDrawList()->AddQuadFilled( + windowTL, ImVec2(windowBR.x, windowTL.y), + windowBR, ImVec2(windowTL.x, windowBR.y), + ImColor(1.f, 1.f, 1.f, 0.25f)); + ImGui::GetWindowDrawList()->PopClipRect(); + + if (ImGui::Button("This is a test!")) {} + + // Render all GUI elements over top of viewport overlays + + split.SetCurrentChannel(ImGui::GetWindowDrawList(), 0); + + const auto &projected = m_map->GetProjected(); + std::vector groups = m_map->GroupProjectables({ 10.f, 10.f }, {}); + + // Depth sort groups (further groups drawn first / under closer groups) + std::sort(groups.begin(), groups.end(), [](const auto &a, const auto &b){ return a.screenpos.z > b.screenpos.z; }); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + // Then draw "under" the GUI elements so we can use ImGui::IsItemHovered et al. + for (auto &group : groups) { + ImVec2 itempos = { group.screenpos.x, group.screenpos.y }; + ImVec2 iconSize = { ImGui::GetFontSize(), ImGui::GetFontSize() }; + + const Projectable &item = projected[group.tracks[0]]; + if (group.type == Projectable::OBJECT && item.base == Projectable::SYSTEMBODY) { + ImColor icon_col(0xFFC8C8C8); + const char *bodyName = item.ref.sbody->GetName().c_str(); + + ImVec2 drawPos = windowTL + itempos - iconSize * 0.5f; + ImGui::GetWindowDrawList()->AddText(drawPos, icon_col, GetBodyIcon(item.ref.sbody)); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 12)); + ImVec2 textPos = windowTL + itempos + ImVec2(iconSize.x, -ImGui::GetFontSize() * 0.5f); + ImVec2 textSize = ImGui::CalcTextSize(bodyName); + // label shadow + ImGui::GetWindowDrawList()->AddText(textPos + ImVec2(1.f, 1.f), IM_COL32_BLACK, bodyName); + // body label + ImGui::GetWindowDrawList()->AddText(textPos, icon_col, bodyName); + ImGui::PopFont(); + + ImRect hoverRect(drawPos, drawPos + iconSize); + hoverRect.Add(textPos + textSize); + + if (ImGui::ItemHoverable(hoverRect, 0, 0)) { + ImGui::SetTooltip("%s", bodyName); + + if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + m_map->SetSelectedObject(item); + m_editor->SetSelectedBody(const_cast(item.ref.sbody)); + } + + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + m_map->ViewSelectedObject(); + } + } + } + } + + ImGui::PopFont(); + + split.Merge(ImGui::GetWindowDrawList()); +} diff --git a/src/editor/system/SystemEditorViewport.h b/src/editor/system/SystemEditorViewport.h new file mode 100644 index 00000000000..ed43623e30e --- /dev/null +++ b/src/editor/system/SystemEditorViewport.h @@ -0,0 +1,44 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "ViewportWindow.h" + +namespace Background { + class Container; +} + +class StarSystem; +class SystemMapViewport; + +namespace Editor { + + class SystemEditor; + + class SystemEditorViewport : public ViewportWindow { + public: + SystemEditorViewport(EditorApp *app, SystemEditor *editor); + ~SystemEditorViewport(); + + void SetSystem(RefCountedPtr system); + + protected: + void OnUpdate(float deltaTime) override; + void OnRender(Graphics::Renderer *renderer) override; + + void OnHandleInput(bool clicked, bool released, ImVec2 mousePos) override; + + void OnDraw() override; + bool OnCloseRequested() override; + + const char *GetWindowName() override { return "Viewport"; } + + private: + EditorApp *m_app; + SystemEditor *m_editor; + + std::unique_ptr m_map; + std::unique_ptr m_background; + }; +}; From 505edca5dab7c2ac15f62c2d4b642fe35cccb712 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 28 Aug 2023 20:44:05 -0400 Subject: [PATCH 14/50] SystemEditor: More ergonomic body editing - Move angle parameters to slider inputs - Allow specifying body radius/mass/orbit distance in multiple units - General usability improvement --- src/editor/EditorIcons.h | 2 + src/editor/system/GalaxyEditAPI.cpp | 115 +++++----- src/editor/system/SystemEditor.cpp | 14 ++ src/editor/system/SystemEditorHelpers.cpp | 250 ++++++++++++++++++++++ src/editor/system/SystemEditorHelpers.h | 33 ++- 5 files changed, 348 insertions(+), 66 deletions(-) create mode 100644 src/editor/system/SystemEditorHelpers.cpp diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h index 3bbbe0314b7..da9b589dd07 100644 --- a/src/editor/EditorIcons.h +++ b/src/editor/EditorIcons.h @@ -15,5 +15,7 @@ #define EICON_PAUSE "\uF055" #define EICON_PLAY "\uF056" +#define EICON_RANDOM "\uF0C7" + #define EICON_AXES "\uF0CA" #define EICON_GRID "\uF0CB" diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 3145effbb2a..dce1e68e753 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -105,23 +105,26 @@ void StarSystem::EditorAPI::ReorderBodyIndex(StarSystem *system) void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem *undo) { - float buttonSize = ImGui::GetFrameHeight(); - ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - buttonSize - ImGui::GetStyle().ItemSpacing.x); - - ImGui::InputText("##Name", &system->m_name); - if (Draw::UndoHelper("Edit System Name", undo)) - AddUndoSingleValue(undo, &system->m_name); - - ImGui::SameLine(); - if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { + ImGui::BeginGroup(); + if (Draw::RandomButton()) { system->m_name.clear(); NameGenerator::GetSystemName(*&system->m_name, rng); } + + ImGui::InputText("Name", &system->m_name); + ImGui::EndGroup(); + if (Draw::UndoHelper("Edit System Name", undo)) AddUndoSingleValue(undo, &system->m_name); - ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextUnformatted("Name"); + // ImGui::SameLine(); + // if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { + // } + // if (Draw::UndoHelper("Edit System Name", undo)) + // AddUndoSingleValue(undo, &system->m_name); + + // ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); + // ImGui::TextUnformatted("Name"); } void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) @@ -154,7 +157,7 @@ void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) Draw::EditEnum("Edit System Government", "Government", "PolitGovType", reinterpret_cast(&system->m_polit.govType), Polit::GovType::GOV_MAX - 1, undo); - ImGui::InputFixed("Lawlessness", &system->m_polit.lawlessness); + Draw::InputFixedSlider("Lawlessness", &system->m_polit.lawlessness); if (Draw::UndoHelper("Edit System Lawlessness", undo)) AddUndoSingleValue(undo, &system->m_polit.lawlessness); } @@ -350,40 +353,42 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * bool orbitChanged = false; auto updateBodyOrbit = [=](){ UpdateBodyOrbit(body); }; - orbitChanged |= ImGui::InputFixedAU("Semi-Major Axis", &body->m_semiMajorAxis); + orbitChanged |= Draw::InputFixedDistance("Semi-Major Axis", &body->m_semiMajorAxis); if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) AddUndoSingleValueClosure(undo, &body->m_semiMajorAxis, updateBodyOrbit); - orbitChanged |= ImGui::InputFixed("Eccentricity", &body->m_eccentricity); + orbitChanged |= Draw::InputFixedSlider("Eccentricity", &body->m_eccentricity); if (Draw::UndoHelper("Edit Eccentricity", undo)) AddUndoSingleValueClosure(undo, &body->m_eccentricity, updateBodyOrbit); - ImGui::BeginDisabled(); - ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); - ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); - ImGui::EndDisabled(); - - orbitChanged |= ImGui::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); - if (Draw::UndoHelper("Edit Axial Tilt", undo)) - AddUndoSingleValueClosure(undo, &body->m_axialTilt, updateBodyOrbit); - - orbitChanged |= ImGui::InputFixedDegrees("Inclination", &body->m_inclination); + orbitChanged |= Draw::InputFixedDegrees("Inclination", &body->m_inclination); if (Draw::UndoHelper("Edit Inclination", undo)) AddUndoSingleValueClosure(undo, &body->m_inclination, updateBodyOrbit); - orbitChanged |= ImGui::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); + orbitChanged |= Draw::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); if (Draw::UndoHelper("Edit Orbital Offset", undo)) AddUndoSingleValueClosure(undo, &body->m_orbitalOffset, updateBodyOrbit); - orbitChanged |= ImGui::InputFixedDegrees("Orbital Phase at Start", &body->m_orbitalPhaseAtStart); - if (Draw::UndoHelper("Edit Orbital Phase at Start", undo)) + orbitChanged |= Draw::InputFixedDegrees("Orbital Phase", &body->m_orbitalPhaseAtStart); + if (Draw::UndoHelper("Edit Orbital Phase", undo)) AddUndoSingleValueClosure(undo, &body->m_orbitalPhaseAtStart, updateBodyOrbit); - orbitChanged |= ImGui::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); + ImGui::BeginDisabled(); + ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); + ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); + ImGui::EndDisabled(); + + ImGui::SeparatorText("Rotation Parameters"); + + orbitChanged |= Draw::InputFixedDegrees("Axial Tilt", &body->m_axialTilt); + if (Draw::UndoHelper("Edit Axial Tilt", undo)) + AddUndoSingleValueClosure(undo, &body->m_axialTilt, updateBodyOrbit); + + orbitChanged |= Draw::InputFixedDegrees("Rotation at Start", &body->m_rotationalPhaseAtStart); if (Draw::UndoHelper("Edit Rotational Phase at Start", undo)) AddUndoSingleValueClosure(undo, &body->m_rotationalPhaseAtStart, updateBodyOrbit); - orbitChanged |= ImGui::InputFixed("Rotation Period (Days)", &body->m_rotationPeriod, 1.0, 10.0); + orbitChanged |= ImGui::InputFixed("Rotation Period", &body->m_rotationPeriod, 1.0, 10.0, "%.3f days"); if (Draw::UndoHelper("Edit Rotation Period", undo)) AddUndoSingleValueClosure(undo, &body->m_rotationPeriod, updateBodyOrbit); @@ -409,11 +414,11 @@ void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem if (body->GetType() == TYPE_STARPORT_SURFACE) { ImGui::SeparatorText("Surface Parameters"); - ImGui::InputFixedDegrees("Latitude", &body->m_inclination); + Draw::InputFixedDegrees("Latitude", &body->m_inclination); if (Draw::UndoHelper("Edit Latitude", undo)) AddUndoSingleValue(undo, &body->m_inclination); - ImGui::InputFixedDegrees("Longitude", &body->m_orbitalOffset); + Draw::InputFixedDegrees("Longitude", &body->m_orbitalOffset); if (Draw::UndoHelper("Edit Longitude", undo)) AddUndoSingleValue(undo, &body->m_orbitalOffset); } else { @@ -427,8 +432,6 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) { bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5); - ImGui::InputText("Name", &body->m_name); if (Draw::UndoHelper("Edit Name", undo)) AddUndoSingleValue(undo, &body->m_name); @@ -440,74 +443,72 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) AddUndoSingleValue(undo, &body->m_seed); if (body->GetSuperType() < SUPERTYPE_STARPORT) { - ImGui::InputFixed(isStar ? "Radius (sol)" : "Radius (earth)", &body->m_radius); + + ImGui::SeparatorText("Body Parameters"); + + Draw::InputFixedMass("Mass", &body->m_mass, isStar); + if (Draw::UndoHelper("Edit Mass", undo)) + AddUndoSingleValue(undo, &body->m_mass); + + Draw::InputFixedRadius("Radius", &body->m_radius, isStar); if (Draw::UndoHelper("Edit Radius", undo)) AddUndoSingleValue(undo, &body->m_radius); - ImGui::InputFixed("Aspect Ratio", &body->m_aspectRatio); + Draw::InputFixedSlider("Aspect Ratio", &body->m_aspectRatio, 0.0, 2.0); if (Draw::UndoHelper("Edit Aspect Ratio", undo)) AddUndoSingleValue(undo, &body->m_aspectRatio); - ImGui::InputFixed(isStar ? "Mass (sol)" : "Mass (earth)", &body->m_mass); - if (Draw::UndoHelper("Edit Mass", undo)) - AddUndoSingleValue(undo, &body->m_mass); - - ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10); + ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10, "%d°K"); if (Draw::UndoHelper("Edit Temperature", undo)) AddUndoSingleValue(undo, &body->m_averageTemp); } else { EditStarportProperties(body, undo); - - ImGui::PopItemWidth(); return; } - // TODO: orbital parameters not needed for root body - - EditOrbitalParameters(body, undo); + if (body->GetParent()) { + EditOrbitalParameters(body, undo); + } if (isStar) { - ImGui::PopItemWidth(); return; } ImGui::SeparatorText("Surface Parameters"); - ImGui::InputFixed("Metallicity", &body->m_metallicity); + Draw::InputFixedSlider("Metallicity", &body->m_metallicity); if (Draw::UndoHelper("Edit Metallicity", undo)) AddUndoSingleValue(undo, &body->m_metallicity); - ImGui::InputFixed("Volcanicity", &body->m_volcanicity); + Draw::InputFixedSlider("Volcanicity", &body->m_volcanicity); if (Draw::UndoHelper("Edit Volcanicity", undo)) AddUndoSingleValue(undo, &body->m_volcanicity); - ImGui::InputFixed("Atmosphere Density", &body->m_volatileGas); + Draw::InputFixedSlider("Atm. Density", &body->m_volatileGas); if (Draw::UndoHelper("Edit Atmosphere Density", undo)) AddUndoSingleValue(undo, &body->m_volatileGas); - ImGui::InputFixed("Atmosphere Oxidizing", &body->m_atmosOxidizing); - if (Draw::UndoHelper("Edit Atmosphere Oxidizing", undo)) + Draw::InputFixedSlider("Atm. Oxygen", &body->m_atmosOxidizing); + if (Draw::UndoHelper("Edit Atmosphere Oxygen", undo)) AddUndoSingleValue(undo, &body->m_atmosOxidizing); - ImGui::InputFixed("Ocean Coverage", &body->m_volatileLiquid); + Draw::InputFixedSlider("Ocean Coverage", &body->m_volatileLiquid); if (Draw::UndoHelper("Edit Ocean Coverage", undo)) AddUndoSingleValue(undo, &body->m_volatileLiquid); - ImGui::InputFixed("Ice Coverage", &body->m_volatileIces); + Draw::InputFixedSlider("Ice Coverage", &body->m_volatileIces); if (Draw::UndoHelper("Edit Ice Coverage", undo)) AddUndoSingleValue(undo, &body->m_volatileIces); // TODO: unused by other code - // ImGui::InputFixed("Human Activity", &body->m_humanActivity); + // Draw::InputFixedSlider("Human Activity", &body->m_humanActivity); // if (Draw::UndoHelper("Edit Human Activity", undo)) // AddUndoSingleValue(undo, &body->m_humanActivity); - ImGui::InputFixed("Life", &body->m_life); + Draw::InputFixedSlider("Life", &body->m_life); if (Draw::UndoHelper("Edit Life", undo)) AddUndoSingleValue(undo, &body->m_life); EditEconomicProperties(body, undo); - - ImGui::PopItemWidth(); } diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 9711b064372..9e1bea16215 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -244,10 +244,15 @@ void SystemEditor::DrawInterface() ImGui::End(); if (ImGui::Begin(PROPERTIES_WND_ID)) { + // Adjust default window label position + ImGui::PushItemWidth(ImFloor(ImGui::GetWindowSize().x * 0.55f)); + if (m_selectedBody) DrawBodyProperties(); else DrawSystemProperties(); + + ImGui::PopItemWidth(); } ImGui::End(); @@ -396,6 +401,9 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) std::string name = fmt::format("{} {}###{:x}", GetBodyIcon(body), body->GetName(), bodyId); bool open = ImGui::TreeNodeEx(name.c_str(), flags); + if (body == m_selectedBody) + ImGui::SetItemDefaultFocus(); + if (!isRoot) { HandleOutlinerDragDrop(body); } @@ -417,7 +425,13 @@ void SystemEditor::DrawBodyProperties() ImGui::Spacing(); + ImGui::PushID(m_selectedBody); + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); + SystemBody::EditorAPI::EditProperties(m_selectedBody, GetUndo()); + + ImGui::PopFont(); + ImGui::PopID(); } void SystemEditor::DrawSystemProperties() diff --git a/src/editor/system/SystemEditorHelpers.cpp b/src/editor/system/SystemEditorHelpers.cpp new file mode 100644 index 00000000000..a2b1d0fe5a7 --- /dev/null +++ b/src/editor/system/SystemEditorHelpers.cpp @@ -0,0 +1,250 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "SystemEditorHelpers.h" +#include "core/macros.h" +#include "gameconsts.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" + +using namespace Editor; + +enum DistanceUnits { + DISTANCE_AU, // Astronomical units + DISTANCE_LS, // Light-seconds + DISTANCE_KM, // Kilometers +}; + +const char *distance_labels[] = { + "AU", + "ls", + "km" +}; + +const char *distance_formats[] = { + "%.4f AU", + "%.4f ls", + "%.2f km" +}; + +const double distance_multipliers[] = { + 1.0, + 499.00478, + AU / 1000.0 +}; + +enum MassUnits { + MASS_SOLS, // Solar masses + MASS_EARTH, // Earth masses + MASS_MT, // Kilograms x 1,000,000,000 +}; + +const char *mass_labels[] = { + "Solar Masses", + "Earth Masses", + "Megatonnes" +}; + +const char *mass_formats[] = { + "%.4f Sol", + "%.4f Earth", + "%.2f Mt" +}; + +const double KG_TO_MT = 1000000000; + +const double SOL_MASS_MT = SOL_MASS / KG_TO_MT; +const double EARTH_MASS_MT = EARTH_MASS / KG_TO_MT; +const double SOL_TO_EARTH_MASS = SOL_MASS / EARTH_MASS; + +enum RadiusUnits { + RADIUS_SOLS, // Solar masses + RADIUS_EARTH, // Earth masses + RADIUS_KM, // Kilometers +}; + +const char *radius_labels[] = { + "Solar Radii", + "Earth Radii", + "km" +}; + +const char *radius_formats[] = { + "%.4f Sol", + "%.4f Earth", + "%.2f km" +}; + +const double SOL_RADIUS_KM = SOL_RADIUS / 1000.0; +const double EARTH_RADIUS_KM = EARTH_RADIUS / 1000.0; +const double SOL_TO_EARTH_RADIUS = SOL_RADIUS / EARTH_RADIUS; + +void Draw::SubtractItemWidth() +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + float used_width = window->DC.CursorPos.x - IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - used_width); +} + +bool Draw::InputFixedSlider(const char *str, fixed *val, double val_min, double val_max, const char *format, ImGuiInputTextFlags flags) +{ + double val_d = val->ToDouble(); + + bool changed = ImGui::SliderScalar(str, ImGuiDataType_Double, &val_d, &val_min, &val_max, format, ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoRoundToFormat); + // delay one frame before writing back the value for the undo system to push a value + if (changed && !ImGui::IsItemActivated()) + *val = fixed::FromDouble(val_d); + + return changed && !ImGui::IsItemActivated(); +} + +bool Draw::InputFixedDegrees(const char *str, fixed *val, ImGuiInputTextFlags flags) +{ + double val_d = RAD2DEG(val->ToDouble()); + const double val_min = -360.0; + const double val_max = 360.0; + + bool changed = ImGui::SliderScalar(str, ImGuiDataType_Double, &val_d, &val_min, &val_max, "%.3f°", ImGuiSliderFlags_NoRoundToFormat); + // bool changed = ImGui::InputDouble(str, &val_d, 1.0, 10.0, "%.3f°", flags | ImGuiInputTextFlags_EnterReturnsTrue); + // delay one frame before writing back the value for the undo system to push a value + if (changed && !ImGui::IsItemActivated()) + *val = fixed::FromDouble(DEG2RAD(val_d)); + + return changed && !ImGui::IsItemActivated(); +} + +bool Draw::InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags flags) +{ + ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::BeginGroup(); + ImGui::PushID(str); + + ImGuiID unit_type_id = ImGui::GetID("#UnitType"); + + int unit_type = DISTANCE_AU; + + double val_d = val->ToDouble(); + if (val_d < 0.0001) + unit_type = DISTANCE_LS; + if (val_d < 0.000001) + unit_type = DISTANCE_KM; + + unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); + + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + if (ImGui::Combo("##Unit", &unit_type, distance_labels, COUNTOF(distance_labels))) { + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + } + + double val_step = unit_type == DISTANCE_KM ? 1.0 : unit_type == DISTANCE_LS ? 0.1 : 0.01; + val_d *= distance_multipliers[unit_type]; + + ImGui::SameLine(0.f, 1.f); + Draw::SubtractItemWidth(); + bool changed = ImGui::InputDouble(str, &val_d, val_step, val_step * 10.0, distance_formats[unit_type], flags | ImGuiInputTextFlags_EnterReturnsTrue); + if (changed) + *val = fixed::FromDouble(val_d / distance_multipliers[unit_type]); + + ImGui::PopID(); + ImGui::EndGroup(); + + return changed; +} + +bool Draw::InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags) +{ + ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::BeginGroup(); + ImGui::PushID(str); + + ImGuiID unit_type_id = ImGui::GetID("#UnitType"); + + int unit_type = MASS_SOLS; + if (!is_solar) + unit_type = MASS_EARTH; + + double val_d = val->ToDouble(); + if (!is_solar && val_d < 0.0000001) + unit_type = MASS_MT; + + unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); + + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + if (ImGui::Combo("##Unit", &unit_type, mass_labels, COUNTOF(mass_labels))) { + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + } + double val_step = 0.01; + + if (is_solar && unit_type != MASS_SOLS) + val_d *= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_MT; + if (!is_solar && unit_type != MASS_EARTH) + val_d *= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_MT; + + ImGui::SameLine(0.f, 1.f); + Draw::SubtractItemWidth(); + bool changed = ImGui::InputDouble(str, &val_d, val_step, val_step * 10.0, mass_formats[unit_type], flags | ImGuiInputTextFlags_EnterReturnsTrue); + + if (is_solar && unit_type != MASS_SOLS) + val_d /= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_MT; + if (!is_solar && unit_type != MASS_EARTH) + val_d /= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_MT; + + if (changed) + *val = fixed::FromDouble(val_d); + + ImGui::PopID(); + ImGui::EndGroup(); + + return changed; +} + +bool Draw::InputFixedRadius(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags) +{ + ImGuiStyle &style = ImGui::GetStyle(); + + ImGui::BeginGroup(); + ImGui::PushID(str); + + ImGuiID unit_type_id = ImGui::GetID("#UnitType"); + + int unit_type = RADIUS_SOLS; + if (!is_solar) + unit_type = RADIUS_EARTH; + + double val_d = val->ToDouble(); + if (!is_solar && val_d < 0.1) + unit_type = RADIUS_KM; + + unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); + + ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + if (ImGui::Combo("##Unit", &unit_type, radius_labels, COUNTOF(radius_labels))) { + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + } + double val_step = 0.01; + + if (is_solar && unit_type != RADIUS_SOLS) + val_d *= unit_type == RADIUS_EARTH ? SOL_TO_EARTH_MASS : SOL_RADIUS_KM; + if (!is_solar && unit_type != RADIUS_EARTH) + val_d *= unit_type == RADIUS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_RADIUS_KM; + + ImGui::SameLine(0.f, 1.f); + Draw::SubtractItemWidth(); + bool changed = ImGui::InputDouble(str, &val_d, val_step, val_step * 10.0, radius_formats[unit_type], flags | ImGuiInputTextFlags_EnterReturnsTrue); + + if (is_solar && unit_type != RADIUS_SOLS) + val_d /= unit_type == RADIUS_EARTH ? SOL_TO_EARTH_MASS : SOL_RADIUS_KM; + if (!is_solar && unit_type != RADIUS_EARTH) + val_d /= unit_type == RADIUS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_RADIUS_KM; + + if (changed) + *val = fixed::FromDouble(val_d); + + ImGui::PopID(); + ImGui::EndGroup(); + + return changed; +} diff --git a/src/editor/system/SystemEditorHelpers.h b/src/editor/system/SystemEditorHelpers.h index b769b922083..bc5ac550972 100644 --- a/src/editor/system/SystemEditorHelpers.h +++ b/src/editor/system/SystemEditorHelpers.h @@ -3,11 +3,12 @@ #pragma once +#include "EditorIcons.h" +#include "MathUtil.h" #include "imgui/imgui.h" #include "imgui/imgui_stdlib.h" #include "fixed.h" -#include "libs.h" namespace ImGui { inline bool InputFixed(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1, const char *format = "%.4f", ImGuiInputTextFlags flags = 0) @@ -20,18 +21,32 @@ namespace ImGui { return changed; } - inline bool InputFixedAU(const char *str, fixed *val, double step = 0.01, double step_fast = 0.1) + inline bool InputInt(const char* label, int* v, int step, int step_fast, const char *format, ImGuiInputTextFlags flags = 0) { - return InputFixed(str, val, step, step_fast, "%.4f AU"); + return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags); } +} - inline bool InputFixedDegrees(const char *str, fixed *val, double step = 1.0, double step_fast = 10.0, const char *format = "%.3f°", ImGuiInputTextFlags flags = 0) +namespace Editor::Draw { + + // Subtract the currently used space on this line and apply it to the next drawn item + void SubtractItemWidth(); + + inline bool RandomButton() { - double val_d = RAD2DEG(val->ToDouble()); - bool changed = ImGui::InputDouble(str, &val_d, step, step_fast, format, flags | ImGuiInputTextFlags_EnterReturnsTrue); - if (changed) - *val = fixed::FromDouble(DEG2RAD(val_d)); + ImGuiStyle &style = ImGui::GetStyle(); + bool ret = ImGui::Button(EICON_RANDOM); - return changed; + ImGui::SameLine(0.f, style.ItemInnerSpacing.x); + Draw::SubtractItemWidth(); + + return ret; } + + bool InputFixedSlider(const char *str, fixed *val, double val_min = 0.0, double val_max = 1.0, const char *format = "%.4f", ImGuiInputTextFlags flags = 0); + bool InputFixedDegrees(const char *str, fixed *val, ImGuiInputTextFlags flags = 0); + bool InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags flags = 0); + bool InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags = 0); + bool InputFixedRadius(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags = 0); + }; From d329d51b7f367c03d28a513ef4d38a883b4f21e9 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Tue, 29 Aug 2023 21:38:48 -0400 Subject: [PATCH 15/50] SystemEditor: add Arg. of Periapsis and tooltips - Allow specifying argument of periapsis for SystemBodies - Ensure orbit generation for bodies matches what system generation produces. - Show derived stats: orbit period, orbit velocity at AP/PE. --- src/editor/EditorDraw.cpp | 16 +++++++++ src/editor/EditorDraw.h | 3 ++ src/editor/EditorIcons.h | 2 ++ src/editor/system/GalaxyEditAPI.cpp | 41 +++++++++++++++++++++-- src/editor/system/SystemEditorHelpers.cpp | 8 ++--- src/editor/system/SystemEditorHelpers.h | 2 +- 6 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 0edc85ad52b..6caf30ba0a4 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -7,6 +7,7 @@ #include "EnumStrings.h" #include "UndoStepType.h" +#include "editor/EditorIcons.h" #include "editor/UndoSystem.h" #include "imgui/imgui.h" @@ -336,3 +337,18 @@ Draw::DragDropTarget Draw::HierarchyDragDrop(const char *type, ImGuiID targetID, ImGui::PopID(); return ret; } + +void Draw::HelpMarker(const char* desc, bool same_line) +{ + if (same_line) + ImGui::SameLine(ImGui::GetContentRegionAvail().x /*- ImGui::GetFontSize()*/); + + ImGui::TextDisabled(EICON_INFO); + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(desc); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index ce9df7a3959..5441d28b577 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -86,6 +86,9 @@ namespace Editor::Draw { // Begin tri-mode drag-drop handling on a DragDropTarget HierarchyDragDrop(const char *type, ImGuiID targetID, void *data, void *outData, size_t dataSize); + // Show a help tooltip + void HelpMarker(const char* desc, bool same_line = true); + } inline bool operator==(const ImVec2 &a, const ImVec2 &b) diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h index da9b589dd07..51d42e96784 100644 --- a/src/editor/EditorIcons.h +++ b/src/editor/EditorIcons.h @@ -15,6 +15,8 @@ #define EICON_PAUSE "\uF055" #define EICON_PLAY "\uF056" +#define EICON_INFO "\uF088" + #define EICON_RANDOM "\uF0C7" #define EICON_AXES "\uF0CA" diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index dce1e68e753..13dff02d427 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -26,6 +26,8 @@ namespace { (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')); } + + static constexpr double SECONDS_TO_DAYS = 1.0 / (3600.0 * 24.0); } void StarSystem::EditorAPI::ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) @@ -341,9 +343,16 @@ void SystemBody::EditorAPI::UpdateOrbitAroundParent(SystemBody *body, SystemBody body->m_orbit.SetPhase(body->m_orbitalPhaseAtStart.ToDouble()); - double latitude = body->m_inclination.ToDouble(); + // orbit longitude of ascending node double longitude = body->m_orbitalOffset.ToDouble(); - body->m_orbit.SetPlane(matrix3x3d::RotateY(longitude) * matrix3x3d::RotateX(-0.5 * M_PI + latitude)); + // orbit inclination + double inclination = body->m_inclination.ToDouble(); + // orbit argument of periapsis + double argument = body->m_argOfPeriapsis.ToDouble(); + body->m_orbit.SetPlane( + matrix3x3d::RotateY(-longitude) * + matrix3x3d::RotateX(-0.5 * M_PI + inclination) * + matrix3x3d::RotateZ(argument)); } void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem *undo) @@ -361,21 +370,47 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * if (Draw::UndoHelper("Edit Eccentricity", undo)) AddUndoSingleValueClosure(undo, &body->m_eccentricity, updateBodyOrbit); - orbitChanged |= Draw::InputFixedDegrees("Inclination", &body->m_inclination); + orbitChanged |= Draw::InputFixedDegrees("Inclination", &body->m_inclination, 0.0, 180.0); if (Draw::UndoHelper("Edit Inclination", undo)) AddUndoSingleValueClosure(undo, &body->m_inclination, updateBodyOrbit); orbitChanged |= Draw::InputFixedDegrees("Orbital Offset", &body->m_orbitalOffset); if (Draw::UndoHelper("Edit Orbital Offset", undo)) AddUndoSingleValueClosure(undo, &body->m_orbitalOffset, updateBodyOrbit); + Draw::HelpMarker("Longitude of Ascending Node"); + + orbitChanged |= Draw::InputFixedDegrees("Arg. of Periapsis", &body->m_argOfPeriapsis); + if (Draw::UndoHelper("Edit Argument of Periapsis", undo)) + AddUndoSingleValueClosure(undo, &body->m_argOfPeriapsis, updateBodyOrbit); + Draw::HelpMarker("Argument of Periapsis\nRelative to Longitude of Ascending Node"); orbitChanged |= Draw::InputFixedDegrees("Orbital Phase", &body->m_orbitalPhaseAtStart); if (Draw::UndoHelper("Edit Orbital Phase", undo)) AddUndoSingleValueClosure(undo, &body->m_orbitalPhaseAtStart, updateBodyOrbit); + Draw::HelpMarker("True Anomaly at Epoch\nRelative to Argument of Periapsis"); ImGui::BeginDisabled(); ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); + + double orbit_period = body->GetOrbit().Period() * SECONDS_TO_DAYS; + ImGui::InputDouble("Orbital Period", &orbit_period, 0.0, 0.0, "%.2f days"); + + if (body->GetParent()) { + // calculate the time offset from periapsis at epoch + double orbit_time_at_start = (body->GetOrbit().GetOrbitalPhaseAtStart() / (2.0 * M_PI)) * body->GetOrbit().Period(); + + double orbit_vel_ap = body->GetOrbit().OrbitalVelocityAtTime( + body->GetParent()->GetMass(), + body->GetOrbit().Period() * 0.5 - orbit_time_at_start).Length() / 1000.0; + + double orbit_vel_pe = body->GetOrbit().OrbitalVelocityAtTime( + body->GetParent()->GetMass(), + -orbit_time_at_start).Length() / 1000.0; + + ImGui::InputDouble("Orbital Velocity (AP)", &orbit_vel_ap, 0.0, 0.0, "%.2f km/s"); + ImGui::InputDouble("Orbital Velocity (PE)", &orbit_vel_pe, 0.0, 0.0, "%.2f km/s"); + } ImGui::EndDisabled(); ImGui::SeparatorText("Rotation Parameters"); diff --git a/src/editor/system/SystemEditorHelpers.cpp b/src/editor/system/SystemEditorHelpers.cpp index a2b1d0fe5a7..55c4fbdf93c 100644 --- a/src/editor/system/SystemEditorHelpers.cpp +++ b/src/editor/system/SystemEditorHelpers.cpp @@ -99,11 +99,9 @@ bool Draw::InputFixedSlider(const char *str, fixed *val, double val_min, double return changed && !ImGui::IsItemActivated(); } -bool Draw::InputFixedDegrees(const char *str, fixed *val, ImGuiInputTextFlags flags) +bool Draw::InputFixedDegrees(const char *str, fixed *val, double val_min, double val_max, ImGuiInputTextFlags flags) { double val_d = RAD2DEG(val->ToDouble()); - const double val_min = -360.0; - const double val_max = 360.0; bool changed = ImGui::SliderScalar(str, ImGuiDataType_Double, &val_d, &val_min, &val_max, "%.3f°", ImGuiSliderFlags_NoRoundToFormat); // bool changed = ImGui::InputDouble(str, &val_d, 1.0, 10.0, "%.3f°", flags | ImGuiInputTextFlags_EnterReturnsTrue); @@ -126,9 +124,9 @@ bool Draw::InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags f int unit_type = DISTANCE_AU; double val_d = val->ToDouble(); - if (val_d < 0.0001) + if (val_d < 0.001) unit_type = DISTANCE_LS; - if (val_d < 0.000001) + if (val_d < 0.00001) unit_type = DISTANCE_KM; unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); diff --git a/src/editor/system/SystemEditorHelpers.h b/src/editor/system/SystemEditorHelpers.h index bc5ac550972..f059c49cfea 100644 --- a/src/editor/system/SystemEditorHelpers.h +++ b/src/editor/system/SystemEditorHelpers.h @@ -44,7 +44,7 @@ namespace Editor::Draw { } bool InputFixedSlider(const char *str, fixed *val, double val_min = 0.0, double val_max = 1.0, const char *format = "%.4f", ImGuiInputTextFlags flags = 0); - bool InputFixedDegrees(const char *str, fixed *val, ImGuiInputTextFlags flags = 0); + bool InputFixedDegrees(const char *str, fixed *val, double val_min = -360.0, double val_max = 360.0, ImGuiInputTextFlags flags = 0); bool InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags flags = 0); bool InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags = 0); bool InputFixedRadius(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags = 0); From a4db85d47b5fc0dcd3859c4bc5c562bb8a350bdb Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 30 Aug 2023 05:05:08 -0400 Subject: [PATCH 16/50] SystemEditor: add viewport time controls --- src/editor/EditorIcons.h | 9 ++ src/editor/system/SystemEditorViewport.cpp | 131 +++++++++++++++++---- src/editor/system/SystemEditorViewport.h | 6 + 3 files changed, 121 insertions(+), 25 deletions(-) diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h index 51d42e96784..16a31a2a29e 100644 --- a/src/editor/EditorIcons.h +++ b/src/editor/EditorIcons.h @@ -12,9 +12,18 @@ #define EICON_SPACE_STATION "\uF063" #define EICON_SURFACE_STATION "\uF073" +#define EICON_STOP "\uF054" #define EICON_PAUSE "\uF055" #define EICON_PLAY "\uF056" +#define EICON_REWIND3 "\uF064" +#define EICON_REWIND2 "\uF065" +#define EICON_REWIND1 "\uF066" +#define EICON_TIMESTOP "\uF067" +#define EICON_FORWARD1 "\uF068" +#define EICON_FORWARD2 "\uF069" +#define EICON_FORWARD3 "\uF06A" + #define EICON_INFO "\uF088" #define EICON_RANDOM "\uF0C7" diff --git a/src/editor/system/SystemEditorViewport.cpp b/src/editor/system/SystemEditorViewport.cpp index 3cca0f20eed..68f75936b93 100644 --- a/src/editor/system/SystemEditorViewport.cpp +++ b/src/editor/system/SystemEditorViewport.cpp @@ -28,6 +28,8 @@ SystemEditorViewport::SystemEditorViewport(EditorApp *app, SystemEditor *editor) { m_map.reset(new SystemMapViewport(m_app)); + m_map->SetShowGravpoints(true); + m_map->svColor[SystemMapViewport::GRID] = Color(0x24242BFF); m_map->svColor[SystemMapViewport::GRID_LEG] = Color(0x787878FF); m_map->svColor[SystemMapViewport::SYSTEMBODY] = Color(0xB5BCE3FF).Shade(0.5); @@ -56,6 +58,8 @@ bool SystemEditorViewport::OnCloseRequested() void SystemEditorViewport::OnUpdate(float deltaTime) { m_map->Update(deltaTime); + + m_map->AddObjectTrack({ Projectable::types(Projectable::_MAX + 1), Projectable::BODY, static_cast(nullptr), vector3d(1e12, 0.0, 0.0) }); } void SystemEditorViewport::OnRender(Graphics::Renderer *r) @@ -77,18 +81,20 @@ void SystemEditorViewport::OnDraw() split.SetCurrentChannel(ImGui::GetWindowDrawList(), 1); - ImVec2 windowTL = ImGui::GetWindowPos(); - ImVec2 windowBR = ImVec2(windowTL.x + ImGui::GetWindowSize().x, windowTL.y + ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.f); - ImGui::GetWindowDrawList()->PushClipRect(windowTL, windowBR); - ImGui::GetWindowDrawList()->AddQuadFilled( - windowTL, ImVec2(windowBR.x, windowTL.y), - windowBR, ImVec2(windowTL.x, windowBR.y), - ImColor(1.f, 1.f, 1.f, 0.25f)); + // Draw background + ImVec2 toolbar_bg_size = ImVec2(ImGui::GetWindowSize().x, ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.f); + ImColor toolbar_bg_color = ImGui::GetStyle().Colors[ImGuiCol_FrameBg]; + + ImGui::GetWindowDrawList()->PushClipRect(ImGui::GetWindowPos(), ImGui::GetWindowPos() + ImGui::GetWindowSize()); + ImGui::GetWindowDrawList()->AddRectFilled( + ImGui::GetWindowPos(), ImGui::GetWindowPos() + toolbar_bg_size, + toolbar_bg_color); ImGui::GetWindowDrawList()->PopClipRect(); - if (ImGui::Button("This is a test!")) {} + DrawTimelineControls(); // Render all GUI elements over top of viewport overlays + // (Items rendered earlier take priority when using IsItemHovered) split.SetCurrentChannel(ImGui::GetWindowDrawList(), 0); @@ -100,33 +106,27 @@ void SystemEditorViewport::OnDraw() ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + ImRect screen_rect = ImRect(ImVec2(0, 0), ImGui::GetWindowSize()); + // Then draw "under" the GUI elements so we can use ImGui::IsItemHovered et al. for (auto &group : groups) { ImVec2 itempos = { group.screenpos.x, group.screenpos.y }; ImVec2 iconSize = { ImGui::GetFontSize(), ImGui::GetFontSize() }; + // Simple screen clipping rejection test + if (!screen_rect.Contains(itempos)) + continue; + const Projectable &item = projected[group.tracks[0]]; if (group.type == Projectable::OBJECT && item.base == Projectable::SYSTEMBODY) { ImColor icon_col(0xFFC8C8C8); - const char *bodyName = item.ref.sbody->GetName().c_str(); - ImVec2 drawPos = windowTL + itempos - iconSize * 0.5f; - ImGui::GetWindowDrawList()->AddText(drawPos, icon_col, GetBodyIcon(item.ref.sbody)); + std::string label = group.tracks.size() > 1 ? + fmt::format("{} ({})", item.ref.sbody->GetName(), group.tracks.size()) : + item.ref.sbody->GetName(); - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 12)); - ImVec2 textPos = windowTL + itempos + ImVec2(iconSize.x, -ImGui::GetFontSize() * 0.5f); - ImVec2 textSize = ImGui::CalcTextSize(bodyName); - // label shadow - ImGui::GetWindowDrawList()->AddText(textPos + ImVec2(1.f, 1.f), IM_COL32_BLACK, bodyName); - // body label - ImGui::GetWindowDrawList()->AddText(textPos, icon_col, bodyName); - ImGui::PopFont(); - - ImRect hoverRect(drawPos, drawPos + iconSize); - hoverRect.Add(textPos + textSize); - - if (ImGui::ItemHoverable(hoverRect, 0, 0)) { - ImGui::SetTooltip("%s", bodyName); + if (DrawIcon(itempos, icon_col, GetBodyIcon(item.ref.sbody), label.c_str())) { + ImGui::SetTooltip("%s", label.c_str()); if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { m_map->SetSelectedObject(item); @@ -138,9 +138,90 @@ void SystemEditorViewport::OnDraw() } } } + + #if 0 // TODO: visual edit gizmos for body axes + if (int(group.type) == Projectable::_MAX + 1) { + DrawIcon(itempos, IM_COL32_WHITE, EICON_AXES); + } + #endif } ImGui::PopFont(); split.Merge(ImGui::GetWindowDrawList()); } + +void SystemEditorViewport::DrawTimelineControls() +{ + double timeAccel = 0.0; + + ImGui::PushFont(m_app->GetPiGui()->GetFont("icons", ImGui::GetFrameHeight())); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImGui::GetStyle().ItemInnerSpacing); + + ImGui::Button(EICON_REWIND3); + if (ImGui::IsItemActive()) + timeAccel = -3600.0 * 24.0 * 730.0; // 2 years per second + + ImGui::Button(EICON_REWIND2); + if (ImGui::IsItemActive()) + timeAccel = -3600.0 * 24.0 * 60.0; // 2 months per second + + ImGui::Button(EICON_REWIND1); + if (ImGui::IsItemActive()) + timeAccel = -3600.0 * 24.0 * 5.0; // 5 days per second + + bool timeStop = ImGui::ButtonEx(EICON_TIMESTOP, ImVec2(0,0), ImGuiButtonFlags_PressedOnClick); + + ImGui::Button(EICON_FORWARD1); + if (ImGui::IsItemActive()) + timeAccel = 3600.0 * 24.0 * 5.0; // 5 days per second + + ImGui::Button(EICON_FORWARD2); + if (ImGui::IsItemActive()) + timeAccel = 3600.0 * 24.0 * 60.0; // 2 months per second + + ImGui::Button(EICON_FORWARD3); + if (ImGui::IsItemActive()) + timeAccel = 3600.0 * 24.0 * 730.0; // 2 years per second + + ImGui::PopStyleVar(2); + ImGui::PopFont(); + + ImGui::AlignTextToFramePadding(); + + if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) + timeAccel *= 0.1; + + if (!timeStop) + m_map->AccelerateTime(timeAccel); + else + m_map->SetRealTime(); + + ImGui::Text("%s", format_date(m_map->GetTime()).c_str()); +} + +bool SystemEditorViewport::DrawIcon(const ImVec2 &icon_pos, const ImColor &color, const char *icon, const char *label) +{ + ImVec2 icon_size = ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()); + ImVec2 draw_pos = ImGui::GetWindowPos() + icon_pos - icon_size * 0.5f; + + ImGui::GetWindowDrawList()->AddText(draw_pos, color, icon); + + ImRect hover_rect = ImRect(draw_pos, draw_pos + icon_size); + + if (label) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 12)); + ImVec2 text_pos = ImGui::GetWindowPos() + icon_pos + ImVec2(icon_size.x, -ImGui::GetFontSize() * 0.5f); + ImVec2 text_size = ImGui::CalcTextSize(label); + // label shadow + ImGui::GetWindowDrawList()->AddText(text_pos + ImVec2(1.f, 1.f), IM_COL32_BLACK, label); + // body label + ImGui::GetWindowDrawList()->AddText(text_pos, color, label); + ImGui::PopFont(); + + hover_rect.Add(text_pos + text_size); + } + + return ImGui::ItemHoverable(hover_rect, 0, 0); +} diff --git a/src/editor/system/SystemEditorViewport.h b/src/editor/system/SystemEditorViewport.h index ed43623e30e..acfcb948f40 100644 --- a/src/editor/system/SystemEditorViewport.h +++ b/src/editor/system/SystemEditorViewport.h @@ -23,6 +23,8 @@ namespace Editor { void SetSystem(RefCountedPtr system); + SystemMapViewport *GetMap() { return m_map.get(); } + protected: void OnUpdate(float deltaTime) override; void OnRender(Graphics::Renderer *renderer) override; @@ -35,6 +37,10 @@ namespace Editor { const char *GetWindowName() override { return "Viewport"; } private: + + void DrawTimelineControls(); + bool DrawIcon(const ImVec2 &iconPos, const ImColor &color, const char *icon, const char *label = nullptr); + EditorApp *m_app; SystemEditor *m_editor; From f02b30cf34593b629f213a29977f4635358f08fe Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 30 Aug 2023 05:13:51 -0400 Subject: [PATCH 17/50] SystemEditor: open/save file dialogs, context menu - Add open/save file dialogs for editing custom system files. Loading/writing not yet implemented. - Improve handling of add/delete/reorder body operations, defer till end-of-frame. - Add body context menu in outliner with Center/Add New Child/Add New Sibling/Delete operations. - Fix UndoFrame scope issue when right-clicking into viewport from InputText. - Add (disabled) mod panel showing basic information about available mods --- src/editor/system/SystemEditor.cpp | 300 ++++++++++++++++++---- src/editor/system/SystemEditor.h | 33 ++- src/editor/system/SystemEditorHelpers.cpp | 9 + 3 files changed, 286 insertions(+), 56 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 9e1bea16215..e28a6357d8a 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -5,11 +5,13 @@ #include "EditorIcons.h" #include "GalaxyEditAPI.h" +#include "ModManager.h" #include "SystemEditorHelpers.h" #include "EnumStrings.h" #include "FileSystem.h" +#include "SystemView.h" #include "editor/EditorApp.h" #include "editor/EditorDraw.h" #include "editor/UndoSystem.h" @@ -25,6 +27,8 @@ #include "system/SystemBodyUndo.h" #include "system/SystemEditorViewport.h" +#include "portable-file-dialogs/pfd.h" + #include using namespace Editor; @@ -33,6 +37,7 @@ namespace { static constexpr const char *OUTLINE_WND_ID = "Outline"; static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; + static constexpr const char *FILE_MODAL_ID = "File Window Open"; } const char *Editor::GetBodyIcon(const SystemBody *body) { @@ -77,7 +82,7 @@ SystemEditor::SystemEditor(EditorApp *app) : m_app(app), m_undo(new UndoSystem()), m_selectedBody(nullptr), - m_pendingReparent() + m_pendingOp() { GalacticEconomy::Init(); @@ -127,6 +132,7 @@ bool SystemEditor::LoadSystem(const std::string &filepath) m_system = system; m_filepath = filepath; + m_filedir = filepath.substr(0, filepath.rfind('/')), m_viewport->SetSystem(system); @@ -164,6 +170,36 @@ void SystemEditor::End() { } +void SystemEditor::ActivateOpenDialog() +{ + // FIXME: need to handle loading files outside of game data dir + m_openFile.reset(new pfd::open_file( + "Open Custom System File", + FileSystem::JoinPath(FileSystem::GetDataDir(), m_filedir), + { + "Lua System Definition (.lua)", "*.lua", + "JSON System Definition (.json)", "*.json" + }) + ); + + ImGui::OpenPopup(m_fileActionActiveModal); +} + +void SystemEditor::ActivateSaveDialog() +{ + // FIXME: need to handle saving files outside of game data dir + m_saveFile.reset(new pfd::save_file( + "Save Custom System File", + FileSystem::JoinPath(FileSystem::GetDataDir(), m_filedir), + { + "Lua System Definition (.lua)", "*.lua", + "JSON System Definition (.json)", "*.json" + }) + ); + + ImGui::OpenPopup(m_fileActionActiveModal); +} + // ─── Update Loop ───────────────────────────────────────────────────────────── void SystemEditor::HandleInput() @@ -180,24 +216,120 @@ void SystemEditor::Update(float deltaTime) GetUndo()->Undo(); } + m_fileActionActiveModal = ImGui::GetID(FILE_MODAL_ID); + DrawInterface(); - if (m_pendingReparent.body) { - size_t parentIdx = SystemBody::EditorAPI::GetIndexInParent(m_pendingReparent.body); + HandleBodyOperations(); - GetUndo()->BeginEntry("Reorder Body"); + if (m_openFile && m_openFile->ready(0)) { + std::vector resultFiles = m_openFile->result(); + m_openFile.reset(); + + if (!resultFiles.empty()) { + Log::Info("OpenFile: {}", resultFiles[0]); + } + } + + if (m_saveFile && m_saveFile->ready(0)) { + std::string filePath = m_saveFile->result(); + m_saveFile.reset(); + + if (!filePath.empty()) { + Log::Info("SaveFile: {}", filePath); + } + } + + bool fileModalOpen = ImGui::IsPopupOpen(m_fileActionActiveModal, ImGuiPopupFlags_AnyPopupLevel); + + // Finished all pending file operations + if (fileModalOpen && !m_openFile && !m_saveFile) { + auto &popupStack = ImGui::GetCurrentContext()->OpenPopupStack; + for (size_t idx = 0; idx < popupStack.size(); ++idx) { + if (popupStack[idx].PopupId == m_fileActionActiveModal) { + ImGui::ClosePopupToLevel(idx, true); + break; + } + } + } +} + +void SystemEditor::HandleBodyOperations() +{ + if (m_pendingOp.type == BodyRequest::TYPE_None) + return; + + if (m_pendingOp.type == BodyRequest::TYPE_Add) { + + // TODO: generate body parameters according to m_pendingOp.newBodyType + SystemBody *body = StarSystem::EditorAPI::NewBody(m_system.Get()); + + GetUndo()->BeginEntry("Add Body"); GetUndo()->AddUndoStep(m_system.Get()); - GetUndo()->AddUndoStep(m_pendingReparent.body->GetParent(), parentIdx); - GetUndo()->AddUndoStep(m_pendingReparent.parent, m_pendingReparent.body, m_pendingReparent.idx); + + // Mark the body for removal on undo + GetUndo()->AddUndoStep(m_system.Get(), body); + // Add the new body to its parent + GetUndo()->AddUndoStep(m_pendingOp.parent, body); + GetUndo()->AddUndoStep(m_system.Get(), true); + GetUndo()->AddUndoStep(this, body); GetUndo()->EndEntry(); - m_pendingReparent = {}; } - if (ImGui::IsKeyPressed(ImGuiKey_F1)) { - WriteSystem(m_filepath); + if (m_pendingOp.type == BodyRequest::TYPE_Delete) { + + std::vector toDelete { m_pendingOp.body }; + size_t sliceBegin = 0; + + // Iterate over all child bodies of this system body and mark for deletion + while (sliceBegin < toDelete.size()) { + size_t sliceEnd = toDelete.size(); + for (size_t idx = sliceBegin; idx < sliceEnd; idx++) { + if (toDelete[idx]->HasChildren()) + for (auto &child : toDelete[idx]->GetChildren()) + toDelete.push_back(child); + } + sliceBegin = sliceEnd; + } + + GetUndo()->BeginEntry("Delete Body"); + GetUndo()->AddUndoStep(m_system.Get()); + + // Record deletion of each marked body in reverse order (ending with the topmost body to delete) + for (auto &child : reverse_container(toDelete)) { + SystemBody *parent = child->GetParent(); + size_t idx = SystemBody::EditorAPI::GetIndexInParent(child); + GetUndo()->AddUndoStep(parent, idx); + GetUndo()->AddUndoStep(m_system.Get(), nullptr, child, true); + } + + GetUndo()->AddUndoStep(m_system.Get(), true); + GetUndo()->AddUndoStep(this, nullptr); + GetUndo()->EndEntry(); + + } + + if (m_pendingOp.type == BodyRequest::TYPE_Reparent) { + + size_t sourceIdx = SystemBody::EditorAPI::GetIndexInParent(m_pendingOp.body); + + if (m_pendingOp.parent == m_pendingOp.body->GetParent() && m_pendingOp.idx > sourceIdx) { + m_pendingOp.idx -= 1; + } + + GetUndo()->BeginEntry("Reorder Body"); + GetUndo()->AddUndoStep(m_system.Get()); + GetUndo()->AddUndoStep(m_pendingOp.body->GetParent(), sourceIdx); + GetUndo()->AddUndoStep(m_pendingOp.parent, m_pendingOp.body, m_pendingOp.idx); + GetUndo()->AddUndoStep(m_system.Get(), true); + GetUndo()->EndEntry(); + } + + // Clear the pending operation + m_pendingOp = {}; } // ─── Interface Rendering ───────────────────────────────────────────────────── @@ -223,11 +355,32 @@ void SystemEditor::SetupLayout(ImGuiID dockspaceID) void SystemEditor::DrawInterface() { Draw::ShowUndoDebugWindow(GetUndo()); + // ImGui::ShowDemoWindow(); ImGui::ShowMetricsWindow(); static bool isFirstRun = true; - Draw::BeginHostWindow("HostWindow", nullptr, ImGuiWindowFlags_NoSavedSettings); + Draw::BeginHostWindow("HostWindow", nullptr, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar); + + if (ImGui::BeginMenuBar()) { + + if (ImGui::BeginMenu("File")) { + + if (ImGui::MenuItem("Open File", "Ctrl+O")) + ActivateOpenDialog(); + + if (ImGui::MenuItem("Save", "Ctrl+S")) { + WriteSystem(m_filepath); + } + + if (ImGui::MenuItem("Save As", "Ctrl+Shift+S")) + ActivateSaveDialog(); + + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); + } ImGuiID dockspaceID = ImGui::GetID("DockSpace"); @@ -236,6 +389,10 @@ void SystemEditor::DrawInterface() ImGui::DockSpace(dockspaceID); + // BUG: Right-click on button can break undo handling if it happens after active InputText is submitted + // We work around it by rendering the viewport first + m_viewport->Update(m_app->DeltaTime()); + if (ImGui::Begin(OUTLINE_WND_ID)) { ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); DrawOutliner(); @@ -252,14 +409,39 @@ void SystemEditor::DrawInterface() else DrawSystemProperties(); + ImGui::ButtonEx("Break undo system!", ImVec2(0, 0), ImGuiButtonFlags_MouseButtonRight); + ImGui::PopItemWidth(); } ImGui::End(); - m_viewport->Update(m_app->DeltaTime()); +#if 0 + if (ImGui::Begin("ModList")) { + for (const auto &mod : ModManager::EnumerateMods()) { + if (!mod.enabled) + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 64, 64, 255)); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("orbiteer", 14)); + ImGui::TextUnformatted(mod.name.c_str()); + ImGui::PopFont(); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 12)); + ImGui::TextUnformatted(mod.path.c_str()); + ImGui::PopFont(); + + if (!mod.enabled) + ImGui::PopStyleColor(); + + ImGui::Spacing(); + } + } + ImGui::End(); +#endif ImGui::End(); + DrawFileActionModal(); + if (isFirstRun) isFirstRun = false; } @@ -286,47 +468,15 @@ void SystemEditor::DrawOutliner() Draw::BeginHorizontalBar(); if (ImGui::Button("A##AddBody")) { - SystemBody *parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); - SystemBody *body = StarSystem::EditorAPI::NewBody(m_system.Get()); - - GetUndo()->BeginEntry("Add Body"); - GetUndo()->AddUndoStep(m_system.Get()); - GetUndo()->AddUndoStep(m_system.Get(), body); - GetUndo()->AddUndoStep(parent, body); - GetUndo()->AddUndoStep(m_system.Get(), true); - GetUndo()->AddUndoStep(this, body); - GetUndo()->EndEntry(); + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); + m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; } bool canDeleteBody = m_selectedBody && m_selectedBody != m_system->GetRootBody().Get(); if (canDeleteBody && ImGui::Button("D##DeleteBody")) { - - std::vector toDelete { m_selectedBody }; - size_t sliceBegin = 0; - - while (sliceBegin < toDelete.size()) { - size_t sliceEnd = toDelete.size(); - for (size_t idx = sliceBegin; idx < sliceEnd; idx++) { - if (toDelete[idx]->HasChildren()) - for (auto &child : toDelete[idx]->GetChildren()) - toDelete.push_back(child); - } - sliceBegin = sliceEnd; - } - - GetUndo()->BeginEntry("Delete Body"); - GetUndo()->AddUndoStep(m_system.Get()); - - for (auto &child : reverse_container(toDelete)) { - SystemBody *parent = child->GetParent(); - size_t idx = SystemBody::EditorAPI::GetIndexInParent(child); - GetUndo()->AddUndoStep(parent, idx); - GetUndo()->AddUndoStep(m_system.Get(), nullptr, child, true); - } - - GetUndo()->AddUndoStep(m_system.Get(), true); - GetUndo()->AddUndoStep(this, nullptr); - GetUndo()->EndEntry(); + m_pendingOp.type = BodyRequest::TYPE_Delete; + m_pendingOp.body = m_selectedBody; } Draw::EndHorizontalBar(); @@ -371,14 +521,15 @@ void SystemEditor::HandleOutlinerDragDrop(SystemBody *refBody) if (dropTarget != Draw::DragDropTarget::DROP_NONE && refBody != dropBody) { size_t targetIdx = SystemBody::EditorAPI::GetIndexInParent(refBody); - m_pendingReparent.body = dropBody; - m_pendingReparent.parent = dropTarget == Draw::DROP_CHILD ? refBody : refBody->GetParent(); - m_pendingReparent.idx = 0; + m_pendingOp.type = BodyRequest::TYPE_Reparent; + m_pendingOp.body = dropBody; + m_pendingOp.parent = dropTarget == Draw::DROP_CHILD ? refBody : refBody->GetParent(); + m_pendingOp.idx = 0; if (dropTarget == Draw::DROP_BEFORE) - m_pendingReparent.idx = targetIdx; + m_pendingOp.idx = targetIdx; else if (dropTarget == Draw::DROP_AFTER) - m_pendingReparent.idx = targetIdx + 1; + m_pendingOp.idx = targetIdx + 1; } } @@ -409,9 +560,36 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) } if (ImGui::IsItemActivated()) { + m_viewport->GetMap()->SetSelectedObject({ Projectable::OBJECT, Projectable::SYSTEMBODY, body }); m_selectedBody = body; } + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Center")) { + m_viewport->GetMap()->SetViewedObject({ Projectable::OBJECT, Projectable::SYSTEMBODY, body }); + } + + if (ImGui::MenuItem("Add Child")) { + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = body; + m_pendingOp.idx = body->GetNumChildren(); + } + + if (ImGui::MenuItem("Add Sibling")) { + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = body->GetParent(); + m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(body) + 1; + } + + // TODO: "add body" context menu + if (body->GetParent() && ImGui::MenuItem("Delete")) { + m_pendingOp.type = BodyRequest::TYPE_Delete; + m_pendingOp.body = m_selectedBody; + } + + ImGui::EndPopup(); + } + // TODO: custom rendering on body entry, e.g. icon / contents etc. return open && body->GetNumChildren(); @@ -456,3 +634,19 @@ void SystemEditor::DrawSystemProperties() StarSystem::EditorAPI::EditProperties(m_system.Get(), GetUndo()); } + +void SystemEditor::DrawFileActionModal() +{ + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(30, 30)); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + if (ImGui::BeginPopupModal(FILE_MODAL_ID, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); + ImGui::TextUnformatted("Waiting on a file action to complete..."); + ImGui::PopFont(); + ImGui::EndPopup(); + } + ImGui::PopFont(); + ImGui::PopStyleVar(); +} diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 4fce91fcc45..b97b0ddfad8 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -13,6 +13,11 @@ // Forward declaration typedef unsigned int ImGuiID; +namespace pfd { + class open_file; + class save_file; +} // namespace pfd + class Galaxy; class StarSystem; class SystemBody; @@ -38,7 +43,7 @@ class SystemEditor : public Application::Lifecycle { RefCountedPtr GetGalaxy(); void SetSelectedBody(SystemBody *body); - SystemBody *GetSystemBody(); + SystemBody *GetSelectedBody() { return m_selectedBody; } protected: void Start() override; @@ -62,6 +67,13 @@ class SystemEditor : public Application::Lifecycle { void DrawUndoDebug(); + void ActivateOpenDialog(); + void ActivateSaveDialog(); + + void DrawFileActionModal(); + + void HandleBodyOperations(); + UndoSystem *GetUndo() { return m_undo.get(); } private: @@ -80,16 +92,31 @@ class SystemEditor : public Application::Lifecycle { std::unique_ptr m_undo; std::string m_filepath; + std::string m_filedir; SystemBody *m_selectedBody; - struct ReparentRequest { + struct BodyRequest { + enum Type { + TYPE_None, + TYPE_Add, + TYPE_Delete, + TYPE_Reparent + }; + + Type type = TYPE_None; + uint32_t newBodyType = 0; // SystemBody::BodyType SystemBody *parent = nullptr; SystemBody *body = nullptr; size_t idx = 0; }; - ReparentRequest m_pendingReparent; + BodyRequest m_pendingOp; + + std::unique_ptr m_openFile; + std::unique_ptr m_saveFile; + + ImGuiID m_fileActionActiveModal; }; } // namespace Editor diff --git a/src/editor/system/SystemEditorHelpers.cpp b/src/editor/system/SystemEditorHelpers.cpp index 55c4fbdf93c..37bacdf648a 100644 --- a/src/editor/system/SystemEditorHelpers.cpp +++ b/src/editor/system/SystemEditorHelpers.cpp @@ -145,6 +145,9 @@ bool Draw::InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags f if (changed) *val = fixed::FromDouble(val_d / distance_multipliers[unit_type]); + if (ImGui::IsItemActivated()) + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + ImGui::PopID(); ImGui::EndGroup(); @@ -193,6 +196,9 @@ bool Draw::InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInput if (changed) *val = fixed::FromDouble(val_d); + if (ImGui::IsItemActivated()) + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + ImGui::PopID(); ImGui::EndGroup(); @@ -241,6 +247,9 @@ bool Draw::InputFixedRadius(const char *str, fixed *val, bool is_solar, ImGuiInp if (changed) *val = fixed::FromDouble(val_d); + if (ImGui::IsItemActivated()) + ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); + ImGui::PopID(); ImGui::EndGroup(); From c536f6e9e8e8be0ee2b7ed090b64d4c391330893 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Aug 2023 04:21:11 -0400 Subject: [PATCH 18/50] SystemEditor: use SystemBody orbit generation - Avoids issues with body orbits "snapping" after edit due to mismatch in calculation of body orbit --- src/editor/system/GalaxyEditAPI.cpp | 34 ++--------------------------- src/editor/system/GalaxyEditAPI.h | 3 --- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 13dff02d427..c05e3249935 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -325,42 +325,12 @@ size_t SystemBody::EditorAPI::GetIndexInParent(SystemBody *body) return std::distance(parent->GetChildren().begin(), iter); } -void SystemBody::EditorAPI::UpdateBodyOrbit(SystemBody *body) -{ - body->m_orbMin = body->m_semiMajorAxis - body->m_eccentricity * body->m_semiMajorAxis; - body->m_orbMax = 2 * body->m_semiMajorAxis - body->m_orbMin; - - if (body->m_parent) - UpdateOrbitAroundParent(body, body->m_parent); -} - -void SystemBody::EditorAPI::UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent) -{ - if (parent->GetType() == SystemBody::TYPE_GRAVPOINT) // generalize Kepler's law to multiple stars - body->m_orbit.SetShapeAroundBarycentre(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->GetMass(), body->m_eccentricity.ToDouble()); - else - body->m_orbit.SetShapeAroundPrimary(body->m_semiMajorAxis.ToDouble() * AU, parent->GetMass(), body->m_eccentricity.ToDouble()); - - body->m_orbit.SetPhase(body->m_orbitalPhaseAtStart.ToDouble()); - - // orbit longitude of ascending node - double longitude = body->m_orbitalOffset.ToDouble(); - // orbit inclination - double inclination = body->m_inclination.ToDouble(); - // orbit argument of periapsis - double argument = body->m_argOfPeriapsis.ToDouble(); - body->m_orbit.SetPlane( - matrix3x3d::RotateY(-longitude) * - matrix3x3d::RotateX(-0.5 * M_PI + inclination) * - matrix3x3d::RotateZ(argument)); -} - void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem *undo) { ImGui::SeparatorText("Orbital Parameters"); bool orbitChanged = false; - auto updateBodyOrbit = [=](){ UpdateBodyOrbit(body); }; + auto updateBodyOrbit = [=](){ body->SetOrbitFromParameters(); }; orbitChanged |= Draw::InputFixedDistance("Semi-Major Axis", &body->m_semiMajorAxis); if (Draw::UndoHelper("Edit Semi-Major Axis", undo)) @@ -428,7 +398,7 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * AddUndoSingleValueClosure(undo, &body->m_rotationPeriod, updateBodyOrbit); if (orbitChanged) - UpdateBodyOrbit(body); + body->SetOrbitFromParameters(); } void SystemBody::EditorAPI::EditEconomicProperties(SystemBody *body, UndoSystem *undo) diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 87775679036..c8a485e53aa 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -35,9 +35,6 @@ class SystemBody::EditorAPI { static SystemBody *RemoveChild(SystemBody *parent, size_t idx = -1); static size_t GetIndexInParent(SystemBody *body); - static void UpdateBodyOrbit(SystemBody *body); - static void UpdateOrbitAroundParent(SystemBody *body, SystemBody *parent); - static void EditOrbitalParameters(SystemBody *body, Editor::UndoSystem *undo); static void EditEconomicProperties(SystemBody *body, Editor::UndoSystem *undo); static void EditStarportProperties(SystemBody *body, Editor::UndoSystem *undo); From 1a77c6dfa1dafa253da65e0c91270de575b75352 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Aug 2023 04:22:46 -0400 Subject: [PATCH 19/50] SystemEditor: handle no active system --- src/editor/system/SystemEditor.cpp | 3 ++- src/editor/system/SystemEditorViewport.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index e28a6357d8a..e8310859c40 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -81,6 +81,7 @@ class SystemEditor::UndoSetSelection : public UndoStep { SystemEditor::SystemEditor(EditorApp *app) : m_app(app), m_undo(new UndoSystem()), + m_system(nullptr), m_selectedBody(nullptr), m_pendingOp() { @@ -452,7 +453,7 @@ void SystemEditor::DrawOutliner() std::string name = m_system.Valid() ? m_system->GetName() : ""; - std::string label = fmt::format("System: {}", m_system->GetName()); + std::string label = fmt::format("System: {}", name); if (ImGui::Selectable(label.c_str(), !m_selectedBody)) { m_selectedBody = nullptr; } diff --git a/src/editor/system/SystemEditorViewport.cpp b/src/editor/system/SystemEditorViewport.cpp index 68f75936b93..dfac1ad2087 100644 --- a/src/editor/system/SystemEditorViewport.cpp +++ b/src/editor/system/SystemEditorViewport.cpp @@ -34,6 +34,10 @@ SystemEditorViewport::SystemEditorViewport(EditorApp *app, SystemEditor *editor) m_map->svColor[SystemMapViewport::GRID_LEG] = Color(0x787878FF); m_map->svColor[SystemMapViewport::SYSTEMBODY] = Color(0xB5BCE3FF).Shade(0.5); m_map->svColor[SystemMapViewport::SYSTEMBODY_ORBIT] = Color(0x5ACC0AFF); + + m_background.reset(new Background::Container(m_app->GetRenderer(), m_editor->GetRng())); + m_background->SetDrawFlags(Background::Container::DRAW_SKYBOX); + m_map->SetBackground(m_background.get()); } SystemEditorViewport::~SystemEditorViewport() @@ -44,10 +48,6 @@ void SystemEditorViewport::SetSystem(RefCountedPtr system) { m_map->SetReferenceTime(0.0); // Jan 1 3200 m_map->SetCurrentSystem(system); - - m_background.reset(new Background::Container(m_app->GetRenderer(), m_editor->GetRng())); - m_background->SetDrawFlags(Background::Container::DRAW_SKYBOX); - m_map->SetBackground(m_background.get()); } bool SystemEditorViewport::OnCloseRequested() From 9bec08f540eea4361c767b91eb28d8fcaf402aea Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 31 Aug 2023 18:28:04 -0400 Subject: [PATCH 20/50] SystemEditor: fully functional load/save - Add New File operation to clear and start a new system - Open File can open either Json or Lua format systems - Save / Save As fully functional, only allows saving in Json format --- src/editor/EditorApp.cpp | 8 +- src/editor/system/SystemEditor.cpp | 151 +++++++++++++++++++++++++---- src/editor/system/SystemEditor.h | 22 ++++- 3 files changed, 157 insertions(+), 24 deletions(-) diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index 14636e0a285..ab9485b5684 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -56,14 +56,16 @@ void EditorApp::Initialize(argh::parser &cmdline) return; } - if (cmdline("--system")) { + if (cmdline["--system"]) { std::string systemPath; cmdline("--system") >> systemPath; RefCountedPtr systemEditor(new SystemEditor(this)); - if (!systemPath.empty()) - systemEditor->LoadSystem(systemPath); + if (!systemPath.empty()) { + systemPath = FileSystem::JoinPathBelow(FileSystem::GetDataDir(), systemPath); + systemEditor->LoadSystemFromDisk(systemPath); + } QueueLifecycle(systemEditor); SetAppName("SystemEditor"); diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index e8310859c40..499715e94f6 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -4,7 +4,9 @@ #include "SystemEditor.h" #include "EditorIcons.h" +#include "Json.h" #include "GalaxyEditAPI.h" +#include "JsonUtils.h" #include "ModManager.h" #include "SystemEditorHelpers.h" @@ -12,6 +14,7 @@ #include "FileSystem.h" #include "SystemView.h" +#include "core/StringUtils.h" #include "editor/EditorApp.h" #include "editor/EditorDraw.h" #include "editor/UndoSystem.h" @@ -29,6 +32,7 @@ #include "portable-file-dialogs/pfd.h" +#include #include using namespace Editor; @@ -99,12 +103,101 @@ SystemEditor::~SystemEditor() { } -bool SystemEditor::LoadSystem(const std::string &filepath) +void SystemEditor::NewSystem() { - const CustomSystem *csys = m_systemLoader->LoadSystem(filepath); - if (!csys) + ClearSystem(); + + SystemPath path(0, 0, 0, 0); + + auto *newSystem = new StarSystem::GeneratorAPI(path, m_galaxy, nullptr, GetRng()); + m_system.Reset(newSystem); + + newSystem->SetRootBody(newSystem->NewBody()); +} + +bool SystemEditor::LoadSystemFromDisk(const std::string &absolutePath) +{ + if (ends_with_ci(absolutePath, ".lua")) { + std::string filepath = FileSystem::GetRelativePath(FileSystem::GetDataDir(), absolutePath); + + if (filepath.empty()) { + Log::Error("Cannot read .lua Custom System files from outside the game's data directory!"); + return false; + } + + return LoadSystem(FileSystem::gameDataFiles.Lookup(filepath)); + } else { + std::string dirpath = absolutePath.substr(0, absolutePath.find_last_of("/\\")); + std::string filename = absolutePath.substr(dirpath.size() + 1); + + // Hack: construct a temporary FileSource to load from an arbitrary path + auto fs = FileSystem::FileSourceFS(dirpath); + + return LoadSystem(fs.Lookup(filename)); + } +} + +bool SystemEditor::LoadSystem(const FileSystem::FileInfo &file) +{ + if (!file.Exists()) { + Log::Error("Cannot open file path {}", file.GetAbsolutePath()); return false; + } + ClearSystem(); + + m_filepath = file.GetAbsolutePath(); + m_filedir = file.GetAbsoluteDir(); + + bool ok = false; + if (ends_with_ci(file.GetPath(), ".json")) { + const CustomSystem *csys = m_systemLoader->LoadSystemFromJSON(file.GetName(), JsonUtils::LoadJson(file.Read())); + if (csys) + ok = LoadCustomSystem(csys); + } else if (ends_with_ci(file.GetPath(), ".lua")) { + const CustomSystem *csys = m_systemLoader->LoadSystem(file.GetPath()); + if (csys) + ok = LoadCustomSystem(csys); + } + + if (ok) { + std::string windowTitle = fmt::format("System Editor - {}", m_filepath); + SDL_SetWindowTitle(m_app->GetRenderer()->GetSDLWindow(), windowTitle.c_str()); + } else { + m_filepath.clear(); + } + + + return ok; +} + +bool SystemEditor::WriteSystem(const std::string &filepath) +{ + Log::Info("Writing to path: {}/{}", FileSystem::GetDataDir(), filepath); + // FIXME: need better file-saving interface for the user + // FileSystem::FileSourceFS(FileSystem::GetDataDir()).OpenWriteStream(filepath, FileSystem::FileSourceFS::WRITE_TEXT); + + FILE *f = fopen(filepath.c_str(), "w"); + + if (!f) + return false; + + // StarSystem::EditorAPI::ExportToLua(f, m_system.Get(), m_galaxy.Get()); + + Json systemdef = Json::object(); + + m_system->DumpToJson(systemdef); + + std::string jsonData = systemdef.dump(1, '\t'); + + fwrite(jsonData.data(), 1, jsonData.size(), f); + fclose(f); + + return true; +} + +bool SystemEditor::LoadCustomSystem(const CustomSystem *csys) +{ SystemPath path = {csys->sectorX, csys->sectorY, csys->sectorZ, csys->systemIndex}; Uint32 _init[5] = { Uint32(csys->seed), Uint32(csys->sectorX), Uint32(csys->sectorY), Uint32(csys->sectorZ), UNIVERSE_SEED }; Random rng(_init, 5); @@ -132,26 +225,23 @@ bool SystemEditor::LoadSystem(const std::string &filepath) } m_system = system; - m_filepath = filepath; - m_filedir = filepath.substr(0, filepath.rfind('/')), - m_viewport->SetSystem(system); return true; } -void SystemEditor::WriteSystem(const std::string &filepath) +void SystemEditor::ClearSystem() { - Log::Info("Writing to path: {}/{}", FileSystem::GetDataDir(), filepath); - // FIXME: need better file-saving interface for the user - FILE *f = FileSystem::FileSourceFS(FileSystem::GetDataDir()).OpenWriteStream(filepath, FileSystem::FileSourceFS::WRITE_TEXT); + m_undo->Clear(); - if (!f) - return; + m_system.Reset(); + m_viewport->SetSystem(m_system); + m_selectedBody = nullptr; + m_pendingOp = {}; - StarSystem::EditorAPI::ExportToLua(f, m_system.Get(), m_galaxy.Get()); + m_filepath.clear(); - fclose(f); + SDL_SetWindowTitle(m_app->GetRenderer()->GetSDLWindow(), "System Editor"); } // Here to avoid needing to drag in the Galaxy header in SystemEditor.h @@ -178,6 +268,7 @@ void SystemEditor::ActivateOpenDialog() "Open Custom System File", FileSystem::JoinPath(FileSystem::GetDataDir(), m_filedir), { + "All System Definition Files", "*.lua *.json", "Lua System Definition (.lua)", "*.lua", "JSON System Definition (.json)", "*.json" }) @@ -193,7 +284,6 @@ void SystemEditor::ActivateSaveDialog() "Save Custom System File", FileSystem::JoinPath(FileSystem::GetDataDir(), m_filedir), { - "Lua System Definition (.lua)", "*.lua", "JSON System Definition (.json)", "*.json" }) ); @@ -229,6 +319,7 @@ void SystemEditor::Update(float deltaTime) if (!resultFiles.empty()) { Log::Info("OpenFile: {}", resultFiles[0]); + LoadSystemFromDisk(resultFiles[0]); } } @@ -238,6 +329,10 @@ void SystemEditor::Update(float deltaTime) if (!filePath.empty()) { Log::Info("SaveFile: {}", filePath); + bool success = WriteSystem(filePath); + + if (success) + m_lastSavedUndoStack = m_undo->GetCurrentEntry(); } } @@ -367,11 +462,24 @@ void SystemEditor::DrawInterface() if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Open File", "Ctrl+O")) + if (ImGui::MenuItem("New File", "Ctrl+N")) { + // TODO: show nag dialog if attempting open with unsaved changes + + NewSystem(); + } + + if (ImGui::MenuItem("Open File", "Ctrl+O")) { + // TODO: show nag dialog if attempting open with unsaved changes + ActivateOpenDialog(); + } if (ImGui::MenuItem("Save", "Ctrl+S")) { - WriteSystem(m_filepath); + // Cannot write back .lua files + if (ends_with_ci(m_filepath, ".lua")) + ActivateSaveDialog(); + else + WriteSystem(m_filepath); } if (ImGui::MenuItem("Save As", "Ctrl+Shift+S")) @@ -410,8 +518,6 @@ void SystemEditor::DrawInterface() else DrawSystemProperties(); - ImGui::ButtonEx("Break undo system!", ImVec2(0, 0), ImGuiButtonFlags_MouseButtonRight); - ImGui::PopItemWidth(); } ImGui::End(); @@ -443,6 +549,8 @@ void SystemEditor::DrawInterface() DrawFileActionModal(); + DrawPickSystemModal(); + if (isFirstRun) isFirstRun = false; } @@ -636,6 +744,11 @@ void SystemEditor::DrawSystemProperties() StarSystem::EditorAPI::EditProperties(m_system.Get(), GetUndo()); } +void SystemEditor::DrawPickSystemModal() +{ + +} + void SystemEditor::DrawFileActionModal() { ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index b97b0ddfad8..a660697f044 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -7,6 +7,7 @@ #include "Random.h" #include "RefCounted.h" #include "core/Application.h" +#include "galaxy/SystemPath.h" #include @@ -18,9 +19,14 @@ namespace pfd { class save_file; } // namespace pfd +namespace FileSystem { + class FileInfo; +} // namespace FileSystem + class Galaxy; class StarSystem; class SystemBody; +class CustomSystem; class CustomSystemsDatabase; namespace Editor { @@ -36,8 +42,11 @@ class SystemEditor : public Application::Lifecycle { SystemEditor(EditorApp *app); ~SystemEditor(); - bool LoadSystem(const std::string &filepath); - void WriteSystem(const std::string &filepath); + void NewSystem(); + bool LoadSystemFromDisk(const std::string &absolutePath); + + // Write the currently edited system out to disk as a JSON file + bool WriteSystem(const std::string &filepath); Random &GetRng() { return m_random; } RefCountedPtr GetGalaxy(); @@ -53,6 +62,11 @@ class SystemEditor : public Application::Lifecycle { void HandleInput(); private: + void ClearSystem(); + bool LoadSystem(const FileSystem::FileInfo &file); + bool LoadCustomSystem(const CustomSystem *system); + void LoadSystemFromGalaxy(RefCountedPtr system); + void SetupLayout(ImGuiID dockspaceID); void DrawInterface(); @@ -71,6 +85,7 @@ class SystemEditor : public Application::Lifecycle { void ActivateSaveDialog(); void DrawFileActionModal(); + void DrawPickSystemModal(); void HandleBodyOperations(); @@ -90,6 +105,7 @@ class SystemEditor : public Application::Lifecycle { Random m_random; std::unique_ptr m_undo; + size_t m_lastSavedUndoStack; std::string m_filepath; std::string m_filedir; @@ -116,6 +132,8 @@ class SystemEditor : public Application::Lifecycle { std::unique_ptr m_openFile; std::unique_ptr m_saveFile; + SystemPath m_openSystemPath; + ImGuiID m_fileActionActiveModal; }; From 82315aa14f52ea31e42922a5e7fcf90b6d5b5281 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 2 Sep 2023 04:44:11 -0400 Subject: [PATCH 21/50] SystemEditor: display body mass in Pt - Min representable mass increment is 1.4 Gt or 0.0014 Pt. Megatonnes is not a useful number. - Ensure unit dropdown remains editable even when input is disabled --- src/editor/system/SystemEditorHelpers.cpp | 69 ++++++++++++++++++----- src/editor/system/SystemEditorHelpers.h | 2 +- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/editor/system/SystemEditorHelpers.cpp b/src/editor/system/SystemEditorHelpers.cpp index 37bacdf648a..dbda7ef16aa 100644 --- a/src/editor/system/SystemEditorHelpers.cpp +++ b/src/editor/system/SystemEditorHelpers.cpp @@ -37,25 +37,26 @@ const double distance_multipliers[] = { enum MassUnits { MASS_SOLS, // Solar masses MASS_EARTH, // Earth masses - MASS_MT, // Kilograms x 1,000,000,000 + MASS_PT, // Kilograms x 1e15 }; const char *mass_labels[] = { "Solar Masses", "Earth Masses", - "Megatonnes" + "Pt (× 10¹⁸ kg)", }; const char *mass_formats[] = { "%.4f Sol", "%.4f Earth", - "%.2f Mt" + "%.4f Pt", }; -const double KG_TO_MT = 1000000000; +// KG->T = 1e3 + T->PT = 1e15 +const double KG_TO_PT = 1e18; -const double SOL_MASS_MT = SOL_MASS / KG_TO_MT; -const double EARTH_MASS_MT = EARTH_MASS / KG_TO_MT; +const double SOL_MASS_PT = SOL_MASS / KG_TO_PT; +const double EARTH_MASS_PT = EARTH_MASS / KG_TO_PT; const double SOL_TO_EARTH_MASS = SOL_MASS / EARTH_MASS; enum RadiusUnits { @@ -80,6 +81,32 @@ const double SOL_RADIUS_KM = SOL_RADIUS / 1000.0; const double EARTH_RADIUS_KM = EARTH_RADIUS / 1000.0; const double SOL_TO_EARTH_RADIUS = SOL_RADIUS / EARTH_RADIUS; +namespace ImGui { + void UnDisable() + { + ImGuiContext& g = *GImGui; + + bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (was_disabled) { + g.Style.Alpha = g.DisabledAlphaBackup; + g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled; + } + } + + void ReDisable() + { + ImGuiContext& g = *GImGui; + + bool was_disabled = (g.ItemFlagsStack.back() & ImGuiItemFlags_Disabled) != 0; + if (was_disabled) { + g.Style.Alpha *= g.Style.DisabledAlpha; + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + } + } + + bool IsDisabled() { return (GImGui->ItemFlagsStack.back() & ImGuiItemFlags_Disabled) != 0; } +} + void Draw::SubtractItemWidth() { ImGuiWindow *window = ImGui::GetCurrentWindow(); @@ -87,11 +114,11 @@ void Draw::SubtractItemWidth() ImGui::SetNextItemWidth(ImGui::CalcItemWidth() - used_width); } -bool Draw::InputFixedSlider(const char *str, fixed *val, double val_min, double val_max, const char *format, ImGuiInputTextFlags flags) +bool Draw::InputFixedSlider(const char *str, fixed *val, double val_min, double val_max, const char *format, ImGuiSliderFlags flags) { double val_d = val->ToDouble(); - bool changed = ImGui::SliderScalar(str, ImGuiDataType_Double, &val_d, &val_min, &val_max, format, ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_NoRoundToFormat); + bool changed = ImGui::SliderScalar(str, ImGuiDataType_Double, &val_d, &val_min, &val_max, format, flags | ImGuiSliderFlags_NoRoundToFormat); // delay one frame before writing back the value for the undo system to push a value if (changed && !ImGui::IsItemActivated()) *val = fixed::FromDouble(val_d); @@ -132,13 +159,17 @@ bool Draw::InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags f unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); + ImGui::UnDisable(); if (ImGui::Combo("##Unit", &unit_type, distance_labels, COUNTOF(distance_labels))) { ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); } + ImGui::ReDisable(); double val_step = unit_type == DISTANCE_KM ? 1.0 : unit_type == DISTANCE_LS ? 0.1 : 0.01; val_d *= distance_multipliers[unit_type]; + if (ImGui::IsDisabled()) val_step *= 0.0; + ImGui::SameLine(0.f, 1.f); Draw::SubtractItemWidth(); bool changed = ImGui::InputDouble(str, &val_d, val_step, val_step * 10.0, distance_formats[unit_type], flags | ImGuiInputTextFlags_EnterReturnsTrue); @@ -168,30 +199,35 @@ bool Draw::InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInput unit_type = MASS_EARTH; double val_d = val->ToDouble(); - if (!is_solar && val_d < 0.0000001) - unit_type = MASS_MT; + if (!is_solar && val_d < 0.0001) + unit_type = MASS_PT; unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); + ImGui::UnDisable(); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); if (ImGui::Combo("##Unit", &unit_type, mass_labels, COUNTOF(mass_labels))) { ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); } + ImGui::ReDisable(); + double val_step = 0.01; + if (ImGui::IsDisabled()) val_step *= 0.0; + if (is_solar && unit_type != MASS_SOLS) - val_d *= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_MT; + val_d *= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_PT; if (!is_solar && unit_type != MASS_EARTH) - val_d *= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_MT; + val_d *= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_PT; ImGui::SameLine(0.f, 1.f); Draw::SubtractItemWidth(); bool changed = ImGui::InputDouble(str, &val_d, val_step, val_step * 10.0, mass_formats[unit_type], flags | ImGuiInputTextFlags_EnterReturnsTrue); if (is_solar && unit_type != MASS_SOLS) - val_d /= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_MT; + val_d /= unit_type == MASS_EARTH ? SOL_TO_EARTH_MASS : SOL_MASS_PT; if (!is_solar && unit_type != MASS_EARTH) - val_d /= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_MT; + val_d /= unit_type == MASS_SOLS ? (1.0 / SOL_TO_EARTH_MASS) : EARTH_MASS_PT; if (changed) *val = fixed::FromDouble(val_d); @@ -224,12 +260,17 @@ bool Draw::InputFixedRadius(const char *str, fixed *val, bool is_solar, ImGuiInp unit_type = ImGui::GetStateStorage()->GetInt(unit_type_id, unit_type); + ImGui::UnDisable(); ImGui::SetNextItemWidth(ImGui::GetFrameHeight()); if (ImGui::Combo("##Unit", &unit_type, radius_labels, COUNTOF(radius_labels))) { ImGui::GetStateStorage()->SetInt(unit_type_id, unit_type); } + ImGui::ReDisable(); + double val_step = 0.01; + if (ImGui::IsDisabled()) val_step *= 0.0; + if (is_solar && unit_type != RADIUS_SOLS) val_d *= unit_type == RADIUS_EARTH ? SOL_TO_EARTH_MASS : SOL_RADIUS_KM; if (!is_solar && unit_type != RADIUS_EARTH) diff --git a/src/editor/system/SystemEditorHelpers.h b/src/editor/system/SystemEditorHelpers.h index f059c49cfea..839ccd69811 100644 --- a/src/editor/system/SystemEditorHelpers.h +++ b/src/editor/system/SystemEditorHelpers.h @@ -43,7 +43,7 @@ namespace Editor::Draw { return ret; } - bool InputFixedSlider(const char *str, fixed *val, double val_min = 0.0, double val_max = 1.0, const char *format = "%.4f", ImGuiInputTextFlags flags = 0); + bool InputFixedSlider(const char *str, fixed *val, double val_min = 0.0, double val_max = 1.0, const char *format = "%.4f", ImGuiSliderFlags flags = ImGuiSliderFlags_AlwaysClamp); bool InputFixedDegrees(const char *str, fixed *val, double val_min = -360.0, double val_max = 360.0, ImGuiInputTextFlags flags = 0); bool InputFixedDistance(const char *str, fixed *val, ImGuiInputTextFlags flags = 0); bool InputFixedMass(const char *str, fixed *val, bool is_solar, ImGuiInputTextFlags flags = 0); From 7ffedd83e71d4a1681e5aeef9da1e155e7b580c3 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 2 Sep 2023 04:50:46 -0400 Subject: [PATCH 22/50] SystemEditor: procedurally generate added bodies - One-shot random generation fully generates a body appropriate for the position in the system it was generated - Provide option to recalculate body surface parameters from current mass+radius+orbit - Improve display of derived parameters for current body and sync visibility across bodies - Add atmosphere-related derived parameters for system bodies --- src/editor/system/GalaxyEditAPI.cpp | 233 +++++++++++++++++++++++----- src/editor/system/GalaxyEditAPI.h | 7 +- src/editor/system/SystemEditor.cpp | 44 ++++-- 3 files changed, 231 insertions(+), 53 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index c05e3249935..5a2a8d76455 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -5,6 +5,7 @@ #include "SystemEditorHelpers.h" +#include "core/Log.h" #include "editor/UndoStepType.h" #include "editor/EditorDraw.h" @@ -12,6 +13,7 @@ #include "galaxy/Sector.h" #include "galaxy/Galaxy.h" #include "galaxy/NameGenerator.h" +#include "galaxy/StarSystemGenerator.h" #include "imgui/imgui.h" #include "imgui/imgui_stdlib.h" @@ -30,6 +32,21 @@ namespace { static constexpr double SECONDS_TO_DAYS = 1.0 / (3600.0 * 24.0); } +namespace Editor::Draw { + + // Essentially CollapsingHeader without the frame and with consistent ID regardless of edited body + bool DerivedValues(std::string_view sectionLabel) { + constexpr ImGuiID detailSeed = "Editor Details"_hash32; + + if (ImGui::GetCurrentWindow()->SkipItems) + return false; + + ImGuiID treeId = ImGui::GetIDWithSeed(sectionLabel.data(), sectionLabel.data() + sectionLabel.size(), detailSeed); + return ImGui::TreeNodeBehavior(treeId, ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, "Derived Values"); + } + +} // namespace Editor::Draw + void StarSystem::EditorAPI::ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) { fprintf(f, "-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details\n"); @@ -61,6 +78,47 @@ SystemBody *StarSystem::EditorAPI::NewBody(StarSystem *system) return system->NewBody(); } +SystemBody *StarSystem::EditorAPI::NewBodyAround(StarSystem *system, Random &rng, SystemBody *primary, size_t idx) +{ + StarSystemRandomGenerator gen = {}; + + // Ensure consistent density distribution parameters across multiple runs + const SystemPath &path = system->GetPath(); + Random shellRng { StarSystemRandomGenerator::BODY_SATELLITE_SALT, primary->GetSeed(), uint32_t(path.sectorX), uint32_t(path.sectorY), uint32_t(path.sectorZ), UNIVERSE_SEED }; + + fixed discMin, discBound, discMax; + fixed discDensity = gen.CalcBodySatelliteShellDensity(shellRng, primary, discMin, discMax); + + discBound = fixed(0); + + size_t numChildren = primary->GetNumChildren(); + + // Set orbit slice parameters from surrounding bodies + if (idx > 0) + discMin = primary->m_children[idx - 1]->m_orbMax * fixed(105, 100); + if (idx < numChildren) + discBound = primary->m_children[idx]->m_orbMin; + + // Ensure we have enough discMax to generate a body, even if it means "cheating" + + if (discMin * fixed(12, 10) > discMax) { + Log::Warning("Creating body outside of parent {} natural satellite radius {:.8f}, generation may not be correct.", primary->GetName(), discMax.ToDouble()); + discMax = numChildren > 0 ? primary->m_children[numChildren - 1]->m_orbMax : discMin; + discMax *= fixed(12, 10); + } + + if (discMin > discMax || (discBound != 0 && discMin > discBound)) + return nullptr; + + SystemBody *body = gen.MakeBodyInOrbitSlice(rng, static_cast(system), primary, discMin, discBound, discMax, discDensity); + if (!body) + return nullptr; + + gen.PickPlanetType(body, rng); + + return body; +} + void StarSystem::EditorAPI::AddBody(StarSystem *system, SystemBody *body, size_t idx) { if (idx == size_t(-1)) @@ -118,15 +176,6 @@ void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem if (Draw::UndoHelper("Edit System Name", undo)) AddUndoSingleValue(undo, &system->m_name); - - // ImGui::SameLine(); - // if (ImGui::Button("R", ImVec2(buttonSize, buttonSize))) { - // } - // if (Draw::UndoHelper("Edit System Name", undo)) - // AddUndoSingleValue(undo, &system->m_name); - - // ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); - // ImGui::TextUnformatted("Name"); } void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) @@ -359,29 +408,34 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * AddUndoSingleValueClosure(undo, &body->m_orbitalPhaseAtStart, updateBodyOrbit); Draw::HelpMarker("True Anomaly at Epoch\nRelative to Argument of Periapsis"); - ImGui::BeginDisabled(); - ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); - ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); - double orbit_period = body->GetOrbit().Period() * SECONDS_TO_DAYS; - ImGui::InputDouble("Orbital Period", &orbit_period, 0.0, 0.0, "%.2f days"); + if (Draw::DerivedValues("Orbital Parameters")) { + ImGui::BeginDisabled(); - if (body->GetParent()) { - // calculate the time offset from periapsis at epoch - double orbit_time_at_start = (body->GetOrbit().GetOrbitalPhaseAtStart() / (2.0 * M_PI)) * body->GetOrbit().Period(); + ImGui::InputFixed("Periapsis", &body->m_orbMin, 0.0, 0.0, "%0.6f AU"); + ImGui::InputFixed("Apoapsis", &body->m_orbMax, 0.0, 0.0, "%0.6f AU"); + + double orbit_period = body->GetOrbit().Period() * SECONDS_TO_DAYS; + ImGui::InputDouble("Orbital Period", &orbit_period, 0.0, 0.0, "%.2f days"); + + if (body->GetParent()) { + // calculate the time offset from periapsis at epoch + double orbit_time_at_start = (body->GetOrbit().GetOrbitalPhaseAtStart() / (2.0 * M_PI)) * body->GetOrbit().Period(); - double orbit_vel_ap = body->GetOrbit().OrbitalVelocityAtTime( - body->GetParent()->GetMass(), - body->GetOrbit().Period() * 0.5 - orbit_time_at_start).Length() / 1000.0; + double orbit_vel_ap = body->GetOrbit().OrbitalVelocityAtTime( + body->GetParent()->GetMass(), + body->GetOrbit().Period() * 0.5 - orbit_time_at_start).Length() / 1000.0; - double orbit_vel_pe = body->GetOrbit().OrbitalVelocityAtTime( - body->GetParent()->GetMass(), - -orbit_time_at_start).Length() / 1000.0; + double orbit_vel_pe = body->GetOrbit().OrbitalVelocityAtTime( + body->GetParent()->GetMass(), + -orbit_time_at_start).Length() / 1000.0; - ImGui::InputDouble("Orbital Velocity (AP)", &orbit_vel_ap, 0.0, 0.0, "%.2f km/s"); - ImGui::InputDouble("Orbital Velocity (PE)", &orbit_vel_pe, 0.0, 0.0, "%.2f km/s"); + ImGui::InputDouble("Orbital Velocity (AP)", &orbit_vel_ap, 0.0, 0.0, "%.2f km/s"); + ImGui::InputDouble("Orbital Velocity (PE)", &orbit_vel_pe, 0.0, 0.0, "%.2f km/s"); + } + + ImGui::EndDisabled(); } - ImGui::EndDisabled(); ImGui::SeparatorText("Rotation Parameters"); @@ -399,6 +453,7 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * if (orbitChanged) body->SetOrbitFromParameters(); + } void SystemBody::EditorAPI::EditEconomicProperties(SystemBody *body, UndoSystem *undo) @@ -433,10 +488,15 @@ void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem EditEconomicProperties(body, undo); } -void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) +void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSystem *undo) { bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; + bool bodyChanged = false; + auto updateBodyDerived = [=]() { + body->SetAtmFromParameters(); + }; + ImGui::InputText("Name", &body->m_name); if (Draw::UndoHelper("Edit Name", undo)) AddUndoSingleValue(undo, &body->m_name); @@ -449,23 +509,66 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) if (body->GetSuperType() < SUPERTYPE_STARPORT) { + if (!isStar && ImGui::Button(EICON_RANDOM " Body Stats")) { + GenerateDerivedStats(body, rng, undo); + bodyChanged = true; + } + + ImGui::SetItemTooltip("Generate body type, radius, temperature, and surface parameters using the same method as procedural system generation."); + ImGui::SeparatorText("Body Parameters"); - Draw::InputFixedMass("Mass", &body->m_mass, isStar); + bodyChanged |= Draw::InputFixedMass("Mass", &body->m_mass, isStar); if (Draw::UndoHelper("Edit Mass", undo)) - AddUndoSingleValue(undo, &body->m_mass); + AddUndoSingleValueClosure(undo, &body->m_mass, updateBodyDerived); - Draw::InputFixedRadius("Radius", &body->m_radius, isStar); + bodyChanged |= Draw::InputFixedRadius("Radius", &body->m_radius, isStar); if (Draw::UndoHelper("Edit Radius", undo)) - AddUndoSingleValue(undo, &body->m_radius); + AddUndoSingleValueClosure(undo, &body->m_radius, updateBodyDerived); - Draw::InputFixedSlider("Aspect Ratio", &body->m_aspectRatio, 0.0, 2.0); - if (Draw::UndoHelper("Edit Aspect Ratio", undo)) - AddUndoSingleValue(undo, &body->m_aspectRatio); + ImGui::BeginDisabled(); - ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10, "%d°K"); + double surfaceGrav = body->CalcSurfaceGravity(); + ImGui::InputDouble("Surface Gravity", &surfaceGrav, 0, 0, "%.4fg"); + + ImGui::EndDisabled(); + + if (body->GetSuperType() <= SUPERTYPE_GAS_GIANT && body->GetType() != TYPE_PLANET_ASTEROID) { + + Draw::InputFixedSlider("Aspect Ratio", &body->m_aspectRatio, 0.0, 2.0); + if (Draw::UndoHelper("Edit Aspect Ratio", undo)) + AddUndoSingleValue(undo, &body->m_aspectRatio); + + Draw::HelpMarker("Ratio of body equatorial radius to polar radius, or \"bulge\" around axis of spin."); + + } + + bodyChanged |= ImGui::InputInt("Temperature (K)", &body->m_averageTemp, 1, 10, "%d°K"); if (Draw::UndoHelper("Edit Temperature", undo)) - AddUndoSingleValue(undo, &body->m_averageTemp); + AddUndoSingleValueClosure(undo, &body->m_averageTemp, updateBodyDerived); + + ImGui::Spacing(); + + const bool hasDerived = (body->GetType() != TYPE_GRAVPOINT || body->HasChildren()); + + if (hasDerived && Draw::DerivedValues("Body Parameters")) { + ImGui::BeginDisabled(); + + StarSystemRandomGenerator gen = {}; + + // Ensure consistent density distribution parameters across multiple runs + const SystemPath &path = body->GetStarSystem()->GetPath(); + Random shellRng { StarSystemRandomGenerator::BODY_SATELLITE_SALT, body->GetSeed(), uint32_t(path.sectorX), uint32_t(path.sectorY), uint32_t(path.sectorZ), UNIVERSE_SEED }; + + fixed discMin, discBound, discMax; + fixed discDensity = gen.CalcBodySatelliteShellDensity(shellRng, body, discMin, discMax); + + Draw::InputFixedDistance("Satellite Shell Min", &discMin); + Draw::InputFixedDistance("Satellite Shell Max", &discMax); + ImGui::InputFixed("Shell Density Dist", &discDensity, 0, 0, "%.6f"); + + ImGui::EndDisabled(); + } } else { EditStarportProperties(body, undo); @@ -490,14 +593,23 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) if (Draw::UndoHelper("Edit Volcanicity", undo)) AddUndoSingleValue(undo, &body->m_volcanicity); - Draw::InputFixedSlider("Atm. Density", &body->m_volatileGas); + bool gasGiant = body->GetSuperType() == SystemBody::SUPERTYPE_GAS_GIANT; + + bodyChanged |= Draw::InputFixedSlider("Atm. Density", &body->m_volatileGas, + 0.0, gasGiant ? 50.0 : 1.225, "%.3f kg/m³", 0); if (Draw::UndoHelper("Edit Atmosphere Density", undo)) - AddUndoSingleValue(undo, &body->m_volatileGas); + AddUndoSingleValueClosure(undo, &body->m_volatileGas, updateBodyDerived); + + Draw::HelpMarker("Atmospheric density at the body's nominal surface.\n" + "Earth has a density of 1.225kg/m³ at normal surface pressure and temperature."); Draw::InputFixedSlider("Atm. Oxygen", &body->m_atmosOxidizing); if (Draw::UndoHelper("Edit Atmosphere Oxygen", undo)) AddUndoSingleValue(undo, &body->m_atmosOxidizing); + Draw::HelpMarker("Proportion of oxidizing elements in atmosphere, e.g. CO², O².\n" + "Oxidizing elements are a hallmark of outdoor worlds and are needed for human life to survive."); + Draw::InputFixedSlider("Ocean Coverage", &body->m_volatileLiquid); if (Draw::UndoHelper("Edit Ocean Coverage", undo)) AddUndoSingleValue(undo, &body->m_volatileLiquid); @@ -515,5 +627,50 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, UndoSystem *undo) if (Draw::UndoHelper("Edit Life", undo)) AddUndoSingleValue(undo, &body->m_life); + if (Draw::DerivedValues("Surface Parameters")) { + ImGui::BeginDisabled(); + + if (bodyChanged) + body->SetAtmFromParameters(); + + double pressure_p0 = body->GetAtmSurfacePressure(); + ImGui::InputDouble("Surface Pressure", &pressure_p0, 0.0, 0.0, "%.4f atm"); + + double atmRadius = body->GetAtmRadius() / 1000.0; + ImGui::InputDouble("Atmosphere Height", &atmRadius, 0.0, 0.0, "%.2f km"); + + bool scoopable = body->IsScoopable(); + ImGui::Checkbox("Is Scoopable", &scoopable); + + ImGui::EndDisabled(); + } + EditEconomicProperties(body, undo); } + +void SystemBody::EditorAPI::GenerateDerivedStats(SystemBody *body, Random &rng, UndoSystem *undo) +{ + undo->BeginEntry("Generate Body Parameters"); + + // Back up all potentially-modified body variables + AddUndoSingleValue(undo, &body->m_mass); + AddUndoSingleValue(undo, &body->m_type); + AddUndoSingleValue(undo, &body->m_radius); + AddUndoSingleValue(undo, &body->m_averageTemp); + + AddUndoSingleValue(undo, &body->m_axialTilt); + AddUndoSingleValue(undo, &body->m_rotationPeriod); + + AddUndoSingleValue(undo, &body->m_metallicity); + AddUndoSingleValue(undo, &body->m_volcanicity); + AddUndoSingleValue(undo, &body->m_volatileGas); + AddUndoSingleValue(undo, &body->m_atmosOxidizing); + AddUndoSingleValue(undo, &body->m_volatileLiquid); + AddUndoSingleValue(undo, &body->m_volatileIces); + // AddUndoSingleValue(undo, &body->m_humanActivity); + AddUndoSingleValue(undo, &body->m_life); + + StarSystemRandomGenerator().PickPlanetType(body, rng); + + undo->EndEntry(); +} diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index c8a485e53aa..39a5cc09315 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -13,7 +13,8 @@ class StarSystem::EditorAPI { public: static void ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy); - static SystemBody* NewBody(StarSystem *system); + static SystemBody *NewBody(StarSystem *system); + static SystemBody *NewBodyAround(StarSystem *system, Random &rng, SystemBody *primary, size_t idx); static void AddBody(StarSystem *system, SystemBody *body, size_t idx = -1); static void RemoveBody(StarSystem *system, SystemBody *body); @@ -38,5 +39,7 @@ class SystemBody::EditorAPI { static void EditOrbitalParameters(SystemBody *body, Editor::UndoSystem *undo); static void EditEconomicProperties(SystemBody *body, Editor::UndoSystem *undo); static void EditStarportProperties(SystemBody *body, Editor::UndoSystem *undo); - static void EditProperties(SystemBody *body, Editor::UndoSystem *undo); + static void EditProperties(SystemBody *body, Random &rng, Editor::UndoSystem *undo); + + static void GenerateDerivedStats(SystemBody *body, Random &rng, Editor::UndoSystem *undo); }; diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 499715e94f6..8abb7905417 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -3,20 +3,21 @@ #include "SystemEditor.h" -#include "EditorIcons.h" -#include "Json.h" #include "GalaxyEditAPI.h" -#include "JsonUtils.h" -#include "ModManager.h" #include "SystemEditorHelpers.h" +#include "SystemBodyUndo.h" +#include "SystemEditorViewport.h" #include "EnumStrings.h" #include "FileSystem.h" - +#include "JsonUtils.h" +#include "ModManager.h" #include "SystemView.h" #include "core/StringUtils.h" + #include "editor/EditorApp.h" #include "editor/EditorDraw.h" +#include "editor/EditorIcons.h" #include "editor/UndoSystem.h" #include "editor/UndoStepType.h" @@ -27,11 +28,10 @@ #include "pigui/PiGui.h" #include "imgui/imgui.h" -#include "system/SystemBodyUndo.h" -#include "system/SystemEditorViewport.h" #include "portable-file-dialogs/pfd.h" +#include #include #include @@ -96,6 +96,12 @@ SystemEditor::SystemEditor(EditorApp *app) : m_viewport.reset(new SystemEditorViewport(m_app, this)); + m_random.seed({ + // generate random values not dependent on app runtime + uint32_t(std::chrono::system_clock::now().time_since_epoch().count()), + UNIVERSE_SEED + }); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } @@ -358,7 +364,18 @@ void SystemEditor::HandleBodyOperations() if (m_pendingOp.type == BodyRequest::TYPE_Add) { // TODO: generate body parameters according to m_pendingOp.newBodyType - SystemBody *body = StarSystem::EditorAPI::NewBody(m_system.Get()); + SystemBody *body; + + if (!m_pendingOp.parent) + body = StarSystem::EditorAPI::NewBody(m_system.Get()); + else + body = StarSystem::EditorAPI::NewBodyAround(m_system.Get(), GetRng(), m_pendingOp.parent, m_pendingOp.idx); + + if (!body) { + Log::Error("Failed to create requested body."); + m_pendingOp = {}; + return; + } GetUndo()->BeginEntry("Add Body"); GetUndo()->AddUndoStep(m_system.Get()); @@ -366,7 +383,7 @@ void SystemEditor::HandleBodyOperations() // Mark the body for removal on undo GetUndo()->AddUndoStep(m_system.Get(), body); // Add the new body to its parent - GetUndo()->AddUndoStep(m_pendingOp.parent, body); + GetUndo()->AddUndoStep(m_pendingOp.parent, body, m_pendingOp.idx); GetUndo()->AddUndoStep(m_system.Get(), true); GetUndo()->AddUndoStep(this, body); @@ -383,8 +400,9 @@ void SystemEditor::HandleBodyOperations() while (sliceBegin < toDelete.size()) { size_t sliceEnd = toDelete.size(); for (size_t idx = sliceBegin; idx < sliceEnd; idx++) { - if (toDelete[idx]->HasChildren()) - for (auto &child : toDelete[idx]->GetChildren()) + SystemBody *body = toDelete[idx]; + if (body->HasChildren()) + for (auto &child : body->GetChildren()) toDelete.push_back(child); } sliceBegin = sliceEnd; @@ -693,7 +711,7 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) // TODO: "add body" context menu if (body->GetParent() && ImGui::MenuItem("Delete")) { m_pendingOp.type = BodyRequest::TYPE_Delete; - m_pendingOp.body = m_selectedBody; + m_pendingOp.body = body; } ImGui::EndPopup(); @@ -715,7 +733,7 @@ void SystemEditor::DrawBodyProperties() ImGui::PushID(m_selectedBody); ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); - SystemBody::EditorAPI::EditProperties(m_selectedBody, GetUndo()); + SystemBody::EditorAPI::EditProperties(m_selectedBody, GetRng(), GetUndo()); ImGui::PopFont(); ImGui::PopID(); From ede360981542301124143e0574e5fe70375f26f0 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 2 Sep 2023 04:57:31 -0400 Subject: [PATCH 23/50] Remove all IMGUI_DEFINE_MATH_OPERATORS - Define is passed on command-line as a project-level command instead --- src/editor/EditorDraw.h | 1 - src/editor/EditorWindow.cpp | 1 - src/editor/ModelViewerWidget.cpp | 1 - src/editor/ViewportWindow.cpp | 1 - src/pigui/PiGuiRenderer.cpp | 1 - 5 files changed, 5 deletions(-) diff --git a/src/editor/EditorDraw.h b/src/editor/EditorDraw.h index 5441d28b577..3247988be7a 100644 --- a/src/editor/EditorDraw.h +++ b/src/editor/EditorDraw.h @@ -3,7 +3,6 @@ #pragma once -#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" diff --git a/src/editor/EditorWindow.cpp b/src/editor/EditorWindow.cpp index 5256163a347..d612df26e8b 100644 --- a/src/editor/EditorWindow.cpp +++ b/src/editor/EditorWindow.cpp @@ -5,7 +5,6 @@ #include "editor/EditorApp.h" -#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" diff --git a/src/editor/ModelViewerWidget.cpp b/src/editor/ModelViewerWidget.cpp index f95536082be..a1753715c1e 100644 --- a/src/editor/ModelViewerWidget.cpp +++ b/src/editor/ModelViewerWidget.cpp @@ -28,7 +28,6 @@ #include "SDL_keycode.h" -#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index 1dae2b2dc3f..db515e77120 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -9,7 +9,6 @@ #include "graphics/Renderer.h" #include "graphics/Texture.h" -#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" diff --git a/src/pigui/PiGuiRenderer.cpp b/src/pigui/PiGuiRenderer.cpp index 23b668f1d20..f4cf1d83893 100644 --- a/src/pigui/PiGuiRenderer.cpp +++ b/src/pigui/PiGuiRenderer.cpp @@ -11,7 +11,6 @@ #include "graphics/VertexBuffer.h" #include "profiler/Profiler.h" -#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui/imgui.h" #include "imgui/imgui_internal.h" From 3220947677bfeba038016dcac272036f11986ba7 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 2 Sep 2023 04:58:20 -0400 Subject: [PATCH 24/50] SystemEditor: remove ExportToLua functions - Json serialization code is more compact and much less bugprone. - Lua custom system definitions are deprecated and will not be generated by the tool. --- src/editor/system/GalaxyEditAPI.cpp | 148 ---------------------------- src/editor/system/GalaxyEditAPI.h | 8 +- 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 5a2a8d76455..cdf9529b7a5 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -47,32 +47,6 @@ namespace Editor::Draw { } // namespace Editor::Draw -void StarSystem::EditorAPI::ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy) -{ - fprintf(f, "-- Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details\n"); - fprintf(f, "-- Licensed under the terms of the GPL v3. See licenses/GPL-3.txt\n\n"); - - SystemBody *rootBody = system->GetRootBody().Get(); - - std::string stars_in_system = SystemBody::EditorAPI::GetStarTypes(rootBody); - - const char *govType = EnumStrings::GetString("PolitGovType", system->GetSysPolit().govType); - - fprintf(f, "local system = CustomSystem:new('%s', { %s })\n\t:govtype('%s')\n\t:short_desc('%s')\n\t:long_desc([[%s]])\n\n", - system->GetName().c_str(), stars_in_system.c_str(), govType, system->GetShortDescription().c_str(), system->GetLongDescription().c_str()); - - fprintf(f, "system:bodies(%s)\n\n", SystemBody::EditorAPI::ExportToLua(f, rootBody).c_str()); - - SystemPath pa = system->GetPath(); - RefCountedPtr sec = galaxy->GetSector(pa); - - fprintf(f, "system:add_to_sector(%d,%d,%d,v(%.4f,%.4f,%.4f))\n", - pa.sectorX, pa.sectorY, pa.sectorZ, - sec->m_systems[pa.systemIndex].GetPosition().x / Sector::SIZE, - sec->m_systems[pa.systemIndex].GetPosition().y / Sector::SIZE, - sec->m_systems[pa.systemIndex].GetPosition().z / Sector::SIZE); -} - SystemBody *StarSystem::EditorAPI::NewBody(StarSystem *system) { return system->NewBody(); @@ -215,128 +189,6 @@ void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) // ─── SystemBody::EditorAPI ─────────────────────────────────────────────────── -// Return a list of star types in the system; expects to be passed the root body -std::string SystemBody::EditorAPI::GetStarTypes(SystemBody *body) -{ - std::string types = ""; - - if (body->GetSuperType() == SystemBody::SUPERTYPE_STAR) { - types = types + "'" + EnumStrings::GetString("BodyType", body->GetType()) + "', "; - } - - for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { - types = types + GetStarTypes(body->m_children[ii]); - } - - return types; -} - -// NOTE: duplicated from StarSystem.cpp -std::string SystemBody::EditorAPI::ExportToLua(FILE *f, SystemBody *body) -{ - const int multiplier = 10000; - - // strip characters that will not work in Lua - std::string code_name = body->GetName(); - std::transform(code_name.begin(), code_name.end(), code_name.begin(), ::tolower); - code_name.erase(remove_if(code_name.begin(), code_name.end(), InvalidSystemNameChar), code_name.end()); - - // Ensure we prepend a character to numbers to avoid generating an invalid identifier - if (isdigit(code_name.front())) - code_name = "body_" + code_name; - - // find the body type index so we can lookup the name - const char *pBodyTypeName = EnumStrings::GetString("BodyType", body->GetType()); - - if (body->GetType() == SystemBody::TYPE_STARPORT_SURFACE) { - fprintf(f, - "local %s = CustomSystemBody:new(\"%s\", '%s')\n" - "\t:seed(%d)" - "\t:latitude(math.deg2rad(%.1f))\n" - "\t:longitude(math.deg2rad(%.1f))\n", - - code_name.c_str(), - body->GetName().c_str(), pBodyTypeName, - body->m_seed, - body->m_inclination.ToDouble() * 180 / M_PI, - body->m_orbitalOffset.ToDouble() * 180 / M_PI); - } else { - fprintf(f, - "local %s = CustomSystemBody:new(\"%s\", '%s')\n" - "\t:radius(f(%d,%d))\n" - "\t:mass(f(%d,%d))\n", - code_name.c_str(), - body->GetName().c_str(), pBodyTypeName, - int(round(body->GetRadiusAsFixed().ToDouble() * multiplier)), multiplier, - int(round(body->GetMassAsFixed().ToDouble() * multiplier)), multiplier); - - if (body->GetAspectRatio() != 1.0) { - fprintf(f, - "\t:equatorial_to_polar_radius(f(%d,%d))\n", - int(round(body->GetAspectRatio() * multiplier)), multiplier); - } - - if (body->GetType() != SystemBody::TYPE_GRAVPOINT) { - fprintf(f, - "\t:seed(%u)\n" - "\t:temp(%d)\n" - "\t:semi_major_axis(f(%d,%d))\n" - "\t:eccentricity(f(%d,%d))\n" - "\t:rotation_period(f(%d,%d))\n" - "\t:axial_tilt(fixed.deg2rad(f(%d,%d)))\n" - "\t:rotational_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" - "\t:orbital_phase_at_start(fixed.deg2rad(f(%d,%d)))\n" - "\t:orbital_offset(fixed.deg2rad(f(%d,%d)))\n", - body->GetSeed(), body->GetAverageTemp(), - int(round(body->GetOrbit().GetSemiMajorAxis() / AU * multiplier)), multiplier, - int(round(body->GetOrbit().GetEccentricity() * multiplier)), multiplier, - int(round(body->m_rotationPeriod.ToDouble() * multiplier)), multiplier, - int(round(RAD2DEG(body->GetAxialTilt()) * multiplier)), multiplier, - int(round(body->m_rotationalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, - int(round(body->m_orbitalPhaseAtStart.ToDouble() * multiplier * 180 / M_PI)), multiplier, - int(round(body->m_orbitalOffset.ToDouble() * multiplier * 180 / M_PI)), multiplier); - - if (body->GetInclinationAsFixed() != fixed(0, 0)) { - fprintf(f, - "\t:inclination(math.deg2rad(%f))\n", - RAD2DEG(body->GetInclinationAsFixed().ToDouble())); - } - - } - - if (body->GetType() == SystemBody::TYPE_PLANET_TERRESTRIAL) - fprintf(f, - "\t:metallicity(f(%d,%d))\n" - "\t:volcanicity(f(%d,%d))\n" - "\t:atmos_density(f(%d,%d))\n" - "\t:atmos_oxidizing(f(%d,%d))\n" - "\t:ocean_cover(f(%d,%d))\n" - "\t:ice_cover(f(%d,%d))\n" - "\t:life(f(%d,%d))\n", - int(round(body->GetMetallicity() * multiplier)), multiplier, - int(round(body->GetVolcanicity() * multiplier)), multiplier, - int(round(body->GetVolatileGas() * multiplier)), multiplier, - int(round(body->GetAtmosOxidizing() * multiplier)), multiplier, - int(round(body->GetVolatileLiquid() * multiplier)), multiplier, - int(round(body->GetVolatileIces() * multiplier)), multiplier, - int(round(body->GetLife() * multiplier)), multiplier); - } - - fprintf(f, "\n"); - - std::string code_list = code_name; - if (body->m_children.size() > 0) { - code_list = code_list + ",\n\t{\n"; - for (Uint32 ii = 0; ii < body->m_children.size(); ii++) { - code_list = code_list + "\t" + ExportToLua(f, body->m_children[ii]) + ",\n"; - } - code_list = code_list + "\t}"; - } - - return code_list; -} - - void SystemBody::EditorAPI::AddChild(SystemBody *parent, SystemBody *child, size_t idx) { if (idx == size_t(-1)) diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 39a5cc09315..9c88946170d 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -4,6 +4,7 @@ #pragma once #include "galaxy/StarSystem.h" +#include "galaxy/SystemBody.h" namespace Editor { class UndoSystem; @@ -11,8 +12,6 @@ namespace Editor { class StarSystem::EditorAPI { public: - static void ExportToLua(FILE *f, StarSystem *system, Galaxy *galaxy); - static SystemBody *NewBody(StarSystem *system); static SystemBody *NewBodyAround(StarSystem *system, Random &rng, SystemBody *primary, size_t idx); @@ -27,11 +26,6 @@ class StarSystem::EditorAPI { class SystemBody::EditorAPI { public: - // Return a list of star types in the system; expects to be passed the root body - static std::string GetStarTypes(SystemBody *body); - // NOTE: duplicated from StarSystem.cpp - static std::string ExportToLua(FILE *f, SystemBody *body); - static void AddChild(SystemBody *parent, SystemBody *child, size_t idx = -1); static SystemBody *RemoveChild(SystemBody *parent, size_t idx = -1); static size_t GetIndexInParent(SystemBody *body); From 34ce3e0911babf5fd9a6070dd577dfc5f127b838 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 2 Sep 2023 20:22:12 -0400 Subject: [PATCH 25/50] SystemEditor: add sort operation - Sorts all bodies in the system by semi-major axis for a "canonical" ordering - Cleanup outliner interface slightly and move add buttons to edit menu bar - More ergonomic "initial-file" loading from cmd-line --- src/editor/EditorApp.cpp | 3 +- src/editor/system/GalaxyEditAPI.cpp | 79 ++++++++++++++++++++++++++--- src/editor/system/GalaxyEditAPI.h | 6 +++ src/editor/system/SystemBodyUndo.h | 24 +++++++++ src/editor/system/SystemEditor.cpp | 70 ++++++++++++++++--------- src/editor/system/SystemEditor.h | 3 +- 6 files changed, 151 insertions(+), 34 deletions(-) diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index ab9485b5684..6f65f210ac6 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -57,8 +57,7 @@ void EditorApp::Initialize(argh::parser &cmdline) } if (cmdline["--system"]) { - std::string systemPath; - cmdline("--system") >> systemPath; + std::string systemPath = cmdline[1]; RefCountedPtr systemEditor(new SystemEditor(this)); diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index cdf9529b7a5..968060f8120 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -17,6 +17,7 @@ #include "imgui/imgui.h" #include "imgui/imgui_stdlib.h" +#include "system/SystemBodyUndo.h" using namespace Editor; @@ -112,29 +113,93 @@ void StarSystem::EditorAPI::RemoveBody(StarSystem *system, SystemBody *body) void StarSystem::EditorAPI::ReorderBodyIndex(StarSystem *system) { size_t index = 0; - system->GetRootBody()->m_path.bodyIndex = index++; + std::vector> orderStack { + { system->GetRootBody().Get(), 0 } + }; + + while (!orderStack.empty()) { + auto &pair = orderStack.back(); + SystemBody *body = pair.first; + + if (pair.second == 0) + // Set body index from hierarchy order + body->m_path.bodyIndex = index++; + + if (pair.second < body->GetNumChildren()) + orderStack.push_back({ body->GetChildren()[pair.second++], 0 }); + else + orderStack.pop_back(); + } + + std::sort(system->m_bodies.begin(), system->m_bodies.end(), [](auto a, auto b) { + return a->m_path.bodyIndex < b->m_path.bodyIndex; + }); +} +void StarSystem::EditorAPI::ReorderBodyHierarchy(StarSystem *system) +{ + size_t index = 0; std::vector> orderStack { { system->GetRootBody().Get(), 0 } }; while (!orderStack.empty()) { auto &pair = orderStack.back(); + SystemBody *body = pair.first; - if (pair.second >= pair.first->GetNumChildren()) { + if (pair.second == 0) + // Sort body order from index + std::sort(body->m_children.begin(), body->m_children.end(), + [](auto *a, auto *b) { return a->m_path.bodyIndex < b->m_path.bodyIndex; }); + + if (pair.second < body->GetNumChildren()) + orderStack.push_back({ body->GetChildren()[pair.second++], 0 }); + else orderStack.pop_back(); - continue; - } + } - SystemBody *body = pair.first->GetChildren()[pair.second++]; - orderStack.push_back({ body, 0 }); + std::sort(system->m_bodies.begin(), system->m_bodies.end(), [](auto a, auto b) { + return a->m_path.bodyIndex < b->m_path.bodyIndex; + }); +} - body->m_path.bodyIndex = index ++; +void StarSystem::EditorAPI::SortBodyHierarchy(StarSystem *system, UndoSystem *undo) +{ + size_t index = 0; + std::vector> orderStack { + { system->GetRootBody().Get(), 0 } + }; + + undo->AddUndoStep(system, false); + + while (!orderStack.empty()) { + auto &pair = orderStack.back(); + SystemBody *body = pair.first; + + if (pair.second == 0) { + + std::stable_sort(body->m_children.begin(), body->m_children.end(), + [](auto *a, auto *b) { return a->m_semiMajorAxis < b->m_semiMajorAxis; }); + + for (SystemBody *body : body->m_children) + AddUndoSingleValue(undo, &body->m_path.bodyIndex); + + body->m_path.bodyIndex = index++; + + } + + if (pair.second < body->GetNumChildren()) { + orderStack.push_back({ body->GetChildren()[pair.second++], 0 }); + } else { + orderStack.pop_back(); + } } std::sort(system->m_bodies.begin(), system->m_bodies.end(), [](auto a, auto b) { return a->m_path.bodyIndex < b->m_path.bodyIndex; }); + + undo->AddUndoStep(system, true); } void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem *undo) diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 9c88946170d..39dee3f84c1 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -18,7 +18,13 @@ class StarSystem::EditorAPI { static void AddBody(StarSystem *system, SystemBody *body, size_t idx = -1); static void RemoveBody(StarSystem *system, SystemBody *body); + // Set body index from hierarchy order static void ReorderBodyIndex(StarSystem *system); + // Set body hierarchy from index order + static void ReorderBodyHierarchy(StarSystem *system); + + // Sort the bodies in the system based on semi-major axis + static void SortBodyHierarchy(StarSystem *system, Editor::UndoSystem *undo); static void EditName(StarSystem *system, Random &rng, Editor::UndoSystem *undo); static void EditProperties(StarSystem *system, Editor::UndoSystem *undo); diff --git a/src/editor/system/SystemBodyUndo.h b/src/editor/system/SystemBodyUndo.h index caed7ba7bdf..fefb965f858 100644 --- a/src/editor/system/SystemBodyUndo.h +++ b/src/editor/system/SystemBodyUndo.h @@ -61,6 +61,30 @@ namespace Editor::SystemEditorUndo { bool m_onRedo; }; + // Helper to sort body hierarchy from index at the end of an undo / redo operation by adding two undo steps + class SortStarSystemBodies : public UndoStep { + public: + SortStarSystemBodies(StarSystem *system, bool isRedo) : + m_system(system), + m_isRedo(isRedo) + { + } + + void Undo() override { + if (!m_isRedo) + StarSystem::EditorAPI::ReorderBodyHierarchy(m_system); + } + + void Redo() override { + if (m_isRedo) + StarSystem::EditorAPI::ReorderBodyHierarchy(m_system); + } + + private: + StarSystem *m_system; + bool m_isRedo; + }; + // UndoStep helper to handle adding or deleting a child SystemBody from a parent class AddRemoveChildBody : public UndoStep { public: diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 8abb7905417..e4e9def594d 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -442,6 +442,14 @@ void SystemEditor::HandleBodyOperations() } + if (m_pendingOp.type == BodyRequest::TYPE_Resort) { + + GetUndo()->BeginEntry("Sort by Semi-Major Axis"); + StarSystem::EditorAPI::SortBodyHierarchy(m_system.Get(), GetUndo()); + GetUndo()->EndEntry(); + + } + // Clear the pending operation m_pendingOp = {}; } @@ -506,6 +514,39 @@ void SystemEditor::DrawInterface() ImGui::EndMenu(); } + if (ImGui::BeginMenu("Edit")) { + + if (m_selectedBody) { + if (ImGui::MenuItem("Add Child", "Ctrl+A")) { + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); + m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; + } + + if (ImGui::MenuItem("Add Sibling", "Ctrl+Shift+A")) { + if (!m_selectedBody || !m_selectedBody->GetParent()) { + return; + } + + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = m_selectedBody->GetParent(); + m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(m_selectedBody) + 1; + m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; + } + + if (m_selectedBody != m_system->GetRootBody() && ImGui::MenuItem("Delete", "Ctrl+W")) { + m_pendingOp.type = BodyRequest::TYPE_Delete; + m_pendingOp.body = m_selectedBody; + } + } + + if (ImGui::MenuItem("Sort Bodies")) { + m_pendingOp.type = BodyRequest::TYPE_Resort; + } + + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); } @@ -578,46 +619,26 @@ void SystemEditor::DrawOutliner() ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); std::string name = m_system.Valid() ? m_system->GetName() : ""; - std::string label = fmt::format("System: {}", name); if (ImGui::Selectable(label.c_str(), !m_selectedBody)) { m_selectedBody = nullptr; } - ImGui::PopFont(); + ImGui::Spacing(); if (!m_system) { + ImGui::PopFont(); return; } - ImGui::Spacing(); - - Draw::BeginHorizontalBar(); - - if (ImGui::Button("A##AddBody")) { - m_pendingOp.type = BodyRequest::TYPE_Add; - m_pendingOp.parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); - m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; - } - - bool canDeleteBody = m_selectedBody && m_selectedBody != m_system->GetRootBody().Get(); - if (canDeleteBody && ImGui::Button("D##DeleteBody")) { - m_pendingOp.type = BodyRequest::TYPE_Delete; - m_pendingOp.body = m_selectedBody; - } - - Draw::EndHorizontalBar(); - - - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); if (ImGui::BeginChild("OutlinerList")) { std::vector> m_systemStack { { m_system->GetRootBody().Get(), 0 } }; if (!DrawBodyNode(m_system->GetRootBody().Get(), true)) { - ImGui::PopFont(); ImGui::EndChild(); + ImGui::PopFont(); return; } @@ -636,6 +657,7 @@ void SystemEditor::DrawOutliner() } } ImGui::EndChild(); + ImGui::PopFont(); } @@ -702,7 +724,7 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) m_pendingOp.idx = body->GetNumChildren(); } - if (ImGui::MenuItem("Add Sibling")) { + if (body->GetParent() && ImGui::MenuItem("Add Sibling")) { m_pendingOp.type = BodyRequest::TYPE_Add; m_pendingOp.parent = body->GetParent(); m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(body) + 1; diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index a660697f044..531a4365bdc 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -117,7 +117,8 @@ class SystemEditor : public Application::Lifecycle { TYPE_None, TYPE_Add, TYPE_Delete, - TYPE_Reparent + TYPE_Reparent, + TYPE_Resort }; Type type = TYPE_None; From b53d83133ee2e7960b4ecb86f54cc1b62fade521 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sun, 3 Sep 2023 23:35:16 -0400 Subject: [PATCH 26/50] SystemEditor: generate default body names --- src/editor/EditorIcons.h | 2 ++ src/editor/system/GalaxyEditAPI.cpp | 43 +++++++++++++++++++++++++++++ src/editor/system/GalaxyEditAPI.h | 3 ++ 3 files changed, 48 insertions(+) diff --git a/src/editor/EditorIcons.h b/src/editor/EditorIcons.h index 16a31a2a29e..c3ea8e112cb 100644 --- a/src/editor/EditorIcons.h +++ b/src/editor/EditorIcons.h @@ -16,6 +16,8 @@ #define EICON_PAUSE "\uF055" #define EICON_PLAY "\uF056" +#define EICON_RESET "\uF05F" + #define EICON_REWIND3 "\uF064" #define EICON_REWIND2 "\uF065" #define EICON_REWIND1 "\uF066" diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 968060f8120..6f7201b6cc3 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -3,6 +3,7 @@ #include "GalaxyEditAPI.h" +#include "EditorIcons.h" #include "SystemEditorHelpers.h" #include "core/Log.h" @@ -91,6 +92,8 @@ SystemBody *StarSystem::EditorAPI::NewBodyAround(StarSystem *system, Random &rng gen.PickPlanetType(body, rng); + SystemBody::EditorAPI::GenerateDefaultName(body); + return body; } @@ -254,6 +257,41 @@ void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) // ─── SystemBody::EditorAPI ─────────────────────────────────────────────────── +void SystemBody::EditorAPI::GenerateDefaultName(SystemBody *body) +{ + SystemBody *parent = body->GetParent(); + + // We're the root body, should probably be named after the system + if (!parent) { + body->m_name = body->GetStarSystem()->GetName(); + return; + } + + // Starports get a consistent default 'identifier' name + if (body->GetSuperType() == SUPERTYPE_STARPORT) { + Random rand({ body->GetSeed(), UNIVERSE_SEED }); + + char ident_1 = rand.Int32('A', 'Z'); + char ident_2 = rand.Int32('A', 'Z'); + + body->m_name = fmt::format("{} {}{}-{:04d}", + body->GetType() == TYPE_STARPORT_ORBITAL ? "Orbital" : "Port", + ident_1, ident_2, rand.Int32(10000)); + return; + } + + // Other bodies get a "hierarchy" name + size_t idx = GetIndexInParent(body); + if (parent->GetSuperType() <= SystemBody::SUPERTYPE_STAR) { + if (idx <= 26) + body->m_name = fmt::format("{} {}", parent->GetName(), char('a' + idx)); + else + body->m_name = fmt::format("{} {}{}", parent->GetName(), char('a' + idx / 26), char('a' + idx % 26)); + } else { + body->m_name = fmt::format("{} {}", parent->GetName(), 1 + idx); + } +} + void SystemBody::EditorAPI::AddChild(SystemBody *parent, SystemBody *child, size_t idx) { if (idx == size_t(-1)) @@ -414,7 +452,12 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSy body->SetAtmFromParameters(); }; + ImGui::BeginGroup(); ImGui::InputText("Name", &body->m_name); + if (ImGui::Button(EICON_RESET " Default Name")) + GenerateDefaultName(body); + ImGui::EndGroup(); + if (Draw::UndoHelper("Edit Name", undo)) AddUndoSingleValue(undo, &body->m_name); diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 39dee3f84c1..e39c705d05f 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -32,6 +32,9 @@ class StarSystem::EditorAPI { class SystemBody::EditorAPI { public: + static void GenerateDefaultName(SystemBody *body); + static void GenerateCustomName(SystemBody *body, Random &rng); + static void AddChild(SystemBody *parent, SystemBody *child, size_t idx = -1); static SystemBody *RemoveChild(SystemBody *parent, size_t idx = -1); static size_t GetIndexInParent(SystemBody *body); From c46381dcc259bb8b0feea9c488c32c3728157d20 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 4 Sep 2023 02:42:02 -0400 Subject: [PATCH 27/50] SystemEditor: add random body name generation --- src/editor/system/GalaxyEditAPI.cpp | 30 +++++++++++++++++++++-------- src/editor/system/GalaxyEditAPI.h | 3 +++ src/editor/system/SystemEditor.cpp | 9 +++++++++ src/editor/system/SystemEditor.h | 3 +++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 6f7201b6cc3..395588394b9 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -18,6 +18,7 @@ #include "imgui/imgui.h" #include "imgui/imgui_stdlib.h" +#include "lua/LuaNameGen.h" #include "system/SystemBodyUndo.h" using namespace Editor; @@ -443,23 +444,36 @@ void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem EditEconomicProperties(body, undo); } -void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSystem *undo) +void SystemBody::EditorAPI::EditBodyName(SystemBody *body, Random &rng, LuaNameGen *nameGen, UndoSystem *undo) { - bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; - - bool bodyChanged = false; - auto updateBodyDerived = [=]() { - body->SetAtmFromParameters(); - }; - ImGui::BeginGroup(); ImGui::InputText("Name", &body->m_name); + if (ImGui::Button(EICON_RESET " Default Name")) GenerateDefaultName(body); + + ImGui::SameLine(); + + // allocate a new random generator here so it can be pushed to lua + RefCountedPtr rand { new Random({ rng.Int32() }) }; + + if (ImGui::Button(EICON_RANDOM " Random Name")) + body->m_name = nameGen->BodyName(body, rand); + ImGui::EndGroup(); if (Draw::UndoHelper("Edit Name", undo)) AddUndoSingleValue(undo, &body->m_name); +} + +void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSystem *undo) +{ + bool isStar = body->GetSuperType() <= SUPERTYPE_STAR; + + bool bodyChanged = false; + auto updateBodyDerived = [=]() { + body->SetAtmFromParameters(); + }; Draw::EditEnum("Edit Body Type", "Body Type", "BodyType", reinterpret_cast(&body->m_type), BodyType::TYPE_MAX, undo); diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index e39c705d05f..87751d88e71 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -6,6 +6,8 @@ #include "galaxy/StarSystem.h" #include "galaxy/SystemBody.h" +class LuaNameGen; + namespace Editor { class UndoSystem; } @@ -42,6 +44,7 @@ class SystemBody::EditorAPI { static void EditOrbitalParameters(SystemBody *body, Editor::UndoSystem *undo); static void EditEconomicProperties(SystemBody *body, Editor::UndoSystem *undo); static void EditStarportProperties(SystemBody *body, Editor::UndoSystem *undo); + static void EditBodyName(SystemBody *body, Random &rng, LuaNameGen *nameGen, Editor::UndoSystem *undo); static void EditProperties(SystemBody *body, Random &rng, Editor::UndoSystem *undo); static void GenerateDerivedStats(SystemBody *body, Random &rng, Editor::UndoSystem *undo); diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index e4e9def594d..f22519c2544 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -25,6 +25,8 @@ #include "galaxy/GalaxyGenerator.h" #include "galaxy/StarSystemGenerator.h" #include "lua/Lua.h" +#include "lua/LuaNameGen.h" +#include "lua/LuaObject.h" #include "pigui/PiGui.h" #include "imgui/imgui.h" @@ -91,6 +93,11 @@ SystemEditor::SystemEditor(EditorApp *app) : { GalacticEconomy::Init(); + LuaObject::RegisterClass(); + LuaObject::RegisterClass(); + + m_nameGen.reset(new LuaNameGen(Lua::manager)); + m_galaxy = GalaxyGenerator::Create(); m_systemLoader.reset(new CustomSystemsDatabase(m_galaxy.Get(), "systems")); @@ -755,6 +762,8 @@ void SystemEditor::DrawBodyProperties() ImGui::PushID(m_selectedBody); ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); + SystemBody::EditorAPI::EditBodyName(m_selectedBody, GetRng(), m_nameGen.get(), GetUndo()); + SystemBody::EditorAPI::EditProperties(m_selectedBody, GetRng(), GetUndo()); ImGui::PopFont(); diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 531a4365bdc..738c96487ed 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -23,6 +23,8 @@ namespace FileSystem { class FileInfo; } // namespace FileSystem +class LuaNameGen; + class Galaxy; class StarSystem; class SystemBody; @@ -103,6 +105,7 @@ class SystemEditor : public Application::Lifecycle { std::unique_ptr m_viewport; Random m_random; + std::unique_ptr m_nameGen; std::unique_ptr m_undo; size_t m_lastSavedUndoStack; From c93cc28198e2232ddbc8d480dfb628bdc2ca0fd2 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 4 Sep 2023 03:58:20 -0400 Subject: [PATCH 28/50] SystemEditor: always make body, fix default name --- src/editor/system/GalaxyEditAPI.cpp | 2 -- src/editor/system/SystemEditor.cpp | 8 +++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 395588394b9..462fa8919fb 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -93,8 +93,6 @@ SystemBody *StarSystem::EditorAPI::NewBodyAround(StarSystem *system, Random &rng gen.PickPlanetType(body, rng); - SystemBody::EditorAPI::GenerateDefaultName(body); - return body; } diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index f22519c2544..3f91a56bc9f 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -379,9 +379,9 @@ void SystemEditor::HandleBodyOperations() body = StarSystem::EditorAPI::NewBodyAround(m_system.Get(), GetRng(), m_pendingOp.parent, m_pendingOp.idx); if (!body) { - Log::Error("Failed to create requested body."); - m_pendingOp = {}; - return; + Log::Error("Body parameters could not be automatically generated for the new body."); + + body = StarSystem::EditorAPI::NewBody(m_system.Get()); } GetUndo()->BeginEntry("Add Body"); @@ -396,6 +396,8 @@ void SystemEditor::HandleBodyOperations() GetUndo()->AddUndoStep(this, body); GetUndo()->EndEntry(); + // Give the body a basic name based on its position in its parent + SystemBody::EditorAPI::GenerateDefaultName(body); } if (m_pendingOp.type == BodyRequest::TYPE_Delete) { From 253d776ab8630a8bd2a01a349fc1706a92eb92a4 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 6 Sep 2023 02:33:19 -0400 Subject: [PATCH 29/50] Editor: add support for editing contained values - UndoClosure wraps an UndoStep with a post-update closure - Added UndoStep templates for managing insertion/removal of values from vectors - Added UndoStep template for editing value inside of numerically-indexed container that may reallocate --- src/editor/UndoStepType.h | 240 +++++++++++++++++++++++++++----------- 1 file changed, 169 insertions(+), 71 deletions(-) diff --git a/src/editor/UndoStepType.h b/src/editor/UndoStepType.h index 841e4d7e5b0..e319ee1ce06 100644 --- a/src/editor/UndoStepType.h +++ b/src/editor/UndoStepType.h @@ -7,6 +7,31 @@ namespace Editor { + // ======================================================================== + // UndoClosure Helper + // ======================================================================== + + // UndoClosure wraps an UndoStep with a closure to be executed after an + // Undo() or Redo() operation. + + template + class UndoClosure : public T { + public: + template + UndoClosure(ClosureType &&closure, Args ...args) : + m_onUpdate(closure), + T(args...) + {} + + void Swap() override { + T::Swap(); + m_onUpdate(); + } + + private: + ClosureType m_onUpdate; + }; + // ======================================================================== // UndoSingleValue Helper // ======================================================================== @@ -48,40 +73,6 @@ namespace Editor { ValueType m_state; }; - template - class UndoSingleValueClosureStep : public UndoStep - { - public: - UndoSingleValueClosureStep(ValueType *type, ClosureType &&updateClosure) : - m_dataRef(type), - m_state(*m_dataRef), - m_onUpdate(std::move(updateClosure)) - { - } - - UndoSingleValueClosureStep(ValueType *type, const ValueType &newValue, ClosureType &&updateClosure) : - m_dataRef(type), - m_state(newValue), - m_onUpdate(std::move(updateClosure)) - { - Swap(); - } - - void Swap() override - { - std::swap(*m_dataRef, m_state); - m_onUpdate(); - } - - bool HasChanged() const override { return !(*m_dataRef == m_state); } - - private: - ValueType *m_dataRef; - ValueType m_state; - - ClosureType m_onUpdate; - }; - // Helper functions to construct the above UndoStep helpers template @@ -99,13 +90,13 @@ namespace Editor { template inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, UpdateClosure closure) { - s->AddUndoStep>(dataRef, std::move(closure)); + s->AddUndoStep>>(std::move(closure), dataRef); } template inline void AddUndoSingleValueClosure(UndoSystem *s, T *dataRef, const T &newValue, UpdateClosure closure) { - s->AddUndoStep>(dataRef, newValue, std::move(closure)); + s->AddUndoStep>>(std::move(closure), dataRef, newValue); } // ======================================================================== @@ -150,70 +141,177 @@ namespace Editor { ValueType m_state; }; - template - class UndoGetSetValueClosureStep : public UndoStep + // Helper functions to construct the above UndoStep helpers + + template + inline void AddUndoGetSetValue(UndoSystem *s, Obj *dataRef) + { + using ValueType = decltype((dataRef->*GetterFn)()); + s->AddUndoStep>(dataRef); + } + + template + inline void AddUndoGetSetValue(UndoSystem *s, Obj *dataRef, const T &newValue) + { + using ValueType = decltype((dataRef->*GetterFn)()); + s->AddUndoStep>(dataRef, newValue); + } + + template + inline void AddUndoGetSetValueClosure(UndoSystem *s, Obj *dataRef, UpdateClosure &&closure) + { + using ValueType = decltype((dataRef->*GetterFn)()); + s->AddUndoStep>>(std::move(closure), dataRef); + } + + template + inline void AddUndoGetSetValueClosure(UndoSystem *s, Obj *dataRef, const T &newValue, UpdateClosure &&closure) + { + using ValueType = decltype((dataRef->*GetterFn)()); + s->AddUndoStep>>(std::move(closure), dataRef, newValue); + } + + // ======================================================================== + // UndoVectorStep Helper + // ======================================================================== + + // UndoVectorStep implements an UndoStep that expresses mutation of a + // std::vector or similar container variable providing insert(), erase(), + // begin(), and size(). + // + // The only requirements are that the value type be Move-Constructible or + // Copy-Constructible, and that the container's memory location will not + // change between creation of the UndoStep and when it is undone/redone. + + template + class UndoVectorStep : public UndoStep { public: - UndoGetSetValueClosureStep(Obj *data, ClosureType &&updateClosure) : - m_dataRef(data), - m_state((m_dataRef->*GetterFn)()), - m_update(std::move(updateClosure)) + using ValueType = typename Container::value_type; + + UndoVectorStep(Container *container, const ValueType &newValue, size_t insertIdx) : + m_container(*container), + m_value {}, + m_idx(insertIdx), + m_insert(true) { + Swap(); } - UndoGetSetValueClosureStep(Obj *data, const ValueType &newValue, ClosureType &&updateClosure) : - m_dataRef(data), - m_state(newValue), - m_update(std::move(updateClosure)) + UndoVectorStep(Container *container, size_t removeIdx) : + m_container(*container), + m_value {}, + m_idx(removeIdx), + m_insert(false) { Swap(); } - // two-way swap with opaque setter/getter functions and update closure - void Swap() { - ValueType t = (m_dataRef->*GetterFn)(); - std::swap(t, m_state); - (m_dataRef->*SetterFn)(std::move(t)); + void Swap() override + { + if (m_insert) { + m_container.insert(m_container.begin() + m_idx, std::move(m_value)); + } else { + m_value = std::move(m_container[m_idx]); + m_container.erase(m_container.begin() + m_idx); + } + + m_insert = !m_insert; + } + + private: + Container &m_container; + ValueType m_value; + size_t m_idx; + bool m_insert; + }; + + // ======================================================================== + // UndoVectorSingleValue Helper + // ======================================================================== - m_update(); + // UndoVectorSingleValue implements an UndoStep that expresses mutation of + // a value contained in a std::vector or similar container variable + // providing operator[](size_t) and size() + // + // The only requirements are that the value type be Move-Constructible or + // Copy-Constructible, and that the container's memory location will not + // change between creation of the UndoStep and when it is undone/redone. + // + // For containers that reallocate memory on mutation (e.g. std::vector) + // this UndoStep is a replacement for UndoSingleValueStep. + + template + class UndoVectorSingleValueStep : public UndoStep + { + public: + using ValueType = typename Container::value_type; + + UndoVectorSingleValueStep(Container *container, size_t idx) : + m_container(*container), + m_index(idx), + m_state(m_container[idx]) + { } - bool HasChanged() const override { return !((m_dataRef->*GetterFn)() == m_state); } + void Swap() override { std::swap(m_container[m_index], m_state); } + + bool HasChanged() const override { return !(m_container[m_index] == m_state); } private: - Obj *m_dataRef; + Container &m_container; + size_t m_index; ValueType m_state; - ClosureType m_update; }; // Helper functions to construct the above UndoStep helpers - template - inline void AddUndoGetSetValue(UndoSystem *s, Obj *dataRef) + template + inline void AddUndoVectorInsert(UndoSystem *s, T *containerRef, const Value &value, size_t idx = -1) { - using ValueType = decltype((dataRef->*GetterFn)()); - s->AddUndoStep>(dataRef); + if (idx == size_t(-1)) + idx = containerRef->size(); + + s->AddUndoStep>(containerRef, value, idx); } - template - inline void AddUndoGetSetValue(UndoSystem *s, Obj *dataRef, const T &newValue) + template + inline void AddUndoVectorErase(UndoSystem *s, T *containerRef, size_t idx = -1) { - using ValueType = decltype((dataRef->*GetterFn)()); - s->AddUndoStep>(dataRef, newValue); + if (idx == size_t(-1)) + idx = containerRef->size() - 1; + + s->AddUndoStep>(containerRef, idx); } - template - inline void AddUndoGetSetValueClosure(UndoSystem *s, Obj *dataRef, UpdateClosure &&closure) + template + inline void AddUndoVectorSingleValue(UndoSystem *s, T *containerRef, size_t idx = -1) { - using ValueType = decltype((dataRef->*GetterFn)()); - s->AddUndoStep>(dataRef, std::move(closure)); + if (idx == size_t(-1)) + idx = containerRef->size() - 1; + + s->AddUndoStep>(containerRef, idx); } - template - inline void AddUndoGetSetValueClosure(UndoSystem *s, Obj *dataRef, const T &newValue, UpdateClosure &&closure) + template + inline void AddUndoVectorInsertClosure(UndoSystem *s, T *containerRef, const Value &value, size_t idx, UpdateClosure closure) { - using ValueType = decltype((dataRef->*GetterFn)()); - s->AddUndoStep>(dataRef, newValue, std::move(closure)); + s->AddUndoStep>>(std::move(closure), containerRef, value, idx); + } + + template + inline void AddUndoVectorEraseClosure(UndoSystem *s, T *containerRef, size_t idx, UpdateClosure closure) + { + s->AddUndoStep>>(std::move(closure), containerRef, idx); + } + + template + inline void AddUndoVectorSingleValue(UndoSystem *s, T *containerRef, size_t idx, UpdateClosure closure) + { + if (idx == size_t(-1)) + idx = containerRef->size() - 1; + + s->AddUndoStep>>(std::move(closure), containerRef, idx); } } // namespace Editor From f6df87cee682f8f781cbb30c1f49c04151acbc58 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 6 Sep 2023 03:51:19 -0400 Subject: [PATCH 30/50] SystemEditor: edit custom system properties - Edit sector path and position - Add and edit system "comment" field - Edit "other names" for system - Allow the user to request load-time random generation of faction, lawlessness, and explored state --- src/editor/system/GalaxyEditAPI.cpp | 142 +++++++++++++++++++++++++--- src/editor/system/GalaxyEditAPI.h | 18 +++- src/editor/system/SystemEditor.cpp | 51 ++++++++-- src/editor/system/SystemEditor.h | 3 + 4 files changed, 194 insertions(+), 20 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 462fa8919fb..a48b088b3c9 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -7,6 +7,7 @@ #include "SystemEditorHelpers.h" #include "core/Log.h" +#include "core/macros.h" #include "editor/UndoStepType.h" #include "editor/EditorDraw.h" @@ -33,6 +34,12 @@ namespace { } static constexpr double SECONDS_TO_DAYS = 1.0 / (3600.0 * 24.0); + + static const char *explored_labels[] = { + "Randomly Generated", + "Explored at Start", + "Unexplored", + }; } namespace Editor::Draw { @@ -217,41 +224,152 @@ void StarSystem::EditorAPI::EditName(StarSystem *system, Random &rng, UndoSystem if (Draw::UndoHelper("Edit System Name", undo)) AddUndoSingleValue(undo, &system->m_name); -} -void StarSystem::EditorAPI::EditProperties(StarSystem *system, UndoSystem *undo) -{ - // TODO: other names + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Other Names"); + + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().WindowPadding.x * 2.f, 0.f); + ImGui::Button("+", ImVec2(ImGui::GetFrameHeight(), ImGui::GetFrameHeight())); + if (Draw::UndoHelper("Add System Other Name", undo)) + AddUndoVectorInsert(undo, &system->m_other_names, ""); + + float window_height = ImGui::GetFrameHeightWithSpacing() * std::min(size_t(4), system->m_other_names.size()) + ImGui::GetStyle().WindowPadding.y * 2.f; + + if (system->m_other_names.size() > 0) { + ImGui::BeginChild("##Other Names", ImVec2(0, window_height), true); + + for (size_t idx = 0; idx < system->m_other_names.size(); idx++) { + + ImGui::PushID(idx); + + if (ImGui::Button("-")) { + undo->BeginEntry("Remove System Other Name"); + AddUndoVectorErase(undo, &system->m_other_names, idx); + undo->EndEntry(); + + ImGui::PopID(); + continue; + } + + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##name", &system->m_other_names[idx]); + + if (Draw::UndoHelper("Edit System Other Name", undo)) + AddUndoVectorSingleValue(undo, &system->m_other_names, idx); - ImGui::InputText("Short Description", &system->m_shortDesc); + ImGui::PopID(); + + } + ImGui::EndChild(); + + ImGui::Spacing(); + } + + ImGui::SeparatorText("Short Description"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##Short Description", &system->m_shortDesc); if (Draw::UndoHelper("Edit System Short Description", undo)) AddUndoSingleValue(undo, &system->m_shortDesc); - ImGui::InputTextMultiline("Long Description", &system->m_longDesc, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); + ImGui::SeparatorText("Long Description"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextMultiline("##Long Description", &system->m_longDesc, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5.f + ImGui::GetStyle().WindowPadding.y * 2.f)); if (Draw::UndoHelper("Edit System Long Description", undo)) AddUndoSingleValue(undo, &system->m_longDesc); +} +void StarSystem::EditorAPI::EditProperties(StarSystem *system, CustomSystemInfo &custom, FactionsDatabase *factions, UndoSystem *undo) +{ ImGui::SeparatorText("Generation Parameters"); ImGui::InputInt("Seed", reinterpret_cast(&system->m_seed)); if (Draw::UndoHelper("Edit Seed", undo)) AddUndoSingleValue(undo, &system->m_seed); - bool explored = system->m_explored == ExplorationState::eEXPLORED_AT_START; - ImGui::Checkbox("Explored", &explored); - if (Draw::UndoHelper("Edit System Explored", undo)) - AddUndoSingleValue(undo, &system->m_explored, explored ? eEXPLORED_AT_START : eUNEXPLORED); + bool comboOpen = Draw::ComboUndoHelper("Edit System Exploration State", "Explored State", explored_labels[custom.explored], undo); + if (comboOpen) { + if (ImGui::IsWindowAppearing()) { + AddUndoSingleValue(undo, &custom.explored); + } + + for (size_t idx = 0; idx < COUNTOF(explored_labels); idx++) { + if (ImGui::Selectable(explored_labels[idx], idx == custom.explored)) + custom.explored = CustomSystemInfo::ExplorationState(idx); + } - ImGui::SeparatorText("Economic Parameters"); + ImGui::EndCombo(); + } + + if (Draw::LayoutHorizontal("Sector Path", 3, ImGui::GetFontSize())) { + ImGui::InputInt("X", &system->m_path.sectorX, 0, 0); + ImGui::InputInt("Y", &system->m_path.sectorY, 0, 0); + ImGui::InputInt("Z", &system->m_path.sectorZ, 0, 0); + + Draw::EndLayout(); + } - // TODO: faction + if (Draw::UndoHelper("Edit System Sector", undo)) + AddUndoSingleValue(undo, &system->m_path); + + if (Draw::LayoutHorizontal("Position in Sector", 3, ImGui::GetFontSize())) { + ImGui::SliderFloat("X", &system->m_pos.x, 0.f, 8.f, "%.3f ly", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat("Y", &system->m_pos.y, 0.f, 8.f, "%.3f ly", ImGuiSliderFlags_AlwaysClamp); + ImGui::SliderFloat("Z", &system->m_pos.z, 0.f, 8.f, "%.3f ly", ImGuiSliderFlags_AlwaysClamp); + + Draw::EndLayout(); + } + + if (Draw::UndoHelper("Edit System Position", undo)) + AddUndoSingleValue(undo, &system->m_pos); + + ImGui::SeparatorText("Legal Parameters"); Draw::EditEnum("Edit System Government", "Government", "PolitGovType", reinterpret_cast(&system->m_polit.govType), Polit::GovType::GOV_MAX - 1, undo); + ImGui::Checkbox("Random Faction", &custom.randomFaction); + if (Draw::UndoHelper("Edit Faction", undo)) + AddUndoSingleValue(undo, &custom.randomFaction); + + ImGui::BeginDisabled(custom.randomFaction); + + if (Draw::ComboUndoHelper("Edit Faction", "Faction", custom.faction.c_str(), undo)) { + if (ImGui::IsWindowAppearing()) + AddUndoSingleValue(undo, &custom.faction); + + for (size_t factionIdx = 0; factionIdx < factions->GetNumFactions(); factionIdx++) { + const Faction *fac = factions->GetFaction(factionIdx); + + if (ImGui::Selectable(fac->name.c_str(), fac->name == custom.faction)) + custom.faction = fac->name; + } + + ImGui::EndCombo(); + } + + ImGui::EndDisabled(); + + ImGui::Checkbox("Random Lawlessness", &custom.randomLawlessness); + if (Draw::UndoHelper("Edit Lawlessness", undo)) + AddUndoSingleValue(undo, &custom.randomLawlessness); + + ImGui::BeginDisabled(custom.randomLawlessness); + Draw::InputFixedSlider("Lawlessness", &system->m_polit.lawlessness); if (Draw::UndoHelper("Edit System Lawlessness", undo)) AddUndoSingleValue(undo, &system->m_polit.lawlessness); + + ImGui::EndDisabled(); + + ImGui::SeparatorText("Author Comments"); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextMultiline("##Comment", &custom.comment, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5.f + ImGui::GetStyle().WindowPadding.y * 2.f)); + + if (Draw::UndoHelper("Edit Comments", undo)) + AddUndoSingleValue(undo, &custom.comment); + } // ─── SystemBody::EditorAPI ─────────────────────────────────────────────────── diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 87751d88e71..570170d19fa 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -7,9 +7,25 @@ #include "galaxy/SystemBody.h" class LuaNameGen; +class FactionsDatabase; namespace Editor { class UndoSystem; + + struct CustomSystemInfo { + enum ExplorationState { + EXPLORE_Random = 0, + EXPLORE_ExploredAtStart, + EXPLORE_Unexplored, + }; + + ExplorationState explored = EXPLORE_Random; + bool randomLawlessness = true; + bool randomFaction = true; + + std::string faction; + std::string comment; + }; } class StarSystem::EditorAPI { @@ -29,7 +45,7 @@ class StarSystem::EditorAPI { static void SortBodyHierarchy(StarSystem *system, Editor::UndoSystem *undo); static void EditName(StarSystem *system, Random &rng, Editor::UndoSystem *undo); - static void EditProperties(StarSystem *system, Editor::UndoSystem *undo); + static void EditProperties(StarSystem *system, Editor::CustomSystemInfo &custom, FactionsDatabase *factions, Editor::UndoSystem *undo); }; class SystemBody::EditorAPI { diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 3f91a56bc9f..341363e4d7c 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -24,6 +24,7 @@ #include "galaxy/Galaxy.h" #include "galaxy/GalaxyGenerator.h" #include "galaxy/StarSystemGenerator.h" +#include "graphics/Renderer.h" #include "lua/Lua.h" #include "lua/LuaNameGen.h" #include "lua/LuaObject.h" @@ -88,6 +89,7 @@ SystemEditor::SystemEditor(EditorApp *app) : m_app(app), m_undo(new UndoSystem()), m_system(nullptr), + m_systemInfo(), m_selectedBody(nullptr), m_pendingOp() { @@ -126,6 +128,11 @@ void SystemEditor::NewSystem() m_system.Reset(newSystem); newSystem->SetRootBody(newSystem->NewBody()); + + SysPolit polit = {}; + polit.govType = Polit::GOV_NONE; + + newSystem->SetSysPolit(polit); } bool SystemEditor::LoadSystemFromDisk(const std::string &absolutePath) @@ -164,9 +171,14 @@ bool SystemEditor::LoadSystem(const FileSystem::FileInfo &file) bool ok = false; if (ends_with_ci(file.GetPath(), ".json")) { - const CustomSystem *csys = m_systemLoader->LoadSystemFromJSON(file.GetName(), JsonUtils::LoadJson(file.Read())); + const Json &data = JsonUtils::LoadJson(file.Read()); + + const CustomSystem *csys = m_systemLoader->LoadSystemFromJSON(file.GetName(), data); if (csys) ok = LoadCustomSystem(csys); + + if (ok) + m_systemInfo.comment = data["comment"]; } else if (ends_with_ci(file.GetPath(), ".lua")) { const CustomSystem *csys = m_systemLoader->LoadSystem(file.GetPath()); if (csys) @@ -201,6 +213,21 @@ bool SystemEditor::WriteSystem(const std::string &filepath) m_system->DumpToJson(systemdef); + if (m_systemInfo.randomFaction) + systemdef.erase("faction"); + else + systemdef["faction"] = m_systemInfo.faction; + + if (m_systemInfo.randomLawlessness) + systemdef.erase("lawlessness"); + + if (m_systemInfo.explored == CustomSystemInfo::EXPLORE_Random) + systemdef.erase("explored"); + else + systemdef["explored"] = m_systemInfo.explored == CustomSystemInfo::EXPLORE_ExploredAtStart; + + systemdef["comment"] = m_systemInfo.comment; + std::string jsonData = systemdef.dump(1, '\t'); fwrite(jsonData.data(), 1, jsonData.size(), f); @@ -240,6 +267,15 @@ bool SystemEditor::LoadCustomSystem(const CustomSystem *csys) m_system = system; m_viewport->SetSystem(system); + CustomSystemInfo::ExplorationState explored = csys->explored ? + CustomSystemInfo::EXPLORE_ExploredAtStart : + CustomSystemInfo::EXPLORE_Unexplored; + + m_systemInfo.explored = csys->want_rand_explored ? CustomSystemInfo::EXPLORE_Random : explored; + m_systemInfo.randomLawlessness = csys->want_rand_lawlessness; + m_systemInfo.randomFaction = csys->faction == nullptr; + m_systemInfo.faction = csys->faction ? csys->faction->name : ""; + return true; } @@ -248,6 +284,7 @@ void SystemEditor::ClearSystem() m_undo->Clear(); m_system.Reset(); + m_systemInfo = {}; m_viewport->SetSystem(m_system); m_selectedBody = nullptr; m_pendingOp = {}; @@ -579,13 +616,15 @@ void SystemEditor::DrawInterface() if (ImGui::Begin(PROPERTIES_WND_ID)) { // Adjust default window label position - ImGui::PushItemWidth(ImFloor(ImGui::GetWindowSize().x * 0.55f)); + ImGui::PushItemWidth(ImFloor(ImGui::GetWindowSize().x * 0.6f)); + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); if (m_selectedBody) DrawBodyProperties(); else DrawSystemProperties(); + ImGui::PopFont(); ImGui::PopItemWidth(); } ImGui::End(); @@ -762,13 +801,11 @@ void SystemEditor::DrawBodyProperties() ImGui::Spacing(); ImGui::PushID(m_selectedBody); - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); SystemBody::EditorAPI::EditBodyName(m_selectedBody, GetRng(), m_nameGen.get(), GetUndo()); SystemBody::EditorAPI::EditProperties(m_selectedBody, GetRng(), GetUndo()); - ImGui::PopFont(); ImGui::PopID(); } @@ -789,10 +826,10 @@ void SystemEditor::DrawSystemProperties() ImGui::Spacing(); - Random rng (Uint32(m_app->GetTime() * 4.0) ^ m_system->GetSeed()); - StarSystem::EditorAPI::EditName(m_system.Get(), rng, GetUndo()); + StarSystem::EditorAPI::EditName(m_system.Get(), GetRng(), GetUndo()); + + StarSystem::EditorAPI::EditProperties(m_system.Get(), m_systemInfo, m_galaxy->GetFactions(), GetUndo()); - StarSystem::EditorAPI::EditProperties(m_system.Get(), GetUndo()); } void SystemEditor::DrawPickSystemModal() diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 738c96487ed..ee0169bd2ad 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -8,6 +8,7 @@ #include "RefCounted.h" #include "core/Application.h" #include "galaxy/SystemPath.h" +#include "GalaxyEditAPI.h" #include @@ -102,6 +103,8 @@ class SystemEditor : public Application::Lifecycle { RefCountedPtr m_system; std::unique_ptr m_systemLoader; + CustomSystemInfo m_systemInfo; + std::unique_ptr m_viewport; Random m_random; From 1e5a525db56a861c2dc565310b531736206e5659 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 00:17:00 -0400 Subject: [PATCH 31/50] SystemEditor: update starport orbit when changed --- src/editor/system/GalaxyEditAPI.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index a48b088b3c9..a481447202a 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -543,16 +543,22 @@ void SystemBody::EditorAPI::EditEconomicProperties(SystemBody *body, UndoSystem void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem *undo) { + bool orbitChanged = false; + if (body->GetType() == TYPE_STARPORT_SURFACE) { ImGui::SeparatorText("Surface Parameters"); - Draw::InputFixedDegrees("Latitude", &body->m_inclination); + orbitChanged |= Draw::InputFixedDegrees("Latitude", &body->m_inclination); if (Draw::UndoHelper("Edit Latitude", undo)) - AddUndoSingleValue(undo, &body->m_inclination); + AddUndoSingleValueClosure(undo, &body->m_inclination, [=](){ body->SetOrbitFromParameters(); }); - Draw::InputFixedDegrees("Longitude", &body->m_orbitalOffset); + orbitChanged |= Draw::InputFixedDegrees("Longitude", &body->m_orbitalOffset); if (Draw::UndoHelper("Edit Longitude", undo)) - AddUndoSingleValue(undo, &body->m_orbitalOffset); + AddUndoSingleValueClosure(undo, &body->m_orbitalOffset, [=](){ body->SetOrbitFromParameters(); }); + + if (orbitChanged) + body->SetOrbitFromParameters(); + } else { EditOrbitalParameters(body, undo); } From dfbbaf5f4855f349189d78cf425098db0151d91d Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 00:20:27 -0400 Subject: [PATCH 32/50] Editor: add ActionBinder utility - Multi-functional class responsible for shortcut handling and menu rendering of predicated user actions. - Wraps display and management of action shortcuts. - Can be easily extended to use i18n and allow user-set keyboard shortcuts. - Can be extended to render toolbar buttons instead of menu items. - Reduces needed code to implement all contained functionality significantly. --- src/editor/ActionBinder.cpp | 200 ++++++++++++++++++++++++++++++++++++ src/editor/ActionBinder.h | 118 +++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 src/editor/ActionBinder.cpp create mode 100644 src/editor/ActionBinder.h diff --git a/src/editor/ActionBinder.cpp b/src/editor/ActionBinder.cpp new file mode 100644 index 00000000000..7200dec6047 --- /dev/null +++ b/src/editor/ActionBinder.cpp @@ -0,0 +1,200 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "ActionBinder.h" +#include "core/StringUtils.h" +#include "fmt/core.h" + +#include +#include +#include + +using namespace Editor; + +// wrap double-dereference when working with variant of pointer types +template +auto get_if(std::variant &pv) +{ + T *ptr = std::get_if(&pv); + return ptr ? *ptr : nullptr; +} + +ActionBinder::ActionBinder() +{ +} + +ActionBinder::~ActionBinder() +{ +} + +// static +std::string ActionBinder::FormatShortcut(ImGuiKeyChord shortcut) +{ + char name[24]; + ImGui::GetKeyChordName(shortcut, name, sizeof(name)); + + return std::string(name); +} + +void ActionBinder::Update() +{ + for (auto &[id, action] : m_actionStorage) { + if (!action.shortcut) + continue; + + if (ImGui::Shortcut(action.shortcut, 0, ImGuiInputFlags_RouteGlobal)) { + if (action.predicate.empty() || action.predicate()) + action.action(); + } + } +} + +void ActionBinder::DrawGroupOverviewEntry(Group &group) +{ + if (group.isMenu) + if (!ImGui::TreeNodeEx(group.label.c_str(), ImGuiTreeNodeFlags_FramePadding)) + return; + + for (auto &entry : group.entries) { + if (auto *action = get_if(entry)) { + ImGui::TextUnformatted(action->label.c_str()); + + if (action->shortcut) { + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(FormatShortcut(action->shortcut).c_str()); + } + } else if (auto *group = get_if(entry)) { + DrawGroupOverviewEntry(*group); + } + } + + if (group.isMenu) + ImGui::TreePop(); +} + +void ActionBinder::DrawOverview(const char *title, bool *pOpen) +{ + if (ImGui::Begin(title, pOpen)) { + for (auto &groupId : m_topLevelGroups) { + DrawGroupOverviewEntry(m_groupStorage.at(groupId)); + } + } + ImGui::End(); +} + +void ActionBinder::DrawMenuBar() +{ + for (auto &groupId : m_topLevelGroups) { + Group &group = m_groupStorage.at(groupId); + + if (group.isMenu) + DrawMenuInternal(group, true); + } +} + +void ActionBinder::DrawMenuInternal(Group &group, bool submitMenuItem) +{ + if (group.entries.empty()) + return; + + if (submitMenuItem && !ImGui::BeginMenu(group.label.c_str())) + return; + + ImGui::PushID(group.label.c_str()); + + size_t numActions = 0; + for (auto &entry : group.entries) { + + if (ActionEntry *action = get_if(entry)) { + + numActions++; + + bool enabled = action->predicate.empty() || action->predicate(); + ImGui::BeginDisabled(!enabled); + + std::string shortcut = action->shortcut ? FormatShortcut(action->shortcut) : ""; + if (ImGui::MenuItem(action->label.c_str(), shortcut.c_str())) { + action->action(); + } + + ImGui::EndDisabled(); + + } else if (Group *subGroup = get_if(entry)) { + + if (numActions) { + numActions = 0; + ImGui::Separator(); + } + + DrawMenuInternal(*subGroup, subGroup->isMenu); + + } + } + + ImGui::PopID(); + + if (submitMenuItem) + ImGui::EndMenu(); +} + +void ActionBinder::BeginInternal(std::string id, bool menu) +{ + for (std::string_view name : SplitString(id, ".")) { + + std::string lookupId = !m_activeGroupStack.empty() ? + fmt::format("{}.{}", m_activeGroupStack.back().first, name) : + std::string(name); + + // Create the new group if it doesn't exist + if (!m_groupStorage.count(lookupId)) { + m_groupStorage.try_emplace(lookupId, name, menu); + + // nothing on the stack, could be a top-level entry + if (m_activeGroupStack.empty()) + m_topLevelGroups.push_back(lookupId); + // add a group entry to our previous group + else { + Group *group = m_activeGroupStack.back().second; + group->entries.emplace_back(&m_groupStorage.at(lookupId)); + } + } + + m_activeGroupStack.push_back({ lookupId, &m_groupStorage.at(lookupId) }); + } +} + +void ActionBinder::EndInternal() +{ + assert(!m_activeGroupStack.empty()); + + m_activeGroupStack.pop_back(); +} + +void ActionBinder::AddInternal(std::string id, ActionEntry &&entry) +{ + if (m_activeGroupStack.empty()) + return; + + Group *group = m_activeGroupStack.back().second; + + std::string qualified_id = fmt::format("{}.{}", m_activeGroupStack.back().first, id); + auto iter = m_actionStorage.emplace(qualified_id, std::move(entry)).first; + + group->entries.push_back(&iter->second); +} + +ActionEntry *ActionBinder::GetAction(std::string id) +{ + if (!m_actionStorage.count(id)) + return nullptr; + + return &m_actionStorage.at(id); +} + +void ActionBinder::TriggerAction(std::string id) +{ + ActionEntry *entry = GetAction(id); + + if (entry && (entry->predicate.empty() || entry->predicate())) + entry->action(); +} diff --git a/src/editor/ActionBinder.h b/src/editor/ActionBinder.h new file mode 100644 index 00000000000..19bdc1146da --- /dev/null +++ b/src/editor/ActionBinder.h @@ -0,0 +1,118 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "imgui/imgui.h" + +#include + +#include +#include +#include + +namespace Editor { + + // Represents an action the user can execute within an editor context + // that should be associated with some window-global shortcut. + // The predicate will be evaluated to determine if the entry is enabled. + struct ActionEntry { + template + ActionEntry(std::string_view label, ImGuiKeyChord shortcut, Functor f) : + label(label), + shortcut(shortcut), + action(f) + {} + + template + ActionEntry(std::string_view label, ImGuiKeyChord shortcut, Predicate p, Functor f) : + label(label), + shortcut(shortcut), + predicate(p), + action(f) + {} + + std::string label; + const char *fontIcon; + ImGuiKeyChord shortcut; + + sigc::slot predicate; + sigc::slot action; + }; + + class ActionBinder { + public: + ActionBinder(); + ~ActionBinder(); + + struct Group; + + using GroupEntry = std::variant; + struct Group { + Group(std::string_view name, bool menu) : + label(std::string(name)), + isMenu(menu) + {} + + std::string label; + std::vector entries; + bool isMenu; + }; + + // process all actions and determine if their shortcuts are activated + void Update(); + + // Draw debug window displaying all registered actions + void DrawOverview(const char *title, bool *pOpen = nullptr); + + // Draw all groups registered in this ActionBinder as a main menu bar + void DrawMenuBar(); + + // draw the GroupEntry named by 'id' in the context of an existing + // dropdown menu (i.e. do not submit a top-level BeginMenu) + void DrawGroup(std::string id) { DrawMenuInternal(m_groupStorage.at(id), false); } + + // draw the GroupEntry named by 'id' as a submenu (with a top-level BeginMenu) + void DrawMenu(std::string id) { DrawMenuInternal(m_groupStorage.at(id), true); } + + // draw the GroupEntry named by 'id' in the context of a button toolbar + // void DrawToolbar(std::string id) + + static std::string FormatShortcut(ImGuiKeyChord shortcut); + + // Begin a group or menu with the given ID. ID is a qualified domain + // name relative to the current ID stack. + ActionBinder &BeginGroup(std::string id) { BeginInternal(id, false); return *this; } + ActionBinder &BeginMenu(std::string id) { BeginInternal(id, true); return *this; } + void EndGroup() { EndInternal(); } + void EndMenu() { EndInternal(); } + + // Add the given action to the currently open group. Fails if no group + // is open. The action can be referenced by the fully-qualified id + // 'group-id[.group-id[...]].action-id'. + ActionBinder &AddAction(std::string id, ActionEntry &&entry) { AddInternal(id, std::move(entry)); return *this; } + + std::vector &GetGroups() { return m_topLevelGroups; } + + ActionEntry *GetAction(std::string id); + void TriggerAction(std::string id); + + private: + void BeginInternal(std::string id, bool menu); + void EndInternal(); + + void AddInternal(std::string id, ActionEntry &&entry); + + void DrawMenuInternal(Group &group, bool menuHeading); + + void DrawGroupOverviewEntry(Group &group); + + std::map m_actionStorage; + std::map m_groupStorage; + + std::vector m_topLevelGroups; + + std::vector> m_activeGroupStack; + }; + +} From 51a792c9b5d24b8cd15ae8160ef196e3e857af56 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 00:22:18 -0400 Subject: [PATCH 33/50] SystemEditor: add shortcuts, layout management - Load window layout from saved user settings if present - Provide explicit functionality for user to reset window layout - Provide access to various ImGui debugging tools --- src/editor/system/SystemEditor.cpp | 282 +++++++++++++++++++---------- src/editor/system/SystemEditor.h | 60 ++++-- 2 files changed, 226 insertions(+), 116 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 341363e4d7c..46dd8691913 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -15,6 +15,7 @@ #include "SystemView.h" #include "core/StringUtils.h" +#include "editor/ActionBinder.h" #include "editor/EditorApp.h" #include "editor/EditorDraw.h" #include "editor/EditorIcons.h" @@ -91,7 +92,10 @@ SystemEditor::SystemEditor(EditorApp *app) : m_system(nullptr), m_systemInfo(), m_selectedBody(nullptr), - m_pendingOp() + m_contextBody(nullptr), + m_pendingOp(), + m_pendingFileReq(FileRequest_None), + m_menuBinder(new ActionBinder()) { GalacticEconomy::Init(); @@ -112,6 +116,8 @@ SystemEditor::SystemEditor(EditorApp *app) : }); ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + + RegisterMenuActions(); } SystemEditor::~SystemEditor() @@ -286,7 +292,7 @@ void SystemEditor::ClearSystem() m_system.Reset(); m_systemInfo = {}; m_viewport->SetSystem(m_system); - m_selectedBody = nullptr; + SetSelectedBody(nullptr); m_pendingOp = {}; m_filepath.clear(); @@ -301,6 +307,7 @@ void SystemEditor::SetSelectedBody(SystemBody *body) { // note: using const_cast here to work with Projectables which store a const pointer m_selectedBody = body; + m_contextBody = body; } void SystemEditor::Start() @@ -311,6 +318,91 @@ void SystemEditor::End() { } +void SystemEditor::RegisterMenuActions() +{ + m_menuBinder->BeginMenu("File"); + + m_menuBinder->AddAction("New", { + "New File", ImGuiKey_N | ImGuiKey_ModCtrl, + sigc::mem_fun(this, &SystemEditor::NewSystem) + }); + + m_menuBinder->AddAction("Open", { + "Open File", ImGuiKey_O | ImGuiKey_ModCtrl, + sigc::mem_fun(this, &SystemEditor::ActivateOpenDialog) + }); + + m_menuBinder->AddAction("Save", { + "Save", ImGuiKey_S | ImGuiKey_ModCtrl, + [&]() { return m_system.Valid(); }, + [&]() { + // Cannot write back .lua files + if (ends_with_ci(m_filepath, ".lua")) + ActivateSaveDialog(); + else + WriteSystem(m_filepath); + } + }); + + m_menuBinder->AddAction("SaveAs", { + "Save As", ImGuiKey_S | ImGuiKey_ModCtrl | ImGuiKey_ModShift, + sigc::mem_fun(this, &SystemEditor::ActivateSaveDialog) + }); + + m_menuBinder->EndMenu(); + + m_menuBinder->BeginMenu("Edit"); + + auto hasSelectedBody = [&]() { return m_contextBody != nullptr; }; + auto hasParentBody = [&]() { return m_contextBody && m_contextBody->GetParent(); }; + + m_menuBinder->BeginGroup("Body"); + + m_menuBinder->AddAction("Center", { + "Center on Body", {}, hasSelectedBody, + [&]() { + Projectable p = { Projectable::OBJECT, Projectable::SYSTEMBODY, m_contextBody }; + m_viewport->GetMap()->SetViewedObject(p); + } + }); + + m_menuBinder->AddAction("AddChild", { + "Add Child", ImGuiKey_A | ImGuiKey_ModCtrl, hasSelectedBody, + [&]() { + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = m_contextBody ? m_contextBody : m_system->GetRootBody().Get(); + m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; + } + }); + + m_menuBinder->AddAction("AddSibling", { + "Add Sibling", ImGuiKey_A | ImGuiKey_ModCtrl | ImGuiKey_ModShift, hasParentBody, + [&]() { + m_pendingOp.type = BodyRequest::TYPE_Add; + m_pendingOp.parent = m_contextBody->GetParent(); + m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(m_contextBody) + 1; + m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; + } + }); + + m_menuBinder->AddAction("Delete", { + "Delete Body", ImGuiKey_W | ImGuiKey_ModCtrl, hasParentBody, + [&]() { + m_pendingOp.type = BodyRequest::TYPE_Delete; + m_pendingOp.body = m_contextBody; + } + }); + + m_menuBinder->EndGroup(); + + m_menuBinder->AddAction("Sort", { + "Sort Bodies", {}, + [&]() { m_pendingOp.type = BodyRequest::TYPE_Resort; } + }); + + m_menuBinder->EndGroup(); +} + void SystemEditor::ActivateOpenDialog() { // FIXME: need to handle loading files outside of game data dir @@ -363,6 +455,8 @@ void SystemEditor::Update(float deltaTime) HandleBodyOperations(); + m_menuBinder->Update(); + if (m_openFile && m_openFile->ready(0)) { std::vector resultFiles = m_openFile->result(); m_openFile.reset(); @@ -381,8 +475,12 @@ void SystemEditor::Update(float deltaTime) Log::Info("SaveFile: {}", filePath); bool success = WriteSystem(filePath); - if (success) + if (success) { m_lastSavedUndoStack = m_undo->GetCurrentEntry(); + + m_filepath = filePath; + m_filedir = filePath.substr(0, filePath.find_last_of("/\\")); + } } } @@ -391,12 +489,24 @@ void SystemEditor::Update(float deltaTime) // Finished all pending file operations if (fileModalOpen && !m_openFile && !m_saveFile) { auto &popupStack = ImGui::GetCurrentContext()->OpenPopupStack; + for (size_t idx = 0; idx < popupStack.size(); ++idx) { if (popupStack[idx].PopupId == m_fileActionActiveModal) { ImGui::ClosePopupToLevel(idx, true); break; } } + + // Were there any pending operations waiting on a save dialog to close? + if (m_pendingFileReq == FileRequest_New) { + m_pendingFileReq = FileRequest_None; + NewSystem(); + } + + if (m_pendingFileReq == FileRequest_Open) { + m_pendingFileReq = FileRequest_None; + ActivateOpenDialog(); + } } } @@ -518,87 +628,26 @@ void SystemEditor::SetupLayout(ImGuiID dockspaceID) ImGui::DockBuilderDockWindow(VIEWPORT_WND_ID, nodeID); ImGui::DockBuilderFinish(dockspaceID); + + m_binderWindowOpen = false; + m_debugWindowOpen = false; + m_metricsWindowOpen = false; + m_undoStackWindowOpen = false; + m_resetDockingLayout = false; } void SystemEditor::DrawInterface() { - Draw::ShowUndoDebugWindow(GetUndo()); - // ImGui::ShowDemoWindow(); - ImGui::ShowMetricsWindow(); - static bool isFirstRun = true; Draw::BeginHostWindow("HostWindow", nullptr, ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar); - if (ImGui::BeginMenuBar()) { - - if (ImGui::BeginMenu("File")) { - - if (ImGui::MenuItem("New File", "Ctrl+N")) { - // TODO: show nag dialog if attempting open with unsaved changes - - NewSystem(); - } - - if (ImGui::MenuItem("Open File", "Ctrl+O")) { - // TODO: show nag dialog if attempting open with unsaved changes - - ActivateOpenDialog(); - } - - if (ImGui::MenuItem("Save", "Ctrl+S")) { - // Cannot write back .lua files - if (ends_with_ci(m_filepath, ".lua")) - ActivateSaveDialog(); - else - WriteSystem(m_filepath); - } - - if (ImGui::MenuItem("Save As", "Ctrl+Shift+S")) - ActivateSaveDialog(); - - ImGui::EndMenu(); - } - - if (ImGui::BeginMenu("Edit")) { - - if (m_selectedBody) { - if (ImGui::MenuItem("Add Child", "Ctrl+A")) { - m_pendingOp.type = BodyRequest::TYPE_Add; - m_pendingOp.parent = m_selectedBody ? m_selectedBody : m_system->GetRootBody().Get(); - m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; - } - - if (ImGui::MenuItem("Add Sibling", "Ctrl+Shift+A")) { - if (!m_selectedBody || !m_selectedBody->GetParent()) { - return; - } - - m_pendingOp.type = BodyRequest::TYPE_Add; - m_pendingOp.parent = m_selectedBody->GetParent(); - m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(m_selectedBody) + 1; - m_pendingOp.newBodyType = SystemBody::BodyType::TYPE_GRAVPOINT; - } - - if (m_selectedBody != m_system->GetRootBody() && ImGui::MenuItem("Delete", "Ctrl+W")) { - m_pendingOp.type = BodyRequest::TYPE_Delete; - m_pendingOp.body = m_selectedBody; - } - } - - if (ImGui::MenuItem("Sort Bodies")) { - m_pendingOp.type = BodyRequest::TYPE_Resort; - } - - ImGui::EndMenu(); - } - - ImGui::EndMenuBar(); - } + if (ImGui::BeginMenuBar()) + DrawMenuBar(); ImGuiID dockspaceID = ImGui::GetID("DockSpace"); - if (isFirstRun) + if (!ImGui::DockBuilderGetNode(dockspaceID) || m_resetDockingLayout) SetupLayout(dockspaceID); ImGui::DockSpace(dockspaceID); @@ -609,13 +658,15 @@ void SystemEditor::DrawInterface() if (ImGui::Begin(OUTLINE_WND_ID)) { ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); + DrawOutliner(); + ImGui::PopFont(); } ImGui::End(); if (ImGui::Begin(PROPERTIES_WND_ID)) { - // Adjust default window label position + // Adjust default item label position ImGui::PushItemWidth(ImFloor(ImGui::GetWindowSize().x * 0.6f)); ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 13)); @@ -652,6 +703,18 @@ void SystemEditor::DrawInterface() ImGui::End(); #endif + if (m_binderWindowOpen) + m_menuBinder->DrawOverview("Shortcut List", &m_binderWindowOpen); + + if (m_undoStackWindowOpen) + Draw::ShowUndoDebugWindow(GetUndo(), &m_undoStackWindowOpen); + + if (m_metricsWindowOpen) + ImGui::ShowMetricsWindow(&m_metricsWindowOpen); + + if (m_debugWindowOpen) + ImGui::ShowDebugLogWindow(&m_debugWindowOpen); + ImGui::End(); DrawFileActionModal(); @@ -662,6 +725,32 @@ void SystemEditor::DrawInterface() isFirstRun = false; } +void SystemEditor::DrawMenuBar() +{ + m_menuBinder->DrawMenuBar(); + + if (ImGui::BeginMenu("Windows")) { + if (ImGui::MenuItem("Metrics Window", nullptr, m_metricsWindowOpen)) + m_metricsWindowOpen = !m_metricsWindowOpen; + + if (ImGui::MenuItem("Undo Stack", nullptr, m_undoStackWindowOpen)) + m_undoStackWindowOpen = !m_undoStackWindowOpen; + + if (ImGui::MenuItem("Shortcut List", nullptr, m_binderWindowOpen)) + m_binderWindowOpen = !m_binderWindowOpen; + + if (ImGui::MenuItem("ImGui Debug Log", nullptr, m_debugWindowOpen)) + m_debugWindowOpen = !m_debugWindowOpen; + + if (ImGui::MenuItem("Reset Layout")) + m_resetDockingLayout = true; + + ImGui::EndMenu(); + } + + ImGui::EndMenuBar(); +} + void SystemEditor::DrawOutliner() { ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); @@ -734,7 +823,6 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) { ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen | - ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_FramePadding; @@ -758,37 +846,17 @@ bool SystemEditor::DrawBodyNode(SystemBody *body, bool isRoot) if (ImGui::IsItemActivated()) { m_viewport->GetMap()->SetSelectedObject({ Projectable::OBJECT, Projectable::SYSTEMBODY, body }); - m_selectedBody = body; + SetSelectedBody(body); } - if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("Center")) { - m_viewport->GetMap()->SetViewedObject({ Projectable::OBJECT, Projectable::SYSTEMBODY, body }); - } - - if (ImGui::MenuItem("Add Child")) { - m_pendingOp.type = BodyRequest::TYPE_Add; - m_pendingOp.parent = body; - m_pendingOp.idx = body->GetNumChildren(); - } - - if (body->GetParent() && ImGui::MenuItem("Add Sibling")) { - m_pendingOp.type = BodyRequest::TYPE_Add; - m_pendingOp.parent = body->GetParent(); - m_pendingOp.idx = SystemBody::EditorAPI::GetIndexInParent(body) + 1; - } - - // TODO: "add body" context menu - if (body->GetParent() && ImGui::MenuItem("Delete")) { - m_pendingOp.type = BodyRequest::TYPE_Delete; - m_pendingOp.body = body; - } - - ImGui::EndPopup(); + if (ImGui::IsItemClicked(0) && ImGui::IsMouseDoubleClicked(0)) { + m_viewport->GetMap()->SetViewedObject({ Projectable::OBJECT, Projectable::SYSTEMBODY, body }); } // TODO: custom rendering on body entry, e.g. icon / contents etc. + DrawBodyContextMenu(body); + return open && body->GetNumChildren(); } @@ -832,6 +900,20 @@ void SystemEditor::DrawSystemProperties() } +void SystemEditor::DrawBodyContextMenu(SystemBody *body) +{ + if (ImGui::BeginPopupContextItem()) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + m_contextBody = body; + m_menuBinder->DrawGroup("Edit.Body"); + m_contextBody = m_selectedBody; + + ImGui::PopFont(); + ImGui::EndPopup(); + } +} + void SystemEditor::DrawPickSystemModal() { diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index ee0169bd2ad..72463818d4a 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -37,6 +37,7 @@ namespace Editor { class EditorApp; class UndoSystem; class SystemEditorViewport; +class ActionBinder; const char *GetBodyIcon(const SystemBody *body); @@ -57,6 +58,8 @@ class SystemEditor : public Application::Lifecycle { void SetSelectedBody(SystemBody *body); SystemBody *GetSelectedBody() { return m_selectedBody; } + void DrawBodyContextMenu(SystemBody *body); + protected: void Start() override; void Update(float deltaTime) override; @@ -65,6 +68,8 @@ class SystemEditor : public Application::Lifecycle { void HandleInput(); private: + void RegisterMenuActions(); + void ClearSystem(); bool LoadSystem(const FileSystem::FileInfo &file); bool LoadCustomSystem(const CustomSystem *system); @@ -73,6 +78,8 @@ class SystemEditor : public Application::Lifecycle { void SetupLayout(ImGuiID dockspaceID); void DrawInterface(); + void DrawMenuBar(); + bool DrawBodyNode(SystemBody *body, bool isRoot); void HandleOutlinerDragDrop(SystemBody *refBody); void DrawOutliner(); @@ -97,6 +104,31 @@ class SystemEditor : public Application::Lifecycle { private: class UndoSetSelection; + // Pending actions which require a "save/as" interrupt + enum FileRequestType { + FileRequest_None, + FileRequest_Open, + FileRequest_New + }; + + // Pending actions to the body tree hierarchy that should + // be handled at the end of the frame + struct BodyRequest { + enum Type { + TYPE_None, + TYPE_Add, + TYPE_Delete, + TYPE_Reparent, + TYPE_Resort + }; + + Type type = TYPE_None; + uint32_t newBodyType = 0; // SystemBody::BodyType + SystemBody *parent = nullptr; + SystemBody *body = nullptr; + size_t idx = 0; + }; + EditorApp *m_app; RefCountedPtr m_galaxy; @@ -117,31 +149,27 @@ class SystemEditor : public Application::Lifecycle { std::string m_filedir; SystemBody *m_selectedBody; - - struct BodyRequest { - enum Type { - TYPE_None, - TYPE_Add, - TYPE_Delete, - TYPE_Reparent, - TYPE_Resort - }; - - Type type = TYPE_None; - uint32_t newBodyType = 0; // SystemBody::BodyType - SystemBody *parent = nullptr; - SystemBody *body = nullptr; - size_t idx = 0; - }; + SystemBody *m_contextBody; BodyRequest m_pendingOp; + FileRequestType m_pendingFileReq; + std::unique_ptr m_openFile; std::unique_ptr m_saveFile; SystemPath m_openSystemPath; ImGuiID m_fileActionActiveModal; + + std::unique_ptr m_menuBinder; + + bool m_metricsWindowOpen = false; + bool m_undoStackWindowOpen = false; + bool m_binderWindowOpen = false; + bool m_debugWindowOpen = false; + + bool m_resetDockingLayout = false; }; } // namespace Editor From cc8900de65dca750db49c43b21e2a75955ff138e Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 00:35:00 -0400 Subject: [PATCH 34/50] Editor: add "get out of jail" button to fix undo - Resets current undo frame in case of broken/un-closed undo entry --- src/editor/EditorDraw.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/editor/EditorDraw.cpp b/src/editor/EditorDraw.cpp index 6caf30ba0a4..f5dc654af82 100644 --- a/src/editor/EditorDraw.cpp +++ b/src/editor/EditorDraw.cpp @@ -119,7 +119,20 @@ void Draw::ShowUndoDebugWindow(UndoSystem *undo, bool *p_open) return; } + ImGui::AlignTextToFramePadding(); ImGui::Text("Undo Depth: %ld", undo->GetEntryDepth()); + + if (ImGui::IsKeyDown(ImGuiKey_LeftAlt) && undo->GetEntryDepth()) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().WindowPadding.x * 2.f, 0.f); + + // Get out of jail free card to fix a broken undo state + if (ImGui::Button("X")) { + undo->ResetEntry(); + while (undo->GetEntryDepth() > 0) + undo->EndEntry(); + } + } + ImGui::Separator(); size_t numEntries = undo->GetNumEntries(); From 460d98dcf5983f5baff8ed0c8fe1ab9ea25aa7e6 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 16:02:22 -0400 Subject: [PATCH 35/50] SystemEditor: load generated systems from galaxy --- src/editor/system/GalaxyEditAPI.cpp | 10 +- src/editor/system/GalaxyEditAPI.h | 2 + src/editor/system/SystemEditor.cpp | 143 ++++++++++++++++++++++++++-- src/editor/system/SystemEditor.h | 6 +- 4 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index a481447202a..bfc725cdf5a 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -1,8 +1,8 @@ // Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt - #include "GalaxyEditAPI.h" + #include "EditorIcons.h" #include "SystemEditorHelpers.h" @@ -57,6 +57,14 @@ namespace Editor::Draw { } // namespace Editor::Draw +void StarSystem::EditorAPI::RemoveFromCache(StarSystem *system) +{ + if (system->m_cache) { + system->m_cache->RemoveFromAttic(system->GetPath()); + system->m_cache = nullptr; + } +} + SystemBody *StarSystem::EditorAPI::NewBody(StarSystem *system) { return system->NewBody(); diff --git a/src/editor/system/GalaxyEditAPI.h b/src/editor/system/GalaxyEditAPI.h index 570170d19fa..9e28f98537b 100644 --- a/src/editor/system/GalaxyEditAPI.h +++ b/src/editor/system/GalaxyEditAPI.h @@ -30,6 +30,8 @@ namespace Editor { class StarSystem::EditorAPI { public: + static void RemoveFromCache(StarSystem *system); + static SystemBody *NewBody(StarSystem *system); static SystemBody *NewBodyAround(StarSystem *system, Random &rng, SystemBody *primary, size_t idx); diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 46dd8691913..7b2ab1553a6 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -46,6 +46,7 @@ namespace { static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; static constexpr const char *FILE_MODAL_ID = "File Window Open"; + static constexpr const char *PICK_SYSTEM_MODAL_ID = "Load System from Galaxy"; } const char *Editor::GetBodyIcon(const SystemBody *body) { @@ -95,6 +96,7 @@ SystemEditor::SystemEditor(EditorApp *app) : m_contextBody(nullptr), m_pendingOp(), m_pendingFileReq(FileRequest_None), + m_pickSystemPath(), m_menuBinder(new ActionBinder()) { GalacticEconomy::Init(); @@ -124,12 +126,10 @@ SystemEditor::~SystemEditor() { } -void SystemEditor::NewSystem() +void SystemEditor::NewSystem(SystemPath path) { ClearSystem(); - SystemPath path(0, 0, 0, 0); - auto *newSystem = new StarSystem::GeneratorAPI(path, m_galaxy, nullptr, GetRng()); m_system.Reset(newSystem); @@ -285,6 +285,28 @@ bool SystemEditor::LoadCustomSystem(const CustomSystem *csys) return true; } +void SystemEditor::LoadSystemFromGalaxy(RefCountedPtr system) +{ + if (!system->GetRootBody()) { + Log::Error("Randomly-generated system doesn't have a root body"); + return; + } + + ClearSystem(); + + StarSystem::EditorAPI::RemoveFromCache(system.Get()); + + m_system = system; + m_viewport->SetSystem(system); + + bool explored = system->GetExplored() == StarSystem::eEXPLORED_AT_START; + + m_systemInfo.explored = explored ? CustomSystemInfo::EXPLORE_ExploredAtStart : CustomSystemInfo::EXPLORE_Unexplored; + m_systemInfo.randomLawlessness = false; + m_systemInfo.randomFaction = system->GetFaction(); + m_systemInfo.faction = system->GetFaction() ? system->GetFaction()->name : ""; +} + void SystemEditor::ClearSystem() { m_undo->Clear(); @@ -296,6 +318,7 @@ void SystemEditor::ClearSystem() m_pendingOp = {}; m_filepath.clear(); + m_pickSystemPath.systemIndex = 0; SDL_SetWindowTitle(m_app->GetRenderer()->GetSDLWindow(), "System Editor"); } @@ -323,8 +346,8 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->BeginMenu("File"); m_menuBinder->AddAction("New", { - "New File", ImGuiKey_N | ImGuiKey_ModCtrl, - sigc::mem_fun(this, &SystemEditor::NewSystem) + "New System", ImGuiKey_N | ImGuiKey_ModCtrl, + sigc::mem_fun(this, &SystemEditor::ActivatePickSystemDialog) }); m_menuBinder->AddAction("Open", { @@ -337,7 +360,7 @@ void SystemEditor::RegisterMenuActions() [&]() { return m_system.Valid(); }, [&]() { // Cannot write back .lua files - if (ends_with_ci(m_filepath, ".lua")) + if (m_filepath.empty() || ends_with_ci(m_filepath, ".lua")) ActivateSaveDialog(); else WriteSystem(m_filepath); @@ -433,6 +456,11 @@ void SystemEditor::ActivateSaveDialog() ImGui::OpenPopup(m_fileActionActiveModal); } +void SystemEditor::ActivatePickSystemDialog() +{ + ImGui::OpenPopup(m_pickSystemModal); +} + // ─── Update Loop ───────────────────────────────────────────────────────────── void SystemEditor::HandleInput() @@ -450,6 +478,7 @@ void SystemEditor::Update(float deltaTime) } m_fileActionActiveModal = ImGui::GetID(FILE_MODAL_ID); + m_pickSystemModal = ImGui::GetID(PICK_SYSTEM_MODAL_ID); DrawInterface(); @@ -500,7 +529,7 @@ void SystemEditor::Update(float deltaTime) // Were there any pending operations waiting on a save dialog to close? if (m_pendingFileReq == FileRequest_New) { m_pendingFileReq = FileRequest_None; - NewSystem(); + ActivatePickSystemDialog(); } if (m_pendingFileReq == FileRequest_Open) { @@ -916,7 +945,107 @@ void SystemEditor::DrawBodyContextMenu(SystemBody *body) void SystemEditor::DrawPickSystemModal() { + ImVec2 windSize = ImVec2(ImGui::GetMainViewport()->Size.x * 0.5, -1); + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); + ImGui::SetNextWindowSizeConstraints(windSize, windSize); + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + bool open = true; + if (ImGui::BeginPopupModal(PICK_SYSTEM_MODAL_ID, &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); + + if (Draw::LayoutHorizontal("Sector", 3, ImGui::GetFontSize())) { + bool changed = false; + changed |= ImGui::InputInt("X", &m_pickSystemPath.sectorX, 0, 0); + changed |= ImGui::InputInt("Y", &m_pickSystemPath.sectorY, 0, 0); + changed |= ImGui::InputInt("Z", &m_pickSystemPath.sectorZ, 0, 0); + + if (changed) + m_pickSystemPath.systemIndex = 0; + + Draw::EndLayout(); + } + + ImGui::Separator(); + + RefCountedPtr sec = m_galaxy->GetSector(m_pickSystemPath.SectorOnly()); + + ImGui::BeginGroup(); + if (ImGui::BeginChild("Systems", ImVec2(ImGui::GetContentRegionAvail().x * 0.33, -ImGui::GetFrameHeightWithSpacing()))) { + + for (const Sector::System &system : sec->m_systems) { + std::string label = fmt::format("{} ({}x{})", system.GetName(), EICON_SUN, system.GetNumStars()); + if (ImGui::Selectable(label.c_str(), system.idx == m_pickSystemPath.systemIndex)) + m_pickSystemPath.systemIndex = system.idx; + } + + } + ImGui::EndChild(); + + if (ImGui::Button("New System")) { + NewSystem(m_pickSystemPath.SectorOnly()); + ImGui::CloseCurrentPopup(); + } + + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + + if (m_pickSystemPath.systemIndex < sec->m_systems.size()) { + const Sector::System &system = sec->m_systems[m_pickSystemPath.systemIndex]; + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(system.GetName().c_str()); + + ImGui::SameLine(); + ImGui::SameLine(0.f, ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeight()); + if (ImGui::Button(EICON_FORWARD1)) { + + // Load a fully-defined custom system from the custom system def + // NOTE: we cannot (currently) determine which file this custom system originated from + if (system.GetCustomSystem() && !system.GetCustomSystem()->IsRandom()) + LoadCustomSystem(system.GetCustomSystem()); + else + LoadSystemFromGalaxy(m_galaxy->GetStarSystem(m_pickSystemPath)); + + ImGui::CloseCurrentPopup(); + } + + ImGui::PopFont(); + + ImGui::Spacing(); + + ImGui::TextUnformatted("Is Custom:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetCustomSystem() ? "yes" : "no"); + + ImGui::TextUnformatted("Is Explored:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetExplored() == StarSystem::eEXPLORED_AT_START ? "yes" : "no"); + + ImGui::TextUnformatted("Faction:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetFaction() ? system.GetFaction()->name.c_str() : ""); + + ImGui::TextUnformatted("Other Names:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + + ImGui::BeginGroup(); + for (auto &name : system.GetOtherNames()) + ImGui::TextUnformatted(name.c_str()); + ImGui::EndGroup(); + } + ImGui::EndGroup(); + + ImGui::PopFont(); + ImGui::EndPopup(); + } + + ImGui::PopFont(); } void SystemEditor::DrawFileActionModal() diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index 72463818d4a..ab5898a8585 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -46,7 +46,7 @@ class SystemEditor : public Application::Lifecycle { SystemEditor(EditorApp *app); ~SystemEditor(); - void NewSystem(); + void NewSystem(SystemPath path); bool LoadSystemFromDisk(const std::string &absolutePath); // Write the currently edited system out to disk as a JSON file @@ -93,6 +93,7 @@ class SystemEditor : public Application::Lifecycle { void ActivateOpenDialog(); void ActivateSaveDialog(); + void ActivatePickSystemDialog(); void DrawFileActionModal(); void DrawPickSystemModal(); @@ -161,6 +162,9 @@ class SystemEditor : public Application::Lifecycle { SystemPath m_openSystemPath; ImGuiID m_fileActionActiveModal; + ImGuiID m_pickSystemModal; + + SystemPath m_pickSystemPath; std::unique_ptr m_menuBinder; From ab55e9743a3d6dc57d98f89f5b3b5ce776480d8b Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 20:24:03 -0400 Subject: [PATCH 36/50] Editor: add Modal helper class - Fire-and-forget modal management wrapping the peculiarities of working with ImGui modal popups - Don't process action shortcuts while modal is open --- src/editor/ActionBinder.cpp | 8 ++++++ src/editor/EditorApp.cpp | 18 ++++++++++++ src/editor/EditorApp.h | 13 +++++++++ src/editor/Modal.cpp | 57 +++++++++++++++++++++++++++++++++++++ src/editor/Modal.h | 34 ++++++++++++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/editor/Modal.cpp create mode 100644 src/editor/Modal.h diff --git a/src/editor/ActionBinder.cpp b/src/editor/ActionBinder.cpp index 7200dec6047..fdcc1aad469 100644 --- a/src/editor/ActionBinder.cpp +++ b/src/editor/ActionBinder.cpp @@ -38,6 +38,14 @@ std::string ActionBinder::FormatShortcut(ImGuiKeyChord shortcut) void ActionBinder::Update() { + // Don't process shortcuts while a popup is open + if (ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId)) { + for (auto &popup : ImGui::GetCurrentContext()->OpenPopupStack) { + if (popup.Window && popup.Window->Flags & ImGuiWindowFlags_Modal) + return; + } + } + for (auto &[id, action] : m_actionStorage) { if (!action.shortcut) continue; diff --git a/src/editor/EditorApp.cpp b/src/editor/EditorApp.cpp index 6f65f210ac6..1f61a5e8cca 100644 --- a/src/editor/EditorApp.cpp +++ b/src/editor/EditorApp.cpp @@ -4,6 +4,7 @@ #include "EditorApp.h" #include "EditorDraw.h" +#include "Modal.h" #include "FileSystem.h" #include "Lang.h" @@ -82,6 +83,11 @@ void EditorApp::SetAppName(std::string_view name) m_appName = name; } +void EditorApp::PushModalInternal(Modal *modal) +{ + m_modalStack.push_back(RefCountedPtr(modal)); +} + void EditorApp::OnStartup() { Log::GetLog()->SetLogFile("editor.txt"); @@ -144,6 +150,18 @@ void EditorApp::PreUpdate() void EditorApp::PostUpdate() { + // Clean up finished modals + for (int idx = int(m_modalStack.size()) - 1; idx >= 0; --idx) { + if (m_modalStack[idx]->Ready()) + m_modalStack.erase(m_modalStack.begin() + idx); + } + + // Draw modals after cleaning, to ensure application has all of frame+1 + // to process modal results + for (auto &modal : m_modalStack) { + modal->Draw(); + } + GetRenderer()->ClearDepthBuffer(); GetPiGui()->Render(); diff --git a/src/editor/EditorApp.h b/src/editor/EditorApp.h index 5b5a0b45e3f..6bdc772e7bc 100644 --- a/src/editor/EditorApp.h +++ b/src/editor/EditorApp.h @@ -15,6 +15,7 @@ namespace Graphics { } // namespace Graphics namespace Editor { + class Modal; class EditorApp : public GuiApplication { public: @@ -29,6 +30,14 @@ namespace Editor { void SetAppName(std::string_view name); + template + RefCountedPtr PushModal(Args&& ...args) + { + T *modal = new T(this, args...); + PushModalInternal(modal); + return RefCountedPtr(modal); + } + protected: void OnStartup() override; void OnShutdown() override; @@ -40,9 +49,13 @@ namespace Editor { std::vector &GetLoadingTasks() { return m_loadingTasks; } private: + void PushModalInternal(Modal *m); + std::vector m_loadingTasks; Graphics::Renderer *m_renderer; + std::vector> m_modalStack; + std::string m_appName; }; diff --git a/src/editor/Modal.cpp b/src/editor/Modal.cpp new file mode 100644 index 00000000000..b15974f17b1 --- /dev/null +++ b/src/editor/Modal.cpp @@ -0,0 +1,57 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "Modal.h" + +#include "editor/EditorApp.h" +#include "editor/EditorDraw.h" +#include "pigui/PiGui.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" + +using namespace Editor; + +Modal::Modal(EditorApp *app, const char *title, bool canClose) : + m_app(app), + m_title(title), + m_id(0), + m_shouldClose(false), + m_canClose(canClose) +{ +} + +bool Modal::Ready() +{ + return m_id != 0 && !ImGui::IsPopupOpen(m_id, ImGuiPopupFlags_AnyPopupLevel); +} + +void Modal::Close() +{ + m_shouldClose = true; +} + +void Modal::Draw() +{ + if (!m_id) { + m_id = ImGui::GetID(m_title); + ImGui::OpenPopup(m_id); + } + + ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + if (ImGui::BeginPopupModal(m_title, m_canClose ? &m_canClose : nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); + + if (m_shouldClose) + ImGui::CloseCurrentPopup(); + else + DrawInternal(); + + ImGui::PopFont(); + ImGui::EndPopup(); + } + + ImGui::PopFont(); +} diff --git a/src/editor/Modal.h b/src/editor/Modal.h new file mode 100644 index 00000000000..b18f4e635ba --- /dev/null +++ b/src/editor/Modal.h @@ -0,0 +1,34 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "RefCounted.h" + +// forward declaration +using ImGuiID = unsigned int; + +namespace Editor { + + class EditorApp; + + class Modal : public RefCounted { + public: + Modal(EditorApp *app, const char *title, bool canClose); + + bool Ready(); + void Close(); + + virtual void Draw(); + + protected: + virtual void DrawInternal() {} + + EditorApp *m_app; + const char *m_title; + ImGuiID m_id; + bool m_shouldClose; + bool m_canClose; + }; + +} // namespace Editor From f2acfee7734d1ea8229b9a3d54144b40d89e81d1 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 7 Sep 2023 21:30:48 -0400 Subject: [PATCH 37/50] SystemEditor: separate modal code - Implement "unsaved changes" nag modal - Modals defined and managed separately from editor code - Pick system modal serves as "new system" dialog as well as loading systems from galaxy --- src/editor/system/GalaxyEditAPI.cpp | 4 + src/editor/system/SystemEditor.cpp | 343 ++++++++++------------- src/editor/system/SystemEditor.h | 43 +-- src/editor/system/SystemEditorModals.cpp | 175 ++++++++++++ src/editor/system/SystemEditorModals.h | 55 ++++ 5 files changed, 414 insertions(+), 206 deletions(-) create mode 100644 src/editor/system/SystemEditorModals.cpp create mode 100644 src/editor/system/SystemEditorModals.h diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index bfc725cdf5a..0c52f853d31 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -538,6 +538,10 @@ void SystemBody::EditorAPI::EditOrbitalParameters(SystemBody *body, UndoSystem * void SystemBody::EditorAPI::EditEconomicProperties(SystemBody *body, UndoSystem *undo) { + // TODO: system generation currently ignores these fields of a system body + // and overwrites them with randomly-rolled values. + return; + ImGui::SeparatorText("Economic Parameters"); ImGui::InputFixed("Population", &body->m_population); diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index 7b2ab1553a6..a0a2bbe4d5a 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -4,9 +4,10 @@ #include "SystemEditor.h" #include "GalaxyEditAPI.h" -#include "SystemEditorHelpers.h" #include "SystemBodyUndo.h" +#include "SystemEditorHelpers.h" #include "SystemEditorViewport.h" +#include "SystemEditorModals.h" #include "EnumStrings.h" #include "FileSystem.h" @@ -45,8 +46,6 @@ namespace { static constexpr const char *OUTLINE_WND_ID = "Outline"; static constexpr const char *PROPERTIES_WND_ID = "Properties"; static constexpr const char *VIEWPORT_WND_ID = "Viewport"; - static constexpr const char *FILE_MODAL_ID = "File Window Open"; - static constexpr const char *PICK_SYSTEM_MODAL_ID = "Load System from Galaxy"; } const char *Editor::GetBodyIcon(const SystemBody *body) { @@ -96,7 +95,7 @@ SystemEditor::SystemEditor(EditorApp *app) : m_contextBody(nullptr), m_pendingOp(), m_pendingFileReq(FileRequest_None), - m_pickSystemPath(), + m_newSystemPath(), m_menuBinder(new ActionBinder()) { GalacticEconomy::Init(); @@ -139,6 +138,9 @@ void SystemEditor::NewSystem(SystemPath path) polit.govType = Polit::GOV_NONE; newSystem->SetSysPolit(polit); + + // mark current file as unsaved + m_lastSavedUndoStack = size_t(-1); } bool SystemEditor::LoadSystemFromDisk(const std::string &absolutePath) @@ -151,7 +153,7 @@ bool SystemEditor::LoadSystemFromDisk(const std::string &absolutePath) return false; } - return LoadSystem(FileSystem::gameDataFiles.Lookup(filepath)); + return LoadSystemFromFile(FileSystem::gameDataFiles.Lookup(filepath)); } else { std::string dirpath = absolutePath.substr(0, absolutePath.find_last_of("/\\")); std::string filename = absolutePath.substr(dirpath.size() + 1); @@ -159,11 +161,35 @@ bool SystemEditor::LoadSystemFromDisk(const std::string &absolutePath) // Hack: construct a temporary FileSource to load from an arbitrary path auto fs = FileSystem::FileSourceFS(dirpath); - return LoadSystem(fs.Lookup(filename)); + return LoadSystemFromFile(fs.Lookup(filename)); } } -bool SystemEditor::LoadSystem(const FileSystem::FileInfo &file) +bool SystemEditor::LoadSystem(SystemPath path) +{ + RefCountedPtr sec = m_galaxy->GetSector(path.SectorOnly()); + + if (path.systemIndex >= sec->m_systems.size()) { + Log::Error("System {} in sector ({},{},{}) does not exist", path.systemIndex, path.sectorX, path.sectorY, path.sectorZ); + return false; + } + + const Sector::System &system = sec->m_systems.at(path.systemIndex); + + // Load a fully-defined custom system from the custom system def + // NOTE: we cannot (currently) determine which file this custom system originated from + if (system.GetCustomSystem() && !system.GetCustomSystem()->IsRandom()) + LoadCustomSystem(system.GetCustomSystem()); + else + LoadSystemFromGalaxy(m_galaxy->GetStarSystem(path)); + + m_filepath = ""; + m_filedir = ""; + + return true; +} + +bool SystemEditor::LoadSystemFromFile(const FileSystem::FileInfo &file) { if (!file.Exists()) { Log::Error("Cannot open file path {}", file.GetAbsolutePath()); @@ -210,10 +236,10 @@ bool SystemEditor::WriteSystem(const std::string &filepath) FILE *f = fopen(filepath.c_str(), "w"); - if (!f) + if (!f) { + OnSaveComplete(false); return false; - - // StarSystem::EditorAPI::ExportToLua(f, m_system.Get(), m_galaxy.Get()); + } Json systemdef = Json::object(); @@ -239,6 +265,7 @@ bool SystemEditor::WriteSystem(const std::string &filepath) fwrite(jsonData.data(), 1, jsonData.size(), f); fclose(f); + OnSaveComplete(true); return true; } @@ -256,14 +283,9 @@ bool SystemEditor::LoadCustomSystem(const CustomSystem *csys) return false; } - // FIXME: need to run StarSystemPopulateGenerator here to finish filling out system - // Setting up faction affinity etc. requires running full gamut of generator stages - - // auto populateStage = std::make_unique(); - // GalaxyGenerator::StarSystemConfig config; - // config.isCustomOnly = true; - - // populateStage->Apply(rng, m_galaxy, system, &config); + // NOTE: we don't run the PopulateSystem generator here, due to its + // reliance on filled-out Sector information + // As a result, population information will not be correct if (!system->GetRootBody()) { Log::Error("Custom system doesn't have a root body"); @@ -309,7 +331,8 @@ void SystemEditor::LoadSystemFromGalaxy(RefCountedPtr system) void SystemEditor::ClearSystem() { - m_undo->Clear(); + GetUndo()->Clear(); + m_lastSavedUndoStack = 0; m_system.Reset(); m_systemInfo = {}; @@ -318,7 +341,7 @@ void SystemEditor::ClearSystem() m_pendingOp = {}; m_filepath.clear(); - m_pickSystemPath.systemIndex = 0; + m_newSystemPath.systemIndex = 0; SDL_SetWindowTitle(m_app->GetRenderer()->GetSDLWindow(), "System Editor"); } @@ -347,7 +370,7 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->AddAction("New", { "New System", ImGuiKey_N | ImGuiKey_ModCtrl, - sigc::mem_fun(this, &SystemEditor::ActivatePickSystemDialog) + sigc::mem_fun(this, &SystemEditor::ActivateNewSystemDialog) }); m_menuBinder->AddAction("Open", { @@ -358,20 +381,25 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->AddAction("Save", { "Save", ImGuiKey_S | ImGuiKey_ModCtrl, [&]() { return m_system.Valid(); }, - [&]() { - // Cannot write back .lua files - if (m_filepath.empty() || ends_with_ci(m_filepath, ".lua")) - ActivateSaveDialog(); - else - WriteSystem(m_filepath); - } + sigc::mem_fun(this, &SystemEditor::SaveCurrentFile) }); m_menuBinder->AddAction("SaveAs", { "Save As", ImGuiKey_S | ImGuiKey_ModCtrl | ImGuiKey_ModShift, + [&]() { return m_system.Valid(); }, sigc::mem_fun(this, &SystemEditor::ActivateSaveDialog) }); + m_menuBinder->AddAction("Quit", { + "Quit", ImGuiKey_Q | ImGuiKey_ModCtrl, + [this]() { + if (HasUnsavedChanges()) { + m_unsavedFileModal = m_app->PushModal(); + m_pendingFileReq = FileRequest_Quit; + } + } + }); + m_menuBinder->EndMenu(); m_menuBinder->BeginMenu("Edit"); @@ -426,8 +454,46 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->EndGroup(); } +bool SystemEditor::HasUnsavedChanges() +{ + size_t undoStateHash = (m_undo->GetNumEntries() << 32) | m_undo->GetCurrentEntry(); + return (m_system && undoStateHash != m_lastSavedUndoStack); +} + +void SystemEditor::SaveCurrentFile() +{ + // Cannot write back .lua files + if (m_filepath.empty() || ends_with_ci(m_filepath, ".lua")) { + ActivateSaveDialog(); + } else { + WriteSystem(m_filepath); + } +} + +void SystemEditor::OnSaveComplete(bool success) +{ + if (!success) { + // Cancel any pending actions if we failed to save or cancelled the process + m_pendingFileReq = FileRequest_None; + return; + } + + // "Simple" hash of undo state + m_lastSavedUndoStack = (m_undo->GetNumEntries() << 32) | m_undo->GetCurrentEntry(); + + if (m_pendingFileReq != FileRequest_None) { + HandlePendingFileRequest(); + } +} + void SystemEditor::ActivateOpenDialog() { + if (HasUnsavedChanges()) { + m_unsavedFileModal = m_app->PushModal(); + m_pendingFileReq = FileRequest_Open; + return; + } + // FIXME: need to handle loading files outside of game data dir m_openFile.reset(new pfd::open_file( "Open Custom System File", @@ -439,12 +505,11 @@ void SystemEditor::ActivateOpenDialog() }) ); - ImGui::OpenPopup(m_fileActionActiveModal); + m_fileActionModal = m_app->PushModal(); } void SystemEditor::ActivateSaveDialog() { - // FIXME: need to handle saving files outside of game data dir m_saveFile.reset(new pfd::save_file( "Save Custom System File", FileSystem::JoinPath(FileSystem::GetDataDir(), m_filedir), @@ -453,12 +518,18 @@ void SystemEditor::ActivateSaveDialog() }) ); - ImGui::OpenPopup(m_fileActionActiveModal); + m_fileActionModal = m_app->PushModal(); } -void SystemEditor::ActivatePickSystemDialog() +void SystemEditor::ActivateNewSystemDialog() { - ImGui::OpenPopup(m_pickSystemModal); + if (HasUnsavedChanges()) { + m_unsavedFileModal = m_app->PushModal(); + m_pendingFileReq = FileRequest_New; + return; + } + + m_newSystemModal = m_app->PushModal(this, &m_newSystemPath); } // ─── Update Loop ───────────────────────────────────────────────────────────── @@ -477,15 +548,10 @@ void SystemEditor::Update(float deltaTime) GetUndo()->Undo(); } - m_fileActionActiveModal = ImGui::GetID(FILE_MODAL_ID); - m_pickSystemModal = ImGui::GetID(PICK_SYSTEM_MODAL_ID); - DrawInterface(); HandleBodyOperations(); - m_menuBinder->Update(); - if (m_openFile && m_openFile->ready(0)) { std::vector resultFiles = m_openFile->result(); m_openFile.reset(); @@ -502,41 +568,60 @@ void SystemEditor::Update(float deltaTime) if (!filePath.empty()) { Log::Info("SaveFile: {}", filePath); - bool success = WriteSystem(filePath); - - if (success) { - m_lastSavedUndoStack = m_undo->GetCurrentEntry(); + // Update current file path and directory from new "save-as" path + if (WriteSystem(filePath)) { m_filepath = filePath; m_filedir = filePath.substr(0, filePath.find_last_of("/\\")); } + } else { + // Signal cancellation/failure to save + OnSaveComplete(false); } } - bool fileModalOpen = ImGui::IsPopupOpen(m_fileActionActiveModal, ImGuiPopupFlags_AnyPopupLevel); - - // Finished all pending file operations - if (fileModalOpen && !m_openFile && !m_saveFile) { - auto &popupStack = ImGui::GetCurrentContext()->OpenPopupStack; - - for (size_t idx = 0; idx < popupStack.size(); ++idx) { - if (popupStack[idx].PopupId == m_fileActionActiveModal) { - ImGui::ClosePopupToLevel(idx, true); - break; - } - } + // User responded to the unsaved changes modal + if (m_unsavedFileModal && m_unsavedFileModal->Ready()) { + auto result = m_unsavedFileModal->Result(); - // Were there any pending operations waiting on a save dialog to close? - if (m_pendingFileReq == FileRequest_New) { + if (result == UnsavedFileModal::Result_Cancel) { m_pendingFileReq = FileRequest_None; - ActivatePickSystemDialog(); + } else if (result == UnsavedFileModal::Result_No) { + // User doesn't want to save, lose unsaved state + HandlePendingFileRequest(); + } else { + // Trigger the save-file flow + m_menuBinder->TriggerAction("File.Save"); } - if (m_pendingFileReq == FileRequest_Open) { - m_pendingFileReq = FileRequest_None; - ActivateOpenDialog(); - } + m_unsavedFileModal.Reset(); + } + + // Finished with all OS file dialogs, can close the file action modal + if (m_fileActionModal && !m_openFile && !m_saveFile) { + m_fileActionModal->Close(); + m_fileActionModal.Reset(); + } +} + +void SystemEditor::HandlePendingFileRequest() +{ + if (m_pendingFileReq == FileRequest_New) { + ClearSystem(); + ActivateNewSystemDialog(); + } + + if (m_pendingFileReq == FileRequest_Open) { + ClearSystem(); + ActivateOpenDialog(); + } + + if (m_pendingFileReq == FileRequest_Quit) { + ClearSystem(); + RequestEndLifecycle(); } + + m_pendingFileReq = FileRequest_None; } void SystemEditor::HandleBodyOperations() @@ -681,6 +766,11 @@ void SystemEditor::DrawInterface() ImGui::DockSpace(dockspaceID); + // Needs to be inside host-context window + m_menuBinder->Update(); + + ImGui::End(); + // BUG: Right-click on button can break undo handling if it happens after active InputText is submitted // We work around it by rendering the viewport first m_viewport->Update(m_app->DeltaTime()); @@ -744,12 +834,6 @@ void SystemEditor::DrawInterface() if (m_debugWindowOpen) ImGui::ShowDebugLogWindow(&m_debugWindowOpen); - ImGui::End(); - - DrawFileActionModal(); - - DrawPickSystemModal(); - if (isFirstRun) isFirstRun = false; } @@ -777,6 +861,10 @@ void SystemEditor::DrawMenuBar() ImGui::EndMenu(); } + if (HasUnsavedChanges()) { + ImGui::Text("*"); + } + ImGui::EndMenuBar(); } @@ -942,124 +1030,3 @@ void SystemEditor::DrawBodyContextMenu(SystemBody *body) ImGui::EndPopup(); } } - -void SystemEditor::DrawPickSystemModal() -{ - ImVec2 windSize = ImVec2(ImGui::GetMainViewport()->Size.x * 0.5, -1); - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); - ImGui::SetNextWindowSizeConstraints(windSize, windSize); - - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); - bool open = true; - if (ImGui::BeginPopupModal(PICK_SYSTEM_MODAL_ID, &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); - - if (Draw::LayoutHorizontal("Sector", 3, ImGui::GetFontSize())) { - bool changed = false; - changed |= ImGui::InputInt("X", &m_pickSystemPath.sectorX, 0, 0); - changed |= ImGui::InputInt("Y", &m_pickSystemPath.sectorY, 0, 0); - changed |= ImGui::InputInt("Z", &m_pickSystemPath.sectorZ, 0, 0); - - if (changed) - m_pickSystemPath.systemIndex = 0; - - Draw::EndLayout(); - } - - ImGui::Separator(); - - RefCountedPtr sec = m_galaxy->GetSector(m_pickSystemPath.SectorOnly()); - - ImGui::BeginGroup(); - if (ImGui::BeginChild("Systems", ImVec2(ImGui::GetContentRegionAvail().x * 0.33, -ImGui::GetFrameHeightWithSpacing()))) { - - for (const Sector::System &system : sec->m_systems) { - std::string label = fmt::format("{} ({}x{})", system.GetName(), EICON_SUN, system.GetNumStars()); - - if (ImGui::Selectable(label.c_str(), system.idx == m_pickSystemPath.systemIndex)) - m_pickSystemPath.systemIndex = system.idx; - } - - } - ImGui::EndChild(); - - if (ImGui::Button("New System")) { - NewSystem(m_pickSystemPath.SectorOnly()); - ImGui::CloseCurrentPopup(); - } - - ImGui::EndGroup(); - - ImGui::SameLine(); - ImGui::BeginGroup(); - - if (m_pickSystemPath.systemIndex < sec->m_systems.size()) { - const Sector::System &system = sec->m_systems[m_pickSystemPath.systemIndex]; - - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); - - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted(system.GetName().c_str()); - - ImGui::SameLine(); - ImGui::SameLine(0.f, ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeight()); - if (ImGui::Button(EICON_FORWARD1)) { - - // Load a fully-defined custom system from the custom system def - // NOTE: we cannot (currently) determine which file this custom system originated from - if (system.GetCustomSystem() && !system.GetCustomSystem()->IsRandom()) - LoadCustomSystem(system.GetCustomSystem()); - else - LoadSystemFromGalaxy(m_galaxy->GetStarSystem(m_pickSystemPath)); - - ImGui::CloseCurrentPopup(); - } - - ImGui::PopFont(); - - ImGui::Spacing(); - - ImGui::TextUnformatted("Is Custom:"); - ImGui::SameLine(ImGui::CalcItemWidth()); - ImGui::TextUnformatted(system.GetCustomSystem() ? "yes" : "no"); - - ImGui::TextUnformatted("Is Explored:"); - ImGui::SameLine(ImGui::CalcItemWidth()); - ImGui::TextUnformatted(system.GetExplored() == StarSystem::eEXPLORED_AT_START ? "yes" : "no"); - - ImGui::TextUnformatted("Faction:"); - ImGui::SameLine(ImGui::CalcItemWidth()); - ImGui::TextUnformatted(system.GetFaction() ? system.GetFaction()->name.c_str() : ""); - - ImGui::TextUnformatted("Other Names:"); - ImGui::SameLine(ImGui::CalcItemWidth()); - - ImGui::BeginGroup(); - for (auto &name : system.GetOtherNames()) - ImGui::TextUnformatted(name.c_str()); - ImGui::EndGroup(); - } - ImGui::EndGroup(); - - ImGui::PopFont(); - ImGui::EndPopup(); - } - - ImGui::PopFont(); -} - -void SystemEditor::DrawFileActionModal() -{ - ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5, ImGuiCond_Always, ImVec2(0.5, 0.5)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(30, 30)); - - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); - if (ImGui::BeginPopupModal(FILE_MODAL_ID, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 14)); - ImGui::TextUnformatted("Waiting on a file action to complete..."); - ImGui::PopFont(); - ImGui::EndPopup(); - } - ImGui::PopFont(); - ImGui::PopStyleVar(); -} diff --git a/src/editor/system/SystemEditor.h b/src/editor/system/SystemEditor.h index ab5898a8585..dec605cf97a 100644 --- a/src/editor/system/SystemEditor.h +++ b/src/editor/system/SystemEditor.h @@ -3,6 +3,8 @@ #pragma once +#include "SystemEditorModals.h" + #include "Input.h" #include "Random.h" #include "RefCounted.h" @@ -47,6 +49,7 @@ class SystemEditor : public Application::Lifecycle { ~SystemEditor(); void NewSystem(SystemPath path); + bool LoadSystem(SystemPath path); bool LoadSystemFromDisk(const std::string &absolutePath); // Write the currently edited system out to disk as a JSON file @@ -68,12 +71,23 @@ class SystemEditor : public Application::Lifecycle { void HandleInput(); private: - void RegisterMenuActions(); - - void ClearSystem(); - bool LoadSystem(const FileSystem::FileInfo &file); + bool LoadSystemFromFile(const FileSystem::FileInfo &file); bool LoadCustomSystem(const CustomSystem *system); void LoadSystemFromGalaxy(RefCountedPtr system); + void ClearSystem(); + + void RegisterMenuActions(); + + bool HasUnsavedChanges(); + void SaveCurrentFile(); + void OnSaveComplete(bool success); + + void ActivateOpenDialog(); + void ActivateSaveDialog(); + void ActivateNewSystemDialog(); + + void HandlePendingFileRequest(); + void HandleBodyOperations(); void SetupLayout(ImGuiID dockspaceID); void DrawInterface(); @@ -91,25 +105,17 @@ class SystemEditor : public Application::Lifecycle { void DrawUndoDebug(); - void ActivateOpenDialog(); - void ActivateSaveDialog(); - void ActivatePickSystemDialog(); - - void DrawFileActionModal(); - void DrawPickSystemModal(); - - void HandleBodyOperations(); - UndoSystem *GetUndo() { return m_undo.get(); } private: class UndoSetSelection; - // Pending actions which require a "save/as" interrupt + // Pending file actions which triggered an unsaved changes modal enum FileRequestType { FileRequest_None, FileRequest_Open, - FileRequest_New + FileRequest_New, + FileRequest_Quit }; // Pending actions to the body tree hierarchy that should @@ -161,10 +167,11 @@ class SystemEditor : public Application::Lifecycle { SystemPath m_openSystemPath; - ImGuiID m_fileActionActiveModal; - ImGuiID m_pickSystemModal; + RefCountedPtr m_fileActionModal; + RefCountedPtr m_unsavedFileModal; + RefCountedPtr m_newSystemModal; - SystemPath m_pickSystemPath; + SystemPath m_newSystemPath; std::unique_ptr m_menuBinder; diff --git a/src/editor/system/SystemEditorModals.cpp b/src/editor/system/SystemEditorModals.cpp new file mode 100644 index 00000000000..dad3b18dc09 --- /dev/null +++ b/src/editor/system/SystemEditorModals.cpp @@ -0,0 +1,175 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#include "SystemEditorModals.h" +#include "SystemEditor.h" + +#include "editor/EditorApp.h" +#include "editor/EditorDraw.h" +#include "editor/EditorIcons.h" +#include "editor/Modal.h" + +#include "galaxy/Galaxy.h" +#include "galaxy/Sector.h" +#include "galaxy/SystemPath.h" +#include "pigui/PiGui.h" + +#include "imgui/imgui.h" +#include "imgui/imgui_internal.h" + +using namespace Editor; + +FileActionOpenModal::FileActionOpenModal(EditorApp *app) : + Modal(app, "File Window Open", false) +{ +} + +void FileActionOpenModal::Draw() +{ + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(30, 30)); + + Modal::Draw(); + + ImGui::PopStyleVar(); +} + +void FileActionOpenModal::DrawInternal() +{ + ImGui::TextUnformatted("Waiting on a file action to complete..."); +} + +// ============================================================================= + +UnsavedFileModal::UnsavedFileModal(EditorApp *app) : + Modal(app, "Unsaved Changes", true) +{ +} + +void UnsavedFileModal::DrawInternal() +{ + ImGui::TextUnformatted("The current file has unsaved changes."); + ImGui::Spacing(); + ImGui::TextUnformatted("Do you want to save the current file before proceeding?"); + + ImGui::NewLine(); + + float width = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) * 0.5f; + + if (ImGui::Button("No", ImVec2(width, 0))) { + m_result = Result_No; + ImGui::CloseCurrentPopup(); + } + + ImGui::SameLine(); + + if (ImGui::Button("Yes", ImVec2(width, 0))) { + m_result = Result_Yes; + ImGui::CloseCurrentPopup(); + } +} + +// ============================================================================= + +NewSystemModal::NewSystemModal(EditorApp *app, SystemEditor *editor, SystemPath *path) : + Modal(app, "New System from Galaxy", true), + m_editor(editor), + m_path(path) +{ +} + +void NewSystemModal::Draw() +{ + ImVec2 windSize = ImVec2(ImGui::GetMainViewport()->Size.x * 0.5, -1); + ImGui::SetNextWindowSizeConstraints(windSize, windSize); + + Modal::Draw(); +} + +void NewSystemModal::DrawInternal() +{ + if (Draw::LayoutHorizontal("Sector", 3, ImGui::GetFontSize())) { + bool changed = false; + changed |= ImGui::InputInt("X", &m_path->sectorX, 0, 0); + changed |= ImGui::InputInt("Y", &m_path->sectorY, 0, 0); + changed |= ImGui::InputInt("Z", &m_path->sectorZ, 0, 0); + + if (changed) + m_path->systemIndex = 0; + + Draw::EndLayout(); + } + + ImGui::Separator(); + + RefCountedPtr sec = m_editor->GetGalaxy()->GetSector(m_path->SectorOnly()); + + ImGui::BeginGroup(); + if (ImGui::BeginChild("Systems", ImVec2(ImGui::GetContentRegionAvail().x * 0.33, -ImGui::GetFrameHeightWithSpacing()))) { + + for (const Sector::System &system : sec->m_systems) { + std::string label = fmt::format("{} ({}x{})", system.GetName(), EICON_SUN, system.GetNumStars()); + + if (ImGui::Selectable(label.c_str(), system.idx == m_path->systemIndex)) + m_path->systemIndex = system.idx; + + if (ImGui::IsItemClicked() && ImGui::IsMouseDoubleClicked(0)) { + m_editor->LoadSystem(system.GetPath()); + ImGui::CloseCurrentPopup(); + } + + } + + } + ImGui::EndChild(); + + if (ImGui::Button("New System")) { + m_editor->NewSystem(m_path->SectorOnly()); + ImGui::CloseCurrentPopup(); + } + + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + + if (m_path->systemIndex < sec->m_systems.size()) { + const Sector::System &system = sec->m_systems[m_path->systemIndex]; + + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted(system.GetName().c_str()); + + ImGui::SameLine(); + ImGui::SameLine(0.f, ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeight()); + if (ImGui::Button(EICON_FORWARD1)) { + m_editor->LoadSystem(m_path->SystemOnly()); + ImGui::CloseCurrentPopup(); + } + + ImGui::PopFont(); + + ImGui::Spacing(); + + ImGui::TextUnformatted("Is Custom:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetCustomSystem() ? "yes" : "no"); + + ImGui::TextUnformatted("Is Explored:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetExplored() == StarSystem::eEXPLORED_AT_START ? "yes" : "no"); + + ImGui::TextUnformatted("Faction:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + ImGui::TextUnformatted(system.GetFaction() ? system.GetFaction()->name.c_str() : ""); + + ImGui::TextUnformatted("Other Names:"); + ImGui::SameLine(ImGui::CalcItemWidth()); + + ImGui::BeginGroup(); + for (auto &name : system.GetOtherNames()) + ImGui::TextUnformatted(name.c_str()); + ImGui::EndGroup(); + } + ImGui::EndGroup(); +} diff --git a/src/editor/system/SystemEditorModals.h b/src/editor/system/SystemEditorModals.h new file mode 100644 index 00000000000..507a6eb5921 --- /dev/null +++ b/src/editor/system/SystemEditorModals.h @@ -0,0 +1,55 @@ +// Copyright © 2008-2023 Pioneer Developers. See AUTHORS.txt for details +// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt + +#pragma once + +#include "editor/Modal.h" + +using ImGuiID = unsigned int; + +class SystemPath; + +namespace Editor { + + class EditorApp; + class SystemEditor; + + class FileActionOpenModal : public Modal { + public: + FileActionOpenModal(EditorApp *app); + + protected: + void Draw() override; + void DrawInternal() override; + }; + + class UnsavedFileModal : public Modal { + public: + UnsavedFileModal(EditorApp *app); + + enum ResultType { + Result_Cancel, + Result_No, + Result_Yes + }; + + ResultType Result() { return m_result; } + + protected: + void DrawInternal() override; + ResultType m_result = Result_Cancel; + }; + + class NewSystemModal : public Modal { + public: + NewSystemModal(EditorApp *app, SystemEditor *editor, SystemPath *path); + + void Draw() override; + + protected: + void DrawInternal() override; + + SystemEditor *m_editor; + SystemPath *m_path; + }; +} From 12600f6c6c7863a742bf730e0c698bd26024e0d1 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 00:14:10 -0400 Subject: [PATCH 38/50] SystemEditor: viewport body context menu - Refactor viewport overlay rendering - Allows using IsItemHovered() et. al. on body labels --- src/editor/ViewportWindow.cpp | 35 ++++++++++-------- src/editor/system/SystemEditor.cpp | 2 +- src/editor/system/SystemEditorViewport.cpp | 41 +++++++++++++++++----- src/editor/system/SystemEditorViewport.h | 2 +- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/editor/ViewportWindow.cpp b/src/editor/ViewportWindow.cpp index db515e77120..60c29d86352 100644 --- a/src/editor/ViewportWindow.cpp +++ b/src/editor/ViewportWindow.cpp @@ -89,20 +89,6 @@ void ViewportWindow::Update(float deltaTime) r->ResolveRenderTarget(m_renderTarget.get(), m_resolveTarget.get(), m_viewportExtents); } - ImGui::BeginChild("##ViewportContainer", ImVec2(0, 0), false, - ImGuiWindowFlags_NoBackground | - ImGuiWindowFlags_NoScrollbar | - ImGuiWindowFlags_NoScrollWithMouse | - ImGuiWindowFlags_AlwaysUseWindowPadding); - - // set Horizontal layout type since we're using this window effectively as a toolbar - ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Horizontal; - - // Draw all "viewport overlay" UI here, to properly route inputs - OnDraw(); - - ImGui::EndChild(); - ImGuiID viewportID = ImGui::GetID("##ViewportOverlay"); ImGui::KeepAliveID(viewportID); @@ -111,6 +97,8 @@ void ViewportWindow::Update(float deltaTime) ImGuiButtonFlags flags = ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_PressedOnClick | + ImGuiButtonFlags_AllowOverlap | + ImGuiButtonFlags_NoSetKeyOwner | ImGuiButtonFlags_MouseButtonMask_; ImRect area = { screenPos, screenPos + size }; @@ -119,6 +107,9 @@ void ViewportWindow::Update(float deltaTime) bool clicked = ImGui::ButtonBehavior(area, viewportID, &m_viewportHovered, &m_viewportActive, flags); + if (m_viewportActive) + ImGui::GetCurrentContext()->ActiveIdAllowOverlap = true; + // if the viewport is hovered/active or we just released it, // update mouse interactions with it if (m_viewportHovered || m_viewportActive || wasPressed) { @@ -132,6 +123,22 @@ void ViewportWindow::Update(float deltaTime) OnHandleInput(clicked, wasPressed && !m_viewportActive, mousePos); } + ImGui::BeginChild("##ViewportContainer", ImVec2(0, 0), false, + ImGuiWindowFlags_NoBackground | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_AlwaysUseWindowPadding); + + // set Horizontal layout type since we're using this window effectively as a toolbar + ImGui::GetCurrentWindow()->DC.LayoutType = ImGuiLayoutType_Horizontal; + + // Draw all "viewport overlay" UI here, to properly route inputs + OnDraw(); + + ImGui::EndChild(); + + if (m_viewportActive) + ImGui::GetCurrentContext()->ActiveIdAllowOverlap = false; } } diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index a0a2bbe4d5a..f1d0a8aec19 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -1020,7 +1020,7 @@ void SystemEditor::DrawSystemProperties() void SystemEditor::DrawBodyContextMenu(SystemBody *body) { if (ImGui::BeginPopupContextItem()) { - ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 16)); + ImGui::PushFont(m_app->GetPiGui()->GetFont("pionillium", 15)); m_contextBody = body; m_menuBinder->DrawGroup("Edit.Body"); diff --git a/src/editor/system/SystemEditorViewport.cpp b/src/editor/system/SystemEditorViewport.cpp index dfac1ad2087..8a94c49327e 100644 --- a/src/editor/system/SystemEditorViewport.cpp +++ b/src/editor/system/SystemEditorViewport.cpp @@ -125,18 +125,22 @@ void SystemEditorViewport::OnDraw() fmt::format("{} ({})", item.ref.sbody->GetName(), group.tracks.size()) : item.ref.sbody->GetName(); - if (DrawIcon(itempos, icon_col, GetBodyIcon(item.ref.sbody), label.c_str())) { - ImGui::SetTooltip("%s", label.c_str()); + bool clicked = DrawIcon(ImGui::GetID(item.ref.sbody), itempos, icon_col, GetBodyIcon(item.ref.sbody), label.c_str()); - if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { - m_map->SetSelectedObject(item); - m_editor->SetSelectedBody(const_cast(item.ref.sbody)); - } + if (clicked) { + m_map->SetSelectedObject(item); + m_editor->SetSelectedBody(const_cast(item.ref.sbody)); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + if (ImGui::IsMouseDoubleClicked(0)) { m_map->ViewSelectedObject(); } } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", label.c_str()); + + m_editor->DrawBodyContextMenu(const_cast(item.ref.sbody)); + } #if 0 // TODO: visual edit gizmos for body axes @@ -201,7 +205,7 @@ void SystemEditorViewport::DrawTimelineControls() ImGui::Text("%s", format_date(m_map->GetTime()).c_str()); } -bool SystemEditorViewport::DrawIcon(const ImVec2 &icon_pos, const ImColor &color, const char *icon, const char *label) +bool SystemEditorViewport::DrawIcon(ImGuiID id, const ImVec2 &icon_pos, const ImColor &color, const char *icon, const char *label) { ImVec2 icon_size = ImVec2(ImGui::GetFontSize(), ImGui::GetFontSize()); ImVec2 draw_pos = ImGui::GetWindowPos() + icon_pos - icon_size * 0.5f; @@ -223,5 +227,24 @@ bool SystemEditorViewport::DrawIcon(const ImVec2 &icon_pos, const ImColor &color hover_rect.Add(text_pos + text_size); } - return ImGui::ItemHoverable(hover_rect, 0, 0); + ImGuiID prevHovered = ImGui::GetHoveredID(); + + ImGuiButtonFlags flags = + ImGuiButtonFlags_MouseButtonLeft | + ImGuiButtonFlags_PressedOnClickRelease | + ImGuiButtonFlags_PressedOnDoubleClick; + + ImGui::ItemAdd(hover_rect, id); + + // Allow interaction with this label + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(hover_rect, id, &hovered, &held, flags); + + // Reset hovered state so viewport ButtonBehavior receives middle-mouse clicks + // (otherwise this label "steals" hovered state and blocks all interaction) + // NOTE: this works with practically any button-like widget, not just ButtonBehavior + if (ImGui::IsItemHovered()) + ImGui::SetHoveredID(prevHovered); + + return pressed; } diff --git a/src/editor/system/SystemEditorViewport.h b/src/editor/system/SystemEditorViewport.h index acfcb948f40..32351c133b7 100644 --- a/src/editor/system/SystemEditorViewport.h +++ b/src/editor/system/SystemEditorViewport.h @@ -39,7 +39,7 @@ namespace Editor { private: void DrawTimelineControls(); - bool DrawIcon(const ImVec2 &iconPos, const ImColor &color, const char *icon, const char *label = nullptr); + bool DrawIcon(ImGuiID id, const ImVec2 &iconPos, const ImColor &color, const char *icon, const char *label = nullptr); EditorApp *m_app; SystemEditor *m_editor; From 50514b88f33e03460cf9ae1613e4066557a32c1a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 00:46:00 -0400 Subject: [PATCH 39/50] Editor: compute hash of undo state for saved check --- src/editor/UndoSystem.cpp | 16 ++++++++++++++++ src/editor/UndoSystem.h | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/src/editor/UndoSystem.cpp b/src/editor/UndoSystem.cpp index aef12fa3e0a..02720c51103 100644 --- a/src/editor/UndoSystem.cpp +++ b/src/editor/UndoSystem.cpp @@ -4,6 +4,9 @@ #include "UndoSystem.h" #include "utils.h" +#define XXH_INLINE_ALL +#include "lz4/xxhash.h" + #include using namespace Editor; @@ -33,6 +36,7 @@ bool UndoEntry::HasChanged() const UndoSystem::UndoSystem() : m_openUndoDepth(0), + m_entryId(0), m_doing(false) { } @@ -64,6 +68,15 @@ const UndoEntry *UndoSystem::GetEntry(size_t stackIdx) const return nullptr; } +size_t UndoSystem::GetStateHash() +{ + size_t hash = "UndoState"_hash; + for (auto &entry_ptr : m_undoStack) + hash = XXH64(&entry_ptr->m_id, sizeof(size_t), hash); + + return hash; +} + void UndoSystem::Undo() { if (!CanUndo()) @@ -102,6 +115,8 @@ void UndoSystem::Clear() m_openUndoEntry.reset(); m_openUndoDepth = 0; + m_entryId = 0; + m_redoStack.clear(); m_undoStack.clear(); } @@ -116,6 +131,7 @@ void UndoSystem::BeginEntry(std::string_view name) m_openUndoEntry.reset(new UndoEntry()); m_openUndoEntry->m_name = name; + m_openUndoEntry->m_id = ++m_entryId; } void UndoSystem::ResetEntry() diff --git a/src/editor/UndoSystem.h b/src/editor/UndoSystem.h index 752aabb4177..7ec54198ecc 100644 --- a/src/editor/UndoSystem.h +++ b/src/editor/UndoSystem.h @@ -69,6 +69,7 @@ class UndoEntry { bool HasChanged() const; StringName m_name; + size_t m_id; std::vector> m_steps; }; @@ -123,6 +124,9 @@ class UndoSystem { // Return a pointer to the given undo entry if stackIdx < GetNumEntries(). const UndoEntry *GetEntry(size_t stackIdx) const; + // Calculate a hash value used to describe the current undo state + size_t GetStateHash(); + bool CanUndo() const { return !m_undoStack.empty(); } bool CanRedo() const { return !m_redoStack.empty(); } @@ -162,6 +166,7 @@ class UndoSystem { std::unique_ptr m_openUndoEntry; size_t m_openUndoDepth; + size_t m_entryId; bool m_doing; }; From c7f9610f89582cd72e0c743cf83fd33fa4f96766 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 00:47:17 -0400 Subject: [PATCH 40/50] SystemEditor: fix unsaved file handling with undo - Populate Pi::luaNameGen to avoid crashes when generating random systems - Undo state uses undo state hash for consistency --- src/editor/system/SystemEditor.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index f1d0a8aec19..d37a1c156d8 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -13,6 +13,7 @@ #include "FileSystem.h" #include "JsonUtils.h" #include "ModManager.h" +#include "Pi.h" // just here for Pi::luaNameGen #include "SystemView.h" #include "core/StringUtils.h" @@ -105,6 +106,8 @@ SystemEditor::SystemEditor(EditorApp *app) : m_nameGen.reset(new LuaNameGen(Lua::manager)); + Pi::luaNameGen = m_nameGen.get(); + m_galaxy = GalaxyGenerator::Create(); m_systemLoader.reset(new CustomSystemsDatabase(m_galaxy.Get(), "systems")); @@ -186,6 +189,9 @@ bool SystemEditor::LoadSystem(SystemPath path) m_filepath = ""; m_filedir = ""; + // mark as unsaved + m_lastSavedUndoStack = size_t(-1); + return true; } @@ -332,7 +338,7 @@ void SystemEditor::LoadSystemFromGalaxy(RefCountedPtr system) void SystemEditor::ClearSystem() { GetUndo()->Clear(); - m_lastSavedUndoStack = 0; + m_lastSavedUndoStack = GetUndo()->GetStateHash(); m_system.Reset(); m_systemInfo = {}; @@ -456,8 +462,7 @@ void SystemEditor::RegisterMenuActions() bool SystemEditor::HasUnsavedChanges() { - size_t undoStateHash = (m_undo->GetNumEntries() << 32) | m_undo->GetCurrentEntry(); - return (m_system && undoStateHash != m_lastSavedUndoStack); + return (m_system && GetUndo()->GetStateHash() != m_lastSavedUndoStack); } void SystemEditor::SaveCurrentFile() @@ -478,8 +483,7 @@ void SystemEditor::OnSaveComplete(bool success) return; } - // "Simple" hash of undo state - m_lastSavedUndoStack = (m_undo->GetNumEntries() << 32) | m_undo->GetCurrentEntry(); + m_lastSavedUndoStack = GetUndo()->GetStateHash(); if (m_pendingFileReq != FileRequest_None) { HandlePendingFileRequest(); From 42d7a9da78d35e0a5824fdbcb5875066dd17cb86 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 01:24:17 -0400 Subject: [PATCH 41/50] SystemView: don't render starports --- src/SystemView.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SystemView.cpp b/src/SystemView.cpp index e438b37986e..395ad92f156 100644 --- a/src/SystemView.cpp +++ b/src/SystemView.cpp @@ -509,6 +509,9 @@ void SystemMapViewport::AddBodyTrack(const SystemBody *b, const vector3d &offset void SystemMapViewport::RenderBody(const SystemBody *b, const vector3d &position, const matrix4x4f &trans) { + if (b->GetSuperType() == SystemBody::SUPERTYPE_STARPORT) + return; + const double radius = b->GetRadius(); matrix4x4f invRot = trans; From 7b3ecd4420325316a97d5e53eaf58fb22a1ddcdc Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Fri, 8 Sep 2023 01:38:25 -0400 Subject: [PATCH 42/50] SystemEditor: minor fixes --- src/editor/system/GalaxyEditAPI.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 0c52f853d31..32c70eb94c7 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -406,7 +406,15 @@ void SystemBody::EditorAPI::GenerateDefaultName(SystemBody *body) } // Other bodies get a "hierarchy" name - size_t idx = GetIndexInParent(body); + + size_t idx = 0; + for (SystemBody *child : parent->m_children) { + if (child == body) + break; + if (child->GetSuperType() != SUPERTYPE_STARPORT) + idx++; + } + if (parent->GetSuperType() <= SystemBody::SUPERTYPE_STAR) { if (idx <= 26) body->m_name = fmt::format("{} {}", parent->GetName(), char('a' + idx)); @@ -617,7 +625,7 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSy if (body->GetSuperType() < SUPERTYPE_STARPORT) { - if (!isStar && ImGui::Button(EICON_RANDOM " Body Stats")) { + if ((!isStar || body->GetType() == TYPE_BROWN_DWARF) && ImGui::Button(EICON_RANDOM " Body Stats")) { GenerateDerivedStats(body, rng, undo); bodyChanged = true; } From 2d080b15df4b4e77c2821e2f4b2ca3c9e713c7e6 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Wed, 13 Sep 2023 01:44:57 -0400 Subject: [PATCH 43/50] Undefine windows macros --- src/editor/system/SystemEditor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index d37a1c156d8..b346bfdbc1a 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -36,6 +36,10 @@ #include "imgui/imgui.h" #include "portable-file-dialogs/pfd.h" +// PFD pulls in windows headers sadly +#undef RegisterClass +#undef min +#undef max #include #include From 7cd5207bd12b5dc3906f0a53d648e1ff0a5b8d7c Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Thu, 14 Sep 2023 17:47:58 -0400 Subject: [PATCH 44/50] Renderer: adaptive vsync, toggle at runtime - Enable adaptive/swap-tear VSync so long updates don't immediately drop framerate to 20/30FPS - Allow the user to toggle VSync on/off at runtime without restart --- src/graphics/Renderer.h | 2 ++ src/graphics/dummy/RendererDummy.h | 2 ++ src/graphics/opengl/RendererGL.cpp | 7 ++++++- src/graphics/opengl/RendererGL.h | 2 ++ src/lua/LuaEngine.cpp | 4 ++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/graphics/Renderer.h b/src/graphics/Renderer.h index 90b5ae62531..5dbdecec142 100644 --- a/src/graphics/Renderer.h +++ b/src/graphics/Renderer.h @@ -68,6 +68,8 @@ namespace Graphics { int GetWindowHeight() const { return m_height; } virtual int GetMaximumNumberAASamples() const = 0; + virtual void SetVSyncEnabled(bool enabled) = 0; + //get supported minimum for z near and maximum for z far values virtual bool GetNearFarRange(float &near_, float &far_) const = 0; diff --git a/src/graphics/dummy/RendererDummy.h b/src/graphics/dummy/RendererDummy.h index 6886e77f7fd..ad57eef9893 100644 --- a/src/graphics/dummy/RendererDummy.h +++ b/src/graphics/dummy/RendererDummy.h @@ -34,6 +34,8 @@ namespace Graphics { virtual int GetMaximumNumberAASamples() const override final { return 0; } virtual bool GetNearFarRange(float &near_, float &far_) const override final { return true; } + virtual void SetVSyncEnabled(bool) override {} + virtual bool BeginFrame() override final { return true; } virtual bool EndFrame() override final { return true; } virtual bool SwapBuffers() override final { return true; } diff --git a/src/graphics/opengl/RendererGL.cpp b/src/graphics/opengl/RendererGL.cpp index 21561856c3e..8cc805e087d 100644 --- a/src/graphics/opengl/RendererGL.cpp +++ b/src/graphics/opengl/RendererGL.cpp @@ -126,7 +126,7 @@ namespace Graphics { SDL_SetWindowTitle(window, vs.title); SDL_ShowCursor(0); - SDL_GL_SetSwapInterval((vs.vsync != 0) ? 1 : 0); + SDL_GL_SetSwapInterval((vs.vsync != 0) ? -1 : 0); return new RendererOGL(window, vs, glContext); } @@ -465,6 +465,11 @@ namespace Graphics { return true; } + void RendererOGL::SetVSyncEnabled(bool enabled) + { + SDL_GL_SetSwapInterval(enabled ? -1 : 0); + } + bool RendererOGL::BeginFrame() { PROFILE_SCOPED() diff --git a/src/graphics/opengl/RendererGL.h b/src/graphics/opengl/RendererGL.h index 3991ae44a36..3942eada3f7 100644 --- a/src/graphics/opengl/RendererGL.h +++ b/src/graphics/opengl/RendererGL.h @@ -56,6 +56,8 @@ namespace Graphics { virtual int GetMaximumNumberAASamples() const override final; virtual bool GetNearFarRange(float &near_, float &far_) const override final; + virtual void SetVSyncEnabled(bool) override; + virtual bool BeginFrame() override final; virtual bool EndFrame() override final; virtual bool SwapBuffers() override final; diff --git a/src/lua/LuaEngine.cpp b/src/lua/LuaEngine.cpp index a48a62ff8b3..f905f4193c0 100644 --- a/src/lua/LuaEngine.cpp +++ b/src/lua/LuaEngine.cpp @@ -20,6 +20,7 @@ #include "Pi.h" #include "Player.h" #include "Random.h" +#include "SDL_video.h" #include "WorldView.h" #include "buildopts.h" #include "core/OS.h" @@ -419,9 +420,12 @@ static int l_engine_set_vsync_enabled(lua_State *l) { if (lua_isnone(l, 1)) return luaL_error(l, "SetVSyncEnabled takes one boolean argument"); + const bool vsync = lua_toboolean(l, 1); Pi::config->SetInt("VSync", (vsync ? 1 : 0)); Pi::config->Save(); + + Pi::renderer->SetVSyncEnabled(vsync); return 0; } From 6e8cbd9069763e4aec0e01024a081efaeb287b7f Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sun, 8 Oct 2023 13:32:50 -0400 Subject: [PATCH 45/50] Restore position of Cydonia - Cydonia depends on a consistent seed value due to automatic starport repositioning --- data/systems/custom/00_sol.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/systems/custom/00_sol.json b/data/systems/custom/00_sol.json index 51362402ea5..c0b88b1c34f 100644 --- a/data/systems/custom/00_sol.json +++ b/data/systems/custom/00_sol.json @@ -687,7 +687,7 @@ "radius": "f0/0", "rotationPeriod": "f0/0", "rotationPhase": "f0/0", - "seed": 4126130448, + "seed": 201299135, "semiMajorAxis": "f0/0", "type": "STARPORT_SURFACE", "volatileIces": "f0/0", @@ -723,7 +723,7 @@ "radius": "f0/0", "rotationPeriod": "f0/0", "rotationPhase": "f0/0", - "seed": 1148906070, + "seed": 2874781459, "semiMajorAxis": "f0/0", "type": "STARPORT_SURFACE", "volatileIces": "f0/0", @@ -759,7 +759,7 @@ "radius": "f0/0", "rotationPeriod": "f0/0", "rotationPhase": "f0/0", - "seed": 1064378724, + "seed": 3046926584, "semiMajorAxis": "f0/0", "type": "STARPORT_SURFACE", "volatileIces": "f0/0", @@ -2759,4 +2759,4 @@ "stars": [ "STAR_G" ] -} \ No newline at end of file +} From a2603a4a4265f98f2ccac749d79ba978c5f26ece Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 14 Oct 2023 19:45:43 -0400 Subject: [PATCH 46/50] SystemEditor: improve new system modal - Better interaction for creating new system / loading existing from Galaxy - Set minimum vertical size for new system modal - Fix crash when sorting bodies without system loaded - Don't clear the current system unless replacing it with a new/loaded system --- src/editor/system/SystemEditor.cpp | 37 +++++++++++++----------- src/editor/system/SystemEditorModals.cpp | 30 +++++++++++-------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/editor/system/SystemEditor.cpp b/src/editor/system/SystemEditor.cpp index b346bfdbc1a..04943ef6669 100644 --- a/src/editor/system/SystemEditor.cpp +++ b/src/editor/system/SystemEditor.cpp @@ -380,12 +380,28 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->AddAction("New", { "New System", ImGuiKey_N | ImGuiKey_ModCtrl, - sigc::mem_fun(this, &SystemEditor::ActivateNewSystemDialog) + [&]() { + if (HasUnsavedChanges()) { + m_unsavedFileModal = m_app->PushModal(); + m_pendingFileReq = FileRequest_New; + return; + } + + ActivateNewSystemDialog(); + } }); m_menuBinder->AddAction("Open", { "Open File", ImGuiKey_O | ImGuiKey_ModCtrl, - sigc::mem_fun(this, &SystemEditor::ActivateOpenDialog) + [&]() { + if (HasUnsavedChanges()) { + m_unsavedFileModal = m_app->PushModal(); + m_pendingFileReq = FileRequest_Open; + return; + } + + ActivateOpenDialog(); + } }); m_menuBinder->AddAction("Save", { @@ -457,7 +473,8 @@ void SystemEditor::RegisterMenuActions() m_menuBinder->EndGroup(); m_menuBinder->AddAction("Sort", { - "Sort Bodies", {}, + "Sort Bodies", ImGuiKey_None, + [&]() { return m_system.Valid(); }, [&]() { m_pendingOp.type = BodyRequest::TYPE_Resort; } }); @@ -496,12 +513,6 @@ void SystemEditor::OnSaveComplete(bool success) void SystemEditor::ActivateOpenDialog() { - if (HasUnsavedChanges()) { - m_unsavedFileModal = m_app->PushModal(); - m_pendingFileReq = FileRequest_Open; - return; - } - // FIXME: need to handle loading files outside of game data dir m_openFile.reset(new pfd::open_file( "Open Custom System File", @@ -531,12 +542,6 @@ void SystemEditor::ActivateSaveDialog() void SystemEditor::ActivateNewSystemDialog() { - if (HasUnsavedChanges()) { - m_unsavedFileModal = m_app->PushModal(); - m_pendingFileReq = FileRequest_New; - return; - } - m_newSystemModal = m_app->PushModal(this, &m_newSystemPath); } @@ -615,12 +620,10 @@ void SystemEditor::Update(float deltaTime) void SystemEditor::HandlePendingFileRequest() { if (m_pendingFileReq == FileRequest_New) { - ClearSystem(); ActivateNewSystemDialog(); } if (m_pendingFileReq == FileRequest_Open) { - ClearSystem(); ActivateOpenDialog(); } diff --git a/src/editor/system/SystemEditorModals.cpp b/src/editor/system/SystemEditorModals.cpp index dad3b18dc09..1e76a119a60 100644 --- a/src/editor/system/SystemEditorModals.cpp +++ b/src/editor/system/SystemEditorModals.cpp @@ -79,8 +79,10 @@ NewSystemModal::NewSystemModal(EditorApp *app, SystemEditor *editor, SystemPath void NewSystemModal::Draw() { - ImVec2 windSize = ImVec2(ImGui::GetMainViewport()->Size.x * 0.5, -1); - ImGui::SetNextWindowSizeConstraints(windSize, windSize); + ImVec2 vpSize = ImGui::GetMainViewport()->Size; + ImVec2 windSize = ImVec2(vpSize.x * 0.5, vpSize.y * 0.333); + ImVec2 windSizeMax = ImVec2(vpSize.x * 0.5, vpSize.y * 0.5); + ImGui::SetNextWindowSizeConstraints(windSize, windSizeMax); Modal::Draw(); } @@ -89,9 +91,9 @@ void NewSystemModal::DrawInternal() { if (Draw::LayoutHorizontal("Sector", 3, ImGui::GetFontSize())) { bool changed = false; - changed |= ImGui::InputInt("X", &m_path->sectorX, 0, 0); - changed |= ImGui::InputInt("Y", &m_path->sectorY, 0, 0); - changed |= ImGui::InputInt("Z", &m_path->sectorZ, 0, 0); + changed |= ImGui::InputInt("X", &m_path->sectorX, 1, 0); + changed |= ImGui::InputInt("Y", &m_path->sectorY, 1, 0); + changed |= ImGui::InputInt("Z", &m_path->sectorZ, 1, 0); if (changed) m_path->systemIndex = 0; @@ -127,6 +129,17 @@ void NewSystemModal::DrawInternal() ImGui::CloseCurrentPopup(); } + ImGui::SetItemTooltip("Create a new empty system in this sector."); + + ImGui::SameLine(); + + if (ImGui::Button("Edit Selected")) { + m_editor->LoadSystem(m_path->SystemOnly()); + ImGui::CloseCurrentPopup(); + } + + ImGui::SetItemTooltip("Load the selected system as a template."); + ImGui::EndGroup(); ImGui::SameLine(); @@ -140,13 +153,6 @@ void NewSystemModal::DrawInternal() ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted(system.GetName().c_str()); - ImGui::SameLine(); - ImGui::SameLine(0.f, ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeight()); - if (ImGui::Button(EICON_FORWARD1)) { - m_editor->LoadSystem(m_path->SystemOnly()); - ImGui::CloseCurrentPopup(); - } - ImGui::PopFont(); ImGui::Spacing(); From 3cad5e46fc3a6e4198bf0af761e35b728a192d51 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 14 Oct 2023 19:53:04 -0400 Subject: [PATCH 47/50] ActionBinder: add font icon constructors --- src/editor/ActionBinder.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/editor/ActionBinder.h b/src/editor/ActionBinder.h index 19bdc1146da..d61ac020775 100644 --- a/src/editor/ActionBinder.h +++ b/src/editor/ActionBinder.h @@ -32,6 +32,23 @@ namespace Editor { action(f) {} + template + ActionEntry(std::string_view label, const char *icon, ImGuiKeyChord shortcut, Functor f) : + label(label), + fontIcon(icon), + shortcut(shortcut), + action(f) + {} + + template + ActionEntry(std::string_view label, const char *icon, ImGuiKeyChord shortcut, Predicate p, Functor f) : + label(label), + fontIcon(icon), + shortcut(shortcut), + predicate(p), + action(f) + {} + std::string label; const char *fontIcon; ImGuiKeyChord shortcut; From 55afc98625071aa983a9ba3b3dfec1f763320a38 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 14 Oct 2023 20:13:01 -0400 Subject: [PATCH 48/50] SystemEditor: station type / heightmap properties --- src/editor/system/GalaxyEditAPI.cpp | 20 ++++++++++++++++++++ src/galaxy/SystemBody.cpp | 6 ++++++ src/terrain/Terrain.cpp | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/editor/system/GalaxyEditAPI.cpp b/src/editor/system/GalaxyEditAPI.cpp index 32c70eb94c7..7577a2b2671 100644 --- a/src/editor/system/GalaxyEditAPI.cpp +++ b/src/editor/system/GalaxyEditAPI.cpp @@ -584,6 +584,14 @@ void SystemBody::EditorAPI::EditStarportProperties(SystemBody *body, UndoSystem } EditEconomicProperties(body, undo); + + ImGui::SeparatorText("Misc. Properties"); + + ImGui::InputText("Model Name", &body->m_spaceStationType); + if (Draw::UndoHelper("Edit Station Model Name", undo)) + AddUndoSingleValue(undo, &body->m_spaceStationType); + + Draw::HelpMarker("Model name (without extension) to use for this starport.\nA random model is chosen if not specified."); } void SystemBody::EditorAPI::EditBodyName(SystemBody *body, Random &rng, LuaNameGen *nameGen, UndoSystem *undo) @@ -743,6 +751,18 @@ void SystemBody::EditorAPI::EditProperties(SystemBody *body, Random &rng, UndoSy if (Draw::UndoHelper("Edit Life", undo)) AddUndoSingleValue(undo, &body->m_life); + ImGui::InputText("HMap Path", &body->m_heightMapFilename); + if (Draw::UndoHelper("Edit Heightmap Path", undo)) + AddUndoSingleValue(undo, &body->m_heightMapFilename); + + Draw::HelpMarker("Path to a custom heightmap file for this body, relative to the game's data directory."); + + ImGui::SliderInt("HMap Fractal", reinterpret_cast(&body->m_heightMapFractal), 0, 1, "%d", ImGuiSliderFlags_AlwaysClamp); + if (Draw::UndoHelper("Edit Heightmap Fractal", undo)) + AddUndoSingleValue(undo, &body->m_heightMapFractal); + + Draw::HelpMarker("Fractal type index for use with a custom heightmap file."); + if (Draw::DerivedValues("Surface Parameters")) { ImGui::BeginDisabled(); diff --git a/src/galaxy/SystemBody.cpp b/src/galaxy/SystemBody.cpp index 3b74a27596c..c856f294d69 100644 --- a/src/galaxy/SystemBody.cpp +++ b/src/galaxy/SystemBody.cpp @@ -117,10 +117,16 @@ void SystemBodyData::LoadFromJson(const Json &obj) m_rings.baseColor = obj.value("ringsBaseColor", {}); } + // NOTE: the following parameters should be replaced with entries + // in a PropertyMap owned by the system body m_spaceStationType = obj.value("spaceStationType", ""); + // HACK: this is to support the current / legacy heightmap fractal system + // Should be replaced with PropertyMap entries and validation moved to Terrain.cpp m_heightMapFilename = obj.value("heightMapFilename", ""); m_heightMapFractal = obj.value("heightMapFractal", 0); + + m_heightMapFractal = std::min(m_heightMapFractal, uint32_t(1)); } SystemBody::SystemBody(const SystemPath &path, StarSystem *system) : diff --git a/src/terrain/Terrain.cpp b/src/terrain/Terrain.cpp index 4647e5048d5..79f7afec74e 100644 --- a/src/terrain/Terrain.cpp +++ b/src/terrain/Terrain.cpp @@ -18,7 +18,7 @@ Terrain *Terrain::InstanceTerrain(const SystemBody *body) // special case for heightmaps // XXX this is terrible but will do for now until we get a unified // heightmap setup. if you add another height fractal, remember to change - // the check in CustomSystem::l_height_map + // the check in CustomSystem::l_height_map / SystemBodyData::LoadFromJson if (!body->GetHeightMapFilename().empty()) { const GeneratorInstancer choices[] = { InstanceGenerator, From 8e189bd7eaf7a305f27cfd7de38e1350e77596b6 Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 14 Oct 2023 21:42:22 -0400 Subject: [PATCH 49/50] SystemGen: improve random starport generation - Generate surface ports at random locations rather than in one great line - Perturb orbital stations to make placement seem more random/built up over time rather than a line of stations sharing the exact same orbit - Significantly reduce the worst-case number of starports generated for a body (on average, 1 starport per 0.6bil population) --- src/galaxy/StarSystemGenerator.cpp | 64 +++++++++++++++++------------- src/galaxy/StarSystemGenerator.h | 2 +- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/galaxy/StarSystemGenerator.cpp b/src/galaxy/StarSystemGenerator.cpp index 37379a40cc6..38d6fcd6fcd 100644 --- a/src/galaxy/StarSystemGenerator.cpp +++ b/src/galaxy/StarSystemGenerator.cpp @@ -1389,34 +1389,34 @@ bool StarSystemRandomGenerator::Apply(Random &rng, RefCountedPtr galaxy, * Position a surface starport anywhere. Space.cpp::MakeFrameFor() ensures it * is on dry land (discarding this position if necessary) */ -void PopulateStarSystemGenerator::PositionSettlementOnPlanet(SystemBody *sbody, std::vector &prevOrbits) +void PopulateStarSystemGenerator::PositionSettlementOnPlanet(SystemBody *sbody, std::vector &prevOrbits) { PROFILE_SCOPED() Random r(sbody->GetSeed()); // used for orientation on planet surface - double r2 = r.Double(); // function parameter evaluation order is implementation-dependent - double r1 = r.Double(); // can't put two rands in the same expression + + fixed longitude = r.Fixed(); // function parameter evaluation order is implementation-dependent + fixed latitude = r.NormFixed(); // can't put two rands in the same expression // try to ensure that stations are far enough apart for (size_t i = 0, iterations = 0; i < prevOrbits.size() && iterations < 128; i++, iterations++) { - const double &orev = prevOrbits[i]; - const double len = fabs(r1 - orev); - if (len < 0.05) { - r2 = r.Double(); - r1 = r.Double(); + const fixed &prev = prevOrbits[i]; + const fixed len = (latitude - prev).Abs(); + if (len < fixed(5, 100)) { + longitude = r.Fixed(); + latitude = r.NormFixed(); i = 0; // reset to start the checking from beginning as we're generating new values. } } - prevOrbits.push_back(r1); - - // pset the orbit - sbody->m_orbit.SetPlane(matrix3x3d::RotateZ(2 * M_PI * r1) * matrix3x3d::RotateY(2 * M_PI * r2)); + prevOrbits.push_back(latitude.ToDouble()); // store latitude and longitude to equivalent orbital parameters to // be accessible easier - sbody->m_inclination = fixed(r1 * 10000, 10000) + FIXED_PI / 2; // latitude - sbody->m_orbitalOffset = FIXED_PI / 2; // longitude + sbody->m_inclination = latitude * FIXED_PI * fixed(1, 2); + sbody->m_orbitalOffset = longitude * FIXED_PI * 2; + + sbody->SetOrbitFromParameters(); } /* @@ -1618,6 +1618,10 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys RefCountedPtr namerand(new Random); namerand->seed(_init, 6); + // XXX: station placement code is in need of improvement; the code currently fails to appear "intelligent" + // with respect to well-spaced orbital shells (e.g. a "station belt" around a high-population planet) + // as well as generating station placement for e.g. research, industrial, or communications stations + if (sbody->GetPopulationAsFixed() < fixed(1, 1000)) return; fixed orbMaxS = fixed(1, 4) * fixed(CalcHillRadius(sbody)); fixed orbMinS = fixed().FromDouble((sbody->CalcAtmosphereParams().atmosRadius + +500000.0 / EARTH_RADIUS)) * AU_EARTH_RADIUS; @@ -1627,13 +1631,16 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys // starports - orbital fixed pop = sbody->GetPopulationAsFixed() + rand.Fixed(); if (orbMinS < orbMaxS) { + // How many stations do we need? pop -= rand.Fixed(); Uint32 NumToMake = 0; while (pop >= 0) { ++NumToMake; - pop -= rand.Fixed(); + pop -= fixed(1, 1) - rand.NormFixed().Abs(); } + + // Always generate a station around a populated high-gravity world if ((NumToMake == 0) and (sbody->CalcSurfaceGravity() > 10.5)) { // 10.5 m/s2 = 1,07 g NumToMake = 1; } @@ -1692,20 +1699,21 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys sp->m_mass = 0; // place stations between min and max orbits to reduce the number of extremely close/fast orbits - sp->m_semiMajorAxis = currOrbit; - sp->m_eccentricity = fixed(); + // This avoids the worst-case of having 3-5 stations all in the exact orbit close to each other + sp->m_semiMajorAxis = currOrbit * rand.NormFixed(fixed(12, 10), fixed(2, 10)); + + // Generate slightly random orbits for each station in the orbital "shell" + // slightly random min/max orbital distance + sp->m_eccentricity = rand.NormFixed().Abs() * fixed(1, 8); + // perturb the orbital plane to avoid all stations falling in line with each other + sp->m_inclination = rand.NormFixed() * fixed(1, 4) * FIXED_PI; + // station spacing around the primary body + sp->m_argOfPeriapsis = rand.Fixed() * FIXED_PI * 2; + // TODO: no axial tilt for stations / axial tilt in general is strangely modeled sp->m_axialTilt = fixed(); - sp->m_orbit.SetShapeAroundPrimary(sp->GetSemiMajorAxis() * AU, centralMass, 0.0); + sp->SetOrbitFromParameters(); - // The rotations around X & Y perturb the orbits just a little bit so that not all stations are exactly within the same plane - // The Z rotation is what gives them the separation in their orbit around the parent body as a whole. - sp->m_orbit.SetPlane( - matrix3x3d::RotateX(rand.Double(M_PI * 0.03125)) * - matrix3x3d::RotateY(rand.Double(M_PI * 0.03125)) * - matrix3x3d::RotateZ(orbitSlt * orbitSeparation)); - - sp->m_inclination = fixed(); sbody->m_children.insert(sbody->m_children.begin(), sp); system->AddSpaceStation(sp); sp->m_orbMin = sp->GetSemiMajorAxisAsFixed(); @@ -1718,11 +1726,11 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys // starports - surface // give it a fighting chance of having a decent number of starports (*3) pop = sbody->GetPopulationAsFixed() + (rand.Fixed() * 3); - std::vector previousOrbits; + std::vector previousOrbits; previousOrbits.reserve(8); int max = 6; while (max-- > 0) { - pop -= rand.Fixed(); + pop -= (fixed(1, 1) - rand.NormFixed()); if (pop < 0) break; SystemBody *sp = system->NewBody(); diff --git a/src/galaxy/StarSystemGenerator.h b/src/galaxy/StarSystemGenerator.h index 676e3f190bf..61eea9cc173 100644 --- a/src/galaxy/StarSystemGenerator.h +++ b/src/galaxy/StarSystemGenerator.h @@ -73,7 +73,7 @@ class PopulateStarSystemGenerator : public StarSystemLegacyGeneratorBase { void SetEconType(RefCountedPtr system); void PopulateAddStations(SystemBody *sbody, StarSystem::GeneratorAPI *system); - void PositionSettlementOnPlanet(SystemBody *sbody, std::vector &prevOrbits); + void PositionSettlementOnPlanet(SystemBody *sbody, std::vector &prevOrbits); void PopulateStage1(SystemBody *sbody, StarSystem::GeneratorAPI *system, fixed &outTotalPop); }; From 309eecc62355c3f0f43aba335840a458f569038a Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Sat, 14 Oct 2023 21:55:22 -0400 Subject: [PATCH 50/50] SystemGen: orbital shell sets station inclination --- src/galaxy/StarSystemGenerator.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/galaxy/StarSystemGenerator.cpp b/src/galaxy/StarSystemGenerator.cpp index 38d6fcd6fcd..22ac93fbff3 100644 --- a/src/galaxy/StarSystemGenerator.cpp +++ b/src/galaxy/StarSystemGenerator.cpp @@ -1668,13 +1668,21 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys // I like to think that we'd fill several "shells" of orbits at once rather than fill one and move out further static const Uint32 MAX_ORBIT_SHELLS = 3; fixed shells[MAX_ORBIT_SHELLS]; + fixed shellIncl[MAX_ORBIT_SHELLS]; + if (innerOrbit != orbMaxS) { shells[0] = innerOrbit; // low shells[1] = innerOrbit + ((orbMaxS - innerOrbit) * fixed(1, 2)); // med shells[2] = orbMaxS; // high + + shellIncl[0] = rand.NormFixed() * FIXED_PI; + shellIncl[1] = rand.NormFixed() * FIXED_PI; + shellIncl[2] = rand.NormFixed() * FIXED_PI; } else { shells[0] = shells[1] = shells[2] = innerOrbit; + shellIncl[0] = shellIncl[1] = shellIncl[2] = rand.NormFixed() * FIXED_PI; } + Uint32 orbitIdx = 0; double orbitSlt = 0.0; const double orbitSeparation = (NumToMake > 1) ? ((M_PI * 2.0) / double(NumToMake - 1)) : M_PI; @@ -1682,6 +1690,7 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys for (Uint32 i = 0; i < NumToMake; i++) { // Pick the orbit we've currently placing a station into. const fixed currOrbit = shells[orbitIdx]; + const fixed currOrbitIncl = shells[orbitIdx]; ++orbitIdx; if (orbitIdx >= MAX_ORBIT_SHELLS) // wrap it { @@ -1706,7 +1715,7 @@ void PopulateStarSystemGenerator::PopulateAddStations(SystemBody *sbody, StarSys // slightly random min/max orbital distance sp->m_eccentricity = rand.NormFixed().Abs() * fixed(1, 8); // perturb the orbital plane to avoid all stations falling in line with each other - sp->m_inclination = rand.NormFixed() * fixed(1, 4) * FIXED_PI; + sp->m_inclination = currOrbitIncl + rand.NormFixed() * fixed(1, 4) * FIXED_PI; // station spacing around the primary body sp->m_argOfPeriapsis = rand.Fixed() * FIXED_PI * 2; // TODO: no axial tilt for stations / axial tilt in general is strangely modeled