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

@@ -9,7 +9,6 @@ add_library(
newmenu.cpp
newproject.cpp
projectexplorer.cpp
projecttreemodel.cpp
studioapp.cpp
)
target_compile_definitions(

View File

@@ -12,9 +12,8 @@
namespace studio {
NewMenu::NewMenu() noexcept {
NewMenu::NewMenu(keel::Context &kctx) noexcept: m_kctx{kctx} {
setTitle("New Item");
setSize({280, 180});
}
void NewMenu::open() noexcept {
@@ -23,6 +22,11 @@ void NewMenu::open() noexcept {
m_itemName = "";
m_typeName = "";
m_path = "";
m_explorer.setModel(buildFileTreeModel(
m_explorer,
[](ox::StringViewCR, ox::FileStat const&s) {
return s.fileType == ox::FileType::Directory;
}).or_value(ox::UPtr<FileTreeModel>{}));
}
void NewMenu::openPath(ox::StringParam path) noexcept {
@@ -49,8 +53,8 @@ void NewMenu::draw(StudioContext &sctx) noexcept {
case Stage::NewItemType:
drawNewItemType(sctx);
break;
case Stage::NewItemName:
drawNewItemName(sctx);
case Stage::NewItemPath:
drawNewItemPath(sctx);
break;
case Stage::NewItemTemplate:
drawNewItemTemplate(sctx);
@@ -79,32 +83,45 @@ void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept {
}
void NewMenu::drawNewItemType(StudioContext const&sctx) noexcept {
setSize({280, 180});
drawWindow(sctx.tctx, m_open, [this] {
ig::ListBox("Item Type", [&](size_t const i) -> ox::CStringView {
return m_types[i]->typeDisplayName();
}, m_types.size(), m_selectedType, {200, 100});
auto const&im = *m_types[m_selectedType];
drawFirstPageButtons(im.itemTemplates().size() == 1 ?
Stage::NewItemName : Stage::NewItemTemplate);
Stage::NewItemPath : Stage::NewItemTemplate);
if (m_stage == Stage::NewItemPath) {
if (m_path.len() == 0) {
m_path = im.defaultPath();
}
std::ignore = m_explorer.setSelectedPath(m_path);
}
});
}
void NewMenu::drawNewItemTemplate(StudioContext &sctx) noexcept {
void NewMenu::drawNewItemTemplate(StudioContext const&sctx) noexcept {
setSize({280, 180});
drawWindow(sctx.tctx, m_open, [this] {
auto const&templates =
m_types[m_selectedType]->itemTemplates();
ig::ListBox("Template", [&](size_t const i) -> ox::CStringView {
return templates[i]->name();
}, templates.size(), m_selectedTemplate, {200, 100});
drawButtons(Stage::NewItemType, Stage::NewItemName);
drawButtons(Stage::NewItemType, Stage::NewItemPath);
});
}
void NewMenu::drawNewItemName(StudioContext &sctx) noexcept {
void NewMenu::drawNewItemPath(StudioContext &sctx) noexcept {
setSize({380, 340});
drawWindow(sctx.tctx, m_open, [this, &sctx] {
if (m_selectedType < m_types.size()) {
ig::InputText("Name", m_itemName);
}
ImGui::NewLine();
ImGui::Text("Path");
auto const vp = ImGui::GetContentRegionAvail();
m_explorer.draw(sctx, {vp.x, vp.y - 50});
drawLastPageButtons(sctx);
});
}
@@ -165,8 +182,10 @@ void NewMenu::finish(StudioContext &sctx) noexcept {
return;
}
auto const&im = *m_types[m_selectedType];
auto const path = m_path.len() ?
im.itemPath(m_itemName, m_path) : im.itemPath(m_itemName);
if (auto p = m_explorer.selectedPath()) {
m_path = std::move(*p);
}
auto const path = sfmt("{}/{}.{}", m_path, m_itemName, im.fileExt());
if (sctx.project->exists(path)) {
oxLogError(ox::Error{1, "New file error: file already exists"});
return;

View File

@@ -8,6 +8,7 @@
#include <ox/event/signal.hpp>
#include <ox/std/string.hpp>
#include <studio/filetreemodel.hpp>
#include <studio/itemmaker.hpp>
#include <studio/popup.hpp>
@@ -19,7 +20,7 @@ class NewMenu final: public Popup {
Closed,
Opening,
NewItemType,
NewItemName,
NewItemPath,
NewItemTemplate,
};
@@ -28,16 +29,18 @@ class NewMenu final: public Popup {
private:
Stage m_stage = Stage::Closed;
keel::Context &m_kctx;
ox::String m_typeName;
ox::IString<255> m_itemName;
ox::String m_path;
ox::Vector<ox::UPtr<studio::ItemMaker>> m_types;
ox::Vector<ox::UPtr<ItemMaker>> m_types;
FileExplorer m_explorer{m_kctx};
size_t m_selectedType = 0;
size_t m_selectedTemplate = 0;
bool m_open = false;
public:
NewMenu() noexcept;
NewMenu(keel::Context &kctx) noexcept;
void openPath(ox::StringParam path) noexcept;
@@ -72,9 +75,9 @@ class NewMenu final: public Popup {
private:
void drawNewItemType(StudioContext const&sctx) noexcept;
void drawNewItemName(StudioContext &sctx) noexcept;
void drawNewItemPath(StudioContext &sctx) noexcept;
void drawNewItemTemplate(StudioContext &sctx) noexcept;
void drawNewItemTemplate(StudioContext const &sctx) noexcept;
void drawButtons(Stage prev, Stage next) noexcept;

View File

@@ -10,51 +10,42 @@
namespace studio {
static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
ProjectExplorer &explorer,
ox::StringParam name,
ox::StringView path,
ProjectTreeModel *parent) noexcept {
auto const fs = explorer.romFs();
OX_REQUIRE(stat, fs->stat(path));
auto out = ox::make_unique<ProjectTreeModel>(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<ox::UniquePtr<ProjectTreeModel>> outChildren;
for (auto const&childName : children) {
if (childName[0] != '.') {
auto const childPath = ox::sfmt("{}/{}", path, childName);
OX_REQUIRE_M(child, buildProjectTreeModel(explorer, childName, childPath, out.get()));
outChildren.emplace_back(std::move(child));
}
}
out->setChildren(std::move(outChildren));
}
return out;
}
ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) {
}
void ProjectExplorer::draw(StudioContext &ctx) noexcept {
auto const viewport = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true);
ImGui::SetNextItemOpen(true);
if (m_treeModel) {
m_treeModel->draw(ctx.tctx);
}
ImGui::EndChild();
}
void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
m_treeModel = std::move(model);
ProjectExplorer::ProjectExplorer(keel::Context &kctx) noexcept:
FileExplorer{kctx, true} {
}
ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept {
OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr));
OX_REQUIRE_M(model, buildFileTreeModel(*this, "Project", "/", nullptr));
setModel(std::move(model));
return {};
}
void ProjectExplorer::fileOpened(ox::StringViewCR path) const noexcept {
fileChosen.emit(path);
}
void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept {
if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) {
if (ImGui::MenuItem("Delete")) {
deleteItem.emit(path);
}
ImGui::EndPopup();
}
}
void ProjectExplorer::dirContextMenu(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();
}
}
}

View File

@@ -7,15 +7,12 @@
#include <ox/event/signal.hpp>
#include <ox/std/memory.hpp>
#include <studio/filetreemodel.hpp>
#include <studio/widget.hpp>
#include "projecttreemodel.hpp"
namespace studio {
class ProjectExplorer: public Widget {
private:
ox::UPtr<ProjectTreeModel> m_treeModel;
turbine::Context &m_ctx;
class ProjectExplorer final: public FileExplorer {
public:
// slots
@@ -24,18 +21,16 @@ class ProjectExplorer: public Widget {
ox::Signal<ox::Error(ox::StringViewCR)> addDir;
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
explicit ProjectExplorer(turbine::Context &ctx) noexcept;
void draw(StudioContext &ctx) noexcept override;
void setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept;
explicit ProjectExplorer(keel::Context &kctx) noexcept;
ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept;
[[nodiscard]]
ox::FileSystem *romFs() noexcept {
return rom(m_ctx);
}
protected:
void fileOpened(ox::StringViewCR path) const noexcept override;
void fileContextMenu(ox::StringViewCR path) const noexcept override;
void dirContextMenu(ox::StringViewCR path) const noexcept override;
};

View File

@@ -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();
}
}
}

View File

@@ -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 {};
}
};
}

View File

@@ -50,7 +50,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
m_sctx(*this, ctx),
m_tctx(ctx),
m_projectDataDir(std::move(projectDataDir)),
m_projectExplorer(m_tctx),
m_projectExplorer(keelCtx(m_tctx)),
m_newProject(m_projectDataDir),
m_aboutPopup(m_tctx) {
turbine::setApplicationData(m_tctx, &m_sctx);
@@ -120,7 +120,8 @@ void StudioUI::draw() noexcept {
ig::s_mainWinHasFocus = ImGui::IsWindowFocused(
ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
if (m_showProjectExplorer) {
m_projectExplorer.draw(m_sctx);
auto const v = ImGui::GetContentRegionAvail();
m_projectExplorer.draw(m_sctx, {300, v.y});
ImGui::SameLine();
}
drawTabBar();

View File

@@ -19,7 +19,6 @@
#include "newmenu.hpp"
#include "newproject.hpp"
#include "projectexplorer.hpp"
#include "projecttreemodel.hpp"
namespace studio {
@@ -40,7 +39,7 @@ class StudioUI: public ox::SignalHandler {
BaseEditor *m_activeEditorOnLastDraw = nullptr;
BaseEditor *m_activeEditor = nullptr;
BaseEditor *m_activeEditorUpdatePending = nullptr;
NewMenu m_newMenu;
NewMenu m_newMenu{keelCtx(m_tctx)};
DeleteConfirmation m_deleteConfirmation;
NewDir m_newDirDialog;
NewProject m_newProject;