Squashed 'deps/nostalgia/' changes from a3d6a58c..d68e6493

d68e6493 [nostalgia/core/studio/tilesheeteditor] Add support for dragging palette to palette selector
1cbc5762 [studio] Complete drag/drop support for files
500b9356 [studio] Make new dir window OK on Enter key
800ca851 [ox/std] Fix possible error that occurs with appending on boundary of small string size
cc466a9f [studio] Add support for adding and deleting directories
9d115584 [nostalgia] Rename player from 'nostalgia' to 'Nostalgia'
a2139c09 [studio] Cleanup unused member
a3e5f27a [ox/std] Fix Mac build
643f95ec [studio] Add confirmation dialog for file deletion, move deletion to Project
69241476 [studio] Add ability to add file through dir context menu
6e2b4fa7 [nostalgia] Cleanup player run in Makefile
4e5c7499 [studio] Add support for deleting files
66229de7 [ox/fs] FileSystem fixes with removing files
7eb37c53 [nostalgia/core/studio/paletteeditor] Fix adding page if there is no existing page
7a21b207 [nostalgia/core] Replace ContextDeleter with safeDelete(Context*)
894be237 [ox/std] Drop ox:: qualifier from safeDelete function for pointee
92e9d9cb [keel,studio] Add support for New Item templates
b29b9a9b [ox/std] Add UAnyPtr
721f8442 [nostalgia/core/studio/tilesheeteditor] Fix subsheet and palette scrolling

git-subtree-dir: deps/nostalgia
git-subtree-split: d68e64931b37d7d8bbaff7b43bf131c7acf2aa97
This commit is contained in:
2025-01-19 13:31:44 -06:00
parent 50b1ed33df
commit 7b7d59cf63
45 changed files with 1024 additions and 239 deletions

View File

@@ -2,8 +2,10 @@ add_library(
StudioAppLib
aboutpopup.cpp
clawviewer.cpp
deleteconfirmation.cpp
filedialogmanager.cpp
main.cpp
newdir.cpp
newmenu.cpp
newproject.cpp
projectexplorer.cpp

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#include "deleteconfirmation.hpp"
namespace studio {
DeleteConfirmation::DeleteConfirmation() noexcept {
setTitle("Delete Item");
}
void DeleteConfirmation::openPath(ox::StringViewCR path) noexcept {
open();
m_path = path;
}
void DeleteConfirmation::open() noexcept {
m_path = "";
m_stage = Stage::Opening;
}
void DeleteConfirmation::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool DeleteConfirmation::isOpen() const noexcept {
return m_open;
}
void DeleteConfirmation::draw(StudioContext &ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
m_stage = Stage::Open;
m_open = true;
[[fallthrough]];
case Stage::Open:
drawWindow(ctx.tctx, m_open, [this] {
ImGui::Text("Are you sure you want to delete %s?", m_path.c_str());
if (ig::PopupControlsOkCancel(m_open, "Yes", "No") != ig::PopupResponse::None) {
deleteFile.emit(m_path);
close();
}
});
break;
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/string.hpp>
#include <studio/context.hpp>
#include <studio/popup.hpp>
namespace studio {
class DeleteConfirmation final: public Popup {
private:
enum class Stage {
Closed,
Opening,
Open,
};
Stage m_stage = Stage::Closed;
bool m_open{};
ox::String m_path;
public:
ox::Signal<ox::Error(ox::StringViewCR path)> deleteFile;
DeleteConfirmation() noexcept;
void openPath(ox::StringViewCR path) noexcept;
void open() noexcept override;
void close() noexcept override;
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(StudioContext &ctx) noexcept override;
};
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#include "newdir.hpp"
namespace studio {
NewDir::NewDir() noexcept {
setTitle("New Directory");
setSize({280, 0});
}
void NewDir::openPath(ox::StringViewCR path) noexcept {
open();
m_path = path;
}
void NewDir::open() noexcept {
m_path = "";
m_stage = Stage::Opening;
}
void NewDir::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool NewDir::isOpen() const noexcept {
return m_open;
}
void NewDir::draw(StudioContext &ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
m_open = true;
[[fallthrough]];
case Stage::Open:
drawWindow(ctx.tctx, m_open, [this] {
if (m_stage == Stage::Opening) {
ImGui::SetKeyboardFocusHere();
}
ig::InputText("Name", m_str);
if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter)) {
newDir.emit(m_path + "/" + m_str);
close();
}
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) {
newDir.emit(m_path + "/" + m_str);
close();
}
});
m_stage = Stage::Open;
break;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/string.hpp>
#include <studio/context.hpp>
#include <studio/popup.hpp>
namespace studio {
class NewDir final: public Popup {
private:
enum class Stage {
Closed,
Opening,
Open,
};
Stage m_stage = Stage::Closed;
bool m_open{};
ox::String m_path;
ox::IString<255> m_str;
public:
ox::Signal<ox::Error(ox::StringViewCR path)> newDir;
NewDir() noexcept;
void openPath(ox::StringViewCR path) noexcept;
void open() noexcept override;
void close() noexcept override;
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(StudioContext &ctx) noexcept override;
[[nodiscard]]
constexpr ox::CStringView value() const noexcept {
return m_str;
}
};
}

View File

@@ -14,7 +14,7 @@ namespace studio {
NewMenu::NewMenu() noexcept {
setTitle("New Item");
setSize({230, 140});
setSize({280, 180});
}
void NewMenu::open() noexcept {
@@ -22,6 +22,12 @@ void NewMenu::open() noexcept {
m_selectedType = 0;
m_itemName = "";
m_typeName = "";
m_path = "";
}
void NewMenu::openPath(ox::StringParam path) noexcept {
open();
m_path = std::move(path);
}
void NewMenu::close() noexcept {
@@ -33,7 +39,7 @@ bool NewMenu::isOpen() const noexcept {
return m_open;
}
void NewMenu::draw(studio::StudioContext &sctx) noexcept {
void NewMenu::draw(StudioContext &sctx) noexcept {
switch (m_stage) {
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
@@ -46,51 +52,73 @@ void NewMenu::draw(studio::StudioContext &sctx) noexcept {
case Stage::NewItemName:
drawNewItemName(sctx);
break;
case Stage::NewItemTemplate:
drawNewItemTemplate(sctx);
break;
case Stage::Closed:
m_open = false;
break;
}
}
void NewMenu::addItemMaker(ox::UniquePtr<studio::ItemMaker> &&im) noexcept {
void NewMenu::addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept {
m_types.emplace_back(std::move(im));
std::sort(
m_types.begin(), m_types.end(),
[](ox::UPtr<ItemMaker> const&im1, ox::UPtr<ItemMaker> const&im2) {
return im1->typeName < im2->typeName;
return im1->typeDisplayName() < im2->typeDisplayName();
});
}
void NewMenu::drawNewItemType(studio::StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, &m_open, [this] {
auto const allocSz = m_types.size() * sizeof(char const*);
auto mem = ox_malloca(allocSz, char const*, nullptr);
auto items = ox::Span{mem.get(), allocSz};
for (auto i = 0u; auto const&im : m_types) {
items[i] = im->typeName.c_str();
++i;
void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept {
for (auto const&im : m_types) {
if (im->installTemplate(tmplt)) {
break;
}
ImGui::ListBox("Item Type", &m_selectedType, items.data(), static_cast<int>(m_types.size()));
drawFirstPageButtons();
}
}
void NewMenu::drawNewItemType(StudioContext const&sctx) noexcept {
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);
});
}
void NewMenu::drawNewItemName(studio::StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, &m_open, [this, &sctx] {
auto const typeIdx = static_cast<std::size_t>(m_selectedType);
if (typeIdx < m_types.size()) {
void NewMenu::drawNewItemTemplate(StudioContext &sctx) noexcept {
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);
});
}
void NewMenu::drawNewItemName(StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, m_open, [this, &sctx] {
if (m_selectedType < m_types.size()) {
ig::InputText("Name", m_itemName);
}
drawLastPageButtons(sctx);
});
}
void NewMenu::drawFirstPageButtons() noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130);
void NewMenu::drawButtons(Stage const prev, Stage const next) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
auto const btnSz = ImVec2(60, 20);
constexpr ImVec2 btnSz{60, 20};
if (ImGui::Button("Back", btnSz)) {
m_stage = prev;
}
ImGui::SameLine();
if (ImGui::Button("Next", btnSz)) {
m_stage = Stage::NewItemName;
m_stage = next;
}
ImGui::SameLine();
if (ImGui::Button("Cancel", btnSz)) {
@@ -99,35 +127,51 @@ void NewMenu::drawFirstPageButtons() noexcept {
}
}
void NewMenu::drawLastPageButtons(studio::StudioContext &sctx) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 138);
void NewMenu::drawFirstPageButtons(Stage const next) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 130);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
if (ImGui::Button("Back")) {
m_stage = Stage::NewItemType;
constexpr ImVec2 btnSz{60, 20};
if (ImGui::Button("Next", btnSz)) {
m_stage = next;
}
ImGui::SameLine();
if (ImGui::Button("Finish")) {
finish(sctx);
}
ImGui::SameLine();
if (ImGui::Button("Quit")) {
if (ImGui::Button("Cancel", btnSz)) {
ImGui::CloseCurrentPopup();
m_stage = Stage::Closed;
}
}
void NewMenu::finish(studio::StudioContext &sctx) noexcept {
void NewMenu::drawLastPageButtons(StudioContext &sctx) noexcept {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 198);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetContentRegionAvail().y - 20);
constexpr ImVec2 btnSz{60, 20};
if (ImGui::Button("Back", btnSz)) {
m_stage = Stage::NewItemType;
}
ImGui::SameLine();
if (ImGui::Button("Finish", btnSz)) {
finish(sctx);
}
ImGui::SameLine();
if (ImGui::Button("Quit", btnSz)) {
ImGui::CloseCurrentPopup();
m_stage = Stage::Closed;
}
}
void NewMenu::finish(StudioContext &sctx) noexcept {
if (m_itemName.len() == 0) {
oxLogError(ox::Error(1, "New file error: no file name"));
oxLogError(ox::Error{1, "New file error: no file name"});
return;
}
auto const&typeMaker = *m_types[static_cast<std::size_t>(m_selectedType)];
if (sctx.project->exists(typeMaker.itemPath(m_itemName))) {
oxLogError(ox::Error(1, "New file error: file already exists"));
auto const&im = *m_types[m_selectedType];
auto const path = m_path.len() ?
im.itemPath(m_itemName, m_path) : im.itemPath(m_itemName);
if (sctx.project->exists(path)) {
oxLogError(ox::Error{1, "New file error: file already exists"});
return;
}
auto const [path, err] = typeMaker.write(sctx, m_itemName);
if (err) {
if (auto const err = im.write(sctx, path, m_selectedTemplate)) {
oxLogError(err);
return;
}

View File

@@ -13,13 +13,14 @@
namespace studio {
class NewMenu: public studio::Popup {
class NewMenu final: public Popup {
public:
enum class Stage {
Closed,
Opening,
NewItemType,
NewItemName,
NewItemTemplate,
};
// emits path parameter
@@ -29,13 +30,17 @@ class NewMenu: public studio::Popup {
Stage m_stage = Stage::Closed;
ox::String m_typeName;
ox::IString<255> m_itemName;
ox::Vector<ox::UniquePtr<studio::ItemMaker>> m_types;
int m_selectedType = 0;
ox::String m_path;
ox::Vector<ox::UPtr<studio::ItemMaker>> m_types;
size_t m_selectedType = 0;
size_t m_selectedTemplate = 0;
bool m_open = false;
public:
NewMenu() noexcept;
void openPath(ox::StringParam path) noexcept;
void open() noexcept override;
void close() noexcept override;
@@ -43,37 +48,70 @@ class NewMenu: public studio::Popup {
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(studio::StudioContext &sctx) noexcept override;
void draw(StudioContext &sctx) noexcept override;
template<typename T>
void addItemType(ox::String name, ox::String parentDir, ox::String fileExt, T itemTempl, ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
void addItemType(
ox::StringParam displayName,
ox::StringParam parentDir,
ox::StringParam fileExt,
T itemTempl,
ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
template<typename T>
void addItemType(ox::String name, ox::String parentDir, ox::String fileExt, ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
void addItemType(
ox::StringParam displayName,
ox::StringParam parentDir,
ox::StringParam fileExt,
ox::ClawFormat pFmt = ox::ClawFormat::Metal) noexcept;
void addItemMaker(ox::UniquePtr<studio::ItemMaker> &&im) noexcept;
void addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept;
void installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept;
private:
void drawNewItemType(studio::StudioContext &sctx) noexcept;
void drawNewItemType(StudioContext const&sctx) noexcept;
void drawNewItemName(studio::StudioContext &sctx) noexcept;
void drawNewItemName(StudioContext &sctx) noexcept;
void drawFirstPageButtons() noexcept;
void drawNewItemTemplate(StudioContext &sctx) noexcept;
void drawLastPageButtons(studio::StudioContext &sctx) noexcept;
void drawButtons(Stage prev, Stage next) noexcept;
void finish(studio::StudioContext &sctx) noexcept;
void drawFirstPageButtons(Stage next) noexcept;
void drawLastPageButtons(StudioContext &sctx) noexcept;
void finish(StudioContext &sctx) noexcept;
};
template<typename T>
void NewMenu::addItemType(ox::String displayName, ox::String parentDir, ox::String fileExt, T itemTempl, ox::ClawFormat pFmt) noexcept {
m_types.emplace_back(ox::make<studio::ItemMakerT<T>>(std::move(displayName), std::move(parentDir), std::move(fileExt), std::move(itemTempl), pFmt));
void NewMenu::addItemType(
ox::StringParam displayName,
ox::StringParam parentDir,
ox::StringParam fileExt,
T itemTempl,
ox::ClawFormat const pFmt) noexcept {
m_types.emplace_back(ox::make<ItemMakerT<T>>(
std::move(displayName),
std::move(parentDir),
std::move(fileExt),
std::move(itemTempl),
pFmt));
}
template<typename T>
void NewMenu::addItemType(ox::String displayName, ox::String parentDir, ox::String fileExt, ox::ClawFormat pFmt) noexcept {
m_types.emplace_back(ox::make<studio::ItemMakerT<T>>(std::move(displayName), std::move(parentDir), std::move(fileExt), pFmt));
void NewMenu::addItemType(
ox::StringParam displayName,
ox::StringParam parentDir,
ox::StringParam fileExt,
ox::ClawFormat const pFmt) noexcept {
m_types.emplace_back(ox::make<ItemMakerT<T>>(
std::move(displayName),
std::move(parentDir),
std::move(fileExt),
pFmt));
}
}

View File

@@ -49,7 +49,7 @@ void NewProject::draw(studio::StudioContext &ctx) noexcept {
}
void NewProject::drawNewProjectName(studio::StudioContext &sctx) noexcept {
drawWindow(sctx.tctx, &m_open, [this, &sctx] {
drawWindow(sctx.tctx, m_open, [this, &sctx] {
ig::InputText("Name", m_projectName);
ImGui::Text("Path: %s", m_projectPath.c_str());
if (ImGui::Button("Browse")) {

View File

@@ -12,12 +12,12 @@ namespace studio {
static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
ProjectExplorer &explorer,
ox::StringView name,
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, ox::String(name), parent);
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());
@@ -37,7 +37,7 @@ static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) {
}
void ProjectExplorer::draw(studio::StudioContext &ctx) noexcept {
void ProjectExplorer::draw(StudioContext &ctx) noexcept {
auto const viewport = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true);
ImGui::SetNextItemOpen(true);
@@ -54,7 +54,7 @@ void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept {
OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr));
setModel(std::move(model));
return ox::Error(0);
return {};
}
}

View File

@@ -12,27 +12,31 @@
namespace studio {
class ProjectExplorer: public studio::Widget {
class ProjectExplorer: public Widget {
private:
ox::UPtr<ProjectTreeModel> m_treeModel;
turbine::Context &m_ctx;
public:
// slots
ox::Signal<ox::Error(ox::StringViewCR)> fileChosen;
ox::Signal<ox::Error(ox::StringViewCR)> addItem;
ox::Signal<ox::Error(ox::StringViewCR)> addDir;
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
explicit ProjectExplorer(turbine::Context &ctx) noexcept;
void draw(studio::StudioContext &ctx) noexcept override;
void draw(StudioContext &ctx) noexcept override;
void setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept;
ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept;
[[nodiscard]]
inline ox::FileSystem *romFs() noexcept {
ox::FileSystem *romFs() noexcept {
return rom(m_ctx);
}
// slots
public:
ox::Signal<ox::Error(ox::StringView const&)> fileChosen;
};
}

View File

@@ -4,13 +4,18 @@
#include <imgui.h>
#include <studio/dragdrop.hpp>
#include <studio/imguiutil.hpp>
#include "projectexplorer.hpp"
#include "projecttreemodel.hpp"
namespace studio {
ProjectTreeModel::ProjectTreeModel(ProjectExplorer &explorer, ox::String name,
ProjectTreeModel *parent) noexcept:
ProjectTreeModel::ProjectTreeModel(
ProjectExplorer &explorer,
ox::StringParam name,
ProjectTreeModel *parent) noexcept:
m_explorer(explorer),
m_parent(parent),
m_name(std::move(name)) {
@@ -23,14 +28,18 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept:
m_children(std::move(other.m_children)) {
}
void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept {
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(ctx);
child->draw(tctx);
}
ImGui::TreePop();
} else {
ig::IDStackItem const idStackItem{m_name};
drawDirContextMenu();
}
} else {
auto const path = fullPath();
@@ -39,7 +48,17 @@ void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept {
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>()});
});
}
}
}
@@ -48,11 +67,19 @@ void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> childr
m_children = std::move(children);
}
ox::BasicString<255> ProjectTreeModel::fullPath() const noexcept {
if (m_parent) {
return m_parent->fullPath() + "/" + ox::StringView(m_name);
}
return {};
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

@@ -17,19 +17,30 @@ class ProjectTreeModel {
ProjectTreeModel *m_parent = nullptr;
ox::String m_name;
ox::Vector<ox::UPtr<ProjectTreeModel>> m_children;
public:
explicit ProjectTreeModel(class ProjectExplorer &explorer, ox::String name,
ProjectTreeModel *parent = nullptr) noexcept;
explicit ProjectTreeModel(
ProjectExplorer &explorer, ox::StringParam name,
ProjectTreeModel *parent = nullptr) noexcept;
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
void draw(turbine::Context &ctx) const 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]]
ox::BasicString<255> fullPath() const noexcept;
Str fullPath() const noexcept {
if (m_parent) {
return m_parent->fullPath<Str>() + "/" + m_name;
}
return {};
}
};
}

View File

@@ -52,6 +52,9 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
m_aboutPopup(m_tctx) {
turbine::setApplicationData(m_tctx, &m_sctx);
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile);
m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile);
ImGui::GetIO().IniFilename = nullptr;
@@ -251,6 +254,10 @@ void StudioUI::loadModule(Module const&mod) noexcept {
for (auto &im : mod.itemMakers(m_sctx)) {
m_newMenu.addItemMaker(std::move(im));
}
auto tmplts = mod.itemTemplates(m_sctx);
for (auto &t : tmplts) {
m_newMenu.installItemTemplate(t);
}
}
void StudioUI::loadModules() noexcept {
@@ -338,6 +345,21 @@ void StudioUI::handleKeyInput() noexcept {
}
}
ox::Error StudioUI::addDir(ox::StringViewCR path) noexcept {
m_newDirDialog.openPath(path);
return {};
}
ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept {
m_newMenu.openPath(path);
return {};
}
ox::Error StudioUI::deleteFile(ox::StringViewCR path) noexcept {
m_deleteConfirmation.openPath(path);
return {};
}
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
std::error_code ec;
std::filesystem::create_directories(toStdStringView(path), ec);
@@ -352,9 +374,11 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
OX_RETURN_ERROR(
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
.moveTo(m_project));
auto const sctx = applicationData<StudioContext>(m_tctx);
sctx->project = m_project.get();
m_sctx.project = m_project.get();
turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_openFiles.clear();

View File

@@ -12,7 +12,10 @@
#include <studio/module.hpp>
#include <studio/project.hpp>
#include <studio/task.hpp>
#include "aboutpopup.hpp"
#include "deleteconfirmation.hpp"
#include "newdir.hpp"
#include "newmenu.hpp"
#include "newproject.hpp"
#include "projectexplorer.hpp"
@@ -38,12 +41,16 @@ class StudioUI: public ox::SignalHandler {
BaseEditor *m_activeEditor = nullptr;
BaseEditor *m_activeEditorUpdatePending = nullptr;
NewMenu m_newMenu;
DeleteConfirmation m_deleteConfirmation;
NewDir m_newDirDialog;
NewProject m_newProject;
AboutPopup m_aboutPopup;
ox::Array<Popup*, 3> const m_popups = {
ox::Array<Popup*, 5> const m_popups = {
&m_newMenu,
&m_newProject,
&m_aboutPopup
&m_aboutPopup,
&m_deleteConfirmation,
&m_newDirDialog,
};
bool m_showProjectExplorer = true;
@@ -83,6 +90,12 @@ class StudioUI: public ox::SignalHandler {
void handleKeyInput() noexcept;
ox::Error addDir(ox::StringViewCR path) noexcept;
ox::Error addFile(ox::StringViewCR path) noexcept;
ox::Error deleteFile(ox::StringViewCR path) noexcept;
ox::Error createOpenProject(ox::StringViewCR path) noexcept;
ox::Error openProjectPath(ox::StringParam path) noexcept;