Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System Editor #5625

Merged
merged 50 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
68b931c
Galaxy: add EditorAPI privileged classes
sturnclaw Aug 12, 2023
14af1da
EditorDraw: add additional utilities
sturnclaw Aug 12, 2023
40af1b8
Add visual editor for custom system definitions
sturnclaw Aug 12, 2023
1d7428e
Random system names, fix identifier leading digits
sturnclaw Aug 14, 2023
6f79d49
Add Editor::Draw::BeginHorizontalBar
sturnclaw Aug 16, 2023
4a7bf4d
Canonize the UndoStep::Swap() pattern
sturnclaw Aug 16, 2023
c76457c
SystemEditor: add/remove bodies, cleanup
sturnclaw Aug 16, 2023
b123d11
Add tree node drag-drop reordering helper func
sturnclaw Aug 18, 2023
8b7f43a
Add drag/drop and automatic body index updates
sturnclaw Aug 18, 2023
1aa1d9b
SystemEditor: add body icons
sturnclaw Aug 19, 2023
a6d9bbd
Editor: pass closure by value
sturnclaw Aug 22, 2023
8b55c7a
SystemEditor: Update body orbits on undo
sturnclaw Aug 22, 2023
f2294cd
SystemEditor: integrate SystemMapViewport
sturnclaw Aug 22, 2023
505edca
SystemEditor: More ergonomic body editing
sturnclaw Aug 29, 2023
d329d51
SystemEditor: add Arg. of Periapsis and tooltips
sturnclaw Aug 30, 2023
a4db85d
SystemEditor: add viewport time controls
sturnclaw Aug 30, 2023
f02b30c
SystemEditor: open/save file dialogs, context menu
sturnclaw Aug 30, 2023
c536f6e
SystemEditor: use SystemBody orbit generation
sturnclaw Aug 31, 2023
1a77c6d
SystemEditor: handle no active system
sturnclaw Aug 31, 2023
9bec08f
SystemEditor: fully functional load/save
sturnclaw Aug 31, 2023
82315aa
SystemEditor: display body mass in Pt
sturnclaw Sep 2, 2023
7ffedd8
SystemEditor: procedurally generate added bodies
sturnclaw Sep 2, 2023
ede3609
Remove all IMGUI_DEFINE_MATH_OPERATORS
sturnclaw Sep 2, 2023
3220947
SystemEditor: remove ExportToLua functions
sturnclaw Sep 2, 2023
34ce3e0
SystemEditor: add sort operation
sturnclaw Sep 3, 2023
b53d831
SystemEditor: generate default body names
sturnclaw Sep 4, 2023
c46381d
SystemEditor: add random body name generation
sturnclaw Sep 4, 2023
c93cc28
SystemEditor: always make body, fix default name
sturnclaw Sep 4, 2023
253d776
Editor: add support for editing contained values
sturnclaw Sep 6, 2023
f6df87c
SystemEditor: edit custom system properties
sturnclaw Sep 6, 2023
1e5a525
SystemEditor: update starport orbit when changed
sturnclaw Sep 7, 2023
dfbbaf5
Editor: add ActionBinder utility
sturnclaw Sep 7, 2023
51a792c
SystemEditor: add shortcuts, layout management
sturnclaw Sep 7, 2023
cc8900d
Editor: add "get out of jail" button to fix undo
sturnclaw Sep 7, 2023
460d98d
SystemEditor: load generated systems from galaxy
sturnclaw Sep 7, 2023
ab55e97
Editor: add Modal helper class
sturnclaw Sep 8, 2023
f2acfee
SystemEditor: separate modal code
sturnclaw Sep 8, 2023
12600f6
SystemEditor: viewport body context menu
sturnclaw Sep 8, 2023
50514b8
Editor: compute hash of undo state for saved check
sturnclaw Sep 8, 2023
c7f9610
SystemEditor: fix unsaved file handling with undo
sturnclaw Sep 8, 2023
42d7a9d
SystemView: don't render starports
sturnclaw Sep 8, 2023
7b3ecd4
SystemEditor: minor fixes
sturnclaw Sep 8, 2023
2d080b1
Undefine windows macros
sturnclaw Sep 13, 2023
7cd5207
Renderer: adaptive vsync, toggle at runtime
sturnclaw Sep 14, 2023
6e8cbd9
Restore position of Cydonia
sturnclaw Oct 8, 2023
a2603a4
SystemEditor: improve new system modal
sturnclaw Oct 14, 2023
3cad5e4
ActionBinder: add font icon constructors
sturnclaw Oct 14, 2023
55afc98
SystemEditor: station type / heightmap properties
sturnclaw Oct 15, 2023
8e189bd
SystemGen: improve random starport generation
sturnclaw Oct 15, 2023
309eecc
SystemGen: orbital shell sets station inclination
sturnclaw Oct 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions data/systems/custom/00_sol.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -2759,4 +2759,4 @@
"stars": [
"STAR_G"
]
}
}
3 changes: 3 additions & 0 deletions src/SystemView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
208 changes: 208 additions & 0 deletions src/editor/ActionBinder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// 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 <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include <algorithm>

using namespace Editor;

// wrap double-dereference when working with variant of pointer types
template<typename T, typename ...Types>
auto get_if(std::variant<Types...> &pv)
{
T *ptr = std::get_if<T>(&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()
{
// 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;

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<ActionEntry *>(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<Group *>(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<ActionEntry *>(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<Group *>(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();
}
135 changes: 135 additions & 0 deletions src/editor/ActionBinder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// 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 <sigc++/sigc++.h>

#include <map>
#include <string>
#include <variant>

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<typename Functor>
ActionEntry(std::string_view label, ImGuiKeyChord shortcut, Functor f) :
label(label),
shortcut(shortcut),
action(f)
{}

template<typename Predicate, typename Functor>
ActionEntry(std::string_view label, ImGuiKeyChord shortcut, Predicate p, Functor f) :
label(label),
shortcut(shortcut),
predicate(p),
action(f)
{}

template<typename Functor>
ActionEntry(std::string_view label, const char *icon, ImGuiKeyChord shortcut, Functor f) :
label(label),
fontIcon(icon),
shortcut(shortcut),
action(f)
{}

template<typename Predicate, typename Functor>
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;

sigc::slot<bool ()> predicate;
sigc::slot<void ()> action;
};

class ActionBinder {
public:
ActionBinder();
~ActionBinder();

struct Group;

using GroupEntry = std::variant<Group *, ActionEntry *>;
struct Group {
Group(std::string_view name, bool menu) :
label(std::string(name)),
isMenu(menu)
{}

std::string label;
std::vector<GroupEntry> 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<std::string> &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<std::string, ActionEntry> m_actionStorage;
std::map<std::string, Group> m_groupStorage;

std::vector<std::string> m_topLevelGroups;

std::vector<std::pair<std::string, Group *>> m_activeGroupStack;
};

}
1 change: 1 addition & 0 deletions src/editor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
list(APPEND EDITOR_SRC_FOLDERS
${CMAKE_CURRENT_SOURCE_DIR}
mfd/
system/
)

# Creates variables EDITOR_CXX_FILES and EDITOR_HXX_FILES
Expand Down
Loading
Loading