Step 342-343: PanelManager + Icons + KeybindingRegistry (24/24 tests)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
92
editor/src/Icons.h
Normal file
92
editor/src/Icons.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
// Step 343: Icon system — Unicode symbols for panels, diagnostics, workflow, AST nodes
|
||||
// No external icon font dependency — pure Unicode + text fallbacks
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace WhetstoneIcons {
|
||||
|
||||
// File types
|
||||
constexpr const char* FileSource = "\xF0\x9F\x93\x84"; // document
|
||||
constexpr const char* FileHeader = "\xF0\x9F\x93\x83"; // page with curl
|
||||
constexpr const char* FileConfig = "\xE2\x9A\x99"; // gear
|
||||
constexpr const char* Folder = "\xF0\x9F\x93\x81"; // folder
|
||||
constexpr const char* FolderOpen = "\xF0\x9F\x93\x82"; // open folder
|
||||
|
||||
// Diagnostics
|
||||
constexpr const char* DiagError = "\xE2\x9B\x94"; // no entry
|
||||
constexpr const char* DiagWarning = "\xE2\x9A\xA0"; // warning
|
||||
constexpr const char* DiagInfo = "\xE2\x84\xB9"; // info
|
||||
constexpr const char* DiagHint = "\xF0\x9F\x92\xA1"; // light bulb
|
||||
|
||||
// Workflow status
|
||||
constexpr const char* Pending = "\xE2\x8F\xB3"; // hourglass
|
||||
constexpr const char* InProgress = "\xE2\x96\xB6"; // play
|
||||
constexpr const char* Complete = "\xE2\x9C\x85"; // check mark
|
||||
constexpr const char* Rejected = "\xE2\x9D\x8C"; // cross mark
|
||||
constexpr const char* Review = "\xF0\x9F\x91\x81"; // eye
|
||||
|
||||
// AST nodes
|
||||
constexpr const char* NodeFunction = "\xC6\x92"; // function ƒ
|
||||
constexpr const char* NodeClass = "\xE2\x97\x86"; // diamond
|
||||
constexpr const char* NodeVariable = "\xF0\x9D\x91\xA5"; // math x
|
||||
constexpr const char* NodeModule = "\xE2\x96\xA3"; // square
|
||||
constexpr const char* NodeAnnotation = "\xF0\x9F\x8F\xB7"; // label
|
||||
|
||||
// Navigation
|
||||
constexpr const char* ArrowRight = "\xE2\x96\xB6";
|
||||
constexpr const char* ArrowDown = "\xE2\x96\xBC";
|
||||
constexpr const char* Search = "\xF0\x9F\x94\x8D";
|
||||
constexpr const char* Settings = "\xE2\x9A\x99";
|
||||
|
||||
// Text fallbacks (ASCII-safe)
|
||||
inline std::string fallback(const std::string& icon) {
|
||||
static std::map<std::string, std::string> fallbacks = {
|
||||
{DiagError, "[ERR]"}, {DiagWarning, "[WARN]"},
|
||||
{DiagInfo, "[INFO]"}, {DiagHint, "[HINT]"},
|
||||
{Pending, "[...]"}, {InProgress, "[>>]"},
|
||||
{Complete, "[OK]"}, {Rejected, "[X]"}, {Review, "[?]"},
|
||||
{NodeFunction, "fn"}, {NodeClass, "cls"},
|
||||
{NodeVariable, "var"}, {NodeModule, "mod"},
|
||||
{FileSource, "[]"}, {Folder, "/"}, {FolderOpen, "/"},
|
||||
};
|
||||
auto it = fallbacks.find(icon);
|
||||
return it != fallbacks.end() ? it->second : icon;
|
||||
}
|
||||
|
||||
// Get icon for AST node type
|
||||
inline std::string iconForNodeType(const std::string& conceptType) {
|
||||
if (conceptType == "Function" || conceptType == "AsyncFunction" ||
|
||||
conceptType == "MethodDeclaration") return NodeFunction;
|
||||
if (conceptType == "ClassDeclaration" || conceptType == "InterfaceDeclaration") return NodeClass;
|
||||
if (conceptType == "Variable") return NodeVariable;
|
||||
if (conceptType == "Module") return NodeModule;
|
||||
if (conceptType.find("Annotation") != std::string::npos) return NodeAnnotation;
|
||||
if (conceptType == "EnumDeclaration") return NodeClass;
|
||||
if (conceptType == "NamespaceDeclaration") return Folder;
|
||||
return NodeModule; // default
|
||||
}
|
||||
|
||||
// Get icon for diagnostic severity
|
||||
inline std::string iconForSeverity(int severity) {
|
||||
switch (severity) {
|
||||
case 1: return DiagError;
|
||||
case 2: return DiagWarning;
|
||||
case 3: return DiagInfo;
|
||||
case 4: return DiagHint;
|
||||
default: return DiagInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// Get icon for workflow status
|
||||
inline std::string iconForWorkflowStatus(const std::string& status) {
|
||||
if (status == "pending" || status == "ready") return Pending;
|
||||
if (status == "assigned" || status == "in-progress") return InProgress;
|
||||
if (status == "complete") return Complete;
|
||||
if (status == "rejected") return Rejected;
|
||||
if (status == "review") return Review;
|
||||
return Pending;
|
||||
}
|
||||
|
||||
} // namespace WhetstoneIcons
|
||||
222
editor/src/KeybindingRegistry.h
Normal file
222
editor/src/KeybindingRegistry.h
Normal file
@@ -0,0 +1,222 @@
|
||||
#pragma once
|
||||
// Step 343-351: Keybinding registry — centralized action→key combo mapping
|
||||
// Headless data model: no ImGui dependency, supports symbol rendering
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
enum class KeyModifier : uint8_t {
|
||||
None = 0,
|
||||
Ctrl = 1 << 0,
|
||||
Alt = 1 << 1,
|
||||
Shift = 1 << 2,
|
||||
Super = 1 << 3
|
||||
};
|
||||
|
||||
inline uint8_t operator|(KeyModifier a, KeyModifier b) {
|
||||
return static_cast<uint8_t>(a) | static_cast<uint8_t>(b);
|
||||
}
|
||||
|
||||
inline bool hasModifier(uint8_t mods, KeyModifier m) {
|
||||
return (mods & static_cast<uint8_t>(m)) != 0;
|
||||
}
|
||||
|
||||
struct KeyCombo {
|
||||
uint8_t modifiers = 0;
|
||||
std::string key; // "S", "P", "Z", "F5", "F8", "Enter", "Escape", etc.
|
||||
|
||||
bool empty() const { return key.empty(); }
|
||||
|
||||
std::string toString() const {
|
||||
std::string result;
|
||||
if (hasModifier(modifiers, KeyModifier::Ctrl)) result += "Ctrl+";
|
||||
if (hasModifier(modifiers, KeyModifier::Alt)) result += "Alt+";
|
||||
if (hasModifier(modifiers, KeyModifier::Shift)) result += "Shift+";
|
||||
if (hasModifier(modifiers, KeyModifier::Super)) result += "Super+";
|
||||
result += key;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Platform-aware symbols (macOS style vs Linux/Windows)
|
||||
std::string toSymbols(bool macOS = false) const {
|
||||
std::string result;
|
||||
if (macOS) {
|
||||
if (hasModifier(modifiers, KeyModifier::Ctrl)) result += "\xE2\x8C\x83"; // ⌃
|
||||
if (hasModifier(modifiers, KeyModifier::Alt)) result += "\xE2\x8C\xA5"; // ⌥
|
||||
if (hasModifier(modifiers, KeyModifier::Shift)) result += "\xE2\x87\xA7"; // ⇧
|
||||
if (hasModifier(modifiers, KeyModifier::Super)) result += "\xE2\x8C\x98"; // ⌘
|
||||
result += key;
|
||||
} else {
|
||||
result = toString(); // Linux/Windows uses text form
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool operator==(const KeyCombo& other) const {
|
||||
return modifiers == other.modifiers && key == other.key;
|
||||
}
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {{"modifiers", modifiers}, {"key", key}};
|
||||
}
|
||||
|
||||
static KeyCombo fromJson(const nlohmann::json& j) {
|
||||
KeyCombo c;
|
||||
c.modifiers = j.value("modifiers", 0);
|
||||
c.key = j.value("key", "");
|
||||
return c;
|
||||
}
|
||||
|
||||
// Helper constructors
|
||||
static KeyCombo ctrl(const std::string& k) {
|
||||
return {static_cast<uint8_t>(KeyModifier::Ctrl), k};
|
||||
}
|
||||
static KeyCombo ctrlShift(const std::string& k) {
|
||||
return {KeyModifier::Ctrl | KeyModifier::Shift, k};
|
||||
}
|
||||
static KeyCombo alt(const std::string& k) {
|
||||
return {static_cast<uint8_t>(KeyModifier::Alt), k};
|
||||
}
|
||||
static KeyCombo fkey(const std::string& k) {
|
||||
return {0, k};
|
||||
}
|
||||
static KeyCombo shiftFkey(const std::string& k) {
|
||||
return {static_cast<uint8_t>(KeyModifier::Shift), k};
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyAction {
|
||||
std::string id; // "save-buffer", "toggle-ast-panel", etc.
|
||||
std::string label; // "Save Buffer", "Toggle AST Panel"
|
||||
std::string category; // "File", "Edit", "View", "Tools", "Workflow"
|
||||
std::string description; // optional longer description
|
||||
};
|
||||
|
||||
class KeybindingRegistry {
|
||||
public:
|
||||
KeybindingRegistry() = default;
|
||||
|
||||
void registerAction(const std::string& id, const std::string& label,
|
||||
const std::string& category, const std::string& description = "") {
|
||||
actions_[id] = {id, label, category, description};
|
||||
}
|
||||
|
||||
void bind(const std::string& actionId, const KeyCombo& combo) {
|
||||
bindings_[actionId] = combo;
|
||||
}
|
||||
|
||||
KeyCombo getBinding(const std::string& actionId) const {
|
||||
auto it = bindings_.find(actionId);
|
||||
return it != bindings_.end() ? it->second : KeyCombo{};
|
||||
}
|
||||
|
||||
std::string getSymbols(const std::string& actionId, bool macOS = false) const {
|
||||
auto combo = getBinding(actionId);
|
||||
return combo.empty() ? "" : combo.toSymbols(macOS);
|
||||
}
|
||||
|
||||
std::optional<std::string> findConflict(const std::string& actionId, const KeyCombo& combo) const {
|
||||
for (const auto& [aid, c] : bindings_) {
|
||||
if (aid != actionId && c == combo) return aid;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<KeyAction> getActions() const {
|
||||
std::vector<KeyAction> result;
|
||||
for (const auto& [id, action] : actions_) result.push_back(action);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<KeyAction> getActionsByCategory(const std::string& category) const {
|
||||
std::vector<KeyAction> result;
|
||||
for (const auto& [id, action] : actions_) {
|
||||
if (action.category == category) result.push_back(action);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::map<std::string, KeyCombo> getAllBindings() const { return bindings_; }
|
||||
|
||||
int actionCount() const { return static_cast<int>(actions_.size()); }
|
||||
int bindingCount() const { return static_cast<int>(bindings_.size()); }
|
||||
|
||||
void loadDefaults() {
|
||||
// File actions
|
||||
registerAction("save-buffer", "Save Buffer", "File");
|
||||
registerAction("save-all", "Save All", "File");
|
||||
registerAction("open-file", "Open File", "File");
|
||||
registerAction("close-buffer", "Close Buffer", "File");
|
||||
|
||||
// Edit actions
|
||||
registerAction("undo", "Undo", "Edit");
|
||||
registerAction("redo", "Redo", "Edit");
|
||||
registerAction("find-in-file", "Find in File", "Edit");
|
||||
registerAction("find-in-project", "Find in Project", "Edit");
|
||||
|
||||
// View actions
|
||||
registerAction("command-palette", "Command Palette", "View");
|
||||
registerAction("toggle-file-tree", "Toggle File Tree", "View");
|
||||
registerAction("toggle-ast-view", "Toggle AST View", "View");
|
||||
registerAction("toggle-diagnostics", "Toggle Diagnostics", "View");
|
||||
registerAction("toggle-annotations", "Toggle Annotations", "View");
|
||||
|
||||
// Tools actions
|
||||
registerAction("run-pipeline", "Run Pipeline", "Tools");
|
||||
registerAction("generate-code", "Generate Code", "Tools");
|
||||
registerAction("infer-annotations", "Infer Annotations", "Tools");
|
||||
registerAction("validate", "Validate", "Tools");
|
||||
|
||||
// Workflow actions
|
||||
registerAction("create-workflow", "Create Workflow", "Workflow");
|
||||
registerAction("route-all", "Route All Tasks", "Workflow");
|
||||
|
||||
// Navigation
|
||||
registerAction("next-diagnostic", "Next Diagnostic", "Navigation");
|
||||
registerAction("prev-diagnostic", "Previous Diagnostic", "Navigation");
|
||||
registerAction("go-to-definition", "Go to Definition", "Navigation");
|
||||
registerAction("go-to-symbol", "Go to Symbol", "Navigation");
|
||||
|
||||
// Default bindings
|
||||
bind("save-buffer", KeyCombo::ctrl("S"));
|
||||
bind("save-all", KeyCombo::ctrlShift("S"));
|
||||
bind("undo", KeyCombo::ctrl("Z"));
|
||||
bind("redo", KeyCombo::ctrlShift("Z"));
|
||||
bind("find-in-file", KeyCombo::ctrl("F"));
|
||||
bind("find-in-project", KeyCombo::ctrlShift("F"));
|
||||
bind("command-palette", KeyCombo::ctrl("P"));
|
||||
bind("toggle-file-tree", KeyCombo::ctrl("B"));
|
||||
bind("toggle-diagnostics", KeyCombo::ctrlShift("D"));
|
||||
bind("run-pipeline", KeyCombo::fkey("F5"));
|
||||
bind("next-diagnostic", KeyCombo::fkey("F8"));
|
||||
bind("prev-diagnostic", KeyCombo::shiftFkey("F8"));
|
||||
bind("go-to-definition", KeyCombo::fkey("F12"));
|
||||
bind("go-to-symbol", KeyCombo::ctrlShift("O"));
|
||||
}
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
nlohmann::json j;
|
||||
nlohmann::json bindingsJson;
|
||||
for (const auto& [id, combo] : bindings_) {
|
||||
bindingsJson[id] = combo.toJson();
|
||||
}
|
||||
j["bindings"] = bindingsJson;
|
||||
return j;
|
||||
}
|
||||
|
||||
void loadFromJson(const nlohmann::json& j) {
|
||||
if (j.contains("bindings")) {
|
||||
for (auto& [id, cj] : j["bindings"].items()) {
|
||||
bindings_[id] = KeyCombo::fromJson(cj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, KeyAction> actions_;
|
||||
std::map<std::string, KeyCombo> bindings_;
|
||||
};
|
||||
230
editor/src/PanelManager.h
Normal file
230
editor/src/PanelManager.h
Normal file
@@ -0,0 +1,230 @@
|
||||
#pragma once
|
||||
// Step 342-343: Panel registration system and docking layout management
|
||||
// Headless data model for panel state — no ImGui dependency
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
enum class DockPosition {
|
||||
Left,
|
||||
CenterTop,
|
||||
CenterBottom,
|
||||
Right,
|
||||
Bottom
|
||||
};
|
||||
|
||||
inline std::string dockPositionToString(DockPosition p) {
|
||||
switch (p) {
|
||||
case DockPosition::Left: return "left";
|
||||
case DockPosition::CenterTop: return "center-top";
|
||||
case DockPosition::CenterBottom: return "center-bottom";
|
||||
case DockPosition::Right: return "right";
|
||||
case DockPosition::Bottom: return "bottom";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
inline DockPosition dockPositionFromString(const std::string& s) {
|
||||
if (s == "left") return DockPosition::Left;
|
||||
if (s == "center-top") return DockPosition::CenterTop;
|
||||
if (s == "center-bottom") return DockPosition::CenterBottom;
|
||||
if (s == "right") return DockPosition::Right;
|
||||
if (s == "bottom") return DockPosition::Bottom;
|
||||
return DockPosition::CenterTop;
|
||||
}
|
||||
|
||||
struct PanelInfo {
|
||||
std::string id;
|
||||
std::string title;
|
||||
DockPosition defaultDock;
|
||||
DockPosition currentDock;
|
||||
bool isVisible = true;
|
||||
int order = 0;
|
||||
std::string icon; // Unicode icon character
|
||||
std::string category; // "navigation", "editor", "analysis", "output"
|
||||
float widthPercent = 0; // 0 = auto
|
||||
float heightPercent = 0; // 0 = auto
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {
|
||||
{"id", id}, {"title", title},
|
||||
{"defaultDock", dockPositionToString(defaultDock)},
|
||||
{"currentDock", dockPositionToString(currentDock)},
|
||||
{"isVisible", isVisible}, {"order", order},
|
||||
{"icon", icon}, {"category", category},
|
||||
{"widthPercent", widthPercent}, {"heightPercent", heightPercent}
|
||||
};
|
||||
}
|
||||
|
||||
static PanelInfo fromJson(const nlohmann::json& j) {
|
||||
PanelInfo p;
|
||||
p.id = j.value("id", "");
|
||||
p.title = j.value("title", "");
|
||||
p.defaultDock = dockPositionFromString(j.value("defaultDock", "center-top"));
|
||||
p.currentDock = dockPositionFromString(j.value("currentDock", j.value("defaultDock", "center-top")));
|
||||
p.isVisible = j.value("isVisible", true);
|
||||
p.order = j.value("order", 0);
|
||||
p.icon = j.value("icon", "");
|
||||
p.category = j.value("category", "");
|
||||
p.widthPercent = j.value("widthPercent", 0.0f);
|
||||
p.heightPercent = j.value("heightPercent", 0.0f);
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
struct DockingLayout {
|
||||
float leftWidth = 20.0f; // percent
|
||||
float rightWidth = 20.0f; // percent
|
||||
float bottomHeight = 25.0f; // percent
|
||||
float centerWidth = 60.0f; // auto-computed
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
return {
|
||||
{"leftWidth", leftWidth}, {"rightWidth", rightWidth},
|
||||
{"bottomHeight", bottomHeight}
|
||||
};
|
||||
}
|
||||
|
||||
static DockingLayout fromJson(const nlohmann::json& j) {
|
||||
DockingLayout l;
|
||||
l.leftWidth = j.value("leftWidth", 20.0f);
|
||||
l.rightWidth = j.value("rightWidth", 20.0f);
|
||||
l.bottomHeight = j.value("bottomHeight", 25.0f);
|
||||
l.centerWidth = 100.0f - l.leftWidth - l.rightWidth;
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
class PanelManager {
|
||||
public:
|
||||
PanelManager() = default;
|
||||
|
||||
void registerPanel(const std::string& id, const std::string& title,
|
||||
DockPosition dock, const std::string& icon = "",
|
||||
const std::string& category = "", int order = 0) {
|
||||
PanelInfo info;
|
||||
info.id = id;
|
||||
info.title = title;
|
||||
info.defaultDock = dock;
|
||||
info.currentDock = dock;
|
||||
info.icon = icon;
|
||||
info.category = category;
|
||||
info.order = order;
|
||||
panels_[id] = info;
|
||||
}
|
||||
|
||||
void setVisible(const std::string& id, bool visible) {
|
||||
auto it = panels_.find(id);
|
||||
if (it != panels_.end()) it->second.isVisible = visible;
|
||||
}
|
||||
|
||||
bool isVisible(const std::string& id) const {
|
||||
auto it = panels_.find(id);
|
||||
return it != panels_.end() && it->second.isVisible;
|
||||
}
|
||||
|
||||
void togglePanel(const std::string& id) {
|
||||
auto it = panels_.find(id);
|
||||
if (it != panels_.end()) it->second.isVisible = !it->second.isVisible;
|
||||
}
|
||||
|
||||
void focusPanel(const std::string& id) {
|
||||
focusedPanel_ = id;
|
||||
}
|
||||
|
||||
std::string getFocusedPanel() const { return focusedPanel_; }
|
||||
|
||||
bool hasPanel(const std::string& id) const {
|
||||
return panels_.count(id) > 0;
|
||||
}
|
||||
|
||||
PanelInfo getPanel(const std::string& id) const {
|
||||
auto it = panels_.find(id);
|
||||
if (it != panels_.end()) return it->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<PanelInfo> getPanelList() const {
|
||||
std::vector<PanelInfo> result;
|
||||
for (const auto& [id, info] : panels_) result.push_back(info);
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const PanelInfo& a, const PanelInfo& b) { return a.order < b.order; });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PanelInfo> getVisiblePanels() const {
|
||||
std::vector<PanelInfo> result;
|
||||
for (const auto& [id, info] : panels_) {
|
||||
if (info.isVisible) result.push_back(info);
|
||||
}
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const PanelInfo& a, const PanelInfo& b) { return a.order < b.order; });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PanelInfo> getPanelsByDock(DockPosition dock) const {
|
||||
std::vector<PanelInfo> result;
|
||||
for (const auto& [id, info] : panels_) {
|
||||
if (info.currentDock == dock && info.isVisible) result.push_back(info);
|
||||
}
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const PanelInfo& a, const PanelInfo& b) { return a.order < b.order; });
|
||||
return result;
|
||||
}
|
||||
|
||||
void movePanelToDock(const std::string& id, DockPosition dock) {
|
||||
auto it = panels_.find(id);
|
||||
if (it != panels_.end()) it->second.currentDock = dock;
|
||||
}
|
||||
|
||||
int panelCount() const { return static_cast<int>(panels_.size()); }
|
||||
|
||||
void registerDefaults() {
|
||||
registerPanel("file-tree", "File Tree", DockPosition::Left, "\xF0\x9F\x93\x81", "navigation", 0);
|
||||
registerPanel("code-editor", "Code Editor", DockPosition::CenterTop, "\xE2\x9C\x8F", "editor", 1);
|
||||
registerPanel("ast-view", "AST View", DockPosition::Right, "\xF0\x9F\x8C\xB3", "analysis", 2);
|
||||
registerPanel("annotations", "Annotations", DockPosition::Right, "\xF0\x9F\x8F\xB7", "analysis", 3);
|
||||
registerPanel("properties", "Properties", DockPosition::Right, "\xE2\x9A\x99", "analysis", 4);
|
||||
registerPanel("diagnostics", "Diagnostics", DockPosition::Bottom, "\xE2\x9A\xA0", "output", 5);
|
||||
registerPanel("output", "Output", DockPosition::Bottom, "\xF0\x9F\x93\x84", "output", 6);
|
||||
registerPanel("workflow", "Workflow", DockPosition::Bottom, "\xF0\x9F\x94\x84", "output", 7);
|
||||
}
|
||||
|
||||
// Layout management
|
||||
DockingLayout& layout() { return layout_; }
|
||||
const DockingLayout& layout() const { return layout_; }
|
||||
|
||||
nlohmann::json toJson() const {
|
||||
nlohmann::json j;
|
||||
j["layout"] = layout_.toJson();
|
||||
nlohmann::json panelsJson = nlohmann::json::array();
|
||||
for (const auto& [id, info] : panels_) {
|
||||
panelsJson.push_back(info.toJson());
|
||||
}
|
||||
j["panels"] = panelsJson;
|
||||
j["focusedPanel"] = focusedPanel_;
|
||||
return j;
|
||||
}
|
||||
|
||||
static PanelManager fromJson(const nlohmann::json& j) {
|
||||
PanelManager pm;
|
||||
if (j.contains("layout")) pm.layout_ = DockingLayout::fromJson(j["layout"]);
|
||||
if (j.contains("panels")) {
|
||||
for (const auto& pj : j["panels"]) {
|
||||
PanelInfo info = PanelInfo::fromJson(pj);
|
||||
pm.panels_[info.id] = info;
|
||||
}
|
||||
}
|
||||
pm.focusedPanel_ = j.value("focusedPanel", "");
|
||||
return pm;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, PanelInfo> panels_;
|
||||
DockingLayout layout_;
|
||||
std::string focusedPanel_;
|
||||
};
|
||||
Reference in New Issue
Block a user