diff --git a/src/olympic/studio/applib/src/CMakeLists.txt b/src/olympic/studio/applib/src/CMakeLists.txt index 2127d8fe..f69e669b 100644 --- a/src/olympic/studio/applib/src/CMakeLists.txt +++ b/src/olympic/studio/applib/src/CMakeLists.txt @@ -9,7 +9,6 @@ add_library( newmenu.cpp newproject.cpp projectexplorer.cpp - projecttreemodel.cpp studioapp.cpp ) target_compile_definitions( diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 464f6718..af527c87 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -10,18 +10,18 @@ namespace studio { -static ox::Result> buildProjectTreeModel( +static ox::Result> buildProjectTreeModel( ProjectExplorer &explorer, ox::StringParam name, ox::StringView path, - ProjectTreeModel *parent) noexcept { + FileTreeModel *parent) noexcept { auto const fs = explorer.romFs(); OX_REQUIRE(stat, fs->stat(path)); - auto out = ox::make_unique(explorer, std::move(name), parent); + auto out = ox::make_unique(explorer, std::move(name), parent); if (stat.fileType == ox::FileType::Directory) { OX_REQUIRE_M(children, fs->ls(path)); std::sort(children.begin(), children.end()); - ox::Vector> outChildren; + ox::Vector> outChildren; for (auto const&childName : children) { if (childName[0] != '.') { auto const childPath = ox::sfmt("{}/{}", path, childName); @@ -34,6 +34,7 @@ static ox::Result> buildProjectTreeModel( return out; } + ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) { } @@ -47,7 +48,7 @@ void ProjectExplorer::draw(StudioContext &ctx) noexcept { ImGui::EndChild(); } -void ProjectExplorer::setModel(ox::UPtr &&model) noexcept { +void ProjectExplorer::setModel(ox::UPtr &&model) noexcept { m_treeModel = std::move(model); } @@ -57,4 +58,33 @@ ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept { return {}; } +void ProjectExplorer::fileOpened(ox::StringViewCR path) const noexcept { + fileChosen.emit(path); +} + +void ProjectExplorer::drawFileContextMenu(ox::StringViewCR path) const noexcept { + if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { + if (ImGui::MenuItem("Delete")) { + deleteItem.emit(path); + } + ImGui::EndPopup(); + } +} + +void ProjectExplorer::drawDirContextMenu(ox::StringViewCR path) const noexcept { + if (ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) { + if (ImGui::MenuItem("Add Item")) { + addItem.emit(path); + } + if (ImGui::MenuItem("Add Directory")) { + addDir.emit(path); + } + if (ImGui::MenuItem("Delete")) { + deleteItem.emit(path); + } + ImGui::EndPopup(); + } +} + + } diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 8ef25761..73b6d583 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -7,14 +7,14 @@ #include #include +#include #include -#include "projecttreemodel.hpp" namespace studio { -class ProjectExplorer: public Widget { +class ProjectExplorer final: public Widget, public FileExplorer { private: - ox::UPtr m_treeModel; + ox::UPtr m_treeModel; turbine::Context &m_ctx; public: @@ -28,7 +28,7 @@ class ProjectExplorer: public Widget { void draw(StudioContext &ctx) noexcept override; - void setModel(ox::UPtr &&model) noexcept; + void setModel(ox::UPtr &&model) noexcept; ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept; @@ -37,6 +37,13 @@ class ProjectExplorer: public Widget { return rom(m_ctx); } + protected: + void fileOpened(ox::StringViewCR path) const noexcept override; + + void drawFileContextMenu(ox::StringViewCR path) const noexcept override; + + void drawDirContextMenu(ox::StringViewCR path) const noexcept override; + }; } diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp deleted file mode 100644 index cc7afd1a..00000000 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#include - -#include -#include - -#include "projectexplorer.hpp" -#include "projecttreemodel.hpp" - -namespace studio { - -ProjectTreeModel::ProjectTreeModel( - ProjectExplorer &explorer, - ox::StringParam name, - ProjectTreeModel *parent) noexcept: - m_explorer(explorer), - m_parent(parent), - m_name(std::move(name)) { -} - -ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept: - m_explorer(other.m_explorer), - m_parent(other.m_parent), - m_name(std::move(other.m_name)), - m_children(std::move(other.m_children)) { -} - -void ProjectTreeModel::draw(turbine::Context &tctx) const noexcept { - constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; - if (!m_children.empty()) { - if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) { - drawDirContextMenu(); - for (auto const&child : m_children) { - child->draw(tctx); - } - ImGui::TreePop(); - } else { - ig::IDStackItem const idStackItem{m_name}; - drawDirContextMenu(); - } - } else { - auto const path = fullPath(); - auto const name = ox::sfmt>("{}##{}", m_name, path); - if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_Leaf)) { - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { - m_explorer.fileChosen.emit(path); - } - if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { - if (ImGui::MenuItem("Delete")) { - m_explorer.deleteItem.emit(path); - } - ImGui::EndPopup(); - } - ImGui::TreePop(); - std::ignore = ig::dragDropSource([this] { - ImGui::Text("%s", m_name.c_str()); - return ig::setDragDropPayload("FileRef", FileRef{fullPath()}); - }); - } - } -} - -void ProjectTreeModel::setChildren(ox::Vector> children) noexcept { - m_children = std::move(children); -} - -void ProjectTreeModel::drawDirContextMenu() const noexcept { - if (ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) { - if (ImGui::MenuItem("Add Item")) { - m_explorer.addItem.emit(fullPath()); - } - if (ImGui::MenuItem("Add Directory")) { - m_explorer.addDir.emit(fullPath()); - } - if (ImGui::MenuItem("Delete")) { - m_explorer.deleteItem.emit(fullPath()); - } - ImGui::EndPopup(); - } -} - -} diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp deleted file mode 100644 index fdfc1fae..00000000 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#pragma once - -#include -#include - -#include - -namespace studio { - -class ProjectTreeModel { - private: - class ProjectExplorer &m_explorer; - ProjectTreeModel *m_parent = nullptr; - ox::String m_name; - ox::Vector> m_children; - - public: - explicit ProjectTreeModel( - ProjectExplorer &explorer, ox::StringParam name, - ProjectTreeModel *parent = nullptr) noexcept; - - ProjectTreeModel(ProjectTreeModel &&other) noexcept; - - void draw(turbine::Context &tctx) const noexcept; - - void setChildren(ox::Vector> children) noexcept; - - private: - void drawDirContextMenu() const noexcept; - - template> - [[nodiscard]] - Str fullPath() const noexcept { - if (m_parent) { - return m_parent->fullPath() + "/" + m_name; - } - return {}; - } - -}; - -} diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 199feac2..a10d8c88 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -19,7 +19,6 @@ #include "newmenu.hpp" #include "newproject.hpp" #include "projectexplorer.hpp" -#include "projecttreemodel.hpp" namespace studio { diff --git a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp new file mode 100644 index 00000000..d04c12cd --- /dev/null +++ b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +#include + +namespace studio { + +class FileExplorer { + + public: + virtual ~FileExplorer() = default; + + virtual void fileOpened(ox::StringViewCR path) const noexcept = 0; + + virtual void drawFileContextMenu(ox::StringViewCR path) const noexcept = 0; + + virtual void drawDirContextMenu(ox::StringViewCR path) const noexcept = 0; + +}; + +class FileTreeModel { + private: + FileExplorer &m_explorer; + FileTreeModel *m_parent = nullptr; + ox::String m_name; + ox::String m_fullPath{m_parent ? m_parent->m_fullPath + "/" + m_name : ox::String{}}; + ox::String m_imguiNodeName{ox::sfmt("{}##{}", m_name, m_fullPath)}; + ox::Vector> m_children; + + public: + virtual ~FileTreeModel() = default; + + explicit FileTreeModel( + FileExplorer &explorer, ox::StringParam name, + FileTreeModel *parent = nullptr) noexcept; + + FileTreeModel(FileTreeModel &&other) noexcept = default; + + void draw(turbine::Context &tctx) const noexcept; + + void setChildren(ox::Vector> children) noexcept; + +}; + +} diff --git a/src/olympic/studio/modlib/src/CMakeLists.txt b/src/olympic/studio/modlib/src/CMakeLists.txt index 9cb4ee94..795f7d72 100644 --- a/src/olympic/studio/modlib/src/CMakeLists.txt +++ b/src/olympic/studio/modlib/src/CMakeLists.txt @@ -2,6 +2,7 @@ add_library( Studio configio.cpp editor.cpp + filetreemodel.cpp imguiutil.cpp module.cpp popup.cpp diff --git a/src/olympic/studio/modlib/src/filetreemodel.cpp b/src/olympic/studio/modlib/src/filetreemodel.cpp new file mode 100644 index 00000000..2992bee1 --- /dev/null +++ b/src/olympic/studio/modlib/src/filetreemodel.cpp @@ -0,0 +1,55 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include +#include + +#include + +namespace studio { + +FileTreeModel::FileTreeModel( + FileExplorer &explorer, + ox::StringParam name, + FileTreeModel *parent) noexcept: + m_explorer{explorer}, + m_parent{parent}, + m_name{std::move(name)} { +} + +void FileTreeModel::draw(turbine::Context &tctx) const noexcept { + constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (!m_children.empty()) { + if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) { + m_explorer.drawDirContextMenu(m_fullPath); + for (auto const&child : m_children) { + child->draw(tctx); + } + ImGui::TreePop(); + } else { + ig::IDStackItem const idStackItem{m_name}; + m_explorer.drawDirContextMenu(m_fullPath); + } + } else { + if (ImGui::TreeNodeEx(m_imguiNodeName.c_str(), ImGuiTreeNodeFlags_Leaf)) { + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { + m_explorer.fileOpened(m_fullPath); + } + m_explorer.drawFileContextMenu(m_fullPath); + ImGui::TreePop(); + std::ignore = ig::dragDropSource([this] { + ImGui::Text("%s", m_name.c_str()); + return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}}); + }); + } + } +} + +void FileTreeModel::setChildren(ox::Vector> children) noexcept { + m_children = std::move(children); +} + +}