Skip to content

Commit

Permalink
Merge pull request #5625 from Web-eWorks/csys-editor
Browse files Browse the repository at this point in the history
Implement a graphical custom system editor
  • Loading branch information
Webster Sheets authored Oct 17, 2023
2 parents 1c3ddff + 309eecc commit 5c6a6d5
Show file tree
Hide file tree
Showing 41 changed files with 4,163 additions and 180 deletions.
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

0 comments on commit 5c6a6d5

Please sign in to comment.