[nostalgia/core/studio] Add insert/delete tile actions and make double click on subsheet open SubsheetEditor

This commit is contained in:
Gary Talent 2022-05-22 16:15:58 -05:00
parent 4a921cf65f
commit 74effd3611
6 changed files with 210 additions and 33 deletions

View File

@ -61,10 +61,10 @@ void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) {
} }
const auto colorCnt = model()->pal().colors.size(); const auto colorCnt = model()->pal().colors.size();
if (key >= core::Key::Num_1 && key <= core::Key::Num_0 + colorCnt) { if (key >= core::Key::Num_1 && key <= core::Key::Num_0 + colorCnt) {
auto idx = ox::min<std::size_t>(key - core::Key::Num_1, colorCnt - 1); auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx); m_tileSheetEditor.setPalIdx(idx);
} else if (key == core::Key::Num_0 && colorCnt >= 10) { } else if (key == core::Key::Num_0 && colorCnt >= 10) {
auto idx = ox::min<std::size_t>(key - core::Key::Num_1 + 9, colorCnt - 1); auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1 + 9), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx); m_tileSheetEditor.setPalIdx(idx);
} }
} }
@ -126,12 +126,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Edit", ImVec2(36, btnHeight))) { if (ImGui::Button("Edit", ImVec2(36, btnHeight))) {
const auto sheet = model()->activeSubSheet(); showSubsheetEditor();
if (sheet->subsheets.size()) {
m_subsheetEditor.show(sheet->name, -1, -1);
} else {
m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
}
} }
TileSheet::SubSheetIdx path; TileSheet::SubSheetIdx path;
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
@ -168,6 +163,9 @@ void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, T
if (ImGui::IsItemClicked()) { if (ImGui::IsItemClicked()) {
model()->setActiveSubsheet(*path); model()->setActiveSubsheet(*path);
} }
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
showSubsheetEditor();
}
if (subsheet->subsheets.size()) { if (subsheet->subsheets.size()) {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("--"); ImGui::Text("--");
@ -196,11 +194,28 @@ studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept {
return model()->undoStack(); return model()->undoStack();
} }
[[nodiscard]]
geo::Vec2 TileSheetEditorImGui::clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept {
clickPos.x -= winPos.x + 10;
clickPos.y -= winPos.y + 10;
return clickPos;
}
ox::Error TileSheetEditorImGui::saveItem() noexcept { ox::Error TileSheetEditorImGui::saveItem() noexcept {
return model()->saveFile(); return model()->saveFile();
} }
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
const auto sheet = model()->activeSubSheet();
if (sheet->subsheets.size()) {
m_subsheetEditor.show(sheet->name, -1, -1);
} else {
m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
}
}
void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
const auto winPos = ImGui::GetWindowPos();
const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y)); const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) { if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
m_framebuffer = glutils::generateFrameBuffer(fbSizei.width, fbSizei.height); m_framebuffer = glutils::generateFrameBuffer(fbSizei.width, fbSizei.height);
@ -214,7 +229,11 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
glViewport(0, 0, fbSizei.width, fbSizei.height); glViewport(0, 0, fbSizei.width, fbSizei.height);
m_tileSheetEditor.draw(); m_tileSheetEditor.draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
ImGui::Image(reinterpret_cast<void*>(m_framebuffer.color.id), static_cast<ImVec2>(fbSize), ImVec2(0, 1), ImVec2(1, 0)); ImGui::Image(
reinterpret_cast<void*>(m_framebuffer.color.id),
static_cast<ImVec2>(fbSize),
ImVec2(0, 1),
ImVec2(1, 0));
// handle input, this must come after drawing // handle input, this must come after drawing
const auto &io = ImGui::GetIO(); const auto &io = ImGui::GetIO();
const auto mousePos = geo::Vec2(io.MousePos); const auto mousePos = geo::Vec2(io.MousePos);
@ -222,7 +241,8 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
const auto wheel = io.MouseWheel; const auto wheel = io.MouseWheel;
const auto wheelh = io.MouseWheelH; const auto wheelh = io.MouseWheelH;
if (wheel != 0) { if (wheel != 0) {
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ? io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl); const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod); m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
} }
if (wheelh != 0) { if (wheelh != 0) {
@ -230,25 +250,31 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
} }
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) { if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
m_prevMouseDownPos = mousePos; m_prevMouseDownPos = mousePos;
auto clickPos = mousePos;
const auto &winPos = ImGui::GetWindowPos();
clickPos.x -= winPos.x + 10;
clickPos.y -= winPos.y + 10;
switch (m_tool) { switch (m_tool) {
case Tool::Draw: case Tool::Draw:
m_tileSheetEditor.clickDraw(fbSize, clickPos); m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::Fill: case Tool::Fill:
m_tileSheetEditor.clickFill(fbSize, clickPos); m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::Select: case Tool::Select:
m_tileSheetEditor.clickSelect(fbSize, clickPos); m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos));
break; break;
case Tool::None: case Tool::None:
break; break;
} }
} }
} }
if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
const auto popupPos = geo::Vec2(ImGui::GetWindowPos());
if (ImGui::MenuItem("Insert Tile")) {
m_tileSheetEditor.insertTile(fbSize, clickPos(winPos, popupPos));
}
if (ImGui::MenuItem("Delete Tile")) {
m_tileSheetEditor.deleteTile(fbSize, clickPos(winPos, popupPos));
}
ImGui::EndPopup();
}
if (io.MouseReleased[0]) { if (io.MouseReleased[0]) {
m_prevMouseDownPos = {-1, -1}; m_prevMouseDownPos = {-1, -1};
m_tileSheetEditor.releaseMouseButton(); m_tileSheetEditor.releaseMouseButton();
@ -258,7 +284,8 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
void TileSheetEditorImGui::drawPaletteSelector() noexcept { void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto sctx = applicationData<studio::StudioContext>(m_ctx); auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto &files = sctx->project->fileList(core::FileExt_npal + 1); const auto &files = sctx->project->fileList(core::FileExt_npal + 1);
const auto first = m_selectedPaletteIdx < files.size() ? files[m_selectedPaletteIdx].c_str() : ""; const auto first = m_selectedPaletteIdx < files.size() ?
files[m_selectedPaletteIdx].c_str() : "";
if (ImGui::BeginCombo("Palette", first, 0)) { if (ImGui::BeginCombo("Palette", first, 0)) {
for (auto n = 0u; n < files.size(); n++) { for (auto n = 0u; n < files.size(); n++) {
const auto selected = (m_selectedPaletteIdx == n); const auto selected = (m_selectedPaletteIdx == n);

View File

@ -78,10 +78,15 @@ class TileSheetEditorImGui: public studio::BaseEditor {
studio::UndoStack *undoStack() noexcept final; studio::UndoStack *undoStack() noexcept final;
[[nodiscard]]
static geo::Vec2 clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept;
protected: protected:
ox::Error saveItem() noexcept override; ox::Error saveItem() noexcept override;
private: private:
void showSubsheetEditor() noexcept;
[[nodiscard]] [[nodiscard]]
constexpr auto model() const noexcept { constexpr auto model() const noexcept {
return m_tileSheetEditor.model(); return m_tileSheetEditor.model();

View File

@ -59,10 +59,12 @@ enum class CommandId {
Draw = 1, Draw = 1,
AddSubSheet = 2, AddSubSheet = 2,
RmSubSheet = 3, RmSubSheet = 3,
UpdateSubSheet = 4, DeleteTile = 4,
Cut = 5, InsertTile = 4,
Paste = 6, UpdateSubSheet = 5,
PaletteChange = 7, Cut = 6,
Paste = 7,
PaletteChange = 8,
}; };
constexpr bool operator==(CommandId c, int i) noexcept { constexpr bool operator==(CommandId c, int i) noexcept {
@ -324,6 +326,128 @@ class RmSubSheetCommand: public TileSheetCommand {
}; };
class InsertTilesCommand: public TileSheetCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
std::size_t m_insertPos = 0;
std::size_t m_insertCnt = 0;
ox::Vector<uint8_t> m_deletedPixels = {};
public:
constexpr InsertTilesCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
const unsigned bytesPerTile = img->bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
m_img = img;
m_idx = idx;
m_insertPos = tileIdx * bytesPerTile;
m_insertCnt = tileCnt * bytesPerTile;
m_deletedPixels.resize(m_insertCnt);
// copy pixels to be erased
{
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
auto dst = m_deletedPixels.data();
auto src = p.data() + p.size() - m_insertCnt;
const auto sz = m_insertCnt * sizeof(decltype(p[0]));
ox_memcpy(dst, src, sz);
}
}
void redo() noexcept final {
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
auto dstPos = m_insertPos + m_insertCnt;
const auto dst = p.data() + dstPos;
const auto src = p.data() + m_insertPos;
ox_memmove(dst, src, p.size() - dstPos);
ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
}
void undo() noexcept final {
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
const auto srcIdx = m_insertPos + m_insertCnt;
const auto src = p.data() + srcIdx;
const auto dst1 = p.data() + m_insertPos;
const auto dst2 = p.data() + p.size() - m_insertCnt;
const auto sz = p.size() - srcIdx;
ox_memmove(dst1, src, sz);
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::InsertTile);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_idx;
}
};
class DeleteTilesCommand: public TileSheetCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
std::size_t m_deletePos = 0;
std::size_t m_deleteSz = 0;
ox::Vector<uint8_t> m_deletedPixels = {};
public:
constexpr DeleteTilesCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
const unsigned bytesPerTile = img->bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
m_img = img;
m_idx = idx;
m_deletePos = tileIdx * bytesPerTile;
m_deleteSz = tileCnt * bytesPerTile;
m_deletedPixels.resize(m_deleteSz);
// copy pixels to be erased
{
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
auto dst = m_deletedPixels.data();
auto src = p.data() + m_deletePos;
const auto sz = m_deleteSz * sizeof(decltype(p[0]));
ox_memcpy(dst, src, sz);
}
}
void redo() noexcept final {
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
auto srcPos = m_deletePos + m_deleteSz;
const auto src = p.data() + srcPos;
const auto dst1 = p.data() + m_deletePos;
const auto dst2 = p.data() + (p.size() - m_deleteSz);
ox_memmove(dst1, src, p.size() - srcPos);
ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
}
void undo() noexcept final {
auto &s = m_img->getSubSheet(m_idx);
auto &p = s.pixels;
const auto src = p.data() + m_deletePos;
const auto dst1 = p.data() + m_deletePos + m_deleteSz;
const auto dst2 = src;
const auto sz = p.size() - m_deletePos - m_deleteSz;
ox_memmove(dst1, src, sz);
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::DeleteTile);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_idx;
}
};
class UpdateSubSheetCommand: public TileSheetCommand { class UpdateSubSheetCommand: public TileSheetCommand {
private: private:
TileSheet *m_img = nullptr; TileSheet *m_img = nullptr;
@ -499,6 +623,14 @@ void TileSheetEditorModel::rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcep
pushCommand(new RmSubSheetCommand(&m_img, idx)); pushCommand(new RmSubSheetCommand(&m_img, idx));
} }
void TileSheetEditorModel::insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
pushCommand(new InsertTilesCommand(&m_img, idx, tileIdx, tileCnt));
}
void TileSheetEditorModel::deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
pushCommand(new DeleteTilesCommand(&m_img, idx, tileIdx, tileCnt));
}
ox::Error TileSheetEditorModel::updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept { ox::Error TileSheetEditorModel::updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept {
pushCommand(new UpdateSubSheetCommand(&m_img, idx, name, cols, rows)); pushCommand(new UpdateSubSheetCommand(&m_img, idx, name, cols, rows));
return OxError(0); return OxError(0);
@ -568,17 +700,8 @@ bool TileSheetEditorModel::updated() const noexcept {
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept { ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
m_updated = true; m_updated = true;
const auto cmdId = cmd->commandId(); const auto cmdId = cmd->commandId();
switch (static_cast<CommandId>(cmdId)) { if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
case CommandId::AddSubSheet: oxReturnError(readObj<Palette>(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal));
case CommandId::RmSubSheet:
case CommandId::Cut:
case CommandId::Draw:
case CommandId::Paste:
case CommandId::UpdateSubSheet:
break;
case CommandId::PaletteChange:
oxReturnError(readObj<Palette>(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal));
break;
} }
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd); auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx()); auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());

View File

@ -65,6 +65,10 @@ class TileSheetEditorModel: public ox::SignalHandler {
void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept; void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept;
void insertTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
void deleteTiles(const TileSheet::SubSheetIdx &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
ox::Error updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept; ox::Error updateSubsheet(const TileSheet::SubSheetIdx &idx, const ox::String &name, int cols, int rows) noexcept;
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept; void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;

View File

@ -50,6 +50,20 @@ void TileSheetEditorView::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcep
m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f); m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
} }
void TileSheetEditorView::insertTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
const auto pt = clickPoint(paneSize, clickPos);
const auto s = m_model.activeSubSheet();
const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
}
void TileSheetEditorView::deleteTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
const auto pt = clickPoint(paneSize, clickPos);
const auto s = m_model.activeSubSheet();
const auto tileIdx = ptToIdx(pt, s->columns) / PixelsPerTile;
m_model.deleteTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
}
void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
const auto pt = clickPoint(paneSize, clickPos); const auto pt = clickPoint(paneSize, clickPos);
m_model.drawCommand(pt, m_palIdx); m_model.drawCommand(pt, m_palIdx);

View File

@ -55,6 +55,10 @@ class TileSheetEditorView: public ox::SignalHandler {
void draw() noexcept; void draw() noexcept;
void insertTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
void deleteTile(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
void clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; void clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
void clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; void clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;