#define VC_ENABLE_REFLECTION #include "gui_xml.hpp" #include #include #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 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 void register_action( UINode& node, const UiXmlReader& reader, const xml::xmlelement& element, const std::string& name, UIAction action ) { auto callback = create_runnable(reader, element, name); if (callback == nullptr) { return; } node.listenAction(action, [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); } } register_action(node, reader, element, "onclick", UIAction::CLICK); register_action(node, reader, element, "onrightclick", UIAction::RIGHT_CLICK); register_action(node, reader, element, "onfocus", UIAction::FOCUS); register_action(node, reader, element, "ondefocus", UIAction::DEFOCUS); register_action(node, reader, element, "ondoubleclick", UIAction::DOUBLE_CLICK); register_action(node, reader, element, "onmouseover", UIAction::MOUSE_OVER); register_action(node, reader, element, "onmouseout", UIAction::MOUSE_OUT); } 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 read_label( const UiXmlReader& reader, const xml::xmlelement& element ) { std::wstring text = parse_inner_text(element, reader.getContext()); auto label = std::make_shared