Add zeml, zelda3 editor markup language, for UI rendering and data manipulation
This commit is contained in:
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