[studio] Make reusable FileTreeModel
This commit is contained in:
parent
e1282b6bae
commit
d15a0df7da
@ -9,7 +9,6 @@ add_library(
|
|||||||
newmenu.cpp
|
newmenu.cpp
|
||||||
newproject.cpp
|
newproject.cpp
|
||||||
projectexplorer.cpp
|
projectexplorer.cpp
|
||||||
projecttreemodel.cpp
|
|
||||||
studioapp.cpp
|
studioapp.cpp
|
||||||
)
|
)
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
|
@ -10,18 +10,18 @@
|
|||||||
|
|
||||||
namespace studio {
|
namespace studio {
|
||||||
|
|
||||||
static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
|
static ox::Result<ox::UniquePtr<FileTreeModel>> buildProjectTreeModel(
|
||||||
ProjectExplorer &explorer,
|
ProjectExplorer &explorer,
|
||||||
ox::StringParam name,
|
ox::StringParam name,
|
||||||
ox::StringView path,
|
ox::StringView path,
|
||||||
ProjectTreeModel *parent) noexcept {
|
FileTreeModel *parent) noexcept {
|
||||||
auto const fs = explorer.romFs();
|
auto const fs = explorer.romFs();
|
||||||
OX_REQUIRE(stat, fs->stat(path));
|
OX_REQUIRE(stat, fs->stat(path));
|
||||||
auto out = ox::make_unique<ProjectTreeModel>(explorer, std::move(name), parent);
|
auto out = ox::make_unique<FileTreeModel>(explorer, std::move(name), parent);
|
||||||
if (stat.fileType == ox::FileType::Directory) {
|
if (stat.fileType == ox::FileType::Directory) {
|
||||||
OX_REQUIRE_M(children, fs->ls(path));
|
OX_REQUIRE_M(children, fs->ls(path));
|
||||||
std::sort(children.begin(), children.end());
|
std::sort(children.begin(), children.end());
|
||||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
|
ox::Vector<ox::UniquePtr<FileTreeModel>> outChildren;
|
||||||
for (auto const&childName : children) {
|
for (auto const&childName : children) {
|
||||||
if (childName[0] != '.') {
|
if (childName[0] != '.') {
|
||||||
auto const childPath = ox::sfmt("{}/{}", path, childName);
|
auto const childPath = ox::sfmt("{}/{}", path, childName);
|
||||||
@ -34,6 +34,7 @@ static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) {
|
ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ void ProjectExplorer::draw(StudioContext &ctx) noexcept {
|
|||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
|
void ProjectExplorer::setModel(ox::UPtr<FileTreeModel> &&model) noexcept {
|
||||||
m_treeModel = std::move(model);
|
m_treeModel = std::move(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,4 +58,33 @@ ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept {
|
|||||||
return {};
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,14 +7,14 @@
|
|||||||
#include <ox/event/signal.hpp>
|
#include <ox/event/signal.hpp>
|
||||||
#include <ox/std/memory.hpp>
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
|
#include <studio/filetreemodel.hpp>
|
||||||
#include <studio/widget.hpp>
|
#include <studio/widget.hpp>
|
||||||
#include "projecttreemodel.hpp"
|
|
||||||
|
|
||||||
namespace studio {
|
namespace studio {
|
||||||
|
|
||||||
class ProjectExplorer: public Widget {
|
class ProjectExplorer final: public Widget, public FileExplorer {
|
||||||
private:
|
private:
|
||||||
ox::UPtr<ProjectTreeModel> m_treeModel;
|
ox::UPtr<FileTreeModel> m_treeModel;
|
||||||
turbine::Context &m_ctx;
|
turbine::Context &m_ctx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -28,7 +28,7 @@ class ProjectExplorer: public Widget {
|
|||||||
|
|
||||||
void draw(StudioContext &ctx) noexcept override;
|
void draw(StudioContext &ctx) noexcept override;
|
||||||
|
|
||||||
void setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept;
|
void setModel(ox::UPtr<FileTreeModel> &&model) noexcept;
|
||||||
|
|
||||||
ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept;
|
ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept;
|
||||||
|
|
||||||
@ -37,6 +37,13 @@ class ProjectExplorer: public Widget {
|
|||||||
return rom(m_ctx);
|
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;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <imgui.h>
|
|
||||||
|
|
||||||
#include <studio/dragdrop.hpp>
|
|
||||||
#include <studio/imguiutil.hpp>
|
|
||||||
|
|
||||||
#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<ox::BasicString<255>>("{}##{}", 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<ox::String>()});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <ox/std/memory.hpp>
|
|
||||||
#include <ox/std/string.hpp>
|
|
||||||
|
|
||||||
#include <turbine/context.hpp>
|
|
||||||
|
|
||||||
namespace studio {
|
|
||||||
|
|
||||||
class ProjectTreeModel {
|
|
||||||
private:
|
|
||||||
class ProjectExplorer &m_explorer;
|
|
||||||
ProjectTreeModel *m_parent = nullptr;
|
|
||||||
ox::String m_name;
|
|
||||||
ox::Vector<ox::UPtr<ProjectTreeModel>> 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<ox::UPtr<ProjectTreeModel>> children) noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void drawDirContextMenu() const noexcept;
|
|
||||||
|
|
||||||
template<typename Str = ox::BasicString<255>>
|
|
||||||
[[nodiscard]]
|
|
||||||
Str fullPath() const noexcept {
|
|
||||||
if (m_parent) {
|
|
||||||
return m_parent->fullPath<Str>() + "/" + m_name;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -19,7 +19,6 @@
|
|||||||
#include "newmenu.hpp"
|
#include "newmenu.hpp"
|
||||||
#include "newproject.hpp"
|
#include "newproject.hpp"
|
||||||
#include "projectexplorer.hpp"
|
#include "projectexplorer.hpp"
|
||||||
#include "projecttreemodel.hpp"
|
|
||||||
|
|
||||||
namespace studio {
|
namespace studio {
|
||||||
|
|
||||||
|
51
src/olympic/studio/modlib/include/studio/filetreemodel.hpp
Normal file
51
src/olympic/studio/modlib/include/studio/filetreemodel.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ox/std/memory.hpp>
|
||||||
|
#include <ox/std/string.hpp>
|
||||||
|
|
||||||
|
#include <turbine/context.hpp>
|
||||||
|
|
||||||
|
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<ox::UPtr<FileTreeModel>> 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<ox::UPtr<FileTreeModel>> children) noexcept;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@ add_library(
|
|||||||
Studio
|
Studio
|
||||||
configio.cpp
|
configio.cpp
|
||||||
editor.cpp
|
editor.cpp
|
||||||
|
filetreemodel.cpp
|
||||||
imguiutil.cpp
|
imguiutil.cpp
|
||||||
module.cpp
|
module.cpp
|
||||||
popup.cpp
|
popup.cpp
|
||||||
|
55
src/olympic/studio/modlib/src/filetreemodel.cpp
Normal file
55
src/olympic/studio/modlib/src/filetreemodel.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <studio/dragdrop.hpp>
|
||||||
|
#include <studio/imguiutil.hpp>
|
||||||
|
|
||||||
|
#include <studio/filetreemodel.hpp>
|
||||||
|
|
||||||
|
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<ox::UPtr<FileTreeModel>> children) noexcept {
|
||||||
|
m_children = std::move(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user