Add zeml, zelda3 editor markup language, for UI rendering and data manipulation
This commit is contained in:
@@ -53,6 +53,7 @@ set(
|
|||||||
app/gui/widgets.cc
|
app/gui/widgets.cc
|
||||||
app/gui/color.cc
|
app/gui/color.cc
|
||||||
app/gui/pipeline.cc
|
app/gui/pipeline.cc
|
||||||
|
app/gui/zeml.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
|
|||||||
297
src/app/gui/zeml.cc
Normal file
297
src/app/gui/zeml.cc
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
|
||||||
|
#include "app/gui/zeml.h"
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace gui {
|
||||||
|
namespace zeml {
|
||||||
|
|
||||||
|
std::vector<Token> Tokenize(const std::string& input) {
|
||||||
|
std::vector<Token> tokens;
|
||||||
|
std::istringstream stream(input);
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
while (stream.get(ch)) {
|
||||||
|
if (isspace(ch)) continue;
|
||||||
|
|
||||||
|
if (ch == '{') {
|
||||||
|
tokens.push_back({TokenType::OpenBrace, "{"});
|
||||||
|
} else if (ch == '}') {
|
||||||
|
tokens.push_back({TokenType::CloseBrace, "}"});
|
||||||
|
} else if (ch == ',') {
|
||||||
|
tokens.push_back({TokenType::Comma, ","});
|
||||||
|
} else if (std::isalnum(ch) || ch == '_') {
|
||||||
|
std::string ident(1, ch);
|
||||||
|
while (stream.get(ch) && (std::isalnum(ch) || ch == '_')) {
|
||||||
|
ident += ch;
|
||||||
|
}
|
||||||
|
stream.unget();
|
||||||
|
tokens.push_back({TokenType::Identifier, ident});
|
||||||
|
} else if (ch == '"' || ch == '\'') {
|
||||||
|
std::string str;
|
||||||
|
char quoteType = ch;
|
||||||
|
while (stream.get(ch) && ch != quoteType) {
|
||||||
|
str += ch;
|
||||||
|
}
|
||||||
|
tokens.push_back({TokenType::String, str});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push_back({TokenType::EndOfStream, ""});
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetType MapType(const std::string& type) {
|
||||||
|
static std::map<std::string, WidgetType> typeMap = {
|
||||||
|
{"Window", WidgetType::Window},
|
||||||
|
{"Button", WidgetType::Button},
|
||||||
|
{"Slider", WidgetType::Slider},
|
||||||
|
{"Text", WidgetType::Text},
|
||||||
|
{"CollapsingHeader", WidgetType::CollapsingHeader},
|
||||||
|
{"Columns", WidgetType::Columns},
|
||||||
|
{"HexInputByte", WidgetType::HexInputByte},
|
||||||
|
{"HexInputWord", WidgetType::HexInputWord},
|
||||||
|
{"Table", WidgetType::Table},
|
||||||
|
{"Selectable", WidgetType::Selectable},
|
||||||
|
{"TableSetupColumn", WidgetType::TableSetupColumn},
|
||||||
|
{"TableHeadersRow", WidgetType::TableHeadersRow},
|
||||||
|
{"TableNextColumn", WidgetType::TableNextColumn},
|
||||||
|
{"Function", WidgetType::Function},
|
||||||
|
{"BeginChild", WidgetType::BeginChild},
|
||||||
|
{"BeginMenu", WidgetType::BeginMenu},
|
||||||
|
{"MenuItem", WidgetType::MenuItem},
|
||||||
|
{"BeginMenuBar", WidgetType::BeginMenuBar},
|
||||||
|
{"Separator", WidgetType::Separator},
|
||||||
|
};
|
||||||
|
return typeMap[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
Node ParseNode(const std::vector<Token>& tokens, size_t& index,
|
||||||
|
const std::map<std::string, void*>& data_bindings) {
|
||||||
|
Node node;
|
||||||
|
if (index >= tokens.size() || tokens[index].type == TokenType::EndOfStream) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index < tokens.size() &&
|
||||||
|
tokens[index].type != TokenType::EndOfStream) {
|
||||||
|
Token token = tokens[index];
|
||||||
|
if (token.type == TokenType::Identifier) {
|
||||||
|
node.type = MapType(token.value);
|
||||||
|
index++; // Move to the next token for attributes
|
||||||
|
node.attributes =
|
||||||
|
ParseAttributes(tokens, index, node.type, data_bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the opening brace indicating the start of child nodes
|
||||||
|
if (index < tokens.size() && tokens[index].type == TokenType::OpenBrace) {
|
||||||
|
index++; // Skip the opening brace
|
||||||
|
|
||||||
|
while (index < tokens.size() &&
|
||||||
|
tokens[index].type != TokenType::CloseBrace) {
|
||||||
|
if (tokens[index].type == TokenType::Comma) {
|
||||||
|
index++; // Skip commas
|
||||||
|
} else {
|
||||||
|
node.children.push_back(ParseNode(tokens, index, data_bindings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < tokens.size() &&
|
||||||
|
tokens[index].type == TokenType::CloseBrace) {
|
||||||
|
index++; // Ensure closing brace is skipped before returning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break; // Exit after processing one complete node
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetAttributes ParseAttributes(
|
||||||
|
const std::vector<Token>& tokens, size_t& index, const WidgetType& type,
|
||||||
|
const std::map<std::string, void*>& data_bindings) {
|
||||||
|
WidgetAttributes attributes;
|
||||||
|
|
||||||
|
while (index < tokens.size() && tokens[index].type != TokenType::CloseBrace) {
|
||||||
|
if (tokens[index].type == TokenType::Identifier) {
|
||||||
|
Token keyToken = tokens[index];
|
||||||
|
index++; // Move to the value token.
|
||||||
|
if (index < tokens.size() && tokens[index].type == TokenType::String) {
|
||||||
|
std::string value = tokens[index].value;
|
||||||
|
index++; // Move past the value.
|
||||||
|
|
||||||
|
if (keyToken.value == "id")
|
||||||
|
attributes.id = value;
|
||||||
|
else if (keyToken.value == "title")
|
||||||
|
attributes.title = value;
|
||||||
|
else if (keyToken.value == "min")
|
||||||
|
attributes.min = std::stod(value);
|
||||||
|
else if (keyToken.value == "max")
|
||||||
|
attributes.max = std::stod(value);
|
||||||
|
else if (keyToken.value == "value")
|
||||||
|
attributes.value = std::stod(value);
|
||||||
|
else if (keyToken.value == "text")
|
||||||
|
attributes.text = value;
|
||||||
|
else if (keyToken.value == "data" &&
|
||||||
|
data_bindings.find(value) != data_bindings.end()) {
|
||||||
|
attributes.data = data_bindings.at(value);
|
||||||
|
} else if (keyToken.value == "count") {
|
||||||
|
attributes.count = std::stoi(value);
|
||||||
|
} else if (keyToken.value == "flags") {
|
||||||
|
attributes.flags = nullptr; // Placeholder for future use
|
||||||
|
} else if (keyToken.value == "size") {
|
||||||
|
attributes.size = ImVec2(0, 0); // Placeholder for future use
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If it's not an identifier or we encounter an open brace, break out.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Render(Node& node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case WidgetType::Window:
|
||||||
|
if (ImGui::Begin(node.attributes.title.c_str())) {
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::Button:
|
||||||
|
if (ImGui::Button(node.attributes.text.c_str())) {
|
||||||
|
ExecuteActions(node.actions, ActionType::Click);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::CollapsingHeader:
|
||||||
|
if (ImGui::CollapsingHeader(node.attributes.title.c_str())) {
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::Columns:
|
||||||
|
ImGui::Columns(node.attributes.count, node.attributes.title.c_str());
|
||||||
|
ImGui::Separator();
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
ImGui::NextColumn();
|
||||||
|
}
|
||||||
|
ImGui::Columns(1);
|
||||||
|
ImGui::Separator();
|
||||||
|
break;
|
||||||
|
case WidgetType::Table:
|
||||||
|
ImGui::BeginTable(node.attributes.id.c_str(), node.attributes.count);
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
break;
|
||||||
|
case WidgetType::TableSetupColumn:
|
||||||
|
ImGui::TableSetupColumn(node.attributes.title.c_str());
|
||||||
|
break;
|
||||||
|
case WidgetType::TableHeadersRow:
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
break;
|
||||||
|
case WidgetType::TableNextColumn:
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::Text:
|
||||||
|
if (node.attributes.data) {
|
||||||
|
// Assuming all data-bound Text widgets use string formatting
|
||||||
|
char formattedText[256];
|
||||||
|
snprintf(formattedText, sizeof(formattedText),
|
||||||
|
node.attributes.text.c_str(), *(int*)node.attributes.data);
|
||||||
|
ImGui::Text("%s", formattedText);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%s", node.attributes.text.c_str());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::Function: {
|
||||||
|
node.actions[0].callback();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WidgetType::BeginChild:
|
||||||
|
if (ImGui::BeginChild(node.attributes.id.c_str(), node.attributes.size)) {
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::BeginMenuBar:
|
||||||
|
if (ImGui::BeginMenuBar()) {
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case WidgetType::BeginMenu: {
|
||||||
|
if (ImGui::BeginMenu(node.attributes.title.c_str())) {
|
||||||
|
for (auto& child : node.children) {
|
||||||
|
Render(child);
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WidgetType::MenuItem: {
|
||||||
|
if (ImGui::MenuItem(node.attributes.title.c_str())) {
|
||||||
|
ExecuteActions(node.actions, ActionType::Click);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WidgetType::Separator:
|
||||||
|
ImGui::Separator();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node Parse(const std::string& yazon_input,
|
||||||
|
const std::map<std::string, void*>& data_bindings) {
|
||||||
|
size_t index = 0;
|
||||||
|
auto tokens = Tokenize(yazon_input);
|
||||||
|
return ParseNode(tokens, index, data_bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExecuteActions(const std::vector<Action>& actions, ActionType type) {
|
||||||
|
for (const auto& action : actions) {
|
||||||
|
if (action.type == type) {
|
||||||
|
action.callback(); // Execute the callback associated with the action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bind(Node* node, std::function<void()> callback) {
|
||||||
|
if (node) {
|
||||||
|
node->actions.push_back({ActionType::Click, callback});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindAction(Node* node, ActionType type, std::function<void()> callback) {
|
||||||
|
if (node) {
|
||||||
|
node->actions.push_back({type, callback});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace zeml
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
190
src/app/gui/zeml.h
Normal file
190
src/app/gui/zeml.h
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_ZEML_H
|
||||||
|
#define YAZE_APP_GUI_ZEML_H
|
||||||
|
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::app::gui::zeml
|
||||||
|
* @brief Zelda Editor Markup Language Functions
|
||||||
|
*/
|
||||||
|
namespace zeml {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum TokenType
|
||||||
|
*/
|
||||||
|
enum class TokenType {
|
||||||
|
Identifier,
|
||||||
|
String,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace,
|
||||||
|
Comma,
|
||||||
|
EndOfStream
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Token
|
||||||
|
*/
|
||||||
|
struct Token {
|
||||||
|
TokenType type;
|
||||||
|
std::string value;
|
||||||
|
} typedef Token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum WidgetType
|
||||||
|
*/
|
||||||
|
enum class WidgetType {
|
||||||
|
Window,
|
||||||
|
Button,
|
||||||
|
Slider,
|
||||||
|
Text,
|
||||||
|
Table,
|
||||||
|
TableSetupColumn,
|
||||||
|
TableHeadersRow,
|
||||||
|
TableNextColumn,
|
||||||
|
CollapsingHeader,
|
||||||
|
Columns,
|
||||||
|
Selectable,
|
||||||
|
Function,
|
||||||
|
BeginChild,
|
||||||
|
BeginMenuBar,
|
||||||
|
BeginMenu,
|
||||||
|
MenuItem,
|
||||||
|
Separator,
|
||||||
|
HexInputByte,
|
||||||
|
HexInputWord,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct WidgetAttributes
|
||||||
|
* @brief Attributes for a widget
|
||||||
|
* @details id, title, min, max, value, text, count, size, flags, data
|
||||||
|
* @details id: unique identifier for the widget
|
||||||
|
* @details title: title for the widget
|
||||||
|
* @details text: text for the widget
|
||||||
|
* @details min: minimum value for the widget
|
||||||
|
* @details max: maximum value for the widget
|
||||||
|
* @details value: value for the widget
|
||||||
|
* @details count: number of columns
|
||||||
|
* @details size: size of the widget
|
||||||
|
* @details flags: flags for the widget
|
||||||
|
* @details data: data to be binded using the data_binding map
|
||||||
|
*/
|
||||||
|
struct WidgetAttributes {
|
||||||
|
std::string id;
|
||||||
|
std::string title; // For Window, Button
|
||||||
|
double min; // For Slider
|
||||||
|
double max; // For Slider
|
||||||
|
double value; // For Slidecar
|
||||||
|
std::string text; // For Text, Button
|
||||||
|
int count = 0; // For Columns
|
||||||
|
ImVec2 size = ImVec2(0, 0);
|
||||||
|
void* flags = nullptr;
|
||||||
|
|
||||||
|
void* data = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum ActionType
|
||||||
|
*/
|
||||||
|
enum class ActionType { Click, Change, Run };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Action
|
||||||
|
*/
|
||||||
|
struct Action {
|
||||||
|
ActionType type;
|
||||||
|
std::function<void()> callback; // Using std::function to hold lambda
|
||||||
|
// expressions or function pointers
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tokenize a zeml string
|
||||||
|
*/
|
||||||
|
std::vector<Token> Tokenize(const std::string& input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Node
|
||||||
|
* @brief Node for a zeml tree
|
||||||
|
*/
|
||||||
|
struct Node {
|
||||||
|
WidgetType type;
|
||||||
|
WidgetAttributes attributes;
|
||||||
|
std::vector<Action> actions;
|
||||||
|
std::vector<Node> children;
|
||||||
|
|
||||||
|
Node* parent = nullptr;
|
||||||
|
|
||||||
|
Node* GetNode(const std::string& searchId) {
|
||||||
|
if (attributes.id == searchId) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
for (Node& child : children) {
|
||||||
|
Node* found = child.GetNode(searchId);
|
||||||
|
if (found != nullptr) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bind a callback to a node
|
||||||
|
*/
|
||||||
|
void Bind(Node* node, std::function<void()> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bind an action to a node
|
||||||
|
*/
|
||||||
|
void BindAction(Node* node, ActionType type, std::function<void()> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Map a string to a widget type
|
||||||
|
*/
|
||||||
|
WidgetType MapType(const std::string& type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ParseNode attributes for a widget
|
||||||
|
*/
|
||||||
|
WidgetAttributes ParseAttributes(
|
||||||
|
const std::vector<Token>& tokens, size_t& index, const WidgetType& type,
|
||||||
|
const std::map<std::string, void*>& data_bindings = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ParseNode a zeml node
|
||||||
|
*/
|
||||||
|
Node ParseNode(const std::vector<Token>& tokens, size_t& index,
|
||||||
|
const std::map<std::string, void*>& data_bindings = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ParseNode a zeml string
|
||||||
|
*/
|
||||||
|
Node Parse(const std::string& yazon_input,
|
||||||
|
const std::map<std::string, void*>& data_bindings = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render a zeml tree
|
||||||
|
*/
|
||||||
|
void Render(Node& node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute actions for a node
|
||||||
|
*/
|
||||||
|
void ExecuteActions(const std::vector<Action>& actions, ActionType type);
|
||||||
|
|
||||||
|
} // namespace zeml
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_YAZON_H_
|
||||||
Reference in New Issue
Block a user