diff --git a/libelemental.vcxproj b/libelemental.vcxproj
index 1e1636c..10a92a8 100644
--- a/libelemental.vcxproj
+++ b/libelemental.vcxproj
@@ -20,6 +20,7 @@
+
diff --git a/libelemental.vcxproj.filters b/libelemental.vcxproj.filters
index 315260c..ded84b8 100644
--- a/libelemental.vcxproj.filters
+++ b/libelemental.vcxproj.filters
@@ -301,6 +301,9 @@
src\el\elements
+
+ src\el
+
diff --git a/src/el/dsl.h b/src/el/dsl.h
new file mode 100644
index 0000000..930a032
--- /dev/null
+++ b/src/el/dsl.h
@@ -0,0 +1,134 @@
+/**
+ ******************************************************************************
+ * Elemental Forms : a lightweight user interface framework *
+ ******************************************************************************
+ * ©2015 Ben Vanik. All rights reserved. Released under the BSD license. *
+ * Portions ©2011-2015 Emil Segerås: https://github.com/fruxo/turbobadger *
+ ******************************************************************************
+ */
+
+#ifndef EL_DSL_H_
+#define EL_DSL_H_
+
+#include
+#include
+#include
+#include
+
+#include "el/color.h"
+#include "el/element.h"
+#include "el/parsing/parse_node.h"
+#include "el/rect.h"
+
+namespace el {
+namespace dsl {
+
+inline std::string operator"" _px(uint64_t value) {
+ return std::to_string(value) + "px";
+}
+inline std::string operator"" _dp(uint64_t value) {
+ return std::to_string(value) + "dp";
+}
+inline std::string operator"" _mm(uint64_t value) {
+ return std::to_string(value) + "mm";
+}
+
+class Node {
+ public:
+ el::parsing::ParseNode* parse_node() const { return parse_node_; }
+
+ Node& set(const char* key, int32_t value) {
+ return set(key, el::Value(value));
+ }
+
+ Node& set(const char* key, float value) { return set(key, el::Value(value)); }
+
+ Node& set(const char* key, const char* value) {
+ return set(key, el::Value(value));
+ }
+
+ Node& set(const char* key, const std::string& value) {
+ return set(key, el::Value(value.c_str()));
+ }
+
+ Node& set(const char* key, el::Rect value) {
+ auto va = new el::ValueArray();
+ va->AddInteger(value.x);
+ va->AddInteger(value.y);
+ va->AddInteger(value.w);
+ va->AddInteger(value.h);
+ auto node = GetOrCreateNode(key);
+ node->value().set_array(va, el::Value::Set::kTakeOwnership);
+ return *this;
+ }
+
+ Node& set(const char* key, el::Value& value) {
+ auto node = GetOrCreateNode(key);
+ node->TakeValue(value);
+ return *this;
+ }
+
+ Node& child_list(std::initializer_list children) {
+ for (auto& child : children) {
+ parse_node_->Add(child.parse_node_);
+ }
+ return *this;
+ }
+
+ protected:
+ Node(const char* name,
+ std::vector> properties = {},
+ std::vector children = {}) {
+ parse_node_ = el::parsing::ParseNode::Create(name);
+ for (auto& prop : properties) {
+ set(prop.first, prop.second);
+ }
+ for (auto& child : children) {
+ parse_node_->Add(child.parse_node_);
+ }
+ }
+
+ el::parsing::ParseNode* GetOrCreateNode(const char* name) {
+ return parse_node_->GetNode(name,
+ el::parsing::ParseNode::MissingPolicy::kCreate);
+ }
+
+ el::parsing::ParseNode* parse_node_ = nullptr;
+};
+
+struct Dimension {
+ Dimension(int32_t value) : value(std::to_string(value) + "px") {}
+ Dimension(const char* value) : value(value) {}
+ Dimension(std::string value) : value(std::move(value)) {}
+ operator std::string() { return value; }
+ std::string value;
+};
+
+struct Id {
+ Id(int32_t value) : is_int(true), int_value(value) {}
+ Id(const char* value) : str_value(value) {}
+ Id(std::string value) : str_value(std::move(value)) {}
+ void set(Node* node, const char* key) {
+ if (is_int) {
+ node->set(key, int_value);
+ } else {
+ node->set(key, str_value);
+ }
+ }
+ bool is_int = false;
+ int32_t int_value = 0;
+ std::string str_value;
+};
+
+struct CloneNode : public Node {
+ using R = CloneNode;
+ CloneNode(const Node& source) : Node(source.parse_node()->name()) {
+ parse_node_->value().Copy(source.parse_node()->value());
+ parse_node_->CloneChildren(source.parse_node());
+ }
+};
+
+} // namespace dsl
+} // namespace el
+
+#endif // EL_DSL_H_
diff --git a/src/el/element.cc b/src/el/element.cc
index e9e2e53..b0599e1 100644
--- a/src/el/element.cc
+++ b/src/el/element.cc
@@ -74,7 +74,9 @@ void Element::RegisterInflater() {
Element::Element() = default;
Element::~Element() {
- assert(!m_parent); // A element must be removed from parent before deleted.
+ // A element must be removed from parent before deleted.
+ RemoveFromParent();
+ assert(!m_parent);
m_packed.is_dying = true;
if (this == hovered_element) {
@@ -108,6 +110,12 @@ void Element::LoadNodeTree(parsing::ParseNode* node) {
return parsing::ElementFactory::get()->LoadNodeTree(this, node);
}
+void Element::LoadNodeTree(const dsl::Node& node) {
+ parsing::ParseNode parse_node;
+ parse_node.Add(node.parse_node());
+ LoadNodeTree(&parse_node);
+}
+
// Sets the id from the given node.
void Element::SetIdFromNode(TBID& id, parsing::ParseNode* node) {
if (!node) return;
diff --git a/src/el/element.h b/src/el/element.h
index 52d8b4a..cb6e19c 100644
--- a/src/el/element.h
+++ b/src/el/element.h
@@ -15,6 +15,7 @@
#include
#include
+#include "el/dsl.h"
#include "el/element_value.h"
#include "el/event.h"
#include "el/font_description.h"
@@ -44,6 +45,8 @@ namespace text {
class FontFace;
} // namespace text
+using util::kInvalidDimension;
+
enum class Align {
kLeft,
kTop,
@@ -277,6 +280,7 @@ class Element : public util::TypedObject,
return LoadData(data.c_str(), data.size());
}
void LoadNodeTree(parsing::ParseNode* node);
+ void LoadNodeTree(const dsl::Node& node);
inline Rect rect() const { return m_rect; }
// Sets the rect for this element in its parent.
@@ -1125,6 +1129,196 @@ class ElementSkinConditionContext : public SkinConditionContext {
Element* m_element = nullptr;
};
+namespace dsl {
+
+using el::Align;
+using el::Axis;
+using ElementState = el::Element::State;
+using el::Gravity;
+using el::Rect;
+using el::TextAlign;
+using el::Visibility;
+
+template
+struct ElementNode : public Node {
+ using R = T;
+ ElementNode(const char* name,
+ std::vector> properties = {},
+ std::vector children = {})
+ : Node(name, std::move(properties), std::move(children)) {}
+
+ // TBID
+ R& id(Id value) {
+ value.set(this, "id");
+ return *reinterpret_cast(this);
+ }
+ // TBID
+ R& group_id(Id value) {
+ value.set(this, "group-id");
+ return *reinterpret_cast(this);
+ }
+ // TBID
+ R& skin(Id value) {
+ value.set(this, "skin");
+ return *reinterpret_cast(this);
+ }
+ // Number
+ R& data(int32_t value) {
+ set("data", value);
+ return *reinterpret_cast(this);
+ }
+ R& data(float value) {
+ set("data", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_group_root(bool value) {
+ set("is-group-root", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_focusable(bool value) {
+ set("is-focusable", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_long_clickable(bool value) {
+ set("want-long-click", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& ignore_input(bool value) {
+ set("ignore-input", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& opacity(float value) {
+ set("opacity", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& connection(std::string value) {
+ set("connection", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& axis(Axis value) {
+ set("axis", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& gravity(Gravity value) {
+ set("gravity", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& visibility(Visibility value) {
+ set("visibility", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& state(ElementState value) {
+ set("state", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_enabled(bool value) {
+ set("state",
+ to_string(value ? ElementState::kNone : ElementState::kDisabled));
+ return *reinterpret_cast(this);
+ }
+ // Rect
+ R& rect(Rect value) {
+ set("rect", std::move(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& width(Dimension value) {
+ set("lp>width", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& min_width(Dimension value) {
+ set("lp>min-width", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& max_width(Dimension value) {
+ set("lp>max-width", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& preferred_width(Dimension value) {
+ set("lp>pref-width", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& height(Dimension value) {
+ set("lp>height", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& min_height(Dimension value) {
+ set("lp>min-height", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& max_height(Dimension value) {
+ set("lp>max-height", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& preferred_height(Dimension value) {
+ set("lp>pref-height", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& tooltip(std::string value) {
+ set("tooltip", value);
+ return *reinterpret_cast(this);
+ }
+ // The Element will be focused automatically the first time its Window is
+ // activated.
+ R& autofocus(bool value) {
+ set("autofocus", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& font(const char* name, int32_t size_px) {
+ set("font>name", name);
+ set("font>size", size_px);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& font_name(std::string value) {
+ set("font>name", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& font_size(Dimension value) {
+ set("font>size", value);
+ return *reinterpret_cast(this);
+ }
+
+ R& clone(Node node) {
+ parse_node_->Add(CloneNode(node).parse_node());
+ return *reinterpret_cast(this);
+ }
+ R& child(Node node) {
+ parse_node_->Add(node.parse_node());
+ return *reinterpret_cast(this);
+ }
+ R& children() { return *reinterpret_cast(this); }
+ template
+ R& children(Node child_node, C... other_children) {
+ child(child_node);
+ children(other_children...);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
+
} // namespace el
#endif // EL_ELEMENT_H_
diff --git a/src/el/elements/box.h b/src/el/elements/box.h
index 4660149..5cea259 100644
--- a/src/el/elements/box.h
+++ b/src/el/elements/box.h
@@ -26,6 +26,14 @@ class Box : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct BoxNode : public ElementNode {
+ using R = BoxNode;
+ BoxNode() : ElementNode("Box") {}
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_BOX_H_
diff --git a/src/el/elements/button.h b/src/el/elements/button.h
index e2ee5ff..c398f04 100644
--- a/src/el/elements/button.h
+++ b/src/el/elements/button.h
@@ -94,6 +94,28 @@ class Button : public Element, protected MessageHandler {
};
} // namespace elements
+namespace dsl {
+
+struct ButtonNode : public ElementNode {
+ using R = ButtonNode;
+ ButtonNode(const char* text = nullptr) : ElementNode("Button") {
+ if (text) {
+ this->text(text);
+ }
+ }
+ //
+ R& text(std::string value) {
+ set("text", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& toggle_mode(bool value) {
+ set("toggle-mode", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_BUTTON_H_
diff --git a/src/el/elements/check_box.h b/src/el/elements/check_box.h
index 2171efb..21542fd 100644
--- a/src/el/elements/check_box.h
+++ b/src/el/elements/check_box.h
@@ -29,6 +29,19 @@ class CheckBox : public parts::BaseRadioCheckBox {
};
} // namespace elements
+namespace dsl {
+
+struct CheckBoxNode : public ElementNode {
+ using R = CheckBoxNode;
+ CheckBoxNode() : ElementNode("CheckBox") {}
+ //
+ R& value(bool value) {
+ set("value", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_CHECK_BOX_H_
diff --git a/src/el/elements/drop_down_button.h b/src/el/elements/drop_down_button.h
index 9a62bb4..203e8af 100644
--- a/src/el/elements/drop_down_button.h
+++ b/src/el/elements/drop_down_button.h
@@ -72,6 +72,19 @@ class DropDownButton : public Button, public ListItemObserver {
};
} // namespace elements
+namespace dsl {
+
+struct DropDownButtonNode : public ItemListElementNode {
+ using R = DropDownButtonNode;
+ DropDownButtonNode() : ItemListElementNode("DropDownButton") {}
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_DROP_DOWN_BUTTON_H_
diff --git a/src/el/elements/group_box.h b/src/el/elements/group_box.h
index 6731c1b..5dbbe39 100644
--- a/src/el/elements/group_box.h
+++ b/src/el/elements/group_box.h
@@ -75,6 +75,29 @@ class GroupBox : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct GroupBoxNode : public ElementNode {
+ using R = GroupBoxNode;
+ GroupBoxNode(const char* text = nullptr) : ElementNode("GroupBox") {
+ if (text) {
+ this->text(text);
+ }
+ }
+ R& content(Node child) { return this->child(child); }
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& text(std::string value) {
+ set("text", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_GROUP_BOX_H_
diff --git a/src/el/elements/icon_box.h b/src/el/elements/icon_box.h
index a371e57..99bc902 100644
--- a/src/el/elements/icon_box.h
+++ b/src/el/elements/icon_box.h
@@ -31,6 +31,14 @@ class IconBox : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct IconBoxNode : public ElementNode {
+ using R = IconBoxNode;
+ IconBoxNode() : ElementNode("IconBox") {}
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_ICON_BOX_H_
diff --git a/src/el/elements/image_box.h b/src/el/elements/image_box.h
index 8e4bfa5..e659183 100644
--- a/src/el/elements/image_box.h
+++ b/src/el/elements/image_box.h
@@ -50,6 +50,23 @@ class ImageBox : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct ImageBoxNode : public ElementNode {
+ using R = ImageBoxNode;
+ ImageBoxNode(const char* filename = nullptr) : ElementNode("ImageBox") {
+ if (filename) {
+ this->filename(filename);
+ }
+ }
+ //
+ R& filename(std::string value) {
+ set("filename", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_IMAGE_BOX_H_
diff --git a/src/el/elements/label.h b/src/el/elements/label.h
index 26d3d20..7dc59f0 100644
--- a/src/el/elements/label.h
+++ b/src/el/elements/label.h
@@ -82,6 +82,28 @@ class Label : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct LabelNode : public ElementNode {
+ using R = LabelNode;
+ LabelNode(const char* text = nullptr) : ElementNode("Label") {
+ if (text) {
+ this->text(text);
+ }
+ }
+ //
+ R& text(std::string value) {
+ set("text", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& text_align(TextAlign value) {
+ set("text-align", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_LABEL_H_
diff --git a/src/el/elements/label_container.h b/src/el/elements/label_container.h
index 648d34f..cc98c7f 100644
--- a/src/el/elements/label_container.h
+++ b/src/el/elements/label_container.h
@@ -52,6 +52,24 @@ class LabelContainer : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct LabelContainerNode : public ElementNode {
+ using R = LabelContainerNode;
+ LabelContainerNode(const char* text, std::vector children = {})
+ : ElementNode("LabelContainer", {}, std::move(children)) {
+ if (text) {
+ this->text(text);
+ }
+ }
+ //
+ R& text(std::string value) {
+ set("text", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_LABEL_CONTAINER_H_
diff --git a/src/el/elements/layout_box.h b/src/el/elements/layout_box.h
index b3452d6..5987458 100644
--- a/src/el/elements/layout_box.h
+++ b/src/el/elements/layout_box.h
@@ -177,6 +177,52 @@ class LayoutBox : public Element {
};
} // namespace elements
+namespace dsl {
+
+using el::elements::LayoutSize;
+using el::elements::LayoutPosition;
+using el::elements::LayoutDistribution;
+using el::elements::LayoutDistributionPosition;
+using el::elements::LayoutOrder;
+using el::elements::LayoutOverflow;
+
+struct LayoutBoxNode : public ElementNode {
+ using R = LayoutBoxNode;
+ LayoutBoxNode(std::vector children = {})
+ : ElementNode("LayoutBox", {}, std::move(children)) {}
+ //
+ R& spacing(Dimension value) {
+ set("spacing", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& size(LayoutSize value) {
+ set("size", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& position(LayoutPosition value) {
+ set("position", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& overflow(LayoutOverflow value) {
+ set("overflow", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& distribution(LayoutDistribution value) {
+ set("distribution", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& distribution_position(LayoutDistributionPosition value) {
+ set("distribution-position", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_LAYOUT_BOX_H_
diff --git a/src/el/elements/list_box.h b/src/el/elements/list_box.h
index 4a1ed25..344ce5d 100644
--- a/src/el/elements/list_box.h
+++ b/src/el/elements/list_box.h
@@ -112,6 +112,19 @@ class ListBox : public Element, public ListItemObserver {
};
} // namespace elements
+namespace dsl {
+
+struct ListBoxNode : public ItemListElementNode {
+ using R = ListBoxNode;
+ ListBoxNode() : ItemListElementNode("ListBox") {}
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_LIST_BOX_H_
diff --git a/src/el/elements/progress_spinner.h b/src/el/elements/progress_spinner.h
index 3d01137..6329aff 100644
--- a/src/el/elements/progress_spinner.h
+++ b/src/el/elements/progress_spinner.h
@@ -56,6 +56,19 @@ class ProgressSpinner : public Element, protected MessageHandler {
};
} // namespace elements
+namespace dsl {
+
+struct ProgressSpinnerNode : public ElementNode {
+ using R = ProgressSpinnerNode;
+ ProgressSpinnerNode() : ElementNode("ProgressSpinner") {}
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_PROGRESS_SPINNER_H_
diff --git a/src/el/elements/radio_button.h b/src/el/elements/radio_button.h
index 87eb445..d68168b 100644
--- a/src/el/elements/radio_button.h
+++ b/src/el/elements/radio_button.h
@@ -30,6 +30,19 @@ class RadioButton : public parts::BaseRadioCheckBox {
};
} // namespace elements
+namespace dsl {
+
+struct RadioButtonNode : public ElementNode {
+ using R = RadioButtonNode;
+ RadioButtonNode() : ElementNode("RadioButton") {}
+ //
+ R& value(bool value) {
+ set("value", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_RADIO_BUTTON_H_
diff --git a/src/el/elements/scroll_bar.h b/src/el/elements/scroll_bar.h
index 31cb072..20612aa 100644
--- a/src/el/elements/scroll_bar.h
+++ b/src/el/elements/scroll_bar.h
@@ -73,6 +73,24 @@ class ScrollBar : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct ScrollBarNode : public ElementNode {
+ using R = ScrollBarNode;
+ ScrollBarNode() : ElementNode("ScrollBar") {}
+ //
+ R& value(float value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& axis(Axis value) {
+ set("axis", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SCROLL_BAR_H_
diff --git a/src/el/elements/scroll_container.h b/src/el/elements/scroll_container.h
index e79863d..b3db2a6 100644
--- a/src/el/elements/scroll_container.h
+++ b/src/el/elements/scroll_container.h
@@ -83,6 +83,32 @@ class ScrollContainer : public Element {
};
} // namespace elements
+namespace dsl {
+
+using el::elements::ScrollMode;
+
+struct ScrollContainerNode : public ElementNode {
+ using R = ScrollContainerNode;
+ ScrollContainerNode(std::vector children = {})
+ : ElementNode("ScrollContainer", {}, std::move(children)) {}
+ //
+ R& adapt_content(bool value) {
+ set("adapt-content", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& adapt_to_content(bool value) {
+ set("adapt-to-content", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& scroll_mode(ScrollMode value) {
+ set("scroll-mode", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SCROLL_CONTAINER_H_
diff --git a/src/el/elements/separator.h b/src/el/elements/separator.h
index 4fa56a7..ed7886e 100644
--- a/src/el/elements/separator.h
+++ b/src/el/elements/separator.h
@@ -26,6 +26,14 @@ class Separator : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct SeparatorNode : public ElementNode {
+ using R = SeparatorNode;
+ SeparatorNode() : ElementNode("Separator") {}
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SEPARATOR_H_
diff --git a/src/el/elements/slider.h b/src/el/elements/slider.h
index ba731b8..61fbd4a 100644
--- a/src/el/elements/slider.h
+++ b/src/el/elements/slider.h
@@ -62,6 +62,34 @@ class Slider : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct SliderNode : public ElementNode {
+ using R = SliderNode;
+ SliderNode() : ElementNode("Slider") {}
+ //
+ R& value(float value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& min(float value) {
+ set("min", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& max(float value) {
+ set("max", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& axis(Axis value) {
+ set("axis", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SLIDER_H_
diff --git a/src/el/elements/spin_box.h b/src/el/elements/spin_box.h
index 5fae375..48c8f32 100644
--- a/src/el/elements/spin_box.h
+++ b/src/el/elements/spin_box.h
@@ -59,6 +59,35 @@ class SpinBox : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct SpinBoxNode : public ElementNode {
+ using R = SpinBoxNode;
+ SpinBoxNode() : ElementNode("SpinBox") {}
+ SpinBoxNode(int32_t default_value, int32_t min_value, int32_t max_value)
+ : ElementNode("SpinBox") {
+ value(default_value);
+ min(min_value);
+ max(max_value);
+ }
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& min(int32_t value) {
+ set("min", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& max(int32_t value) {
+ set("max", value);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SPIN_BOX_H_
diff --git a/src/el/elements/split_container.h b/src/el/elements/split_container.h
index 6077f41..b182b35 100644
--- a/src/el/elements/split_container.h
+++ b/src/el/elements/split_container.h
@@ -85,6 +85,46 @@ class SplitContainer : public Element {
};
} // namespace elements
+namespace dsl {
+
+using el::elements::FixedPane;
+
+struct SplitContainerNode : public ElementNode {
+ using R = SplitContainerNode;
+ SplitContainerNode(std::vector children = {})
+ : ElementNode("SplitContainer", {}, std::move(children)) {}
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& min(int32_t value) {
+ set("min", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& max(int32_t value) {
+ set("max", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& axis(Axis value) {
+ set("axis", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& fixed_pane(FixedPane value) {
+ set("fixed", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ SplitContainerNode& pane(Node content) {
+ parse_node_->Add(content.parse_node());
+ return *this;
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_SPLIT_CONTAINER_H_
diff --git a/src/el/elements/tab_container.h b/src/el/elements/tab_container.h
index 020f06f..3bcb688 100644
--- a/src/el/elements/tab_container.h
+++ b/src/el/elements/tab_container.h
@@ -75,6 +75,33 @@ class TabContainer : public Element {
};
} // namespace elements
+namespace dsl {
+
+struct TabContainerNode : public ElementNode {
+ using R = TabContainerNode;
+ TabContainerNode(std::vector children = {})
+ : ElementNode("TabContainer", {}, std::move(children)) {}
+ //
+ R& value(int32_t value) {
+ set("value", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& align(Align value) {
+ set("align", el::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ // content?
+ // root?
+ TabContainerNode& tab(Node tab_button, Node tab_content) {
+ auto tabs_node = GetOrCreateNode("tabs");
+ tabs_node->Add(tab_button.parse_node());
+ parse_node_->Add(tab_content.parse_node());
+ return *this;
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_TAB_CONTAINER_H_
diff --git a/src/el/elements/text_box.h b/src/el/elements/text_box.h
index 0dee4ea..6af4957 100644
--- a/src/el/elements/text_box.h
+++ b/src/el/elements/text_box.h
@@ -218,6 +218,65 @@ class TextBox : public Element,
};
} // namespace elements
+namespace dsl {
+
+using el::elements::EditType;
+
+struct TextBoxNode : public ElementNode {
+ using R = TextBoxNode;
+ TextBoxNode(const char* text = nullptr) : ElementNode("TextBox") {
+ if (text) {
+ this->text(text);
+ }
+ }
+ //
+ R& text(std::string value) {
+ set("text", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_multiline(bool value) {
+ set("multiline", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_styling(bool value) {
+ set("styling", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_read_only(bool value) {
+ set("readonly", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& is_wrapping(bool value) {
+ set("wrap", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& adapt_to_content(bool value) {
+ set("adapt-to-content", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& virtual_width(Dimension value) {
+ set("virtual-width", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& placeholder(std::string value) {
+ set("placeholder", value);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& type(EditType value) {
+ set("type", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_EDITFIELD_H
diff --git a/src/el/elements/toggle_container.h b/src/el/elements/toggle_container.h
index 97c4d87..d372727 100644
--- a/src/el/elements/toggle_container.h
+++ b/src/el/elements/toggle_container.h
@@ -64,6 +64,31 @@ class ToggleContainer : public Element {
};
} // namespace elements
+namespace dsl {
+
+using el::elements::ToggleAction;
+
+struct ToggleContainerNode : public ElementNode {
+ using R = ToggleContainerNode;
+ ToggleContainerNode() : ElementNode("ToggleContainer") {}
+ //
+ R& value(bool value) {
+ set("value", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+ //
+ R& toggle_action(ToggleAction value) {
+ set("toggle", el::elements::to_string(value));
+ return *reinterpret_cast(this);
+ }
+ //
+ R& invert(bool value) {
+ set("invert", value ? 1 : 0);
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_ELEMENTS_TOGGLE_CONTAINER_H_
diff --git a/src/el/io/memory_file_system.cc b/src/el/io/memory_file_system.cc
index 72a6e34..d39c610 100644
--- a/src/el/io/memory_file_system.cc
+++ b/src/el/io/memory_file_system.cc
@@ -43,7 +43,7 @@ class MemoryFile : public File {
MemoryFileSystem::MemoryFileSystem() = default;
void MemoryFileSystem::AddFile(std::string filename, const void* data,
- size_t length) {
+ size_t length) {
file_entries_[filename] = {reinterpret_cast(data), length};
}
@@ -53,7 +53,7 @@ std::unique_ptr MemoryFileSystem::OpenRead(std::string filename) {
return nullptr;
}
return std::make_unique(it->first, it->second.first,
- it->second.second);
+ it->second.second);
}
} // namespace io
diff --git a/src/el/list_item.h b/src/el/list_item.h
index 382dbe8..0288691 100644
--- a/src/el/list_item.h
+++ b/src/el/list_item.h
@@ -13,6 +13,7 @@
#include
#include
+#include "el/dsl.h"
#include "el/id.h"
#include "el/util/intrusive_list.h"
#include "el/value.h"
@@ -237,6 +238,101 @@ class GenericStringItemSource : public ListItemSourceList {
GenericStringItemSource* target_source);
};
+namespace dsl {
+
+struct Item {
+ std::string id;
+ std::string text;
+ Item(std::string id, std::string text) : id(id), text(text) {}
+};
+
+template
+struct ItemListElementNode : public ElementNode {
+ protected:
+ ItemListElementNode(const char* name) : ElementNode(name) {}
+
+ public:
+ T& item(std::string text) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ auto node = ParseNode::Create("item");
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(text.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ return *reinterpret_cast(this);
+ }
+ T& item(std::string id, std::string text) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ auto node = ParseNode::Create("item");
+ auto id_node = ParseNode::Create("id");
+ id_node->TakeValue(el::Value(id.c_str()));
+ node->Add(id_node);
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(text.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ return *reinterpret_cast(this);
+ }
+ T& item(int32_t id, std::string text) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ auto node = ParseNode::Create("item");
+ auto id_node = ParseNode::Create("id");
+ id_node->TakeValue(el::Value(id));
+ node->Add(id_node);
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(text.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ return *reinterpret_cast(this);
+ }
+ T& items(std::initializer_list items) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ for (auto& item : items) {
+ auto node = ParseNode::Create("item");
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(item.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ }
+ return *reinterpret_cast(this);
+ }
+ T& items(std::initializer_list> items) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ for (auto& item : items) {
+ auto node = ParseNode::Create("item");
+ auto id_node = ParseNode::Create("id");
+ id_node->TakeValue(el::Value(item.first));
+ node->Add(id_node);
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(item.second.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ }
+ return *reinterpret_cast(this);
+ }
+ T& items(std::initializer_list- items) {
+ using parsing::ParseNode;
+ auto items_node = GetOrCreateNode("items");
+ for (auto& item : items) {
+ auto node = ParseNode::Create("item");
+ auto id_node = ParseNode::Create("id");
+ id_node->TakeValue(el::Value(item.id.c_str()));
+ node->Add(id_node);
+ auto text_node = ParseNode::Create("text");
+ text_node->TakeValue(el::Value(item.text.c_str()));
+ node->Add(text_node);
+ items_node->Add(node);
+ }
+ return *reinterpret_cast(this);
+ }
+};
+
+} // namespace dsl
} // namespace el
#endif // EL_LIST_ITEM_H_
diff --git a/src/el/util/object.h b/src/el/util/object.h
index 0daa55d..d6c9c9f 100644
--- a/src/el/util/object.h
+++ b/src/el/util/object.h
@@ -67,7 +67,7 @@ const T* SafeCast(const TypedObject* obj) {
// Implements the methods for safe typecasting without requiring RTTI.
#define TBOBJECT_SUBCLASS(clazz, baseclazz) \
- const char* GetTypeName() const override { return #clazz; } \
+ const char* GetTypeName() const override { return #clazz; } \
bool IsOfTypeId(const el::util::tb_type_id_t type_id) const override { \
return el::util::TypedObject::GetTypeId() == type_id \
? true \
diff --git a/testbed/resources/test_ui.tb.txt b/testbed/resources/test_ui.tb.txt
index 039a9e2..f8ee673 100644
--- a/testbed/resources/test_ui.tb.txt
+++ b/testbed/resources/test_ui.tb.txt
@@ -23,6 +23,7 @@ LayoutBox: axis: y, distribution-position: "left top", distribution: "available"
Button: skin: "Button.flat", text: "Close with dim & alert", id: "Window.close"
Button: skin: "Button.flat", text: "Fullscreen Window", id: "test-fullscreen-window"
Button: skin: "Button.flat", text: "ResourceEditWindow", id: "test-resource-edit"
+ Button: skin: "Button.flat", text: "DSL", id: "test-dsl"
GroupBox: value: 0, text: "Layout tests"
LayoutBox: axis: y, spacing: 0, size: available
diff --git a/testbed/testbed_application.cc b/testbed/testbed_application.cc
index bbb427b..2dc54b8 100644
--- a/testbed/testbed_application.cc
+++ b/testbed/testbed_application.cc
@@ -734,6 +734,87 @@ class FullScreenWindow : public DemoWindow {
}
};
+class DslWindow : public DemoWindow {
+ public:
+ DslWindow() {
+ set_size(640, 480);
+
+ using namespace el::dsl;
+ auto node = LayoutBoxNode()
+ .axis(Axis::kX)
+ .child(LabelNode("foo"))
+ .child(LabelNode("bar"));
+ LoadNodeTree(node);
+
+ LoadNodeTree(BuildUI());
+ }
+
+ dsl::Node BuildSomeControl() {
+ using namespace el::dsl;
+ return LayoutBoxNode()
+ .axis(Axis::kX)
+ .child(LabelNode("foo"))
+ .child(LabelNode("bar"));
+ }
+ dsl::Node BuildUI() {
+ using namespace el::dsl;
+ auto rep_tree = LayoutBoxNode().axis(Axis::kX).child_list({
+ LabelNode("item"), ButtonNode("button"),
+ });
+ return LayoutBoxNode()
+ .id("foo")
+ .position(LayoutPosition::kLeftTop)
+ .axis(Axis::kY)
+ .children(
+ TabContainerNode()
+ .gravity(Gravity::kAll)
+ .axis(Axis::kX)
+ .tab(ButtonNode("Foo"), CloneNode(rep_tree))
+ .tab(ButtonNode("Foo0"), CloneNode(rep_tree))
+ .tab(ButtonNode("Foo1"), LabelNode("bar1")),
+ GroupBoxNode("controls:")
+ .content(LayoutBoxNode()
+ .child(BuildSomeControl())
+ .child(BuildSomeControl())),
+ LabelNode("distribution: preferred").width(32_dp).font_size(3_mm),
+ LayoutBoxNode()
+ .distribution(LayoutDistribution::kPreferred)
+ .child(ButtonNode("tab 0"))
+ .child(TextBoxNode("foo").type(EditType::kPassword))
+ .children(ToggleContainerNode()
+ .value(true)
+ .toggle_action(ToggleAction::kExpanded)
+ .child(TextBoxNode()
+ .placeholder("@search")
+ .gravity(Gravity::kLeftRight)
+ .type(EditType::kSearch)),
+ ButtonNode("fffoo").is_enabled(false))
+ .child(ButtonNode("tab 0"))
+ .child(ButtonNode("tab 0"))
+ .clone(rep_tree)
+ .children(ButtonNode("tab 1"), ButtonNode("tab 2"),
+ ButtonNode("tab 3"), ButtonNode("tab 4")),
+ SpinBoxNode(4, 0, 40), ListBoxNode().items({
+ {1, "a"}, {2, "b"},
+ }),
+ ListBoxNode().item("a").item("id", "b").item(5, "c"),
+ DropDownButtonNode().value(1).items({
+ Item("1", "a"), Item("2", "b"),
+ }),
+ DropDownButtonNode().value(1).items({
+ {1, "a"}, {2, "b"},
+ }),
+ DropDownButtonNode().value(1).items({
+ "a", "b", "c",
+ }));
+ }
+
+ bool OnEvent(const Event& ev) override {
+ //
+ return DemoWindow::OnEvent(ev);
+ }
+};
+
// == MainWindow ==============================================================
MainWindow::MainWindow() {
@@ -865,6 +946,9 @@ bool MainWindow::OnEvent(const Event& ev) {
res_edit_win->Load("resource_edit_test.tb.txt");
parent()->AddChild(res_edit_win);
return true;
+ } else if (ev.target->id() == TBIDC("test-dsl")) {
+ new DslWindow();
+ return true;
} else if (ev.type == EventType::kClick &&
ev.target->id() == TBIDC("debug settings")) {
#ifdef EL_RUNTIME_DEBUG_INFO