diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ac297d0..61bc48fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Raycasting, collisions between rays and box/capsule colliders (#225, **@diogomsmiranda**). - Change speed of the debug-camera using Tab and LShift, positive and negative respectively (#1159, **@diogomsmiranda**) - Console plugin (#875, **@Scarface1809**). +- Menu Bar to Tesseratos (#1234, **@RiscadoA**). ### Changed diff --git a/tools/tesseratos/CMakeLists.txt b/tools/tesseratos/CMakeLists.txt index a7908ebc3..db4d2a930 100644 --- a/tools/tesseratos/CMakeLists.txt +++ b/tools/tesseratos/CMakeLists.txt @@ -3,6 +3,9 @@ set(TESSERATOS_SOURCE "src/tesseratos/main.cpp" + "src/tesseratos/menu_bar/plugin.cpp" + "src/tesseratos/menu_bar/item.cpp" + "src/tesseratos/menu_bar/selected.cpp" "src/tesseratos/asset_explorer/plugin.cpp" "src/tesseratos/asset_explorer/popup.cpp" "src/tesseratos/settings_inspector/plugin.cpp" diff --git a/tools/tesseratos/src/tesseratos/main.cpp b/tools/tesseratos/src/tesseratos/main.cpp index 0c5a9ffcc..5d9a999cc 100644 --- a/tools/tesseratos/src/tesseratos/main.cpp +++ b/tools/tesseratos/src/tesseratos/main.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "asset_explorer/plugin.hpp" @@ -13,6 +14,7 @@ #include "ecs_statistics/plugin.hpp" #include "entity_inspector/plugin.hpp" #include "entity_selector/plugin.hpp" +#include "menu_bar/plugin.hpp" #include "metrics_panel/plugin.hpp" #include "play_pause/plugin.hpp" #include "scene_editor/plugin.hpp" @@ -41,6 +43,7 @@ int main(int argc, char** argv) cubos.plugin(toolboxPlugin); cubos.plugin(entitySelectorPlugin); + cubos.plugin(menuBarPlugin); cubos.plugin(assetExplorerPlugin); cubos.plugin(entityInspectorPlugin); @@ -68,5 +71,20 @@ int main(int argc, char** argv) input.bind(*bindings); }); + cubos.startupSystem("add some menu items just for testing purposes").call([](Commands cmds) { + auto project = cmds.create().add(MenuBarItem{"Project", 0}).entity(); + auto view = cmds.create().add(MenuBarItem{"View", 1}).entity(); + cmds.create().add(MenuBarItem{"Help", 2}); + + cmds.create().add(MenuBarItem{"New", 0}).relatedTo(project, ChildOf{}); + cmds.create().add(MenuBarItem{"Open", 1}).relatedTo(project, ChildOf{}); + cmds.create().add(MenuBarItem{"Save", 2}).relatedTo(project, ChildOf{}); + + cmds.create().add(MenuBarItem{"Console", 0}).relatedTo(view, ChildOf{}); + cmds.create().add(MenuBarItem{"Assets Explorer", 1}).relatedTo(view, ChildOf{}); + cmds.create().add(MenuBarItem{"Entity Inspector", 2}).relatedTo(view, ChildOf{}); + cmds.create().add(MenuBarItem{"World Inspector", 3}).relatedTo(view, ChildOf{}); + }); + cubos.run(); } diff --git a/tools/tesseratos/src/tesseratos/menu_bar/item.cpp b/tools/tesseratos/src/tesseratos/menu_bar/item.cpp new file mode 100644 index 000000000..b9fe31d3f --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/item.cpp @@ -0,0 +1,13 @@ +#include "item.hpp" + +#include +#include +#include + +CUBOS_REFLECT_IMPL(tesseratos::MenuBarItem) +{ + return cubos::core::ecs::TypeBuilder("tesseratos::MenuBarItem") + .withField("name", &MenuBarItem::name) + .withField("order", &MenuBarItem::order) + .build(); +} diff --git a/tools/tesseratos/src/tesseratos/menu_bar/item.hpp b/tools/tesseratos/src/tesseratos/menu_bar/item.hpp new file mode 100644 index 000000000..f9df5730f --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/item.hpp @@ -0,0 +1,30 @@ +/// @file +/// @brief Component @ref tesseratos::MenuBarItem. +/// @ingroup tesseratos-menu-bar-plugin + +#pragma once + +#include + +#include + +namespace tesseratos +{ + /// @brief Component representing a menu bar item. + /// + /// Adds an item to the menu bar at the top of the screen. + /// + /// @ingroup tesseratos-menu-bar-plugin + struct MenuBarItem + { + CUBOS_REFLECT; + + /// @brief Item name. + std::string name{"unnamed"}; + + /// @brief Item order priority, lower values are displayed first. + /// + /// Ties are broken by the item's alphabetical order. + int order{0}; + }; +} // namespace tesseratos diff --git a/tools/tesseratos/src/tesseratos/menu_bar/plugin.cpp b/tools/tesseratos/src/tesseratos/menu_bar/plugin.cpp new file mode 100644 index 000000000..622f1d15a --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/plugin.cpp @@ -0,0 +1,102 @@ +#include "plugin.hpp" +#include + +#include + +#include +#include + +using cubos::core::ecs::Traversal; +using namespace cubos::engine; +using namespace tesseratos; + +namespace +{ + struct Item + { + Entity entity; + MenuBarItem item; + std::vector items; + }; +} // namespace + +static void getChildItems(Item& parent, Query childOf) +{ + for (auto [entity, item] : childOf.pin(1, parent.entity)) + { + Item child{entity, item, {}}; + getChildItems(child, childOf); + parent.items.emplace_back(child); + } + + std::sort(parent.items.begin(), parent.items.end(), [](const Item& a, const Item& b) { + return a.item.order < b.item.order || (a.item.order == b.item.order && a.item.name < b.item.name); + }); +} + +static void showItem(Commands& cmds, const Item& item) +{ + if (item.items.empty()) + { + if (ImGui::MenuItem(item.item.name.c_str())) + { + // Trigger any observers of the selected item. + cmds.add(item.entity, MenuBarSelected{}); + cmds.remove(item.entity); + } + } + else + { + if (ImGui::BeginMenu(item.item.name.c_str())) + { + for (const auto& child : item.items) + { + showItem(cmds, child); + } + ImGui::EndMenu(); + } + } +} + +void tesseratos::menuBarPlugin(Cubos& cubos) +{ + cubos.depends(imguiPlugin); + cubos.depends(transformPlugin); + + cubos.component(); + cubos.component(); + + cubos.system("draw MenuBar") + .tagged(imguiTag) + .with() + .related() + .call([](Commands cmds, Query childOf, Query items) { + if (!ImGui::BeginMainMenuBar()) + { + return; + } + + // Extract item tree from the queries, sorted by the order and name item fields. + std::vector processed{}; + for (auto [entity, item] : items) + { + if (childOf.pin(0, entity).empty()) + { + Item root{entity, item, {}}; + getChildItems(root, childOf); + processed.push_back(root); + } + } + std::sort(processed.begin(), processed.end(), [](const Item& a, const Item& b) { + return a.item.order < b.item.order || (a.item.order == b.item.order && a.item.name < b.item.name); + }); + + // Show the menu bar. + for (const auto& item : processed) + { + showItem(cmds, item); + } + + ImGui::EndMainMenuBar(); + }); +} diff --git a/tools/tesseratos/src/tesseratos/menu_bar/plugin.hpp b/tools/tesseratos/src/tesseratos/menu_bar/plugin.hpp new file mode 100644 index 000000000..604193f74 --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/plugin.hpp @@ -0,0 +1,25 @@ +/// @dir +/// @brief @ref tesseratos-menu-bar-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup tesseratos-menu-bar-plugin + +#pragma once + +#include + +#include "item.hpp" +#include "selected.hpp" + +namespace tesseratos +{ + /// @defgroup tesseratos-menu-bar-plugin Menu Bar + /// @ingroup tesseratos + /// @brief Adds the @ref MenuBarItem and @ref MenuBarSelected components. + + /// @brief Plugin entry function. + /// @param cubos @b Cubos main class + /// @ingroup tesseratos-game-plugin + void menuBarPlugin(cubos::engine::Cubos& cubos); +} // namespace tesseratos diff --git a/tools/tesseratos/src/tesseratos/menu_bar/selected.cpp b/tools/tesseratos/src/tesseratos/menu_bar/selected.cpp new file mode 100644 index 000000000..c014353ce --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/selected.cpp @@ -0,0 +1,8 @@ +#include "selected.hpp" + +#include + +CUBOS_REFLECT_IMPL(tesseratos::MenuBarSelected) +{ + return cubos::core::ecs::TypeBuilder("tesseratos::MenuBarSelected").build(); +} diff --git a/tools/tesseratos/src/tesseratos/menu_bar/selected.hpp b/tools/tesseratos/src/tesseratos/menu_bar/selected.hpp new file mode 100644 index 000000000..cd292140e --- /dev/null +++ b/tools/tesseratos/src/tesseratos/menu_bar/selected.hpp @@ -0,0 +1,17 @@ +/// @file +/// @brief Component @ref tesseratos::MenuBarSelected. +/// @ingroup tesseratos-menu-bar-plugin + +#pragma once + +#include + +namespace tesseratos +{ + /// @brief Component added to menu bar items when they are selected. + /// @ingroup tesseratos-menu-bar-plugin + struct MenuBarSelected + { + CUBOS_REFLECT; + }; +} // namespace tesseratos