Merge commit '449bcf87893e82f31a3f176aa63ec2077a0c3bf2'
All checks were successful
Build / build (push) Successful in 4m8s

This commit is contained in:
Gary Talent 2025-01-27 02:22:07 -06:00
commit eb1a505432
15 changed files with 172 additions and 28 deletions

View File

@ -14,3 +14,4 @@
* Ctrl-<num key> keyboard shortcuts for jumping between tabs. * Ctrl-<num key> keyboard shortcuts for jumping between tabs.
* Fix Palette Editor to ignore keyboard input when popups are open. * Fix Palette Editor to ignore keyboard input when popups are open.
* Palette Editor move color mechanism now uses drag and drop. * Palette Editor move color mechanism now uses drag and drop.
* Add ability to reorder Palette pages.

View File

@ -1,18 +1,6 @@
target_sources(
NostalgiaCore-Studio PRIVATE
commands/addcolorcommand.cpp
commands/addpagecommand.cpp
commands/applycolorallpagescommand.cpp
commands/duplicatepagecommand.cpp
commands/movecolorcommand.cpp
commands/removecolorcommand.cpp
commands/removepagecommand.cpp
commands/renamepagecommand.cpp
commands/updatecolorcommand.cpp
commands/updatecolorinfocommand.cpp
)
target_sources( target_sources(
NostalgiaCore-Studio-ImGui PRIVATE NostalgiaCore-Studio-ImGui PRIVATE
paletteeditor-imgui.cpp paletteeditor-imgui.cpp
) )
add_subdirectory(commands)

View File

@ -0,0 +1,15 @@
target_sources(
NostalgiaCore-Studio PRIVATE
addcolorcommand.cpp
addpagecommand.cpp
applycolorallpagescommand.cpp
duplicatepagecommand.cpp
movecolorcommand.cpp
movepagecommand.cpp
removecolorcommand.cpp
removepagecommand.cpp
renamepagecommand.cpp
updatecolorcommand.cpp
updatecolorinfocommand.cpp
)

View File

@ -12,6 +12,7 @@ enum class PaletteEditorCommandId {
AddPage, AddPage,
DuplicatePage, DuplicatePage,
RemovePage, RemovePage,
MovePage,
AddColor, AddColor,
RemoveColor, RemoveColor,
UpdateColorInfo, UpdateColorInfo,

View File

@ -0,0 +1,40 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "movepagecommand.hpp"
namespace nostalgia::gfx {
MovePageCommand::MovePageCommand(
Palette &pal,
size_t const srcIdx,
size_t const dstIdx) noexcept:
m_pal(pal),
m_srcIdx(srcIdx),
m_dstIdx(dstIdx) {}
int MovePageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::MovePage);
}
ox::Error MovePageCommand::redo() noexcept {
movePage(m_srcIdx, m_dstIdx);
return {};
}
ox::Error MovePageCommand::undo() noexcept {
movePage(m_dstIdx, m_srcIdx);
return {};
}
void MovePageCommand::movePage(
size_t const srcIdx, size_t const dstIdx) noexcept {
auto page = std::move(m_pal.pages[srcIdx]);
std::ignore = m_pal.pages.erase(srcIdx);
m_pal.pages.emplace(dstIdx, std::move(page));
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <studio/studio.hpp>
#include <nostalgia/gfx/palette.hpp>
namespace nostalgia::gfx {
class MovePageCommand: public studio::UndoCommand {
private:
Palette &m_pal;
std::size_t const m_srcIdx = 0;
std::size_t const m_dstIdx = 0;
public:
MovePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
~MovePageCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept override;
ox::Error redo() noexcept override;
ox::Error undo() noexcept override;
private:
void movePage(size_t srcIdx, size_t dstIdx) noexcept;
};
}

View File

@ -11,6 +11,7 @@
#include "commands/applycolorallpagescommand.hpp" #include "commands/applycolorallpagescommand.hpp"
#include "commands/duplicatepagecommand.hpp" #include "commands/duplicatepagecommand.hpp"
#include "commands/movecolorcommand.hpp" #include "commands/movecolorcommand.hpp"
#include "commands/movepagecommand.hpp"
#include "commands/removecolorcommand.hpp" #include "commands/removecolorcommand.hpp"
#include "commands/removepagecommand.hpp" #include "commands/removepagecommand.hpp"
#include "commands/renamepagecommand.hpp" #include "commands/renamepagecommand.hpp"
@ -19,6 +20,7 @@
#include "paletteeditor-imgui.hpp" #include "paletteeditor-imgui.hpp"
namespace nostalgia::gfx { namespace nostalgia::gfx {
namespace ig = studio::ig; namespace ig = studio::ig;
@ -33,6 +35,16 @@ OX_MODEL_BEGIN(ColorDragDrop)
OX_MODEL_FIELD(i) OX_MODEL_FIELD(i)
OX_MODEL_END() OX_MODEL_END()
struct PageDragDrop {
static constexpr auto TypeName = "nostalgia.gfx.PageDragDrop";
static constexpr auto TypeVersion = 1;
uint32_t page{};
};
OX_MODEL_BEGIN(PageDragDrop)
OX_MODEL_FIELD(page)
OX_MODEL_END()
void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept { void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept {
if (!m_show) { if (!m_show) {
return; return;
@ -235,6 +247,16 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) { if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
m_page = i; m_page = i;
} }
std::ignore = ig::dragDropSource([this, i] {
ImGui::Text("%s", m_pal.pages[i].name.c_str());
return ig::setDragDropPayload(PageDragDrop{i});
}, ImGuiDragDropFlags_SourceAllowNullID);
if (ig::DragDropTarget const d; d) {
auto const [src, err] = ig::getDragDropPayload<PageDragDrop>();
if (!err) {
std::ignore = pushCommand<MovePageCommand>(m_pal, src.page, i);
}
}
} }
} }
ImGui::EndTable(); ImGui::EndTable();

View File

@ -214,7 +214,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true); ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
{ {
auto const controlsSize = ImGui::GetContentRegionAvail(); auto const controlsSize = ImGui::GetContentRegionAvail();
ImGui::BeginChild("ToolBox", {s_palViewWidth - 24, 30}, true); ImGui::BeginChild("ToolBox", {168, 32}, true);
{ {
auto const btnSz = ImVec2{45, 14}; auto const btnSz = ImVec2{45, 14};
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) { if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
@ -232,9 +232,10 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
} }
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::BeginChild("OperationsBox", {s_palViewWidth - 24, ig::BtnSz.y + 17}, true); ImGui::SameLine();
ImGui::BeginChild("OperationsBox", {0, 32}, true);
{ {
auto constexpr btnSz = ImVec2{55, ig::BtnSz.y}; auto constexpr btnSz = ImVec2{55, 16};
if (ig::PushButton("Flip X", btnSz)) { if (ig::PushButton("Flip X", btnSz)) {
oxLogError(m_model.flipX()); oxLogError(m_model.flipX());
} }

View File

@ -45,7 +45,7 @@ class TileSheetEditorImGui: public studio::Editor {
[[nodiscard]] [[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; } constexpr bool isOpen() const noexcept { return m_show; }
}; };
static constexpr float s_palViewWidth = 300; static constexpr float s_palViewWidth = 335;
studio::StudioContext &m_sctx; studio::StudioContext &m_sctx;
turbine::Context &m_tctx; turbine::Context &m_tctx;
ox::Vector<ox::String> m_paletteList; ox::Vector<ox::String> m_paletteList;

View File

@ -41,18 +41,35 @@ void RenameFile::draw(StudioContext &ctx) noexcept {
case Stage::Opening: case Stage::Opening:
ImGui::OpenPopup(title().c_str()); ImGui::OpenPopup(title().c_str());
m_open = true; m_open = true;
m_fileExists = false;
m_stage = Stage::Open; m_stage = Stage::Open;
[[fallthrough]]; [[fallthrough]];
case Stage::Open: case Stage::Open:
setSize({250, 0}); setSize({250, 0});
drawWindow(ctx.tctx, m_open, [this] { drawWindow(ctx.tctx, m_open, [this, &ctx] {
if (ImGui::IsWindowAppearing()) { if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere(); ImGui::SetKeyboardFocusHere();
} }
ig::InputText("Name", m_name); m_fileExists = !ig::InputText("Name", m_name) && m_fileExists;
auto const nameInputFocused = ImGui::IsItemFocused();
if (m_fileExists) {
ImGui::Text("File %s already exists.", m_name.c_str());
} else {
ImGui::Text("%s%s", m_path.c_str(), m_name.c_str()); ImGui::Text("%s%s", m_path.c_str(), m_name.c_str());
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) { }
moveFile.emit(m_oldPath, m_path + m_name); bool b{};
auto const response = ig::PopupControlsOkCancel(b);
if (response == ig::PopupResponse::OK ||
(nameInputFocused && ImGui::IsKeyPressed(ImGuiKey_Enter))) {
auto const newPath = m_path + m_name;
if (!ctx.project->exists(newPath)) {
moveFile.emit(m_oldPath, newPath);
close();
} else {
m_open = true;
m_fileExists = true;
}
} else if (response == ig::PopupResponse::Cancel) {
close(); close();
} }
}); });

View File

@ -21,6 +21,7 @@ class RenameFile: public Popup {
ox::String m_oldPath; ox::String m_oldPath;
ox::String m_path; ox::String m_path;
ox::IString<255> m_name; ox::IString<255> m_name;
bool m_fileExists{};
bool m_open{}; bool m_open{};
public: public:

View File

@ -392,6 +392,18 @@ ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR ne
return m_projectExplorer.refreshProjectTreeModel(); return m_projectExplorer.refreshProjectTreeModel();
} }
ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept {
for (size_t i{}; auto &e : m_editors) {
if (path == e->itemPath()) {
oxLogError(m_editors.erase(i).error);
oxLogError(closeFile(path));
break;
}
++i;
}
return m_projectExplorer.refreshProjectTreeModel();
}
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept { ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
std::error_code ec; std::error_code ec;
std::filesystem::create_directories(toStdStringView(path), ec); std::filesystem::create_directories(toStdStringView(path), ec);
@ -412,7 +424,7 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir); m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileDeleted.connect(this, &StudioUI::handleDeleteFile);
m_project->fileMoved.connect(this, &StudioUI::handleMoveFile); m_project->fileMoved.connect(this, &StudioUI::handleMoveFile);
m_openFiles.clear(); m_openFiles.clear();
m_editors.clear(); m_editors.clear();
@ -495,12 +507,16 @@ ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noex
void StudioUI::procFileMoves() noexcept { void StudioUI::procFileMoves() noexcept {
for (auto const &m : m_queuedMoves) { for (auto const &m : m_queuedMoves) {
if (!m_project->exists(m.b)) {
oxLogError(m_project->moveItem(m.a, m.b)); oxLogError(m_project->moveItem(m.a, m.b));
} }
}
m_queuedMoves.clear(); m_queuedMoves.clear();
for (auto const &m : m_queuedDirMoves) { for (auto const &m : m_queuedDirMoves) {
if (!m_project->exists(m.b)) {
oxLogError(m_project->moveDir(m.a, m.b)); oxLogError(m_project->moveDir(m.a, m.b));
} }
}
m_queuedDirMoves.clear(); m_queuedDirMoves.clear();
} }

View File

@ -104,6 +104,8 @@ class StudioUI: public ox::SignalHandler {
ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept; ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
ox::Error handleDeleteFile(ox::StringViewCR path) noexcept;
ox::Error createOpenProject(ox::StringViewCR path) noexcept; ox::Error createOpenProject(ox::StringViewCR path) noexcept;
ox::Error openProjectPath(ox::StringParam path) noexcept; ox::Error openProjectPath(ox::StringParam path) noexcept;

View File

@ -64,13 +64,11 @@ PopupResponse PopupControlsOkCancel(
ImGui::Separator(); ImGui::Separator();
ImGui::SetCursorPosX(popupWidth - 118); ImGui::SetCursorPosX(popupWidth - 118);
if (ImGui::Button(ok.c_str(), btnSz)) { if (ImGui::Button(ok.c_str(), btnSz)) {
ImGui::CloseCurrentPopup();
popupOpen = false; popupOpen = false;
out = PopupResponse::OK; out = PopupResponse::OK;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::IsKeyDown(ImGuiKey_Escape) || ImGui::Button(cancel.c_str(), btnSz)) { if (ImGui::IsKeyDown(ImGuiKey_Escape) || ImGui::Button(cancel.c_str(), btnSz)) {
ImGui::CloseCurrentPopup();
popupOpen = false; popupOpen = false;
out = PopupResponse::Cancel; out = PopupResponse::Cancel;
} }

View File

@ -46,6 +46,10 @@ static ox::Result<ox::Vector<ox::String>> listAllRecursive(ox::FileSystem &fs, o
return std::move(out); return std::move(out);
} }
[[nodiscard]]
static constexpr bool isParentOf(ox::StringViewCR parent, ox::StringViewCR child) noexcept {
return beginsWith(child, parent);
}
Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir): Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir):
m_kctx(ctx), m_kctx(ctx),
@ -99,6 +103,9 @@ ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcep
} }
ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept { ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
if (isParentOf(src, dest)) {
return ox::Error{1, "cannot move parent to a child directory"};
}
OX_REQUIRE(files, listAllRecursive(m_fs, src)); OX_REQUIRE(files, listAllRecursive(m_fs, src));
OX_RETURN_ERROR(m_fs.move(src, dest)); OX_RETURN_ERROR(m_fs.move(src, dest));
fileMoved.emit(src, dest, ox::UUID{}); fileMoved.emit(src, dest, ox::UUID{});