From d351dfa9a820fca7a9a5c416f0d8fd77ff7ac616 Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Sun, 4 Sep 2022 17:21:26 +0100 Subject: [PATCH] clean up UI code --- demo/CMakeLists.txt | 1 + demo/demo/grid.cpp | 59 +++++++++++++--------- demo/demo/robot/programmer.cpp | 82 +++++++++++++++++++++++++++++++ demo/include/grid.hpp | 20 ++++---- demo/include/robot/programmer.hpp | 55 +++++++++++++++++++++ fggl/gfx/ogl4/canvas.cpp | 22 ++++----- fggl/gui/containers.cpp | 75 ++++++++++++++++++++++++++++ fggl/gui/widgets.cpp | 15 ++---- fggl/scenes/menu.cpp | 18 +------ include/fggl/gfx/paint.hpp | 5 +- include/fggl/gui/containers.hpp | 22 +++++++++ include/fggl/gui/widget.hpp | 18 ++++++- include/fggl/gui/widgets.hpp | 11 +++-- 13 files changed, 322 insertions(+), 81 deletions(-) create mode 100644 demo/demo/robot/programmer.cpp create mode 100644 demo/include/robot/programmer.hpp diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index ab4c435..bf34d0c 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(demo demo/rollball.cpp demo/topdown.cpp demo/grid.cpp + demo/robot/programmer.cpp ) diff --git a/demo/demo/grid.cpp b/demo/demo/grid.cpp index 00368b8..0099eef 100644 --- a/demo/demo/grid.cpp +++ b/demo/demo/grid.cpp @@ -106,9 +106,11 @@ namespace demo { std::function<void(void)> callback; }; - GridScene::GridScene(fggl::App &app) : GameBase(app), m_tiles(), m_animator(15.0F), m_grid(nullptr) { + GridScene::GridScene(fggl::App &app) : GameBase(app), m_tiles(), m_animator(15.0F), m_grid(nullptr), m_canvas() { m_animator.add([this](){this->tickPlayer();}); + auto btnGrid = std::make_unique<fggl::gui::GridBox>(0, 2); + std::array<Action, 4> actions{{ {"<", [=]() { this->rotate(true); }}, {">", [=]() { this->rotate(false); }}, @@ -122,20 +124,14 @@ namespace demo { auto btn = std::make_unique<fggl::gui::Button>(pos, size); btn->label(action.name); btn->addCallback([=](){ - this->m_program.m_instructions.push_back(action.callback); + this->m_program.m_instructions.push_back({action.name, action.callback}); }); - m_canvas.add(std::move(btn)); - - if ( pos.x == 0 ) { - pos.x += 32 + 8; - } else { - pos.x = 0; - pos.y += 32 + 8; - } + btnGrid->add(std::move(btn)); } + // control buttons { - fggl::math::vec2i size{32, 32}; + fggl::math::vec2i size{64, 32}; auto btn = std::make_unique<fggl::gui::Button>(pos, size); btn->label("go"); btn->addCallback([=](){ @@ -145,9 +141,32 @@ namespace demo { this->m_program.playing = true; } }); - m_canvas.add(std::move(btn)); + btnGrid->add(std::move(btn)); + } + + { + fggl::math::vec2i size{64, 64}; + auto btn = std::make_unique<fggl::gui::Button>(pos, size); + btn->label("Del"); + btn->addCallback([=](){ + if ( !this->m_program.playing ) { + if ( !m_program.m_instructions.empty() ) { + m_program.m_instructions.pop_back(); + } + } + }); + btnGrid->add(std::move(btn)); } + btnGrid->layout(); + m_canvas.add(std::move(btnGrid)); + + // create a timeline panel + std::unique_ptr<robot::Timeline> timeline = std::make_unique<robot::Timeline>(m_program); + timeline->size({50,700}, {250, 250}); + m_canvas.add(std::move(timeline)); + + m_canvas.layout(); } void GridScene::activate() { @@ -239,18 +258,9 @@ namespace demo { fggl::math::rescale_ndc(cursorPos.x, 0, 1920.f), fggl::math::rescale_ndc(cursorPos.y, 0, 1080.0f) }; + canvas.onMouseOver(projected); - auto *hoverWidget = canvas.getChildAt(projected); - /*if (hoverWidget != m_hover) { - if (m_hover != nullptr) { - m_hover->onExit(); - } - m_hover = hoverWidget; - if (m_hover != nullptr) { - m_hover->onEnter(); - } - }*/ - + // detect clicks if (inputs.mouse.pressed(fggl::input::MouseButton::LEFT)) { auto* widget = canvas.getChildAt(projected); if (widget != nullptr) { @@ -264,6 +274,7 @@ namespace demo { GameBase::update(deltaTime); m_animator.update(deltaTime); + m_canvas.update(deltaTime); update_canvas(input(), m_canvas); } @@ -281,7 +292,7 @@ namespace demo { } robotState.power--; - m_program.m_instructions[ m_program.m_currInstruction ](); + m_program.m_instructions[ m_program.m_currInstruction ].m_func(); m_program.m_currInstruction++; } else { m_program.playing = false; diff --git a/demo/demo/robot/programmer.cpp b/demo/demo/robot/programmer.cpp new file mode 100644 index 0000000..6bdadcd --- /dev/null +++ b/demo/demo/robot/programmer.cpp @@ -0,0 +1,82 @@ +/* + * 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/09/22. +// + +#include "robot/programmer.hpp" + +namespace demo::robot { + + Timeline::Timeline(Program& program) { + m_tracks.push_back(std::ref(program)); + } + + void Timeline::update(float deltaTime) { + auto currSize = size(); + + float trackHeight = m_tracks.size() * 32.0F; + std::size_t widestTrack = 0; + for ( auto& track : m_tracks ) { + widestTrack = std::max(widestTrack, track.get().m_instructions.size()); + } + + float instructionWidth = 32 * widestTrack; + if ( currSize.x < instructionWidth || currSize.y < trackHeight ) { + size( topLeft(), {instructionWidth, trackHeight} ); + } + } + + void Timeline::render(fggl::gfx::Paint &paint) { + fggl::gui::Panel::render(paint); + + renderInstructions(paint); + } + + void Timeline::renderInstructions(fggl::gfx::Paint& paint) { + const auto size = this->size(); + const fggl::math::vec2f barExtents{16, 16}; + + for ( auto track=0U; track < m_tracks.size(); ++track) { + auto& trackRef = m_tracks[track].get(); + + for (auto i = 0U; i < trackRef.m_instructions.size(); ++i) { + auto barCenter = this->topLeft(); + + barCenter.x += (i * (barExtents.x * 2) ) + barExtents.x; + barCenter.y += (track * barExtents.y * 2) + barExtents.y; + + // bar background + auto colour = fggl::gfx::colours::LIGHT_GRAY; + auto textColour = fggl::gfx::colours::DARK_SLATE_GRAY; + if (i % 2 == 0) { + colour = fggl::gfx::colours::WHITE; + } + + if (i == trackRef.m_currInstruction && trackRef.playing) { + colour = fggl::gfx::colours::MIDNIGHT_BLUE; + textColour = fggl::gfx::colours::LIGHT_GRAY; + } + auto rect = fggl::gfx::make_rect(barCenter, barExtents, colour); + paint.fill(rect); + + // bar instruction + auto& instruction = trackRef.m_instructions[i]; + paint.text(instruction.name, barCenter, textColour); + } + } + } + +} diff --git a/demo/include/grid.hpp b/demo/include/grid.hpp index be9561a..158c56d 100644 --- a/demo/include/grid.hpp +++ b/demo/include/grid.hpp @@ -26,6 +26,7 @@ #include "fggl/animation/animator.hpp" #include "fggl/gui/gui.hpp" +#include "robot/programmer.hpp" namespace demo { @@ -46,12 +47,6 @@ namespace demo { float rotationOffset{0.0F}; }; - struct Program { - std::vector<std::function<void(void)>> m_instructions; - uint32_t m_currInstruction; - bool playing = false; - }; - struct RobotState { uint32_t power = 64; }; @@ -65,14 +60,19 @@ namespace demo { void update(float dt) override; void render(fggl::gfx::Graphics& gfx) override; private: + + // level + LevelRules m_levelRules; fggl::entity::grid::TileSet m_tiles; - fggl::animation::FrameAnimator m_animator; std::unique_ptr<DemoGrid> m_grid; - fggl::gui::Container m_canvas; - LevelRules m_levelRules; + // control fggl::entity::EntityID m_player = fggl::entity::INVALID; - Program m_program; + robot::Program m_program; + + // UI + fggl::gui::Container m_canvas; + fggl::animation::FrameAnimator m_animator; void resetPuzzle(); diff --git a/demo/include/robot/programmer.hpp b/demo/include/robot/programmer.hpp new file mode 100644 index 0000000..1025c18 --- /dev/null +++ b/demo/include/robot/programmer.hpp @@ -0,0 +1,55 @@ +/* + * 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/09/22. +// + +#ifndef FGGL_DEMO_INCLUDE_ROBOT_PROGRAMMER_HPP +#define FGGL_DEMO_INCLUDE_ROBOT_PROGRAMMER_HPP + +#include <functional> + +#include "fggl/gui/containers.hpp" + +namespace demo::robot { + + struct Instruction { + const char* name; + std::function<void(void)> m_func; + }; + + struct Program { + std::vector<Instruction> m_instructions; + uint32_t m_currInstruction; + bool playing = false; + }; + + class Timeline : public fggl::gui::Panel { + public: + explicit Timeline(Program& program); + + void update(float deltaTime) override; + void render(fggl::gfx::Paint& paint) override; + + protected: + void renderInstructions(fggl::gfx::Paint& paint); + + private: + std::vector<std::reference_wrapper<Program>> m_tracks; + }; + +} + +#endif //FGGL_DEMO_INCLUDE_ROBOT_PROGRAMMER_HPP diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp index 42b20bc..750e884 100644 --- a/fggl/gfx/ogl4/canvas.cpp +++ b/fggl/gfx/ogl4/canvas.cpp @@ -200,20 +200,18 @@ namespace fggl::gfx::ogl4 { // this is why this is called the slow version, we render each quad as a single call auto &metrics = face->metrics(letter); - float xPos = penPos.x + metrics.bearing.x; - float yPos = (penPos.y - metrics.bearing.y); - float w = metrics.size.x; - float h = metrics.size.y; - - const math::vec3 texCol{1.0f, 1.0f, 1.0f}; + const float xPos = penPos.x + metrics.bearing.x; + const float yPos = (penPos.y - metrics.bearing.y); + const float w = metrics.size.x; + const float h = metrics.size.y; std::array<data::Vertex2D, 6> verts{{ - {{xPos, yPos + h}, texCol, {0.0F, 1.0F}}, - {{xPos, yPos}, texCol, {0.0F, 0.0F}}, - {{xPos + w, yPos}, texCol, {1.0F, 0.0F}}, - {{xPos, yPos + h}, texCol, {0.0F, 1.0F}}, - {{xPos + w, yPos}, texCol, {1.0F, 0.0F}}, - {{xPos + w, yPos + h}, texCol, {1.0F, 1.0F}}, + {{xPos, yPos + h}, textCmd.colour, {0.0F, 1.0F}}, + {{xPos, yPos}, textCmd.colour, {0.0F, 0.0F}}, + {{xPos + w, yPos}, textCmd.colour, {1.0F, 0.0F}}, + {{xPos, yPos + h}, textCmd.colour, {0.0F, 1.0F}}, + {{xPos + w, yPos}, textCmd.colour, {1.0F, 0.0F}}, + {{xPos + w, yPos + h}, textCmd.colour, {1.0F, 1.0F}}, }}; m_vertexList.replace(verts.size(), verts.data()); diff --git a/fggl/gui/containers.cpp b/fggl/gui/containers.cpp index c0232dd..089f90e 100644 --- a/fggl/gui/containers.cpp +++ b/fggl/gui/containers.cpp @@ -42,6 +42,81 @@ namespace fggl::gui { m_dirty = true; } + void Container::onMouseOver(math::vec2 pos) { + Widget::onMouseOver(pos); + auto* childHover = getChildAt(pos); + + for ( auto& child : m_children) { + if ( child.get() != childHover ) { + child->onExit(pos); + } + } + + if ( childHover != nullptr ) { + childHover->onMouseOver(pos); + } + } + + void Container::onEnter(math::vec2i pos) { + Widget::onEnter(pos); + for ( auto& child : m_children ) { + if ( child->contains(pos)) { + child->onEnter(pos); + } + } + } + + void Container::onExit(math::vec2i pos) { + Widget::onExit(pos); + for ( auto& child : m_children) { + child->onExit(pos); + } + } + + GridBox::GridBox(uint32_t rows, uint32_t cols, uint32_t padx, uint32_t pady) : m_rows(rows), m_cols(cols), m_padding(padx, pady) {} + + void GridBox::layout() { + assert( m_rows != 0 || m_cols != 0 ); + + if ( m_rows == 0 ) { + int rows = m_children.size() / m_cols; + + // figure out the width and heights + float* widths = new float[m_cols]{0.0F}; + float* heights = new float[rows]{0.0F}; + for ( auto idx = 0U; idx < m_children.size(); ++idx) { + auto& child = m_children[idx]; + int col = idx % m_cols; + int row = idx / m_cols; + + widths[col] = std::max( child->size().x, widths[col] ); + heights[row] = std::max( child->size().y, heights[row] ); + } + + // populate the grid + fggl::math::vec2i pos{0, 0}; + int row = 0; + int col = 0; + for ( auto& child : m_children ) { + fggl::math::vec2i size{ widths[col], heights[row] }; + child->size(pos, size); + child->layout(); + + // next iter + pos.x += size.x + m_padding.x; + col++; + if ( col == m_cols ) { + col = 0; + row++; + pos.x = 0; + pos.y += size.y + m_padding.y; + } + } + + } + } + + /* Box::Box( LayoutAxis axis ) : m_axis( axis ) {} diff --git a/fggl/gui/widgets.cpp b/fggl/gui/widgets.cpp index a35a8fa..967e857 100644 --- a/fggl/gui/widgets.cpp +++ b/fggl/gui/widgets.cpp @@ -23,8 +23,7 @@ namespace fggl::gui { - Button::Button(math::vec2 pos, math::vec2 size) : Widget(pos, size), m_label(pos, size), m_hover(false), - m_active(false) {} + Button::Button(math::vec2 pos, math::vec2 size) : Widget(pos, size), m_label(pos, size), m_active(false) {} void Button::render(gfx::Paint &paint) { gfx::Path2D path{topLeft()}; @@ -44,14 +43,6 @@ namespace fggl::gui { } } - void Button::onEnter() { - m_hover = true; - } - - void Button::onExit() { - m_hover = false; - } - void Button::addCallback(Callback cb) { m_callbacks.push_back(cb); } @@ -60,6 +51,10 @@ namespace fggl::gui { m_label.text(value); } + void Button::layout() { + m_label.size(topLeft(), size()); + } + std::string Button::label() const { return m_label.text(); } diff --git a/fggl/scenes/menu.cpp b/fggl/scenes/menu.cpp index 1c0a64d..3663914 100644 --- a/fggl/scenes/menu.cpp +++ b/fggl/scenes/menu.cpp @@ -34,27 +34,13 @@ namespace fggl::scenes { // in canvas space math::vec2 projected; projected.x = math::rescale_ndc(m_cursorPos.x, 0, 1920.f); - //projected.y = math::rescale_ndc(m_cursorPos.y, 1080.0f, 0); projected.y = math::rescale_ndc(m_cursorPos.y, 0, 1080.0f); - - auto *hoverWidget = m_canvas.getChildAt(projected); - if (hoverWidget != m_hover) { - if (m_hover != nullptr) { - m_hover->onExit(); - } - m_hover = hoverWidget; - if (m_hover != nullptr) { - m_hover->onEnter(); - } - } + m_canvas.onMouseOver(projected); if (m_inputs->mouse.pressed(MouseButton::LEFT)) { - spdlog::info("clicky clicky: ({}, {})", projected.x, projected.y); - - auto widget = m_canvas.getChildAt(projected); + auto* widget = m_canvas.getChildAt(projected); if (widget != nullptr) { widget->activate(); - spdlog::info("ooo! there is a thing there!"); } } } diff --git a/include/fggl/gfx/paint.hpp b/include/fggl/gfx/paint.hpp index 7985467..fe18e27 100644 --- a/include/fggl/gfx/paint.hpp +++ b/include/fggl/gfx/paint.hpp @@ -284,6 +284,7 @@ namespace fggl::gfx { struct TextCmd { const std::string text; const math::vec2 pos; + const math::vec3 colour; }; class Paint { @@ -299,8 +300,8 @@ namespace fggl::gfx { m_cmds.push_back({PaintType::STROKE, path}); } - void text(const std::string &text, const math::vec2 &pos) { - m_text.push_back({text, pos}); + void text(const std::string &text, const math::vec2 &pos, const math::vec3f colour = fggl::gfx::colours::BLACK) { + m_text.push_back({text, pos, colour}); } const std::vector<PaintCmd> &cmds() const { diff --git a/include/fggl/gui/containers.hpp b/include/fggl/gui/containers.hpp index 005f0bf..a0ab060 100644 --- a/include/fggl/gui/containers.hpp +++ b/include/fggl/gui/containers.hpp @@ -34,15 +34,37 @@ namespace fggl::gui { bool contains(const math::vec2 &point) override; Widget *getChildAt(const math::vec2 &point) override; + + inline void update(float deltaTime) override { + for (auto& child : m_children) { + child->update(deltaTime); + } + } void render(gfx::Paint &paint) override; + void onMouseOver(math::vec2 pos) override; + + void onEnter(math::vec2i pos) override; + void onExit(math::vec2i pos) override; + private: bool m_dirty; + Widget* m_hovered = nullptr; protected: std::vector<std::unique_ptr<Widget>> m_children; }; + class GridBox : public Container { + public: + GridBox(uint32_t rows, uint32_t cols, uint32_t padX = 8, uint32_t padY = 8); + void layout() override; + private: + uint32_t m_rows; + uint32_t m_cols; + math::vec2i m_padding; + }; + class Panel : public Container { public: Panel() = default; diff --git a/include/fggl/gui/widget.hpp b/include/fggl/gui/widget.hpp index 9ef5147..8cc131c 100644 --- a/include/fggl/gui/widget.hpp +++ b/include/fggl/gui/widget.hpp @@ -126,13 +126,27 @@ namespace fggl::gui { return this; } + virtual void update(float deltaTime) = 0; virtual void render(gfx::Paint &paint) = 0; + inline virtual void layout() {}; + inline virtual void activate() {}; - inline virtual void onEnter() {} + inline virtual void onMouseOver(math::vec2 pos) { + if ( !m_hover ) { + onEnter(pos); + } + } + inline virtual void onEnter(math::vec2i /*pos*/) { + m_hover = true; + } + inline virtual void onExit(math::vec2i /*pos*/) { + m_hover = false; + } - inline virtual void onExit() {} + protected: + bool m_hover = false; private: Bounds2D m_bounds; diff --git a/include/fggl/gui/widgets.hpp b/include/fggl/gui/widgets.hpp index ac42a16..9a206f9 100644 --- a/include/fggl/gui/widgets.hpp +++ b/include/fggl/gui/widgets.hpp @@ -60,7 +60,8 @@ namespace fggl::gui { return m_naturalSize; } - void layout(); + void layout() override; + inline void update(float deltaTime) override {} private: std::shared_ptr<gui::FontFace> m_font; @@ -77,20 +78,20 @@ namespace fggl::gui { void render(gfx::Paint &paint) override; void activate() override; - void onEnter() override; - void onExit() override; - void label(const std::string &value); [[nodiscard]] std::string label() const; + inline void update(float deltaTime) override {} + void addCallback(Callback callback); + void layout() override; + private: Label m_label; std::string m_value; std::vector<Callback> m_callbacks; - bool m_hover; bool m_active; }; -- GitLab