Compare commits

..

No commits in common. "master" and "release-d2025.02" have entirely different histories.

38 changed files with 72 additions and 887 deletions

View File

@ -4,7 +4,7 @@ on: [push]
jobs: jobs:
build: build:
runs-on: olympic runs-on: nostalgia
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v3 uses: actions/checkout@v3

View File

@ -93,7 +93,7 @@ purge:
${BC_CMD_RM_RF} compile_commands.json ${BC_CMD_RM_RF} compile_commands.json
.PHONY: test .PHONY: test
test: build test: build
${BC_CMD_ENVRUN} ${BC_CMD_PY3} -m mypy ${BC_VAR_SCRIPTS} ${BC_CMD_ENVRUN} mypy ${BC_VAR_SCRIPTS}
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} test ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} test
.PHONY: test-verbose .PHONY: test-verbose
test-verbose: build test-verbose: build

View File

@ -37,30 +37,6 @@ Error FileSystem::read(const FileAddress &addr, void *buffer, std::size_t size)
} }
} }
Result<Buffer> FileSystem::read(FileAddress const &addr, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
switch (addr.type()) {
case FileAddressType::Inode:
OX_RETURN_ERROR(readFileInode(addr.getInode().value, out.value.data(), size));
break;
case FileAddressType::ConstPath:
case FileAddressType::Path:
OX_RETURN_ERROR(readFilePath(StringView{addr.getPath().value}, out.value.data(), size));
break;
default:
return ox::Error{1};
}
return out;
}
Result<Buffer> FileSystem::read(StringViewCR path, size_t const size) noexcept {
Result<Buffer> out;
out.value.resize(size);
OX_RETURN_ERROR(readFilePath(path, out.value.data(), size));
return out;
}
Result<Buffer> FileSystem::read(const FileAddress &addr) noexcept { Result<Buffer> FileSystem::read(const FileAddress &addr) noexcept {
OX_REQUIRE(s, stat(addr)); OX_REQUIRE(s, stat(addr));
Buffer buff(static_cast<std::size_t>(s.size)); Buffer buff(static_cast<std::size_t>(s.size));
@ -75,33 +51,18 @@ Result<Buffer> FileSystem::read(StringViewCR path) noexcept {
return buff; return buff;
} }
Error FileSystem::read( Error FileSystem::read(const FileAddress &addr, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept {
FileAddress const &addr,
std::size_t const readStart,
std::size_t const readSize,
void *buffer,
std::size_t *size) noexcept {
switch (addr.type()) { switch (addr.type()) {
case FileAddressType::Inode: case FileAddressType::Inode:
return readFileInodeRange(addr.getInode().value, readStart, readSize, buffer, size); return read(addr.getInode().value, readStart, readSize, buffer, size);
case FileAddressType::ConstPath: case FileAddressType::ConstPath:
case FileAddressType::Path: case FileAddressType::Path:
return readFilePathRange(addr.getPath().value, readStart, readSize, buffer, size); return ox::Error(2, "Unsupported for path lookups");
default: default:
return ox::Error(1); return ox::Error(1);
} }
} }
Result<size_t> FileSystem::read(
StringViewCR path,
std::size_t const readStart,
std::size_t const readSize,
Span<char> buff) noexcept {
size_t szOut{buff.size()};
OX_RETURN_ERROR(readFilePathRange(path, readStart, readSize, buff.data(), &szOut));
return szOut;
}
Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept { Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept {
switch (addr.type()) { switch (addr.type()) {
case FileAddressType::Inode: case FileAddressType::Inode:

View File

@ -41,10 +41,6 @@ class FileSystem {
Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept; Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept;
Result<Buffer> read(FileAddress const &addr, size_t size) noexcept;
Result<Buffer> read(StringViewCR path, size_t size) noexcept;
Result<Buffer> read(const FileAddress &addr) noexcept; Result<Buffer> read(const FileAddress &addr) noexcept;
Result<Buffer> read(StringViewCR path) noexcept; Result<Buffer> read(StringViewCR path) noexcept;
@ -57,24 +53,7 @@ class FileSystem {
return readFileInode(inode, buffer, buffSize); return readFileInode(inode, buffer, buffSize);
} }
Error read( Error read(const FileAddress &addr, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept;
FileAddress const &addr,
size_t readStart,
size_t readSize,
void *buffer,
size_t *size) noexcept;
/**
*
* @param path
* @param readStart
* @param readSize
* @param buffer
* @param size
* @return error or number of bytes read
*/
Result<size_t> read(
StringViewCR path, size_t readStart, size_t readSize, ox::Span<char> buff) noexcept;
virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0; virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0;
@ -161,10 +140,7 @@ class FileSystem {
virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0; virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0;
virtual Error readFilePathRange( virtual Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept = 0;
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept = 0;
virtual Error readFileInodeRange(uint64_t inode, size_t readStart, size_t readSize, void *buffer, size_t *size) noexcept = 0;
virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0; virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0;
@ -235,9 +211,6 @@ class FileSystemTemplate: public MemFS {
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override; Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override; Error removePath(StringViewCR path, bool recursive) noexcept override;
Result<const char*> directAccessInode(uint64_t) const noexcept override; Result<const char*> directAccessInode(uint64_t) const noexcept override;
@ -385,13 +358,6 @@ Error FileSystemTemplate<FileStore, Directory>::readFileInodeRange(uint64_t inod
return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size); return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size);
} }
template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
OX_REQUIRE(s, stat(path));
return readFileInodeRange(s.inode, readStart, readSize, buffer, buffSize);
}
template<typename FileStore, typename Directory> template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept { Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept {
OX_REQUIRE(fd, fileSystemData()); OX_REQUIRE(fd, fileSystemData());

View File

@ -154,25 +154,6 @@ Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS"); return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS");
} }
Error PassThroughFS::readFilePathRange(
StringViewCR path, size_t const readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
try {
std::ifstream file(m_path / stripSlash(path), std::ios::binary | std::ios::ate);
auto const size = static_cast<size_t>(file.tellg());
readSize = ox::min(readSize, size);
file.seekg(static_cast<off_t>(readStart), std::ios::beg);
if (readSize > *buffSize) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
return ox::Error{1};
}
file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(readSize));
return {};
} catch (std::fstream::failure const &f) {
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
return ox::Error{2};
}
}
Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept { Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept {
// unsupported // unsupported
return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS"); return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS");

View File

@ -71,9 +71,6 @@ class PassThroughFS: public FileSystem {
Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override; Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;
Error readFilePathRange(
StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;
Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override; Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;
Error removePath(StringViewCR path, bool recursive) noexcept override; Error removePath(StringViewCR path, bool recursive) noexcept override;

View File

@ -5,9 +5,7 @@
* Add TileSheetV5. TileSheetV5 retains the bpp field for the sake of * Add TileSheetV5. TileSheetV5 retains the bpp field for the sake of
CompactTileSheet, but always store it pixel as 8 bpp for itself. CompactTileSheet, but always store it pixel as 8 bpp for itself.
* Add ability to move subsheets in the subsheet tree. * Add ability to move subsheets in the subsheet tree.
* Add Flip X and Flip Y functionality to TileSheet Editor. * Add Flip X and Flip Y button for TileSheet Editor.
* Add rotate functionality to TileSheet Editor.
* Add draw line tool to TileSheet editor
* Replace file picker combo boxes with a browse button and file picker, and * Replace file picker combo boxes with a browse button and file picker, and
support for dragging files from the project explorer. support for dragging files from the project explorer.
* Add ability to create directories. * Add ability to create directories.
@ -17,5 +15,3 @@
* 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. * Add ability to reorder Palette pages.
* Add warning for closing a tab with unsaved changes.
* Add ability to close a tab with Ctrl/Cmd-W

View File

@ -9,6 +9,5 @@ target_sources(
inserttilescommand.cpp inserttilescommand.cpp
palettechangecommand.cpp palettechangecommand.cpp
rmsubsheetcommand.cpp rmsubsheetcommand.cpp
rotatecommand.cpp
updatesubsheetcommand.cpp updatesubsheetcommand.cpp
) )

View File

@ -17,7 +17,6 @@ enum class CommandId {
DeleteTile, DeleteTile,
FlipX, FlipX,
FlipY, FlipY,
Rotate,
InsertTile, InsertTile,
MoveSubSheet, MoveSubSheet,
UpdateSubSheet, UpdateSubSheet,

View File

@ -2,68 +2,15 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/ */
#include <cmath>
#include "drawcommand.hpp" #include "drawcommand.hpp"
namespace nostalgia::gfx { namespace nostalgia::gfx {
constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f) noexcept {
auto const rise = b.y - a.y;
auto const run = b.x - a.x;
auto const slope = static_cast<double>(rise) / static_cast<double>(run);
auto y = static_cast<double>(a.y);
auto const w = abs(run);
if (rise > 0) {
if (run > 0) {
auto constexpr xmod = 1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else if (run < 0) {
auto constexpr xmod = -1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(floor(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else {
f(a.x, a.y, rise);
}
} else if (rise < 0) {
if (run > 0) {
auto constexpr xmod = 1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(floor(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else if (run < 0) {
auto constexpr xmod = -1;
for (int32_t i = 0; i < w; ++i) {
auto const climb = static_cast<int32_t>(ceil(y + slope - y));
f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
y += slope * xmod;
}
} else {
f(a.x, a.y, rise - 1);
}
} else {
auto const xmod = run > 0 ? 1 : -1;
for (int32_t i = 0; i < w; ++i) {
f(a.x + i * xmod, static_cast<int32_t>(y), 1);
}
}
}
DrawCommand::DrawCommand( DrawCommand::DrawCommand(
TileSheet &img, TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx, TileSheet::SubSheetIdx subSheetIdx,
std::size_t idx, std::size_t idx,
int const palIdx) noexcept: int palIdx) noexcept:
m_img(img), m_img(img),
m_subSheetIdx(std::move(subSheetIdx)), m_subSheetIdx(std::move(subSheetIdx)),
m_palIdx(palIdx) { m_palIdx(palIdx) {
@ -74,8 +21,8 @@ DrawCommand::DrawCommand(
DrawCommand::DrawCommand( DrawCommand::DrawCommand(
TileSheet &img, TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx, TileSheet::SubSheetIdx subSheetIdx,
ox::SpanView<std::size_t> const&idxList, ox::Vector<std::size_t> const&idxList,
int const palIdx) noexcept: int palIdx) noexcept:
m_img(img), m_img(img),
m_subSheetIdx(std::move(subSheetIdx)), m_subSheetIdx(std::move(subSheetIdx)),
m_palIdx(palIdx) { m_palIdx(palIdx) {
@ -85,12 +32,11 @@ DrawCommand::DrawCommand(
} }
} }
bool DrawCommand::append(std::size_t const idx) noexcept { bool DrawCommand::append(std::size_t idx) noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx); auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) { if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
// duplicate entries are bad // duplicate entries are bad
auto existing = find_if( auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
return c.idx == idx; return c.idx == idx;
}); });
if (existing == m_changes.cend()) { if (existing == m_changes.cend()) {
@ -102,7 +48,7 @@ bool DrawCommand::append(std::size_t const idx) noexcept {
return false; return false;
} }
bool DrawCommand::append(ox::SpanView<std::size_t> const&idxList) noexcept { bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
auto out = false; auto out = false;
for (auto idx : idxList) { for (auto idx : idxList) {
out = append(idx) || out; out = append(idx) || out;
@ -110,28 +56,6 @@ bool DrawCommand::append(ox::SpanView<std::size_t> const&idxList) noexcept {
return out; return out;
} }
void DrawCommand::lineUpdate(ox::Point a, ox::Point b) noexcept {
std::ignore = undo();
m_changes.clear();
auto &ss = getSubSheet(m_img, m_subSheetIdx);
if (a.x < b.x) { ++b.x; }
if (a.y < b.y) { ++b.y; }
if (a.x > b.x) { --b.x; }
iterateLine(a, b, [this, &ss](int32_t const x, int32_t const y, int32_t const h) {
int const mod = h < 0 ? -1 : 1;
auto const range = h * mod;
for (int32_t i{}; i < range; ++i) {
auto const idx = ptToIdx(x, y + i * mod, ss.columns * TileWidth);
if (idx < ss.pixels.size()) {
if (m_palIdx != getPixel(ss, idx)) {
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(ss, idx));
}
}
}
});
std::ignore = redo();
}
ox::Error DrawCommand::redo() noexcept { ox::Error DrawCommand::redo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx); auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (auto const&c : m_changes) { for (auto const&c : m_changes) {
@ -156,8 +80,4 @@ TileSheet::SubSheetIdx const&DrawCommand::subsheetIdx() const noexcept {
return m_subSheetIdx; return m_subSheetIdx;
} }
void DrawCommand::finish() noexcept {
setObsolete(m_changes.empty());
}
} }

View File

@ -33,14 +33,12 @@ class DrawCommand: public TileSheetCommand {
DrawCommand( DrawCommand(
TileSheet &img, TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx, TileSheet::SubSheetIdx subSheetIdx,
ox::SpanView<std::size_t> const&idxList, ox::Vector<std::size_t> const&idxList,
int palIdx) noexcept; int palIdx) noexcept;
bool append(std::size_t idx) noexcept; bool append(std::size_t idx) noexcept;
bool append(ox::SpanView<std::size_t> const&idxList) noexcept; bool append(ox::Vector<std::size_t> const&idxList) noexcept;
void lineUpdate(ox::Point a, ox::Point b) noexcept;
ox::Error redo() noexcept final; ox::Error redo() noexcept final;
@ -52,8 +50,6 @@ class DrawCommand: public TileSheetCommand {
[[nodiscard]] [[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
void finish() noexcept;
}; };
} }

View File

@ -6,7 +6,7 @@
namespace nostalgia::gfx { namespace nostalgia::gfx {
RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept: gfx::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
m_img(img), m_img(img),
m_idx(std::move(idx)), m_idx(std::move(idx)),
m_parentIdx(m_idx) { m_parentIdx(m_idx) {

View File

@ -1,126 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "rotatecommand.hpp"
namespace nostalgia::gfx {
static void rotateLeft(
TileSheet::SubSheet &ss,
ox::Point const &pt,
ox::Point const &pt1,
ox::Point const &pt2,
int const depth = 0) noexcept {
if (depth >= 4) {
return;
}
auto const dstPt = ox::Point{pt1.x + pt.y, pt2.y - pt.x};
auto const srcIdx = ptToIdx(pt + pt1, ss.columns);
auto const dstIdx = ptToIdx(dstPt, ss.columns);
auto const src = ss.pixels[srcIdx];
auto &dst = ss.pixels[dstIdx];
rotateLeft(ss, dstPt - pt1, pt1, pt2, depth + 1);
dst = src;
}
static void rotateLeft(TileSheet::SubSheet &ss, ox::Point const &pt1, ox::Point const &pt2) noexcept {
auto const w = pt2.x - pt1.x;
auto const h = pt2.y - pt1.y;
for (int x = 0; x <= w / 2; ++x) {
for (int y = 0; y <= h / 2; ++y) {
rotateLeft(ss, {x, y}, pt1, pt2);
}
}
}
static void rotateRight(
TileSheet::SubSheet &ss,
ox::Point const &pt,
ox::Point const &pt1,
ox::Point const &pt2,
int const depth = 0) noexcept {
if (depth >= 4) {
return;
}
auto const dstPt = ox::Point{pt2.x - pt.y, pt1.y + pt.x};
auto const srcIdx = ptToIdx(pt + pt1, ss.columns);
auto const dstIdx = ptToIdx(dstPt, ss.columns);
auto const src = ss.pixels[srcIdx];
auto &dst = ss.pixels[dstIdx];
rotateRight(ss, dstPt - pt1, pt1, pt2, depth + 1);
dst = src;
}
static void rotateRight(TileSheet::SubSheet &ss, ox::Point const &pt1, ox::Point const &pt2) noexcept {
auto const w = pt2.x - pt1.x;
auto const h = pt2.y - pt1.y;
for (int x = 0; x <= w / 2; ++x) {
for (int y = 0; y <= h / 2; ++y) {
rotateRight(ss, {x, y}, pt1, pt2);
}
}
}
RotateCommand::RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
Direction const dir) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_pt2{[this] {
auto &ss = getSubSheet(m_img, m_idx);
return ox::Point{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
}()},
m_dir{dir} {
}
RotateCommand::RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::Point const &pt1,
ox::Point const &pt2,
Direction const dir) noexcept:
m_img(img),
m_idx(std::move(idx)),
m_pt1{pt1},
m_pt2{pt2},
m_dir{dir} {
}
ox::Error RotateCommand::redo() noexcept {
auto &ss = getSubSheet(m_img, m_idx);
switch (m_dir) {
case Direction::Left:
rotateLeft(ss, m_pt1, m_pt2);
break;
case Direction::Right:
rotateRight(ss, m_pt1, m_pt2);
break;
}
return {};
}
ox::Error RotateCommand::undo() noexcept {
auto &ss = getSubSheet(m_img, m_idx);
switch (m_dir) {
case Direction::Left:
rotateRight(ss, m_pt1, m_pt2);
break;
case Direction::Right:
rotateLeft(ss, m_pt1, m_pt2);
break;
}
return {};
}
int RotateCommand::commandId() const noexcept {
return static_cast<int>(CommandId::Rotate);
}
TileSheet::SubSheetIdx const&RotateCommand::subsheetIdx() const noexcept {
return m_idx;
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include "commands.hpp"
namespace nostalgia::gfx {
class RotateCommand: public TileSheetCommand {
public:
enum class Direction {
Right,
Left,
};
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
ox::Point const m_pt1;
ox::Point const m_pt2;
Direction const m_dir;
public:
RotateCommand(TileSheet &img, TileSheet::SubSheetIdx idx, Direction dir) noexcept;
RotateCommand(
TileSheet &img,
TileSheet::SubSheetIdx idx,
ox::Point const &pt1,
ox::Point const &pt2,
Direction dir) noexcept;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
[[nodiscard]]
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
};
}

View File

@ -152,12 +152,6 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do
setCutEnabled(false); setCutEnabled(false);
setPasteEnabled(false); setPasteEnabled(false);
m_model.clearSelection(); m_model.clearSelection();
} else if (key == turbine::Key::Alpha_E) {
m_tool = TileSheetTool::Line;
setCopyEnabled(false);
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key == turbine::Key::Alpha_S) { } else if (key == turbine::Key::Alpha_S) {
m_tool = TileSheetTool::Select; m_tool = TileSheetTool::Select;
setCopyEnabled(true); setCopyEnabled(true);
@ -220,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", {0, 32}, 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)) {
@ -236,34 +230,18 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
m_tool = TileSheetTool::Fill; m_tool = TileSheetTool::Fill;
m_model.clearSelection(); m_model.clearSelection();
} }
ImGui::SameLine();
if (ImGui::Selectable("Line", m_tool == TileSheetTool::Line, 0, btnSz)) {
m_tool = TileSheetTool::Line;
m_model.clearSelection();
}
//ImGui::SameLine();
//size_t i{};
//ig::ComboBox("##Operations", ox::Array<ox::CStringView, 1>{"Operations"}, i);
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::BeginChild("OperationsBox", {0, 35}, ImGuiWindowFlags_NoTitleBar); ImGui::SameLine();
ImGui::BeginChild("OperationsBox", {0, 32}, true);
{ {
if (ImGui::BeginCombo("##Operations", "Operations", 0)) { auto constexpr btnSz = ImVec2{55, 16};
if (ImGui::Selectable("Flip X", false)) { if (ig::PushButton("Flip X", btnSz)) {
oxLogError(m_model.flipX()); oxLogError(m_model.flipX());
} }
if (ImGui::Selectable("Flip Y", false)) { ImGui::SameLine();
oxLogError(m_model.flipY()); if (ig::PushButton("Flip Y", btnSz)) {
} oxLogError(m_model.flipY());
ImGui::BeginDisabled(!m_model.rotateEligible());
if (ImGui::Selectable("Rotate Left", false)) {
oxLogError(m_model.rotateLeft());
}
if (ImGui::Selectable("Rotate Right", false)) {
oxLogError(m_model.rotateRight());
}
ImGui::EndDisabled();
ImGui::EndCombo();
} }
} }
ImGui::EndChild(); ImGui::EndChild();
@ -470,9 +448,6 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
case TileSheetTool::Fill: case TileSheetTool::Fill:
m_view.clickFill(fbSize, clickPos(winPos, mousePos)); m_view.clickFill(fbSize, clickPos(winPos, mousePos));
break; break;
case TileSheetTool::Line:
m_view.clickLine(fbSize, clickPos(winPos, mousePos));
break;
case TileSheetTool::Select: case TileSheetTool::Select:
m_view.clickSelect(fbSize, clickPos(winPos, mousePos)); m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
break; break;

View File

@ -24,7 +24,6 @@
#include "tilesheeteditormodel.hpp" #include "tilesheeteditormodel.hpp"
#include "commands/movesubsheetcommand.hpp" #include "commands/movesubsheetcommand.hpp"
#include "commands/rotatecommand.hpp"
namespace nostalgia::gfx { namespace nostalgia::gfx {
@ -141,15 +140,11 @@ size_t TileSheetEditorModel::palettePage() const noexcept {
return m_palettePage; return m_palettePage;
} }
void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const palIdx) noexcept { void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept {
if (m_lastDrawUpdatePt == pt) {
return;
}
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return; return;
} }
m_lastDrawUpdatePt = pt;
auto const idx = gfx::idx(activeSubSheet, pt); auto const idx = gfx::idx(activeSubSheet, pt);
if (m_ongoingDrawCommand) { if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idx); m_updated = m_updated || m_ongoingDrawCommand->append(idx);
@ -159,31 +154,8 @@ void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const pa
} }
} }
void TileSheetEditorModel::drawLineCommand(ox::Point const &pt, std::size_t const palIdx) noexcept {
if (m_lastDrawUpdatePt == pt) {
return;
}
auto const &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
return;
}
m_lastDrawUpdatePt = pt;
auto const idx = gfx::idx(activeSubSheet, pt);
if (m_ongoingDrawCommand) {
m_ongoingDrawCommand->lineUpdate(m_lineStartPt, pt);
m_updated = true;
} else {
std::ignore = pushCommand(ox::make<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
m_lineStartPt = pt;
}
}
void TileSheetEditorModel::endDrawCommand() noexcept { void TileSheetEditorModel::endDrawCommand() noexcept {
if (m_ongoingDrawCommand) { m_ongoingDrawCommand = nullptr;
m_ongoingDrawCommand->finish();
m_ongoingDrawCommand = nullptr;
}
} }
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept { void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
@ -241,28 +213,6 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept {
} }
} }
ox::Error TileSheetEditorModel::rotateLeft() noexcept {
auto &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Left));
}
ox::Error TileSheetEditorModel::rotateRight() noexcept {
auto &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Right));
}
void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept { void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept {
m_selection.emplace(sel); m_selection.emplace(sel);
m_updated = true; m_updated = true;
@ -303,7 +253,6 @@ ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd)
m_pal = keel::AssetRef<Palette>{}; m_pal = keel::AssetRef<Palette>{};
} }
m_palettePage = ox::min<size_t>(pal().pages.size(), 0); m_palettePage = ox::min<size_t>(pal().pages.size(), 0);
setPalPath();
paletteChanged.emit(); paletteChanged.emit();
} }
auto const tsCmd = dynamic_cast<TileSheetCommand const*>(cmd); auto const tsCmd = dynamic_cast<TileSheetCommand const*>(cmd);
@ -355,16 +304,6 @@ ox::Error TileSheetEditorModel::flipY() noexcept {
return pushCommand(ox::make<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b)); return pushCommand(ox::make<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
} }
bool TileSheetEditorModel::rotateEligible() const noexcept {
if (m_selection) {
auto const w = m_selection->b.x - m_selection->a.x;
auto const h = m_selection->b.y - m_selection->a.y;
return w == h;
}
auto const &ss = activeSubSheet();
return ss.rows == ss.columns;
}
ox::Error TileSheetEditorModel::moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept { ox::Error TileSheetEditorModel::moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept {
return pushCommand(ox::make<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst))); return pushCommand(ox::make<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
} }

View File

@ -34,8 +34,6 @@ class TileSheetEditorModel: public ox::SignalHandler {
class DrawCommand *m_ongoingDrawCommand = nullptr; class DrawCommand *m_ongoingDrawCommand = nullptr;
studio::SelectionTracker m_selTracker; studio::SelectionTracker m_selTracker;
ox::Optional<studio::Selection> m_selection; ox::Optional<studio::Selection> m_selection;
ox::Point m_lineStartPt;
ox::Point m_lastDrawUpdatePt;
bool m_updated = false; bool m_updated = false;
public: public:
@ -73,8 +71,6 @@ class TileSheetEditorModel: public ox::SignalHandler {
void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept; void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void drawLineCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void endDrawCommand() noexcept; void endDrawCommand() noexcept;
void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept; void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;
@ -106,10 +102,6 @@ class TileSheetEditorModel: public ox::SignalHandler {
void fill(ox::Point const&pt, int palIdx) noexcept; void fill(ox::Point const&pt, int palIdx) noexcept;
ox::Error rotateLeft() noexcept;
ox::Error rotateRight() noexcept;
void setSelection(studio::Selection const&sel) noexcept; void setSelection(studio::Selection const&sel) noexcept;
void select(ox::Point const&pt) noexcept; void select(ox::Point const&pt) noexcept;
@ -138,9 +130,6 @@ class TileSheetEditorModel: public ox::SignalHandler {
ox::Error flipY() noexcept; ox::Error flipY() noexcept;
[[nodiscard]]
bool rotateEligible() const noexcept;
ox::Error moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept; ox::Error moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept;
private: private:

View File

@ -11,8 +11,7 @@
namespace nostalgia::gfx { namespace nostalgia::gfx {
TileSheetEditorView::TileSheetEditorView( TileSheetEditorView::TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack):
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
m_model(sctx, path, undoStack), m_model(sctx, path, undoStack),
m_pixelsDrawer(m_model) { m_pixelsDrawer(m_model) {
glBindVertexArray(0); glBindVertexArray(0);
@ -77,11 +76,6 @@ void TileSheetEditorView::clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clic
m_model.drawCommand(pt, m_palIdx); m_model.drawCommand(pt, m_palIdx);
} }
void TileSheetEditorView::clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
auto const pt = clickPoint(paneSize, clickPos);
m_model.drawLineCommand(pt, m_palIdx);
}
void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept { void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
auto const pt = clickPoint(paneSize, clickPos); auto const pt = clickPoint(paneSize, clickPos);
m_model.select(pt); m_model.select(pt);
@ -96,7 +90,6 @@ void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
switch (tool) { switch (tool) {
case TileSheetTool::Draw: case TileSheetTool::Draw:
case TileSheetTool::Fill: case TileSheetTool::Fill:
case TileSheetTool::Line:
m_model.endDrawCommand(); m_model.endDrawCommand();
break; break;
case TileSheetTool::Select: case TileSheetTool::Select:

View File

@ -23,7 +23,6 @@ enum class TileSheetTool {
Select, Select,
Draw, Draw,
Fill, Fill,
Line,
}; };
[[nodiscard]] [[nodiscard]]
@ -35,8 +34,6 @@ constexpr auto toString(TileSheetTool t) noexcept {
return "Draw"; return "Draw";
case TileSheetTool::Fill: case TileSheetTool::Fill:
return "Fill"; return "Fill";
case TileSheetTool::Line:
return "Line";
case TileSheetTool::None: case TileSheetTool::None:
return "None"; return "None";
} }
@ -56,7 +53,7 @@ class TileSheetEditorView: public ox::SignalHandler {
std::size_t m_palIdx = 0; std::size_t m_palIdx = 0;
public: public:
TileSheetEditorView(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack); TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
~TileSheetEditorView() override = default; ~TileSheetEditorView() override = default;
@ -68,8 +65,6 @@ class TileSheetEditorView: public ox::SignalHandler {
void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept; void clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickLine(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept; void clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept; void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;

View File

@ -9,7 +9,6 @@ if(NOT WIN32)
endif() endif()
if(COMMAND OBJCOPY_FILE) if(COMMAND OBJCOPY_FILE)
set(LOAD_KEEL_MODS FALSE)
set_target_properties(Nostalgia set_target_properties(Nostalgia
PROPERTIES PROPERTIES
LINK_FLAGS ${LINKER_FLAGS} LINK_FLAGS ${LINKER_FLAGS}
@ -18,16 +17,8 @@ if(COMMAND OBJCOPY_FILE)
OBJCOPY_FILE(Nostalgia) OBJCOPY_FILE(Nostalgia)
#PADBIN_FILE(Nostalgia) #PADBIN_FILE(Nostalgia)
else()
set(LOAD_KEEL_MODS TRUE)
endif() endif()
target_compile_definitions(
Nostalgia PRIVATE
OLYMPIC_LOAD_KEEL_MODULES=$<BOOL:${LOAD_KEEL_MODS}>
OLYMPIC_GUI_APP=1
)
target_link_libraries( target_link_libraries(
Nostalgia Nostalgia
NostalgiaKeelModules NostalgiaKeelModules

View File

@ -33,7 +33,7 @@ endif()
install( install(
FILES FILES
ns_logo.icns ns.icns
DESTINATION DESTINATION
${NOSTALGIA_DIST_RESOURCES}/icons ${NOSTALGIA_DIST_RESOURCES}/icons
) )

View File

@ -9,7 +9,7 @@
<string>Nostalgia Studio</string> <string>Nostalgia Studio</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>icons/ns_logo.icns</string> <string>icons/ns.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>net.drinkingtea.nostalgia.studio</string> <string>net.drinkingtea.nostalgia.studio</string>
@ -18,7 +18,7 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>dev build</string> <string>0.0.0</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>12.0.0</string> <string>12.0.0</string>
@ -30,6 +30,6 @@
<string>True</string> <string>True</string>
<key>NSHumanReadableCopyright</key> <key>NSHumanReadableCopyright</key>
<string>Copyright (c) 2016-2025 Gary Talent &lt;gary@drinkingtea.net&gt;</string> <string>Copyright (c) 2016-2023 Gary Talent &lt;gary@drinkingtea.net&gt;</string>
</dict> </dict>
</plist> </plist>

Binary file not shown.

View File

@ -15,8 +15,6 @@ constexpr auto K1HdrSz = 40;
ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept; ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept;
ox::Result<ox::UUID> regenerateUuidHeader(ox::Buffer &buff) noexcept;
ox::Error writeUuidHeader(ox::Writer_c auto &writer, ox::UUID const&uuid) noexcept { ox::Error writeUuidHeader(ox::Writer_c auto &writer, ox::UUID const&uuid) noexcept {
OX_RETURN_ERROR(write(writer, "K1;")); OX_RETURN_ERROR(write(writer, "K1;"));
OX_RETURN_ERROR(uuid.toString(writer)); OX_RETURN_ERROR(uuid.toString(writer));

View File

@ -8,25 +8,15 @@ namespace keel {
ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept { ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept {
if (buff.size() < K1HdrSz) [[unlikely]] { if (buff.size() < K1HdrSz) [[unlikely]] {
return ox::Error{1, "Insufficient data to contain complete Keel header"}; return ox::Error(1, "Insufficient data to contain complete Keel header");
} }
constexpr ox::StringView k1Hdr = "K1;"; constexpr ox::StringView k1Hdr = "K1;";
if (k1Hdr != ox::StringView{buff.data(), k1Hdr.bytes()}) [[unlikely]] { if (k1Hdr != ox::StringView(buff.data(), k1Hdr.bytes())) [[unlikely]] {
return ox::Error{2, "No Keel asset header data"}; return ox::Error(2, "No Keel asset header data");
} }
return ox::UUID::fromString(ox::StringView(&buff[k1Hdr.bytes()], 36)); return ox::UUID::fromString(ox::StringView(&buff[k1Hdr.bytes()], 36));
} }
ox::Result<ox::UUID> regenerateUuidHeader(ox::Buffer &buff) noexcept {
OX_RETURN_ERROR(readUuidHeader(buff));
OX_REQUIRE(id, ox::UUID::generate());
auto const str = id.toString();
for (size_t i = 0; i < ox::UUIDStr::cap(); ++i) {
buff[i + 3] = str[i];
}
return id;
}
ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::BufferView buff) noexcept { ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::BufferView buff) noexcept {
std::size_t offset = 0; std::size_t offset = 0;
if (!readUuidHeader(buff).error) { if (!readUuidHeader(buff).error) {

View File

@ -53,12 +53,10 @@ static ox::Error buildUuidMap(Context &ctx, ox::StringViewCR path) noexcept {
OX_REQUIRE_M(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f})); OX_REQUIRE_M(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f}));
OX_REQUIRE(stat, ctx.rom->stat(filePath)); OX_REQUIRE(stat, ctx.rom->stat(filePath));
if (stat.fileType == ox::FileType::NormalFile) { if (stat.fileType == ox::FileType::NormalFile) {
ox::Array<char, K1HdrSz> buff; OX_REQUIRE(data, ctx.rom->read(filePath));
OX_RETURN_ERROR( auto const [hdr, err] = readAssetHeader(data);
ctx.rom->read(filePath, 0, buff.size(), buff));
auto const [uuid, err] = readUuidHeader(buff);
if (!err) { if (!err) {
createUuidMapping(ctx, filePath, uuid); createUuidMapping(ctx, filePath, hdr.uuid);
} }
} else if (stat.fileType == ox::FileType::Directory) { } else if (stat.fileType == ox::FileType::Directory) {
if (!beginsWith(f, ".")) { if (!beginsWith(f, ".")) {

View File

@ -5,7 +5,6 @@ add_library(
deleteconfirmation.cpp deleteconfirmation.cpp
filedialogmanager.cpp filedialogmanager.cpp
main.cpp main.cpp
makecopypopup.cpp
newdir.cpp newdir.cpp
newmenu.cpp newmenu.cpp
newproject.cpp newproject.cpp

View File

@ -1,81 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "makecopypopup.hpp"
namespace studio {
ox::Error MakeCopyPopup::open(ox::StringViewCR path) noexcept {
m_stage = Stage::Opening;
OX_REQUIRE(idx, ox::findIdx(path.rbegin(), path.rend(), '/'));
m_srcPath = path;
m_dirPath = substr(path, 0, idx + 1);
m_title = sfmt("Copy {}", path);
m_fileName = "";
m_errMsg = "";
return {};
}
void MakeCopyPopup::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool MakeCopyPopup::isOpen() const noexcept {
return m_open;
}
void MakeCopyPopup::draw(StudioContext const &ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
case Stage::Opening:
ImGui::OpenPopup(m_title.c_str());
m_stage = Stage::Open;
m_open = true;
[[fallthrough]];
case Stage::Open:
ig::centerNextWindow(ctx.tctx);
ImGui::SetNextWindowSize({250, 0});
constexpr auto modalFlags =
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize;
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
ig::InputText("Name", m_fileName);
if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter)) {
accept(ctx);
}
ImGui::Text("%s", m_errMsg.c_str());
bool open = true;
switch (ig::PopupControlsOkCancel(open)) {
case ig::PopupResponse::None:
break;
case ig::PopupResponse::OK:
accept(ctx);
break;
case ig::PopupResponse::Cancel:
close();
break;
}
ImGui::EndPopup();
}
break;
}
}
void MakeCopyPopup::accept(StudioContext const &ctx) noexcept {
auto const p = sfmt("{}{}", m_dirPath, m_fileName);
if (!ctx.project->exists(p)) {
makeCopy.emit(m_srcPath, p);
close();
} else {
m_errMsg = sfmt("{} already exists", p);
}
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/string.hpp>
#include <studio/context.hpp>
#include <studio/imguiutil.hpp>
namespace studio {
class MakeCopyPopup {
private:
enum class Stage {
Closed,
Opening,
Open,
};
Stage m_stage = Stage::Closed;
bool m_open{};
ox::String m_errMsg;
ox::String m_title{"Copy File"};
ox::String m_srcPath;
ox::String m_dirPath;
ox::IString<255> m_fileName;
public:
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> makeCopy;
ox::Error open(ox::StringViewCR path) noexcept;
void close() noexcept;
[[nodiscard]]
bool isOpen() const noexcept;
void draw(StudioContext const &ctx) noexcept;
private:
void accept(StudioContext const &ctx) noexcept;
};
}

View File

@ -42,22 +42,19 @@ void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept {
if (ImGui::MenuItem("Rename")) { if (ImGui::MenuItem("Rename")) {
renameItem.emit(path); renameItem.emit(path);
} }
if (ImGui::MenuItem("Make Copy")) {
makeCopy.emit(path);
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
} }
void ProjectExplorer::dirContextMenu(ox::StringViewCR path) const noexcept { void ProjectExplorer::dirContextMenu(ox::StringViewCR path) const noexcept {
if (ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) { if (path.len() && ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) {
if (ImGui::MenuItem("Add Item")) { if (ImGui::MenuItem("Add Item")) {
addItem.emit(path); addItem.emit(path);
} }
if (ImGui::MenuItem("Add Directory")) { if (ImGui::MenuItem("Add Directory")) {
addDir.emit(path); addDir.emit(path);
} }
if (path.len() && ImGui::MenuItem("Delete")) { if (ImGui::MenuItem("Delete")) {
deleteItem.emit(path); deleteItem.emit(path);
} }
ImGui::EndPopup(); ImGui::EndPopup();

View File

@ -20,7 +20,6 @@ class ProjectExplorer final: public FileExplorer {
ox::Signal<ox::Error(ox::StringViewCR)> addDir; ox::Signal<ox::Error(ox::StringViewCR)> addDir;
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem; ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
ox::Signal<ox::Error(ox::StringViewCR)> renameItem; ox::Signal<ox::Error(ox::StringViewCR)> renameItem;
ox::Signal<ox::Error(ox::StringViewCR)> makeCopy;
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveDir; ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveDir;
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveItem; ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveItem;

View File

@ -59,13 +59,11 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile);
m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile); m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile);
m_projectExplorer.makeCopy.connect(this, &StudioUI::makeCopyDlg);
m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove); m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove);
m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove); m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove);
m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove); m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove);
m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile); m_newMenu.finished.connect(this, &StudioUI::openFile);
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
loadModules(); loadModules();
// open project and files // open project and files
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx)); auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
@ -136,8 +134,6 @@ void StudioUI::draw() noexcept {
for (auto const p : m_popups) { for (auto const p : m_popups) {
p->draw(m_sctx); p->draw(m_sctx);
} }
m_closeFileConfirm.draw(m_sctx);
m_copyFilePopup.draw(m_sctx);
} }
ImGui::End(); ImGui::End();
handleKeyInput(); handleKeyInput();
@ -218,7 +214,7 @@ void StudioUI::drawTabs() noexcept {
auto open = true; auto open = true;
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0; auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0; auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
auto const flags = unsavedChanges | selected | ImGuiTabItemFlags_NoAssumedClosure; auto const flags = unsavedChanges | selected;
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) { if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
if (m_activeEditor != e.get()) [[unlikely]] { if (m_activeEditor != e.get()) [[unlikely]] {
m_activeEditor = e.get(); m_activeEditor = e.get();
@ -233,10 +229,7 @@ void StudioUI::drawTabs() noexcept {
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] { if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
m_activeEditor->onActivated(); m_activeEditor->onActivated();
} }
if (m_closeActiveTab) [[unlikely]] { if (open) [[likely]] {
ImGui::SetTabItemClosed(e->itemDisplayName().c_str());
} else if (open) [[likely]] {
e->draw(m_sctx); e->draw(m_sctx);
} }
m_activeEditorOnLastDraw = e.get(); m_activeEditorOnLastDraw = e.get();
@ -244,39 +237,21 @@ void StudioUI::drawTabs() noexcept {
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
if (!open) { if (!open) {
if (e->unsavedChanges()) { e->close();
m_closeFileConfirm.open(); if (m_activeEditor == (*it).get()) {
} else { m_activeEditor = nullptr;
e->close(); }
if (m_activeEditor == (*it).get()) { try {
m_activeEditor = nullptr; OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
} } catch (ox::Exception const&ex) {
try { oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
OX_THROW_ERROR(m_editors.erase(it).moveTo(it)); } catch (std::exception const&ex) {
} catch (ox::Exception const&ex) { oxErrf("Editor tab deletion failed: {}\n", ex.what());
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
} catch (std::exception const&ex) {
oxErrf("Editor tab deletion failed: {}\n", ex.what());
}
} }
} else { } else {
++it; ++it;
} }
} }
if (m_closeActiveTab) [[unlikely]] {
if (m_activeEditor) {
auto const idx = find_if(
m_editors.begin(), m_editors.end(),
[this](ox::UPtr<BaseEditor> const &e) {
return m_activeEditor == e.get();
});
if (idx != m_editors.end()) {
oxLogError(m_editors.erase(idx.offset()).error);
}
m_activeEditor = nullptr;
}
m_closeActiveTab = false;
}
} }
void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept { void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept {
@ -353,14 +328,6 @@ void StudioUI::handleKeyInput() noexcept {
if (m_activeEditor && m_activeEditor->pasteEnabled()) { if (m_activeEditor && m_activeEditor->pasteEnabled()) {
m_activeEditor->paste(); m_activeEditor->paste();
} }
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
if (m_activeEditor) {
if (m_activeEditor->unsavedChanges()) {
m_closeFileConfirm.open();
} else {
oxLogError(closeCurrentFile());
}
}
} else if (ImGui::IsKeyPressed(ImGuiKey_X)) { } else if (ImGui::IsKeyPressed(ImGuiKey_X)) {
if (m_activeEditor && m_activeEditor->cutEnabled()) { if (m_activeEditor && m_activeEditor->cutEnabled()) {
m_activeEditor->cut(); m_activeEditor->cut();
@ -426,12 +393,13 @@ ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR ne
} }
ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept { ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept {
for (auto &e : m_editors) { for (size_t i{}; auto &e : m_editors) {
if (path == e->itemPath()) { if (path == e->itemPath()) {
oxLogError(m_editors.erase(i).error);
oxLogError(closeFile(path)); oxLogError(closeFile(path));
m_closeActiveTab = true;
break; break;
} }
++i;
} }
return m_projectExplorer.refreshProjectTreeModel(); return m_projectExplorer.refreshProjectTreeModel();
} }
@ -453,7 +421,6 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
m_sctx.project = m_project.get(); m_sctx.project = m_project.get();
turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath())); turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem); m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
m_copyFilePopup.makeCopy.connect(m_sctx.project, &Project::copyItem);
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);
@ -516,28 +483,6 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
return {}; return {};
} }
ox::Error StudioUI::makeCopyDlg(ox::StringViewCR path) noexcept {
return m_copyFilePopup.open(path);
}
ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) noexcept {
if (response == ig::PopupResponse::OK && m_activeEditor) {
return closeCurrentFile();
}
return {};
}
ox::Error StudioUI::closeCurrentFile() noexcept {
for (auto &e : m_editors) {
if (m_activeEditor == e.get()) {
oxLogError(closeFile(e->itemPath()));
m_closeActiveTab = true;
break;
}
}
return {};
}
ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
if (!m_openFiles.contains(path)) { if (!m_openFiles.contains(path)) {
return {}; return {};

View File

@ -9,14 +9,12 @@
#include <ox/std/string.hpp> #include <ox/std/string.hpp>
#include <studio/editor.hpp> #include <studio/editor.hpp>
#include <studio/imguiutil.hpp>
#include <studio/module.hpp> #include <studio/module.hpp>
#include <studio/project.hpp> #include <studio/project.hpp>
#include <studio/task.hpp> #include <studio/task.hpp>
#include "aboutpopup.hpp" #include "aboutpopup.hpp"
#include "deleteconfirmation.hpp" #include "deleteconfirmation.hpp"
#include "makecopypopup.hpp"
#include "newdir.hpp" #include "newdir.hpp"
#include "newmenu.hpp" #include "newmenu.hpp"
#include "newproject.hpp" #include "newproject.hpp"
@ -42,14 +40,11 @@ class StudioUI: public ox::SignalHandler {
BaseEditor *m_activeEditorOnLastDraw = nullptr; BaseEditor *m_activeEditorOnLastDraw = nullptr;
BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditor = nullptr;
BaseEditor *m_activeEditorUpdatePending = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr;
bool m_closeActiveTab{};
ox::Vector<ox::Pair<ox::String>> m_queuedMoves; ox::Vector<ox::Pair<ox::String>> m_queuedMoves;
ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves; ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves;
NewMenu m_newMenu{keelCtx(m_tctx)}; NewMenu m_newMenu{keelCtx(m_tctx)};
DeleteConfirmation m_deleteConfirmation; DeleteConfirmation m_deleteConfirmation;
NewDir m_newDirDialog; NewDir m_newDirDialog;
ig::QuestionPopup m_closeFileConfirm{"Close File?", "This file has unsaved changes. Close?"};
MakeCopyPopup m_copyFilePopup;
RenameFile m_renameFile; RenameFile m_renameFile;
NewProject m_newProject; NewProject m_newProject;
AboutPopup m_aboutPopup; AboutPopup m_aboutPopup;
@ -119,12 +114,6 @@ class StudioUI: public ox::SignalHandler {
ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept; ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept;
ox::Error makeCopyDlg(ox::StringViewCR path) noexcept;
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
ox::Error closeCurrentFile() noexcept;
ox::Error closeFile(ox::StringViewCR path) noexcept; ox::Error closeFile(ox::StringViewCR path) noexcept;
ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept; ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept;

View File

@ -225,18 +225,6 @@ PopupResponse PopupControlsOkCancel(
[[nodiscard]] [[nodiscard]]
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz = {285, 0}); bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz = {285, 0});
/**
*
* @param lbl
* @param list
* @param selectedIdx
* @return true if new value selected, false otherwise
*/
bool ComboBox(
ox::CStringView lbl,
ox::SpanView<ox::CStringView> list,
size_t &selectedIdx) noexcept;
/** /**
* *
* @param lbl * @param lbl
@ -303,34 +291,6 @@ class FilePicker {
}; };
class QuestionPopup {
private:
enum class Stage {
Closed,
Opening,
Open,
};
Stage m_stage = Stage::Closed;
bool m_open{};
ox::String m_title;
ox::String m_question;
public:
ox::Signal<ox::Error(ig::PopupResponse)> response;
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
void open() noexcept;
void close() noexcept;
[[nodiscard]]
bool isOpen() const noexcept;
void draw(StudioContext &ctx, ImVec2 const &sz = {}) noexcept;
};
[[nodiscard]] [[nodiscard]]
bool mainWinHasFocus() noexcept; bool mainWinHasFocus() noexcept;

View File

@ -92,8 +92,6 @@ class Project: public ox::SignalHandler {
ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept; ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept;
ox::Error copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept; ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept; ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept;

View File

@ -90,25 +90,6 @@ bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show,
return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags); return ImGui::BeginPopupModal(popupName.c_str(), &show, modalFlags);
} }
bool ComboBox(
ox::CStringView lbl,
ox::SpanView<ox::CStringView> list,
size_t &selectedIdx) noexcept {
bool out{};
auto const first = selectedIdx < list.size() ? list[selectedIdx].c_str() : "";
if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
for (auto i = 0u; i < list.size(); ++i) {
const auto selected = (selectedIdx == i);
if (ImGui::Selectable(list[i].c_str(), selected) && selectedIdx != i) {
selectedIdx = i;
out = true;
}
}
ImGui::EndCombo();
}
return out;
}
bool ComboBox( bool ComboBox(
ox::CStringView lbl, ox::CStringView lbl,
ox::Span<const ox::String> list, ox::Span<const ox::String> list,
@ -225,60 +206,6 @@ void FilePicker::show() noexcept {
m_show = true; m_show = true;
} }
QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
m_title{std::move(title)},
m_question{std::move(question)} {
}
void QuestionPopup::open() noexcept {
m_stage = Stage::Opening;
}
void QuestionPopup::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool QuestionPopup::isOpen() const noexcept {
return m_open;
}
void QuestionPopup::draw(StudioContext &ctx, ImVec2 const &sz) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
case Stage::Opening:
ImGui::OpenPopup(m_title.c_str());
m_stage = Stage::Open;
m_open = true;
[[fallthrough]];
case Stage::Open:
centerNextWindow(ctx.tctx);
ImGui::SetNextWindowSize(static_cast<ImVec2>(sz));
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
ImGui::Text("%s", m_question.c_str());
auto const r = PopupControlsOkCancel(m_open, "Yes", "No");
switch (r) {
case PopupResponse::None:
break;
case PopupResponse::OK:
response.emit(r);
close();
break;
case PopupResponse::Cancel:
response.emit(r);
close();
break;
}
ImGui::EndPopup();
}
break;
}
}
bool s_mainWinHasFocus{}; bool s_mainWinHasFocus{};
bool mainWinHasFocus() noexcept { bool mainWinHasFocus() noexcept {
return s_mainWinHasFocus; return s_mainWinHasFocus;

View File

@ -97,14 +97,6 @@ ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
return m_fs.stat(path); return m_fs.stat(path);
} }
ox::Error Project::copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_REQUIRE_M(buff, loadBuff(src));
OX_REQUIRE(id, keel::regenerateUuidHeader(buff));
OX_RETURN_ERROR(writeBuff(dest, buff));
createUuidMapping(m_kctx, dest, id);
return {};
}
ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept { ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_RETURN_ERROR(m_fs.move(src, dest)); OX_RETURN_ERROR(m_fs.move(src, dest));
OX_RETURN_ERROR(keel::updatePath(m_kctx, src, dest)); OX_RETURN_ERROR(keel::updatePath(m_kctx, src, dest));

View File

@ -7,7 +7,9 @@
namespace studio { namespace studio {
ox::Error UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept { ox::Error UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
m_stack.resize(m_stackIdx); for (auto const i = m_stackIdx; i < m_stack.size();) {
std::ignore = m_stack.erase(i);
}
OX_RETURN_ERROR(cmd->redo()); OX_RETURN_ERROR(cmd->redo());
redoTriggered.emit(cmd.get()); redoTriggered.emit(cmd.get());
changeTriggered.emit(cmd.get()); changeTriggered.emit(cmd.get());
@ -23,29 +25,22 @@ ox::Error UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
} }
ox::Error UndoStack::redo() noexcept { ox::Error UndoStack::redo() noexcept {
while (m_stackIdx < m_stack.size()) { if (m_stackIdx < m_stack.size()) {
auto const &c = m_stack[m_stackIdx]; auto &c = m_stack[m_stackIdx];
OX_RETURN_ERROR(c->redo());
++m_stackIdx; ++m_stackIdx;
if (!c->isObsolete()) { redoTriggered.emit(c.get());
OX_RETURN_ERROR(c->redo()); changeTriggered.emit(c.get());
redoTriggered.emit(c.get());
changeTriggered.emit(c.get());
break;
}
} }
return {}; return {};
} }
ox::Error UndoStack::undo() noexcept { ox::Error UndoStack::undo() noexcept {
while (m_stackIdx) { if (m_stackIdx) {
--m_stackIdx; auto &c = m_stack[--m_stackIdx];
auto const &c = m_stack[m_stackIdx]; OX_RETURN_ERROR(c->undo());
if (!c->isObsolete()) { undoTriggered.emit(c.get());
OX_RETURN_ERROR(c->undo()); changeTriggered.emit(c.get());
undoTriggered.emit(c.get());
changeTriggered.emit(c.get());
break;
}
} }
return {}; return {};
} }