Squashed 'deps/nostalgia/' changes from 1af4da43..1cc1d561

1cc1d561 [studio] Add a file explorer to NewMenu to choose where new files go
d15a0df7 [studio] Make reusable FileTreeModel
e1282b6b [studio] Fix build
5fe7c14c [nostalgia/sample_project] Rename TileSheet files using new file ext
42165ba2 [nostalgia/gfx] Change default file extension for TileSheets to nts

git-subtree-dir: deps/nostalgia
git-subtree-split: 1cc1d561e2edfd454335b0cb4d85332e8588237d
This commit is contained in:
2025-01-22 23:13:07 -06:00
parent 6fdf744dd2
commit 3fddeeee3e
28 changed files with 817 additions and 218 deletions

View File

@@ -0,0 +1,138 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <algorithm>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <turbine/context.hpp>
#include "widget.hpp"
namespace studio {
constexpr void safeDelete(class FileTreeModel *m) noexcept;
class FileExplorer: public ox::SignalHandler {
friend class FileTreeModel;
private:
keel::Context &m_kctx;
class FileTreeModel const *m_selected{};
bool const m_fileDraggable{};
ox::UPtr<FileTreeModel> m_treeModel;
public:
explicit FileExplorer(keel::Context &kctx, bool const fileDraggable = false) noexcept:
m_kctx{kctx},
m_fileDraggable{fileDraggable} {}
virtual ~FileExplorer() = default;
void draw(StudioContext &ctx, ImVec2 const &sz) const noexcept;
void setModel(ox::UPtr<FileTreeModel> &&model, bool selectRoot = false) noexcept;
ox::Error setSelectedPath(ox::StringViewCR path) noexcept;
[[nodiscard]]
ox::Optional<ox::String> selectedPath() const;
virtual void fileOpened(ox::StringViewCR path) const noexcept;
void drawFileContextMenu(ox::CStringViewCR path) const noexcept;
void drawDirContextMenu(ox::CStringViewCR path) const noexcept;
[[nodiscard]]
ox::FileSystem &romFs() const noexcept {
return *m_kctx.rom;
}
protected:
virtual void fileContextMenu(ox::StringViewCR path) const noexcept;
virtual void dirContextMenu(ox::StringViewCR path) const noexcept;
void setSelectedNode(FileTreeModel const *const node) noexcept {
m_selected = node;
}
[[nodiscard]]
bool selected(FileTreeModel const *const node) const noexcept {
return m_selected == node;
}
[[nodiscard]]
bool fileDraggable() const noexcept {
return m_fileDraggable;
}
private:
ox::Result<bool> setSelectedPath(ox::StringViewCR path, FileTreeModel const&node) noexcept;
};
class FileTreeModel {
private:
FileExplorer &m_explorer;
ox::String m_name;
ox::String m_fullPath;
ox::Vector<ox::UPtr<FileTreeModel>> m_children;
ox::FileType const m_fileType{};
public:
explicit FileTreeModel(
FileExplorer &explorer,
ox::StringParam name,
ox::FileType fileType,
FileTreeModel const *parent = nullptr) noexcept;
virtual ~FileTreeModel() = default;
FileTreeModel(FileTreeModel &&other) noexcept = default;
void draw(turbine::Context &tctx) const noexcept;
void setChildren(ox::Vector<ox::UPtr<FileTreeModel>> children) noexcept;
[[nodiscard]]
ox::Vector<ox::UPtr<FileTreeModel>> const&children() const noexcept {
return m_children;
}
[[nodiscard]]
bool isEmptyDir() const noexcept;
[[nodiscard]]
ox::String const &path() const noexcept {
return m_fullPath;
}
};
constexpr void safeDelete(FileTreeModel *m) noexcept {
delete m;
}
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
FileExplorer &explorer,
ox::StringParam name,
ox::StringViewCR path,
FileTreeModel *parent = nullptr,
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter =
[](ox::StringViewCR, ox::FileStat const&) {return true;},
bool showEmptyDirs = true) noexcept;
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
FileExplorer &explorer,
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter =
[](ox::StringViewCR, ox::FileStat const&) {return true;},
bool showEmptyDirs = true) noexcept;
}

View File

@@ -95,9 +95,9 @@ class ItemMaker {
public:
constexpr ItemMaker(
ox::StringParam pName,
ox::StringParam pParentDir,
ox::StringViewCR pParentDir,
ox::StringParam pFileExt) noexcept:
m_parentDir{std::move(pParentDir)},
m_parentDir{sfmt("/{}", pParentDir)},
m_fileExt{std::move(pFileExt)},
m_typeDisplayName{std::move(pName)} {
}
@@ -128,6 +128,11 @@ class ItemMaker {
return m_templates;
}
[[nodiscard]]
ox::String const&fileExt() const noexcept {
return m_fileExt;
}
[[nodiscard]]
ox::String const&defaultPath() const noexcept {
return m_parentDir;
@@ -140,7 +145,7 @@ class ItemMaker {
[[nodiscard]]
ox::String itemPath(ox::StringViewCR pName) const noexcept {
return ox::sfmt("/{}/{}.{}", m_parentDir, pName, m_fileExt);
return ox::sfmt("{}/{}.{}", m_parentDir, pName, m_fileExt);
}
[[nodiscard]]
@@ -196,12 +201,12 @@ class ItemMakerT final: public ItemMaker {
public:
constexpr ItemMakerT(
ox::StringParam pDisplayName,
ox::StringParam pParentDir,
ox::StringViewCR pParentDir,
ox::StringParam fileExt,
ox::ClawFormat const pFmt = ox::ClawFormat::Metal) noexcept:
ItemMaker(
std::move(pDisplayName),
std::move(pParentDir),
pParentDir,
std::move(fileExt)),
m_fmt{pFmt} {
installTemplate(ox::make_unique<ItemTemplateT<T>>());
@@ -209,13 +214,13 @@ class ItemMakerT final: public ItemMaker {
constexpr ItemMakerT(
ox::StringParam pDisplayName,
ox::StringParam pParentDir,
ox::StringViewCR pParentDir,
ox::StringParam fileExt,
T const&pItem,
ox::ClawFormat const pFmt) noexcept:
ItemMaker(
std::move(pDisplayName),
std::move(pParentDir),
pParentDir,
std::move(fileExt)),
m_fmt{pFmt} {
installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));
@@ -223,13 +228,13 @@ class ItemMakerT final: public ItemMaker {
constexpr ItemMakerT(
ox::StringParam pDisplayName,
ox::StringParam pParentDir,
ox::StringViewCR pParentDir,
ox::StringParam fileExt,
T &&pItem,
ox::ClawFormat const pFmt) noexcept:
ItemMaker(
std::move(pDisplayName),
std::move(pParentDir),
pParentDir,
std::move(fileExt)),
m_fmt{pFmt} {
installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));

View File

@@ -37,10 +37,28 @@ class Module {
template<typename Editor>
[[nodiscard]]
studio::EditorMaker editorMaker(studio::StudioContext &ctx, ox::StringParam ext) noexcept {
EditorMaker editorMaker(StudioContext &ctx, ox::StringParam ext) noexcept {
return {
{std::move(ext)},
[&ctx](ox::StringViewCR path) -> ox::Result<studio::BaseEditor*> {
[&ctx](ox::StringViewCR path) -> ox::Result<BaseEditor*> {
return ox::makeCatch<Editor>(ctx, path);
}
};
}
template<typename Editor>
[[nodiscard]]
EditorMaker editorMaker(StudioContext &ctx, std::initializer_list<ox::StringView> exts) noexcept {
return {
[&exts] {
ox::Vector<ox::String> fileTypes;
fileTypes.reserve(exts.size());
for (auto &s : exts) {
fileTypes.emplace_back(s);
}
return fileTypes;
}(),
[&ctx](ox::StringViewCR path) -> ox::Result<BaseEditor*> {
return ox::makeCatch<Editor>(ctx, path);
}
};

View File

@@ -0,0 +1,16 @@
/*
* 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>
#include <studio/filetreemodel.hpp>
namespace studio {
}

View File

@@ -9,6 +9,7 @@
#include <studio/dragdrop.hpp>
#include <studio/editor.hpp>
#include <studio/filedialog.hpp>
#include <studio/filetreemodel.hpp>
#include <studio/imguiutil.hpp>
#include <studio/module.hpp>
#include <studio/itemmaker.hpp>

View File

@@ -2,6 +2,8 @@ add_library(
Studio
configio.cpp
editor.cpp
projectfilepicker.cpp
filetreemodel.cpp
imguiutil.cpp
module.cpp
popup.cpp

View File

@@ -0,0 +1,172 @@
/*
* 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 {
void FileExplorer::draw(StudioContext &ctx, ImVec2 const &sz) const noexcept {
ImGui::BeginChild("ProjectExplorer", sz, true);
ImGui::SetNextItemOpen(true);
if (m_treeModel) {
m_treeModel->draw(ctx.tctx);
}
ImGui::EndChild();
}
void FileExplorer::setModel(ox::UPtr<FileTreeModel> &&model, bool const selectRoot) noexcept {
m_treeModel = std::move(model);
setSelectedNode(selectRoot ? m_treeModel.get() : nullptr);
}
ox::Error FileExplorer::setSelectedPath(ox::StringViewCR path) noexcept {
return setSelectedPath(path, *m_treeModel).error;
}
[[nodiscard]]
ox::Optional<ox::String> FileExplorer::selectedPath() const {
if (m_selected) {
return ox::Optional<ox::String>{ox::in_place, m_selected->path()};
}
return ox::Optional<ox::String>{};
}
void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {}
void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept {
ig::IDStackItem const idStackItem{path};
fileContextMenu(path);
}
void FileExplorer::drawDirContextMenu(ox::CStringViewCR path) const noexcept {
ig::IDStackItem const idStackItem{path};
dirContextMenu(path);
}
void FileExplorer::fileContextMenu(ox::StringViewCR) const noexcept {}
void FileExplorer::dirContextMenu(ox::StringViewCR) const noexcept {}
ox::Result<bool> FileExplorer::setSelectedPath(
ox::StringViewCR path, FileTreeModel const&node) noexcept {
if (path == node.path()) {
m_selected = &node;
return {};
}
for (auto &c : node.children()) {
OX_REQUIRE(done, setSelectedPath(path, *c));
if (done) {
break;
}
}
return {};
}
FileTreeModel::FileTreeModel(
FileExplorer &explorer,
ox::StringParam name,
ox::FileType const fileType,
FileTreeModel const *const parent) noexcept:
m_explorer{explorer},
m_name{std::move(name)},
m_fullPath{parent ? sfmt("{}/{}", parent->m_fullPath, m_name) : ox::String{}},
m_fileType{fileType} {}
void FileTreeModel::draw(turbine::Context &tctx) const noexcept {
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
auto const selected = m_explorer.selected(this) ? ImGuiTreeNodeFlags_Selected : 0;
if (!m_children.empty()) {
auto const nodeOpen = ImGui::TreeNodeEx(m_name.c_str(), dirFlags | selected);
if (ImGui::IsItemClicked()) {
m_explorer.setSelectedNode(this);
}
ig::IDStackItem const idStackItem{m_name};
m_explorer.drawDirContextMenu(m_fullPath);
if (nodeOpen) {
for (auto const&child : m_children) {
child->draw(tctx);
}
ImGui::TreePop();
}
} else {
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
m_explorer.fileOpened(m_fullPath);
}
if (ImGui::IsItemClicked()) {
m_explorer.setSelectedNode(this);
}
m_explorer.drawFileContextMenu(m_fullPath);
ImGui::TreePop();
if (m_explorer.fileDraggable()) {
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);
}
bool FileTreeModel::isEmptyDir() const noexcept {
return m_children.empty() && m_fileType == ox::FileType::Directory;
}
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
FileExplorer &explorer,
ox::StringParam name,
ox::StringViewCR path,
FileTreeModel *parent,
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const &filter,
bool const showEmptyDirs) noexcept {
auto const&fs = explorer.romFs();
OX_REQUIRE(stat, fs.stat(path));
auto out = ox::make_unique<FileTreeModel>(explorer, std::move(name), stat.fileType, parent);
if (stat.fileType == ox::FileType::Directory) {
OX_REQUIRE_M(children, fs.ls(path));
std::sort(children.begin(), children.end());
ox::Vector<ox::UPtr<FileTreeModel>> outChildren;
for (auto const&childName : children) {
if (childName[0] != '.') {
auto const childPath = ox::sfmt("{}/{}", path, childName);
OX_REQUIRE(childStat, fs.stat(childPath));
if (filter(childPath, childStat)) {
OX_REQUIRE_M(child, buildFileTreeModel(
explorer,
childName,
childPath,
out.get(),
filter,
showEmptyDirs));
auto const emptyDir = child->isEmptyDir();
if (!emptyDir || showEmptyDirs) {
outChildren.emplace_back(std::move(child));
}
}
}
}
out->setChildren(std::move(outChildren));
}
return out;
}
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
FileExplorer &explorer,
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter,
bool const showEmptyDirs) noexcept {
return buildFileTreeModel(explorer, "Project", "/", nullptr, filter, showEmptyDirs);
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/filetreemodel.hpp>
#include <studio/projectfilepicker.hpp>
namespace studio {
class ProjectFilePicker: public FileExplorer {
public:
explicit ProjectFilePicker(keel::Context &kctx) noexcept: FileExplorer{kctx} {}
protected:
};
}