[nostalgia/core/studio] Add insert/delete tile actions and make double click on subsheet open SubsheetEditor
This commit is contained in:
parent
4a921cf65f
commit
74effd3611
@ -61,10 +61,10 @@ void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) {
|
||||
}
|
||||
const auto colorCnt = model()->pal().colors.size();
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
@ -126,12 +126,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Edit", ImVec2(36, btnHeight))) {
|
||||
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);
|
||||
}
|
||||
showSubsheetEditor();
|
||||
}
|
||||
TileSheet::SubSheetIdx path;
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
@ -168,6 +163,9 @@ void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, T
|
||||
if (ImGui::IsItemClicked()) {
|
||||
model()->setActiveSubsheet(*path);
|
||||
}
|
||||
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
|
||||
showSubsheetEditor();
|
||||
}
|
||||
if (subsheet->subsheets.size()) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("--");
|
||||
@ -196,11 +194,28 @@ studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept {
|
||||
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 {
|
||||
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 {
|
||||
const auto winPos = ImGui::GetWindowPos();
|
||||
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) {
|
||||
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);
|
||||
m_tileSheetEditor.draw();
|
||||
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
|
||||
const auto &io = ImGui::GetIO();
|
||||
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 wheelh = io.MouseWheelH;
|
||||
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);
|
||||
}
|
||||
if (wheelh != 0) {
|
||||
@ -230,25 +250,31 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
||||
}
|
||||
if (io.MouseDown[0] && 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) {
|
||||
case Tool::Draw:
|
||||
m_tileSheetEditor.clickDraw(fbSize, clickPos);
|
||||
m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Fill:
|
||||
m_tileSheetEditor.clickFill(fbSize, clickPos);
|
||||
m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Select:
|
||||
m_tileSheetEditor.clickSelect(fbSize, clickPos);
|
||||
m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::None:
|
||||
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]) {
|
||||
m_prevMouseDownPos = {-1, -1};
|
||||
m_tileSheetEditor.releaseMouseButton();
|
||||
@ -258,7 +284,8 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
||||
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
|
||||
auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
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)) {
|
||||
for (auto n = 0u; n < files.size(); n++) {
|
||||
const auto selected = (m_selectedPaletteIdx == n);
|
||||
|
@ -78,10 +78,15 @@ class TileSheetEditorImGui: public studio::BaseEditor {
|
||||
|
||||
studio::UndoStack *undoStack() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
static geo::Vec2 clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept;
|
||||
|
||||
protected:
|
||||
ox::Error saveItem() noexcept override;
|
||||
|
||||
private:
|
||||
void showSubsheetEditor() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto model() const noexcept {
|
||||
return m_tileSheetEditor.model();
|
||||
|
@ -59,10 +59,12 @@ enum class CommandId {
|
||||
Draw = 1,
|
||||
AddSubSheet = 2,
|
||||
RmSubSheet = 3,
|
||||
UpdateSubSheet = 4,
|
||||
Cut = 5,
|
||||
Paste = 6,
|
||||
PaletteChange = 7,
|
||||
DeleteTile = 4,
|
||||
InsertTile = 4,
|
||||
UpdateSubSheet = 5,
|
||||
Cut = 6,
|
||||
Paste = 7,
|
||||
PaletteChange = 8,
|
||||
};
|
||||
|
||||
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 {
|
||||
private:
|
||||
TileSheet *m_img = nullptr;
|
||||
@ -499,6 +623,14 @@ void TileSheetEditorModel::rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcep
|
||||
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 {
|
||||
pushCommand(new UpdateSubSheetCommand(&m_img, idx, name, cols, rows));
|
||||
return OxError(0);
|
||||
@ -568,17 +700,8 @@ bool TileSheetEditorModel::updated() const noexcept {
|
||||
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
|
||||
m_updated = true;
|
||||
const auto cmdId = cmd->commandId();
|
||||
switch (static_cast<CommandId>(cmdId)) {
|
||||
case CommandId::AddSubSheet:
|
||||
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;
|
||||
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
|
||||
oxReturnError(readObj<Palette>(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal));
|
||||
}
|
||||
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
|
||||
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
|
||||
|
@ -65,6 +65,10 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
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;
|
||||
|
||||
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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 {
|
||||
const auto pt = clickPoint(paneSize, clickPos);
|
||||
m_model.drawCommand(pt, m_palIdx);
|
||||
|
@ -55,6 +55,10 @@ class TileSheetEditorView: public ox::SignalHandler {
|
||||
|
||||
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 clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
|
||||
|
Loading…
Reference in New Issue
Block a user