diff --git a/demo/data/gui.yaml b/demo/data/gui.yaml new file mode 100644 index 0000000000000000000000000000000000000000..44aeecf791e0d7cc96de67097bf0be3d2c237a52 --- /dev/null +++ b/demo/data/gui.yaml @@ -0,0 +1,17 @@ +--- +- define: label + attrs: + value: """ +- define: button + children: + - template: label +- define: textinput + children: + - template: label +- define: checkbox + attrs: + state: False +- define: radio + attrs: + state: False +- define: frame diff --git a/demo/demo/grid.cpp b/demo/demo/grid.cpp index 9f373c5d12517cc50dcecd249a471b1e79a3de5b..b3c2760db6ca05d813ae4b330f028e29912a84bf 100644 --- a/demo/demo/grid.cpp +++ b/demo/demo/grid.cpp @@ -20,6 +20,7 @@ #include "fggl/assets/loader.hpp" #include "fggl/entity/gridworld/zone.hpp" +#include "fggl/gui/renderer/renderer.hpp" using namespace fggl::gfx::colours; @@ -223,6 +224,30 @@ namespace demo { } } } + + // UI test + fggl::gui::model::Widget widget; + widget.set("position", fggl::math::vec2{200.0F, 100.F}); + widget.set("size", fggl::math::vec2{500.0F, 300.F}); + fggl::gui::model::attr_box_set(widget, "padding", 5.0F); + widget.set("colour", fggl::gfx::colours::BLANCHED_ALMOND); + + fggl::gui::model::Widget handle; + handle.set("border::bottom",5.0F); + handle.set("position", fggl::math::vec2{0.0F, 0.0F}); + handle.set("size", fggl::math::vec2{INFINITY, 50.0F}); + handle.set("text", "hello, world!"); + fggl::gui::model::attr_box_set(handle, "padding", 5.0F); + handle.set("colour", fggl::gfx::colours::ORANGE); + widget.addChild(handle); + + fggl::gui::model::Widget content; + content.set("position", fggl::math::vec2{0.0F, 50.0F}); + content.set("size", fggl::math::vec2{INFINITY, INFINITY}); + content.set("colour", fggl::gfx::colours::BURLYWOOD); + widget.addChild(content); + + fggl::gui::renderer::visit(widget, paint); } diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index 9007ff0d49d0f172ea7070313964b20e5240723e..28461506a0a3726505142e16739aa3799fbc2445 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -56,12 +56,10 @@ target_sources(${PROJECT_NAME} input/input.cpp input/mouse.cpp input/camera_input.cpp +) - gui/widget.cpp - gui/widgets.cpp - gui/containers.cpp - gui/fonts.cpp - ) +# GUI support +add_subdirectory(gui) # yaml-cpp for configs and storage find_package(yaml-cpp) @@ -70,9 +68,6 @@ target_link_libraries(fggl PUBLIC yaml-cpp) # model loading add_subdirectory(data/assimp) -find_package(Freetype) -target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype) - # Graphics backend add_subdirectory(gfx) add_subdirectory(audio) diff --git a/fggl/gui/CMakeLists.txt b/fggl/gui/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..dc776db0f7946050b112c747864b63c78a30d022 --- /dev/null +++ b/fggl/gui/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources( ${PROJECT_NAME} + PRIVATE + widget.cpp + widgets.cpp + containers.cpp + fonts.cpp + + model/parser.cpp + model/structure.cpp + renderer/renderer.cpp +) + +find_package(Freetype) +target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype) + diff --git a/fggl/gui/model/parser.cpp b/fggl/gui/model/parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ab42e05c7a4ffc7833d1dbfc422bca3e616998ba --- /dev/null +++ b/fggl/gui/model/parser.cpp @@ -0,0 +1,59 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 11/03/23. +// + +#include "fggl/gui/model/parser.hpp" +#include <yaml-cpp/yaml.h> + +namespace fggl::gui::model { + + Widget* YamlToWidgetTree(WidgetFactory& factory, const YAML::Node& config) { + Widget* root; + if ( config["template"] ) { + root = factory.build( config["template"].as<std::string>()); + } else { + root = factory.buildEmpty(); + } + + // deal with attrs + for ( auto attr : config["attrs"] ) { + root->set(attr.first.as<std::string>(), attr.second.as<std::string>()); + } + + // deal with child nodes + for ( auto child : config["children"] ) { + Widget* childWidget = YamlToWidgetTree(factory, child); + root->addChild(*childWidget); + } + + // are we a template definition? + if ( config["define"] ) { + factory.push( config["define"].as<std::string>(), *root ); + } + + return root; + } + + inline Widget* parseFile(WidgetFactory& factory, const std::string& path) { + YAML::Node root = YAML::LoadFile(path); + if ( !root ){ + return nullptr; + } + return YamlToWidgetTree(factory, root); + } + +} \ No newline at end of file diff --git a/fggl/gui/model/structure.cpp b/fggl/gui/model/structure.cpp new file mode 100644 index 0000000000000000000000000000000000000000..baf5d17f5ce5936b5b91c3b1dd2cefe66833afce --- /dev/null +++ b/fggl/gui/model/structure.cpp @@ -0,0 +1,23 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 11/03/23. +// + +#include "fggl/gui/model/structure.hpp" + +namespace fggl::gui::model { + +} \ No newline at end of file diff --git a/fggl/gui/renderer/renderer.cpp b/fggl/gui/renderer/renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8c6d98848e65da825db3aceeea1e192d2cd1b813 --- /dev/null +++ b/fggl/gui/renderer/renderer.cpp @@ -0,0 +1,107 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 11/03/23. +// + +#include "fggl/gui/renderer/renderer.hpp" + +namespace fggl::gui::renderer { + + void draw_box(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 bottomRight) { + path.moveTo({topLeft.x, topLeft.y}); + path.pathTo({bottomRight.x, topLeft.y}); + path.pathTo({bottomRight.x, bottomRight.y}); + path.pathTo({topLeft.x, bottomRight.y}); + path.pathTo({topLeft.x, topLeft.y}); + } + + void draw_border_patch(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour) { + gfx::Path2D path({0,0}); + + path.colour(colour); + // draw edges + draw_box(path, {bounds.left + size.left, bounds.top}, {bounds.right - size.right, bounds.top + size.top} ); + draw_box(path, {bounds.right - size.right, bounds.top + size.top}, {bounds.right, bounds.bottom - size.bottom} ); + draw_box(path, {bounds.left + size.left, bounds.bottom - size.bottom}, {bounds.right - size.right, bounds.bottom} ); + draw_box(path, {bounds.left, bounds.top + size.top}, {bounds.left + size.left, bounds.bottom - size.bottom} ); + + // draw-corners + draw_box(path, {bounds.left, bounds.top}, {bounds.left + size.left, bounds.top + size.top} ); + draw_box(path, {bounds.right - size.right, bounds.top}, {bounds.right, bounds.top + size.top} ); + draw_box(path, {bounds.left, bounds.bottom - size.bottom}, {bounds.left + size.left, bounds.bottom} ); + draw_box(path, {bounds.right - size.right, bounds.bottom - size.bottom}, {bounds.right, bounds.bottom}); + paint.fill(path); + } + + void draw_border_solid(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour) { + gfx::Path2D path({0,0}); + + path.colour(colour); + // draw edges + draw_box(path, {bounds.left, bounds.top}, {bounds.right, bounds.top + size.top} ); + draw_box(path, {bounds.right - size.right, bounds.top + size.top}, {bounds.right, bounds.bottom - size.bottom} ); + draw_box(path, {bounds.left, bounds.bottom - size.bottom}, {bounds.right, bounds.bottom} ); + draw_box(path, {bounds.left, bounds.top + size.top}, {bounds.left + size.left, bounds.bottom - size.bottom} ); + + paint.fill(path); + } + + void draw_background_solid(gfx::Paint& paint, Box& bounds, math::vec3 colour) { + gfx::Path2D path({0,0}); + path.colour(colour); + draw_box(path, {bounds.left, bounds.top}, {bounds.right, bounds.bottom} ); + paint.fill(path); + } + + void visit(const model::Widget& root, gfx::Paint& paint, Box offset) { + // get border size + auto border = get_box(root, "border"); + + // calculate box bounds + auto pos = get_vec2(root, "position"); + auto size = get_vec2(root, "size"); + + auto bounds = getBounds(pos, size); + bounds.top += offset.top; + bounds.left += offset.left; + + // deal with right hand size bounds + bounds.right = std::min( size.x, offset.width() ); + bounds.right += offset.left; + + // deal with bottom bounds + bounds.bottom = std::min( size.y, offset.height() ); + bounds.bottom += offset.top; + + auto background = bounds.trim(border); + + draw_background_solid(paint, background, get_vec3_rgb(root, "colour")); + draw_border_patch(paint, bounds, border, get_vec3_rgb(root, "border::colour")); + + auto padding = get_box(root, "padding"); + background = background.trim(padding); + + if ( root.hasAttr("text") ) { + auto text = root.get<std::string>("text"); + paint.text(text, {background.left, background.top + 15}); + } + + for (const auto& child : root) { + visit(child, paint, background); + } + } + +} // namespace fggl::gui:;renderer \ No newline at end of file diff --git a/include/fggl/gui/model/parser.hpp b/include/fggl/gui/model/parser.hpp new file mode 100644 index 0000000000000000000000000000000000000000..608aaa70ff4f82d16a25bf4c0c261d13e7860b3a --- /dev/null +++ b/include/fggl/gui/model/parser.hpp @@ -0,0 +1,48 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 04/03/23. +// + +#ifndef FGGL_GUI_MODEL_PARSER_HPP +#define FGGL_GUI_MODEL_PARSER_HPP + +#include <fggl/gui/model/structure.hpp> + +namespace fggl::gui::model { + + class WidgetFactory { + public: + inline Widget* build(std::string templateName) { + return new Widget(m_templates.at(templateName)); + } + + inline Widget* buildEmpty() { + return new Widget(); + } + + inline void push(std::string name, const Widget& definition) { + m_templates[name] = definition; + } + + private: + std::map<std::string, Widget> m_templates; + }; + + Widget* parseFile(WidgetFactory& factory, const std::string& path); + +} // namespace fggl::gui::model + +#endif //FGGL_GUI_MODEL_PARSER_HPP diff --git a/include/fggl/gui/model/structure.hpp b/include/fggl/gui/model/structure.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a8cd119bbf348585e47089da3adb65c47c0ce825 --- /dev/null +++ b/include/fggl/gui/model/structure.hpp @@ -0,0 +1,128 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 04/03/23. +// + +#ifndef FGGL_GUI_MODEL_STRUCTURE_HPP +#define FGGL_GUI_MODEL_STRUCTURE_HPP + +#include <map> +#include <variant> +#include <vector> +#include <string> + +#include <fggl/math/types.hpp> + +namespace fggl::gui::model { + + using AttrKey = std::string; + using AttrValue = std::variant<bool, int, float, std::string, math::vec2, math::vec3>; + + class Widget { + public: + using ChildItr = std::vector<Widget>::iterator; + using ChildItrConst = std::vector<Widget>::const_iterator; + + inline bool isRoot() const { + return m_parent == nullptr; + } + + inline bool isLeaf() const { + return m_children.empty(); + } + + inline bool hasAttr(const AttrKey& key) const { + return m_attrs.contains(key); + } + + inline std::map<AttrKey, AttrValue> attrs() { + return m_attrs; + } + + template<typename type> + inline type get(const AttrKey& key) const { + return std::get<type>(m_attrs.at(key)); + } + + template<typename type> + type get_or_default(const AttrKey& key) const { + auto itr = m_attrs.find(key); + if ( itr == m_attrs.end() ) { + return type(); + } else { + return std::get<type>(itr->second); + } + } + + template<typename type> + inline void set(const AttrKey& key, type value) { + m_attrs[key] = value; + } + + inline void addChild(const Widget& element) { + auto childItr = m_children.insert( m_children.cend(), element ); + childItr->m_parent = this; + } + + inline ChildItr begin() { + return m_children.begin(); + } + + inline ChildItrConst begin() const noexcept { + return m_children.begin(); + } + + inline ChildItrConst cbegin() const noexcept { + return m_children.cbegin(); + } + + inline ChildItr end() { + return m_children.end(); + } + + inline ChildItrConst end() const { + return m_children.end(); + } + + inline ChildItrConst cend() const { + return m_children.cend(); + } + + private: + Widget *m_parent; + std::vector<Widget> m_children; + std::map< AttrKey, AttrValue > m_attrs; + }; + + void attr_box_set(Widget& widget, const AttrKey& key, auto top, auto right, auto bottom, auto left) { + widget.set(key + "::top", top); + widget.set(key + "::right", right); + widget.set(key + "::bottom", bottom); + widget.set(key + "::left", left); + } + + inline void attr_box_set(Widget& widget, const AttrKey& key, auto vert, auto horz) { + attr_box_set(widget, key, vert, horz, vert, horz); + } + + inline void attr_box_set(Widget& widget, const AttrKey& key, auto value) { + attr_box_set(widget, key, value, value, value, value); + } + + +} // namespace fggl::gui::model + +#endif //FGGL_GUI_MODEL_STRUCTURE_HPP diff --git a/include/fggl/gui/renderer/renderer.hpp b/include/fggl/gui/renderer/renderer.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c90d8034c9ac6af88c4f1fab4ebe71a098adb4d8 --- /dev/null +++ b/include/fggl/gui/renderer/renderer.hpp @@ -0,0 +1,104 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 04/03/23. +// + +#ifndef FGGL_GUI_RENDERER_RENDERER_HPP +#define FGGL_GUI_RENDERER_RENDERER_HPP + +#include <fggl/gui/model/structure.hpp> +#include <fggl/gfx/paint.hpp> + +namespace fggl::gui::renderer { + + struct Box { + float top; + float right; + float bottom; + float left; + + inline Box trim(Box border) const { + return { + top + border.top, + right - border.right, + bottom - border.bottom, + left + border.left + }; + } + + inline Box expand(Box border) const { + return { + top - border.top, + right + border.right, + bottom + border.bottom, + left - border.left + }; + } + + inline float width() const { + return right - left; + } + + inline float height() const { + return bottom - top; + } + }; + + inline math::vec2 get_vec2(const model::Widget& root, const std::string& name) { + if ( !root.hasAttr(name) ) { + return {}; + } + return root.get<math::vec2>(name); + } + + inline math::vec3 get_vec3_rgb(const model::Widget& root, const std::string& name) { + if ( !root.hasAttr(name) ) { + return {}; + } + return root.get<math::vec3>(name); + } + + inline Box get_box(const model::Widget& root, const std::string& name) { + return { + root.get_or_default<float>(name + "::top"), + root.get_or_default<float>(name + "::right"), + root.get_or_default<float>(name + "::bottom"), + root.get_or_default<float>(name + "::left") + }; + } + + inline Box getBounds(math::vec2 pos, math::vec2 size) { + return { + pos.y, + pos.x + size.x, + pos.y + size.y, + pos.x + }; + } + + void draw_box(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 bottomRight); + + void draw_border_patch(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour); + + void draw_border_solid(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour); + + void draw_background_solid(gfx::Paint& paint, Box& bounds, math::vec3 colour); + + void visit(const model::Widget& root, gfx::Paint& paint, Box offset = {0.0F, 1024.0F, 768.0F, 0.0F}); + +} // namespace fggl::gui::renderer + +#endif //FGGL_GUI_RENDERER_RENDERER_HPP