960 lines
30 KiB
C++
960 lines
30 KiB
C++
#define VC_ENABLE_REFLECTION
|
|
#include "gui_xml.hpp"
|
|
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
|
|
#include "GUI.hpp"
|
|
#include "elements/Button.hpp"
|
|
#include "elements/Canvas.hpp"
|
|
#include "elements/CheckBox.hpp"
|
|
#include "elements/Image.hpp"
|
|
#include "elements/InlineFrame.hpp"
|
|
#include "elements/InputBindBox.hpp"
|
|
#include "elements/InventoryView.hpp"
|
|
#include "elements/Menu.hpp"
|
|
#include "elements/ModelViewer.hpp"
|
|
#include "elements/Panel.hpp"
|
|
#include "elements/SelectBox.hpp"
|
|
#include "elements/SplitBox.hpp"
|
|
#include "elements/TextBox.hpp"
|
|
#include "elements/TrackBar.hpp"
|
|
#include "engine/Engine.hpp"
|
|
#include "frontend/locale.hpp"
|
|
#include "frontend/menu.hpp"
|
|
#include "items/Inventory.hpp"
|
|
#include "logic/scripting/scripting.hpp"
|
|
#include "maths/voxmaths.hpp"
|
|
#include "util/stringutil.hpp"
|
|
|
|
using namespace gui;
|
|
|
|
static Align align_from_string(std::string_view str, Align def) {
|
|
if (str == "left") return Align::LEFT;
|
|
if (str == "center") return Align::CENTER;
|
|
if (str == "right") return Align::RIGHT;
|
|
if (str == "top") return Align::TOP;
|
|
if (str == "bottom") return Align::BOTTOM;
|
|
return def;
|
|
}
|
|
|
|
static Gravity gravity_from_string(const std::string& str) {
|
|
static const std::unordered_map<std::string, Gravity> gravity_names {
|
|
{"top-left", Gravity::TOP_LEFT},
|
|
{"top-center", Gravity::TOP_CENTER},
|
|
{"top-right", Gravity::TOP_RIGHT},
|
|
{"center-left", Gravity::CENTER_LEFT},
|
|
{"center-center", Gravity::CENTER_CENTER},
|
|
{"center-right", Gravity::CENTER_RIGHT},
|
|
{"bottom-left", Gravity::BOTTOM_LEFT},
|
|
{"bottom-center", Gravity::BOTTOM_CENTER},
|
|
{"bottom-right", Gravity::BOTTOM_RIGHT},
|
|
};
|
|
auto found = gravity_names.find(str);
|
|
if (found != gravity_names.end()) {
|
|
return found->second;
|
|
}
|
|
return Gravity::NONE;
|
|
}
|
|
|
|
static runnable create_runnable(
|
|
const UiXmlReader& reader,
|
|
const xml::xmlelement& element,
|
|
const std::string& name
|
|
) {
|
|
if (element.has(name)) {
|
|
const std::string& text = element.attr(name).getText();
|
|
if (!text.empty()) {
|
|
return scripting::create_runnable(
|
|
reader.getEnvironment(), text, reader.getFilename()
|
|
);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static OnAction create_action(
|
|
const UiXmlReader& reader,
|
|
const xml::xmlelement& element,
|
|
const std::string& name
|
|
) {
|
|
auto callback = create_runnable(reader, element, name);
|
|
if (callback == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return [callback](GUI&) { callback(); };
|
|
}
|
|
|
|
/// @brief Read basic UINode properties
|
|
static void read_uinode(
|
|
const UiXmlReader& reader, const xml::xmlelement& element, UINode& node
|
|
) {
|
|
if (element.has("id")) {
|
|
node.setId(element.attr("id").getText());
|
|
}
|
|
if (element.has("pos")) {
|
|
node.setPos(element.attr("pos").asVec2());
|
|
}
|
|
if (element.has("min-size")) {
|
|
node.setMinSize(element.attr("min-size").asVec2());
|
|
}
|
|
if (element.has("size")) {
|
|
node.setSize(element.attr("size").asVec2());
|
|
}
|
|
if (element.has("color")) {
|
|
glm::vec4 color = element.attr("color").asColor();
|
|
glm::vec4 hoverColor = color;
|
|
glm::vec4 pressedColor = color;
|
|
if (element.has("hover-color")) {
|
|
hoverColor = node.getHoverColor();
|
|
}
|
|
if (element.has("pressed-color")) {
|
|
pressedColor = node.getPressedColor();
|
|
}
|
|
node.setColor(color);
|
|
node.setHoverColor(hoverColor);
|
|
node.setPressedColor(pressedColor);
|
|
}
|
|
if (element.has("margin")) {
|
|
node.setMargin(element.attr("margin").asVec4());
|
|
}
|
|
if (element.has("z-index")) {
|
|
node.setZIndex(element.attr("z-index").asInt());
|
|
}
|
|
if (element.has("interactive")) {
|
|
node.setInteractive(element.attr("interactive").asBool());
|
|
}
|
|
if (element.has("visible")) {
|
|
node.setVisible(element.attr("visible").asBool());
|
|
}
|
|
if (element.has("enabled")) {
|
|
node.setEnabled(element.attr("enabled").asBool());
|
|
}
|
|
if (element.has("position-func")) {
|
|
node.setPositionFunc(scripting::create_vec2_supplier(
|
|
reader.getEnvironment(),
|
|
element.attr("position-func").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("size-func")) {
|
|
node.setSizeFunc(scripting::create_vec2_supplier(
|
|
reader.getEnvironment(),
|
|
element.attr("size-func").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("hover-color")) {
|
|
node.setHoverColor(element.attr("hover-color").asColor());
|
|
}
|
|
if (element.has("pressed-color")) {
|
|
node.setPressedColor(element.attr("pressed-color").asColor());
|
|
}
|
|
node.setAlign(
|
|
align_from_string(element.attr("align", "").getText(), node.getAlign())
|
|
);
|
|
|
|
if (element.has("gravity")) {
|
|
node.setGravity(gravity_from_string(element.attr("gravity").getText()));
|
|
}
|
|
|
|
if (element.has("tooltip")) {
|
|
auto tooltip = util::str2wstr_utf8(element.attr("tooltip").getText());
|
|
if (!tooltip.empty() && tooltip[0] == '@') {
|
|
tooltip = langs::get(
|
|
tooltip.substr(1), util::str2wstr_utf8(reader.getContext())
|
|
);
|
|
}
|
|
node.setTooltip(tooltip);
|
|
}
|
|
if (element.has("tooltip-delay")) {
|
|
node.setTooltipDelay(element.attr("tooltip-delay").asFloat());
|
|
}
|
|
if (element.has("cursor")) {
|
|
CursorShape cursor;
|
|
if (CursorShapeMeta.getItem(element.attr("cursor").getText(), cursor)) {
|
|
node.setCursor(cursor);
|
|
}
|
|
}
|
|
|
|
if (auto onclick = create_action(reader, element, "onclick")) {
|
|
node.listenClick(onclick);
|
|
}
|
|
|
|
if (auto onclick = create_action(reader, element, "onrightclick")) {
|
|
node.listenRightClick(onclick);
|
|
}
|
|
|
|
if (auto onfocus = create_action(reader, element, "onfocus")) {
|
|
node.listenFocus(onfocus);
|
|
}
|
|
|
|
if (auto ondefocus = create_action(reader, element, "ondefocus")) {
|
|
node.listenDefocus(ondefocus);
|
|
}
|
|
|
|
if (auto ondoubleclick = create_action(reader, element, "ondoubleclick")) {
|
|
node.listenDoubleClick(ondoubleclick);
|
|
}
|
|
}
|
|
|
|
static void read_container_impl(
|
|
UiXmlReader& reader,
|
|
const xml::xmlelement& element,
|
|
Container& container,
|
|
bool subnodes
|
|
) {
|
|
read_uinode(reader, element, container);
|
|
|
|
if (element.has("scrollable")) {
|
|
container.setScrollable(element.attr("scrollable").asBool());
|
|
}
|
|
if (element.has("scroll-step")) {
|
|
container.setScrollStep(element.attr("scroll-step").asInt());
|
|
}
|
|
if (!subnodes) {
|
|
return;
|
|
}
|
|
for (auto& sub : element.getElements()) {
|
|
if (sub->isText()) continue;
|
|
auto subnode = reader.readUINode(*sub);
|
|
if (subnode) {
|
|
container.add(subnode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UiXmlReader::readUINode(
|
|
UiXmlReader& reader, const xml::xmlelement& element, Container& container
|
|
) {
|
|
read_container_impl(reader, element, container, true);
|
|
}
|
|
|
|
void UiXmlReader::readUINode(
|
|
const UiXmlReader& reader, const xml::xmlelement& element, UINode& node
|
|
) {
|
|
read_uinode(reader, element, node);
|
|
}
|
|
|
|
static void read_base_panel_impl(
|
|
UiXmlReader& reader, const xml::xmlelement& element, BasePanel& panel
|
|
) {
|
|
read_container_impl(reader, element, panel, false);
|
|
|
|
if (element.has("padding")) {
|
|
glm::vec4 padding = element.attr("padding").asVec4();
|
|
panel.setPadding(padding);
|
|
panel.refresh();
|
|
}
|
|
if (element.has("orientation")) {
|
|
auto& oname = element.attr("orientation").getText();
|
|
if (oname == "horizontal") {
|
|
panel.setOrientation(Orientation::HORIZONTAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void read_panel_impl(
|
|
UiXmlReader& reader,
|
|
const xml::xmlelement& element,
|
|
Panel& panel,
|
|
bool subnodes = true
|
|
) {
|
|
read_base_panel_impl(reader, element, panel);
|
|
|
|
if (element.has("size")) {
|
|
panel.setResizing(false);
|
|
}
|
|
if (element.has("max-length")) {
|
|
panel.setMaxLength(element.attr("max-length").asInt());
|
|
}
|
|
if (element.has("min-length")) {
|
|
panel.setMinLength(element.attr("min-length").asInt());
|
|
}
|
|
if (element.has("orientation")) {
|
|
auto& oname = element.attr("orientation").getText();
|
|
if (oname == "horizontal") {
|
|
panel.setOrientation(Orientation::HORIZONTAL);
|
|
}
|
|
}
|
|
if (subnodes) {
|
|
for (auto& sub : element.getElements()) {
|
|
if (sub->isText()) continue;
|
|
auto subnode = reader.readUINode(*sub);
|
|
if (subnode) {
|
|
panel.add(subnode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::wstring parse_inner_text(
|
|
const xml::xmlelement& element, const std::string& context
|
|
) {
|
|
std::wstring text = L"";
|
|
for (const auto& elem : element.getElements()) {
|
|
if (!elem->isText()) {
|
|
continue;
|
|
}
|
|
std::string source = elem->getInnerText();
|
|
util::trim(source);
|
|
text = util::str2wstr_utf8(source);
|
|
if (text[0] == '@') {
|
|
if (context.empty()) {
|
|
text = langs::get(text.substr(1));
|
|
} else {
|
|
text = langs::get(text.substr(1), util::str2wstr_utf8(context));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_label(
|
|
const UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
std::wstring text = parse_inner_text(element, reader.getContext());
|
|
auto label = std::make_shared<Label>(reader.getGUI(), text);
|
|
read_uinode(reader, element, *label);
|
|
if (element.has("valign")) {
|
|
label->setVerticalAlign(align_from_string(
|
|
element.attr("valign").getText(), label->getVerticalAlign()
|
|
));
|
|
}
|
|
if (element.has("supplier")) {
|
|
label->textSupplier(scripting::create_wstring_supplier(
|
|
reader.getEnvironment(),
|
|
element.attr("supplier").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("autoresize")) {
|
|
label->setAutoResize(element.attr("autoresize").asBool());
|
|
}
|
|
if (element.has("multiline")) {
|
|
label->setMultiline(element.attr("multiline").asBool());
|
|
if (!element.has("valign")) {
|
|
label->setVerticalAlign(Align::TOP);
|
|
}
|
|
}
|
|
if (element.has("text-wrap")) {
|
|
label->setTextWrapping(element.attr("text-wrap").asBool());
|
|
}
|
|
if (element.has("markup")) {
|
|
label->setMarkup(element.attr("markup").getText());
|
|
}
|
|
return label;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_container(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto container = std::make_shared<Container>(reader.getGUI(), glm::vec2());
|
|
read_container_impl(reader, element, *container, true);
|
|
return container;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_split_box(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
float splitPos = element.attr("split-pos", "0.5").asFloat();
|
|
Orientation orientation =
|
|
element.attr("orientation", "vertical").getText() == "horizontal"
|
|
? Orientation::HORIZONTAL
|
|
: Orientation::VERTICAL;
|
|
auto splitBox = std::make_shared<SplitBox>(
|
|
reader.getGUI(), glm::vec2(), splitPos, orientation
|
|
);
|
|
read_base_panel_impl(reader, element, *splitBox);
|
|
for (auto& sub : element.getElements()) {
|
|
if (sub->isText()) continue;
|
|
auto subnode = reader.readUINode(*sub);
|
|
if (subnode) {
|
|
splitBox->add(subnode);
|
|
}
|
|
}
|
|
return splitBox;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_model_viewer(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto model = element.attr("src", "").getText();
|
|
auto viewer =
|
|
std::make_shared<ModelViewer>(reader.getGUI(), glm::vec2(), model);
|
|
read_container_impl(reader, element, *viewer, true);
|
|
if (element.has("center")) {
|
|
viewer->setCenter(element.attr("center").asVec3());
|
|
}
|
|
if (element.has("cam-rotation")) {
|
|
viewer->setRotation(glm::radians(element.attr("cam-rotation").asVec3())
|
|
);
|
|
}
|
|
return viewer;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_panel(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
float interval = element.attr("interval", "2").asFloat();
|
|
auto panel = std::make_shared<Panel>(
|
|
reader.getGUI(), glm::vec2(), glm::vec4(), interval
|
|
);
|
|
read_panel_impl(reader, element, *panel);
|
|
return panel;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_button(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto& gui = reader.getGUI();
|
|
glm::vec4 padding = element.attr("padding", "10").asVec4();
|
|
|
|
std::shared_ptr<Button> button;
|
|
auto& elements = element.getElements();
|
|
if (!elements.empty() && !elements[0]->isText()) {
|
|
auto inner = reader.readUINode(*elements.at(0));
|
|
if (inner != nullptr) {
|
|
button = std::make_shared<Button>(gui, inner, padding);
|
|
} else {
|
|
button = std::make_shared<Button>(gui, L"", padding, nullptr);
|
|
}
|
|
read_panel_impl(reader, element, *button, false);
|
|
} else {
|
|
std::wstring text = parse_inner_text(element, reader.getContext());
|
|
button = std::make_shared<Button>(gui, text, padding, nullptr);
|
|
read_panel_impl(reader, element, *button, true);
|
|
}
|
|
if (element.has("text-align")) {
|
|
button->setTextAlign(align_from_string(
|
|
element.attr("text-align").getText(), button->getTextAlign()
|
|
));
|
|
}
|
|
return button;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_select(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto& gui = reader.getGUI();
|
|
glm::vec4 padding = element.attr("padding", "10").asVec4();
|
|
int contentWidth = element.attr("width", "100").asInt();
|
|
|
|
auto& elements = element.getElements();
|
|
std::vector<SelectBox::Option> options;
|
|
SelectBox::Option selected;
|
|
|
|
for (const auto& elem : elements) {
|
|
const auto& tag = elem->getTag();
|
|
if (tag != "option") {
|
|
continue;
|
|
}
|
|
auto value = elem->attr("value").getText();
|
|
auto text = parse_inner_text(*elem, reader.getContext());
|
|
options.push_back(SelectBox::Option {std::move(value), std::move(text)}
|
|
);
|
|
}
|
|
|
|
if (element.has("selected")) {
|
|
auto selectedValue = element.attr("selected").getText();
|
|
selected.value = selectedValue;
|
|
selected.text = L"";
|
|
for (const auto& option : options) {
|
|
if (option.value == selectedValue) {
|
|
selected.text = option.text;
|
|
}
|
|
}
|
|
if (selected.text.empty()) {
|
|
selected.text = util::str2wstr_utf8(selected.value);
|
|
}
|
|
}
|
|
|
|
auto innerText = parse_inner_text(element, "");
|
|
if (!innerText.empty()) {
|
|
selected.text = innerText;
|
|
}
|
|
|
|
auto selectBox = std::make_shared<SelectBox>(
|
|
gui,
|
|
std::move(options),
|
|
std::move(selected),
|
|
contentWidth,
|
|
std::move(padding)
|
|
);
|
|
if (element.has("onselect")) {
|
|
auto callback = scripting::create_string_consumer(
|
|
reader.getEnvironment(),
|
|
element.attr("onselect").getText(),
|
|
reader.getFilename()
|
|
);
|
|
selectBox->listenChange([callback = std::move(callback)](
|
|
GUI&, const std::string& value
|
|
) { callback(value); });
|
|
}
|
|
read_panel_impl(reader, element, *selectBox, false);
|
|
return selectBox;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_check_box(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto text = parse_inner_text(element, reader.getContext());
|
|
bool checked = element.attr("checked", "false").asBool();
|
|
auto checkbox = std::make_shared<FullCheckBox>(
|
|
reader.getGUI(), text, glm::vec2(32), checked
|
|
);
|
|
read_panel_impl(reader, element, *checkbox);
|
|
|
|
if (element.has("consumer")) {
|
|
checkbox->setConsumer(scripting::create_bool_consumer(
|
|
reader.getEnvironment(),
|
|
element.attr("consumer").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
|
|
if (element.has("supplier")) {
|
|
checkbox->setSupplier(scripting::create_bool_supplier(
|
|
reader.getEnvironment(),
|
|
element.attr("supplier").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
return checkbox;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_text_box(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto placeholder =
|
|
util::str2wstr_utf8(element.attr("placeholder", "").getText());
|
|
auto hint = util::str2wstr_utf8(element.attr("hint", "").getText());
|
|
if (!hint.empty() && hint[0] == '@') {
|
|
hint = langs::get(
|
|
hint.substr(1), util::str2wstr_utf8(reader.getContext())
|
|
);
|
|
}
|
|
auto text = parse_inner_text(element, reader.getContext());
|
|
auto textbox = std::make_shared<TextBox>(
|
|
reader.getGUI(), placeholder, glm::vec4(0.0f)
|
|
);
|
|
textbox->setHint(hint);
|
|
|
|
read_container_impl(reader, element, *textbox, true);
|
|
if (element.has("padding")) {
|
|
glm::vec4 padding = element.attr("padding").asVec4();
|
|
textbox->setPadding(padding);
|
|
glm::vec2 size = textbox->getSize();
|
|
textbox->setSize(glm::vec2(
|
|
size.x + padding.x + padding.z, size.y + padding.y + padding.w
|
|
));
|
|
}
|
|
textbox->setText(text);
|
|
|
|
if (element.has("syntax")) {
|
|
textbox->setSyntax(element.attr("syntax").getText());
|
|
}
|
|
if (element.has("multiline")) {
|
|
textbox->setMultiline(element.attr("multiline").asBool());
|
|
}
|
|
if (element.has("text-wrap")) {
|
|
textbox->setTextWrapping(element.attr("text-wrap").asBool());
|
|
}
|
|
if (element.has("editable")) {
|
|
textbox->setEditable(element.attr("editable").asBool());
|
|
}
|
|
if (element.has("autoresize")) {
|
|
textbox->setAutoResize(element.attr("autoresize").asBool());
|
|
}
|
|
if (element.has("line-numbers")) {
|
|
textbox->setShowLineNumbers(element.attr("line-numbers").asBool());
|
|
}
|
|
if (element.has("keep-line-selection")) {
|
|
textbox->setKeepLineSelection(
|
|
element.attr("keep-line-selection").asBool()
|
|
);
|
|
}
|
|
if (element.has("markup")) {
|
|
textbox->setMarkup(element.attr("markup").getText());
|
|
}
|
|
if (element.has("consumer")) {
|
|
textbox->setTextConsumer(scripting::create_wstring_consumer(
|
|
reader.getEnvironment(),
|
|
element.attr("consumer").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("sub-consumer")) {
|
|
textbox->setTextSubConsumer(scripting::create_wstring_consumer(
|
|
reader.getEnvironment(),
|
|
element.attr("sub-consumer").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("supplier")) {
|
|
textbox->setTextSupplier(scripting::create_wstring_supplier(
|
|
reader.getEnvironment(),
|
|
element.attr("supplier").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("focused-color")) {
|
|
textbox->setFocusedColor(element.attr("focused-color").asColor());
|
|
}
|
|
if (element.has("error-color")) {
|
|
textbox->setErrorColor(element.attr("error-color").asColor());
|
|
}
|
|
if (element.has("text-color")) {
|
|
textbox->setTextColor(element.attr("text-color").asColor());
|
|
}
|
|
if (element.has("validator")) {
|
|
textbox->setTextValidator(scripting::create_wstring_validator(
|
|
reader.getEnvironment(),
|
|
element.attr("validator").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (element.has("oncontrolkey")) {
|
|
textbox->setOnControlCombination(scripting::create_key_handler(
|
|
reader.getEnvironment(),
|
|
element.attr("oncontrolkey").getText(),
|
|
reader.getFilename()
|
|
));
|
|
}
|
|
if (auto onUpPressed = create_runnable(reader, element, "onup")) {
|
|
textbox->setOnUpPressed(onUpPressed);
|
|
}
|
|
if (auto onDownPressed = create_runnable(reader, element, "ondown")) {
|
|
textbox->setOnDownPressed(onDownPressed);
|
|
}
|
|
return textbox;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_image(
|
|
const UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
std::string src = element.attr("src", "").getText();
|
|
auto image = std::make_shared<Image>(reader.getGUI(), src);
|
|
read_uinode(reader, element, *image);
|
|
|
|
if (element.has("region")) {
|
|
auto vec = element.attr("region").asVec4();
|
|
image->setRegion(UVRegion(vec.x, vec.y, vec.z, vec.w));
|
|
}
|
|
return image;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_canvas(
|
|
const UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto size = glm::uvec2 {32, 32};
|
|
if (element.has("size")) {
|
|
size = element.attr("size").asVec2();
|
|
}
|
|
auto image =
|
|
std::make_shared<Canvas>(reader.getGUI(), ImageFormat::rgba8888, size);
|
|
read_uinode(reader, element, *image);
|
|
return image;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_track_bar(
|
|
const UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
const auto& env = reader.getEnvironment();
|
|
const auto& file = reader.getFilename();
|
|
float minv = element.attr("min", "0.0").asFloat();
|
|
float maxv = element.attr("max", "1.0").asFloat();
|
|
float def = element.attr("value", "0.0").asFloat();
|
|
float step = element.attr("step", "1.0").asFloat();
|
|
int trackWidth = element.attr("track-width", "12").asInt();
|
|
auto bar = std::make_shared<TrackBar>(
|
|
reader.getGUI(), minv, maxv, def, step, trackWidth
|
|
);
|
|
read_uinode(reader, element, *bar);
|
|
if (element.has("consumer")) {
|
|
bar->setConsumer(scripting::create_number_consumer(
|
|
env, element.attr("consumer").getText(), file
|
|
));
|
|
}
|
|
if (element.has("sub-consumer")) {
|
|
bar->setSubConsumer(scripting::create_number_consumer(
|
|
env, element.attr("sub-consumer").getText(), file
|
|
));
|
|
}
|
|
if (element.has("supplier")) {
|
|
bar->setSupplier(scripting::create_number_supplier(
|
|
env, element.attr("supplier").getText(), file
|
|
));
|
|
}
|
|
if (element.has("track-color")) {
|
|
bar->setTrackColor(element.attr("track-color").asColor());
|
|
}
|
|
if (element.has("change-on-release")) {
|
|
bar->setChangeOnRelease(element.attr("change-on-release").asBool());
|
|
}
|
|
return bar;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_input_bind_box(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto bindname = element.attr("binding").getText();
|
|
auto& found = reader.getGUI().getInput().getBindings().require(bindname);
|
|
glm::vec4 padding = element.attr("padding", "6").asVec4();
|
|
auto bindbox =
|
|
std::make_shared<InputBindBox>(reader.getGUI(), found, padding);
|
|
read_panel_impl(reader, element, *bindbox);
|
|
return bindbox;
|
|
}
|
|
|
|
static slotcallback read_slot_func(
|
|
InventoryView* view,
|
|
const UiXmlReader& reader,
|
|
const xml::xmlelement& element,
|
|
const std::string& attr
|
|
) {
|
|
auto consumer = scripting::create_int_array_consumer(
|
|
reader.getEnvironment(), element.attr(attr).getText()
|
|
);
|
|
return [=](uint slot, ItemStack&) {
|
|
int args[] {
|
|
static_cast<int>(view->getInventory()->getId()),
|
|
static_cast<int>(slot)};
|
|
consumer(args, 2);
|
|
};
|
|
}
|
|
|
|
static void read_slot(
|
|
InventoryView* view, UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
int index = element.attr("index", "0").asInt();
|
|
bool itemSource = element.attr("item-source", "false").asBool();
|
|
bool taking = element.attr("taking", "true").asBool();
|
|
bool placing = element.attr("placing", "true").asBool();
|
|
SlotLayout layout(
|
|
index, glm::vec2(), true, itemSource, nullptr, nullptr, nullptr
|
|
);
|
|
if (element.has("pos")) {
|
|
layout.position = element.attr("pos").asVec2();
|
|
}
|
|
if (element.has("updatefunc")) {
|
|
layout.updateFunc = read_slot_func(view, reader, element, "updatefunc");
|
|
}
|
|
if (element.has("sharefunc")) {
|
|
layout.shareFunc = read_slot_func(view, reader, element, "sharefunc");
|
|
}
|
|
if (element.has("onrightclick")) {
|
|
layout.rightClick =
|
|
read_slot_func(view, reader, element, "onrightclick");
|
|
}
|
|
layout.taking = taking;
|
|
layout.placing = placing;
|
|
auto slot = view->addSlot(layout);
|
|
reader.readUINode(reader, element, *slot);
|
|
view->add(slot);
|
|
}
|
|
|
|
static void read_slots_grid(
|
|
InventoryView* view,
|
|
const UiXmlReader& reader,
|
|
const xml::xmlelement& element
|
|
) {
|
|
int startIndex = element.attr("start-index", "0").asInt();
|
|
int rows = element.attr("rows", "0").asInt();
|
|
int cols = element.attr("cols", "0").asInt();
|
|
int count = element.attr("count", "0").asInt();
|
|
const int slotSize = InventoryView::SLOT_SIZE;
|
|
bool taking = element.attr("taking", "true").asBool();
|
|
bool placing = element.attr("placing", "true").asBool();
|
|
int interval = element.attr("interval", "-1").asInt();
|
|
if (interval < 0) {
|
|
interval = InventoryView::SLOT_INTERVAL;
|
|
}
|
|
int padding = element.attr("padding", "-1").asInt();
|
|
if (padding < 0) {
|
|
padding = interval;
|
|
}
|
|
if (rows == 0) {
|
|
rows = ceildiv(count, cols);
|
|
} else if (cols == 0) {
|
|
cols = ceildiv(count, rows);
|
|
} else if (count == 0) {
|
|
count = rows * cols;
|
|
}
|
|
bool itemSource = element.attr("item-source", "false").asBool();
|
|
SlotLayout layout(
|
|
-1, glm::vec2(), true, itemSource, nullptr, nullptr, nullptr
|
|
);
|
|
if (element.has("pos")) {
|
|
layout.position = element.attr("pos").asVec2();
|
|
}
|
|
if (element.has("updatefunc")) {
|
|
layout.updateFunc = read_slot_func(view, reader, element, "updatefunc");
|
|
}
|
|
if (element.has("sharefunc")) {
|
|
layout.shareFunc = read_slot_func(view, reader, element, "sharefunc");
|
|
}
|
|
if (element.has("onrightclick")) {
|
|
layout.rightClick =
|
|
read_slot_func(view, reader, element, "onrightclick");
|
|
}
|
|
layout.padding = padding;
|
|
layout.taking = taking;
|
|
layout.placing = placing;
|
|
|
|
int idx = 0;
|
|
for (int row = 0; row < rows; row++) {
|
|
for (int col = 0; col < cols; col++, idx++) {
|
|
if (idx >= count) {
|
|
return;
|
|
}
|
|
SlotLayout slotLayout = layout;
|
|
slotLayout.index = startIndex + idx;
|
|
slotLayout.position += glm::vec2(
|
|
padding + col * (slotSize + interval),
|
|
padding + (rows - row - 1) * (slotSize + interval)
|
|
);
|
|
auto slot = view->addSlot(slotLayout);
|
|
view->add(slot, slotLayout.position);
|
|
}
|
|
}
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_inventory(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto view = std::make_shared<InventoryView>(reader.getGUI());
|
|
view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f)); // TODO: fixme
|
|
reader.addIgnore("slot");
|
|
reader.addIgnore("slots-grid");
|
|
reader.readUINode(reader, element, *view);
|
|
|
|
for (auto& sub : element.getElements()) {
|
|
if (sub->getTag() == "slot") {
|
|
read_slot(view.get(), reader, *sub);
|
|
} else if (sub->getTag() == "slots-grid") {
|
|
read_slots_grid(view.get(), reader, *sub);
|
|
}
|
|
}
|
|
return view;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_page_box(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto& gui = reader.getGUI();
|
|
auto menu = std::make_shared<Menu>(gui);
|
|
menu->setPageLoader(gui.getMenu()->getPageLoader());
|
|
read_container_impl(reader, element, *menu, true);
|
|
|
|
return menu;
|
|
}
|
|
|
|
static std::shared_ptr<UINode> read_iframe(
|
|
UiXmlReader& reader, const xml::xmlelement& element
|
|
) {
|
|
auto& gui = reader.getGUI();
|
|
auto iframe = std::make_shared<InlineFrame>(gui);
|
|
read_container_impl(reader, element, *iframe, true);
|
|
|
|
std::string src = element.attr("src", "").getText();
|
|
iframe->setSrc(src);
|
|
return iframe;
|
|
}
|
|
|
|
UiXmlReader::UiXmlReader(gui::GUI& gui, scriptenv&& env)
|
|
: gui(gui), env(std::move(env)) {
|
|
contextStack.emplace("");
|
|
add("image", read_image);
|
|
add("canvas", read_canvas);
|
|
add("iframe", read_iframe);
|
|
add("label", read_label);
|
|
add("panel", read_panel);
|
|
add("button", read_button);
|
|
add("select", read_select);
|
|
add("textbox", read_text_box);
|
|
add("pagebox", read_page_box);
|
|
add("splitbox", read_split_box);
|
|
add("checkbox", read_check_box);
|
|
add("trackbar", read_track_bar);
|
|
add("container", read_container);
|
|
add("bindbox", read_input_bind_box);
|
|
add("modelviewer", read_model_viewer);
|
|
add("inventory", read_inventory);
|
|
}
|
|
|
|
void UiXmlReader::add(const std::string& tag, uinode_reader reader) {
|
|
readers[tag] = std::move(reader);
|
|
}
|
|
|
|
bool UiXmlReader::hasReader(const std::string& tag) const {
|
|
return readers.find(tag) != readers.end();
|
|
}
|
|
|
|
void UiXmlReader::addIgnore(const std::string& tag) {
|
|
ignored.insert(tag);
|
|
}
|
|
|
|
std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element
|
|
) {
|
|
if (element.has("if")) {
|
|
const auto& cond = element.attr("if").getText();
|
|
if (cond.empty() || cond == "false" || cond == "nil") return nullptr;
|
|
}
|
|
if (element.has("ifnot")) {
|
|
const auto& cond = element.attr("ifnot").getText();
|
|
if (!(cond.empty() || cond == "false" || cond == "nil")) return nullptr;
|
|
}
|
|
|
|
const std::string& tag = element.getTag();
|
|
auto found = readers.find(tag);
|
|
if (found == readers.end()) {
|
|
if (ignored.find(tag) != ignored.end()) {
|
|
return nullptr;
|
|
}
|
|
throw std::runtime_error("unsupported element '" + tag + "'");
|
|
}
|
|
|
|
bool hascontext = element.has("context");
|
|
if (hascontext) {
|
|
contextStack.push(element.attr("context").getText());
|
|
}
|
|
auto node = found->second(*this, element);
|
|
if (hascontext) {
|
|
contextStack.pop();
|
|
}
|
|
return node;
|
|
}
|
|
|
|
std::shared_ptr<UINode> UiXmlReader::readXML(
|
|
const std::string& filename, const std::string& source
|
|
) {
|
|
this->filename = filename;
|
|
auto document = xml::parse(filename, source);
|
|
return readUINode(*document->getRoot());
|
|
}
|
|
|
|
std::shared_ptr<UINode> UiXmlReader::readXML(
|
|
const std::string& filename, const xml::xmlelement& root
|
|
) {
|
|
this->filename = filename;
|
|
return readUINode(root);
|
|
}
|
|
|
|
const std::string& UiXmlReader::getContext() const {
|
|
return contextStack.top();
|
|
}
|
|
|
|
const std::string& UiXmlReader::getFilename() const {
|
|
return filename;
|
|
}
|
|
|
|
const scriptenv& UiXmlReader::getEnvironment() const {
|
|
return env;
|
|
}
|
|
|
|
gui::GUI& UiXmlReader::getGUI() const {
|
|
return gui;
|
|
}
|