diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index 1a8546fd..e6b13d0b 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -118,18 +118,18 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) { if (!popupOpen) { auto const colorCnt = pal.pages[m_model.palettePage()].size(); if (key == turbine::Key::Alpha_D) { - m_tool = Tool::Draw; + m_tool = TileSheetTool::Draw; setCopyEnabled(false); setCutEnabled(false); setPasteEnabled(false); m_model.clearSelection(); } else if (key == turbine::Key::Alpha_S) { - m_tool = Tool::Select; + m_tool = TileSheetTool::Select; setCopyEnabled(true); setCutEnabled(true); setPasteEnabled(true); } else if (key == turbine::Key::Alpha_F) { - m_tool = Tool::Fill; + m_tool = TileSheetTool::Fill; setCopyEnabled(false); setCutEnabled(false); setPasteEnabled(false); @@ -173,17 +173,17 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true); { auto const btnSz = ImVec2(45, 14); - if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) { - m_tool = Tool::Select; + if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) { + m_tool = TileSheetTool::Select; } ImGui::SameLine(); - if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) { - m_tool = Tool::Draw; + if (ImGui::Selectable("Draw", m_tool == TileSheetTool::Draw, 0, btnSz)) { + m_tool = TileSheetTool::Draw; m_model.clearSelection(); } ImGui::SameLine(); - if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) { - m_tool = Tool::Fill; + if (ImGui::Selectable("Fill", m_tool == TileSheetTool::Fill, 0, btnSz)) { + m_tool = TileSheetTool::Fill; m_model.clearSelection(); } } @@ -370,16 +370,16 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept { if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) { m_prevMouseDownPos = mousePos; switch (m_tool) { - case Tool::Draw: + case TileSheetTool::Draw: m_view.clickDraw(fbSize, clickPos(winPos, mousePos)); break; - case Tool::Fill: + case TileSheetTool::Fill: m_view.clickFill(fbSize, clickPos(winPos, mousePos)); break; - case Tool::Select: + case TileSheetTool::Select: m_view.clickSelect(fbSize, clickPos(winPos, mousePos)); break; - case Tool::None: + case TileSheetTool::None: break; } } @@ -396,7 +396,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept { } if (io.MouseReleased[0]) { m_prevMouseDownPos = {-1, -1}; - m_view.releaseMouseButton(); + m_view.releaseMouseButton(m_tool); } } diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp index dc9b2c03..b730fedb 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp @@ -16,13 +16,6 @@ namespace nostalgia::core { -enum class Tool { - None, - Draw, - Fill, - Select, -}; - class TileSheetEditorImGui: public studio::Editor { private: @@ -71,7 +64,7 @@ class TileSheetEditorImGui: public studio::Editor { TileSheetEditorModel &m_model; float m_palViewWidth = 300; ox::Vec2 m_prevMouseDownPos; - Tool m_tool = Tool::Draw; + TileSheetTool m_tool = TileSheetTool::Draw; public: TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path); diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index 01dde8c1..fa11b01d 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -55,52 +55,55 @@ TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::Stri } void TileSheetEditorModel::cut() { + if (!m_selection) { + return; + } TileSheetClipboard blankCb; auto cb = ox::make_unique(); - const auto &s = activeSubSheet(); - for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) { - for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) { - auto pt = ox::Point(x, y); - const auto idx = core::idx(s, pt); - const auto c = getPixel(s, m_img.bpp, idx); - pt.x -= m_selectionBounds.x; - pt.y -= m_selectionBounds.y; - cb->addPixel(pt, c); - blankCb.addPixel(pt, 0); - } - } - const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin; - const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight); + auto const&s = activeSubSheet(); + iterateSelectionRows(*m_selection, [&](int x, int y) { + auto pt = ox::Point{x, y}; + auto const idx = core::idx(s, pt); + auto const c = getPixel(s, m_img.bpp, idx); + pt -= m_selection->a; + cb->addPixel(pt, c); + blankCb.addPixel(pt, 0); + }); + auto const pt1 = m_selection->a; + auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; turbine::setClipboardObject(m_tctx, std::move(cb)); pushCommand(ox::make(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); } void TileSheetEditorModel::copy() { - auto cb = ox::make_unique(); - for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) { - for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) { - auto pt = ox::Point(x, y); - const auto &s = activeSubSheet(); - const auto idx = core::idx(s, pt); - const auto c = getPixel(s, m_img.bpp, idx); - pt.x -= m_selectionBounds.x; - pt.y -= m_selectionBounds.y; - cb->addPixel(pt, c); - } + if (!m_selection) { + return; } + auto cb = ox::make_unique(); + iterateSelectionRows(*m_selection, [&](int x, int y) { + auto pt = ox::Point{x, y}; + const auto&s = activeSubSheet(); + const auto idx = core::idx(s, pt); + const auto c = getPixel(s, m_img.bpp, idx); + pt -= m_selection->a; + cb->addPixel(pt, c); + }); turbine::setClipboardObject(m_tctx, std::move(cb)); } void TileSheetEditorModel::paste() { + if (!m_selection) { + return; + } auto [cb, err] = turbine::getClipboardObject(m_tctx); if (err) { oxLogError(err); oxErrf("Could not read clipboard: {}", toStr(err)); return; } - const auto &s = activeSubSheet(); - const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin; - const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight); + auto const&s = activeSubSheet(); + auto const pt1 = m_selection->a; + auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; pushCommand(ox::make(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); } @@ -206,30 +209,24 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept { } void TileSheetEditorModel::select(ox::Point const&pt) noexcept { - if (!m_selectionOngoing) { - m_selectionOrigin = pt; - m_selectionOngoing = true; - m_selectionBounds = {pt, pt}; - m_updated = true; - } else if (m_selectionBounds.pt2() != pt) { - m_selectionBounds = {m_selectionOrigin, pt}; + if (m_selTracker.updateCursorPoint(pt)) { + m_selection.emplace(m_selTracker.selection()); m_updated = true; } } void TileSheetEditorModel::completeSelection() noexcept { - m_selectionOngoing = false; auto &s = activeSubSheet(); - auto pt = m_selectionBounds.pt2(); - pt.x = ox::min(s.columns * TileWidth, pt.x); - pt.y = ox::min(s.rows * TileHeight, pt.y); - m_selectionBounds.setPt2(pt); + m_selTracker.finishSelection(); + m_selection.emplace(m_selTracker.selection()); + auto &pt = m_selection->b; + pt.x = ox::min(s.columns * TileWidth - 1, pt.x); + pt.y = ox::min(s.rows * TileHeight - 1, pt.y); } void TileSheetEditorModel::clearSelection() noexcept { m_updated = true; - m_selectionOrigin = {-1, -1}; - m_selectionBounds = {{-1, -1}, {-1, -1}}; + m_selection.reset(); } bool TileSheetEditorModel::updated() const noexcept { @@ -266,9 +263,9 @@ ox::Error TileSheetEditorModel::saveFile() noexcept { } bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept { - const auto &s = activeSubSheet(); - const auto pt = idxToPt(static_cast(idx), s.columns); - return m_selectionBounds.contains(pt); + auto const&s = activeSubSheet(); + auto const pt = idxToPt(static_cast(idx), s.columns); + return m_selection && m_selection->contains(pt); } void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept { diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index f4fe90e5..68211dcd 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -33,10 +33,9 @@ class TileSheetEditorModel: public ox::SignalHandler { size_t m_palettePage{}; studio::UndoStack &m_undoStack; class DrawCommand *m_ongoingDrawCommand = nullptr; + studio::SelectionTracker m_selTracker; + ox::Optional m_selection; bool m_updated = false; - bool m_selectionOngoing = false; - ox::Point m_selectionOrigin = {-1, -1}; - ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}}; public: TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack); diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp index 666ce143..5583c8ee 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp @@ -84,9 +84,18 @@ void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clic m_model.fill(pt, static_cast(m_palIdx)); } -void TileSheetEditorView::releaseMouseButton() noexcept { - m_model.endDrawCommand(); - m_model.completeSelection(); +void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept { + switch (tool) { + case TileSheetTool::Draw: + m_model.endDrawCommand(); + break; + case TileSheetTool::Select: + m_model.completeSelection(); + break; + case TileSheetTool::Fill: + case TileSheetTool::None: + break; + } } void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept { diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp index d421237d..52897386 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp @@ -18,7 +18,8 @@ namespace nostalgia::core { -enum class TileSheetTool: int { +enum class TileSheetTool { + None, Select, Draw, Fill, @@ -33,6 +34,8 @@ constexpr auto toString(TileSheetTool t) noexcept { return "Draw"; case TileSheetTool::Fill: return "Fill"; + case TileSheetTool::None: + return "None"; } return ""; } @@ -66,7 +69,7 @@ class TileSheetEditorView: public ox::SignalHandler { void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept; - void releaseMouseButton() noexcept; + void releaseMouseButton(TileSheetTool tool) noexcept; void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept; diff --git a/src/olympic/studio/modlib/include/studio/selectiontracker.hpp b/src/olympic/studio/modlib/include/studio/selectiontracker.hpp index ea21bad2..ad6882bc 100644 --- a/src/olympic/studio/modlib/include/studio/selectiontracker.hpp +++ b/src/olympic/studio/modlib/include/studio/selectiontracker.hpp @@ -18,6 +18,11 @@ struct Selection { constexpr ox::Size size() const noexcept { return {b.x - a.x, b.y - a.y}; } + [[nodiscard]] + constexpr bool contains(ox::Point const&pt) const noexcept { + return a.x <= pt.x && a.y <= pt.y + && b.x >= pt.x && b.y >= pt.y; + } }; constexpr auto iterateSelection(studio::Selection const&sel, auto const&cb) { @@ -36,6 +41,22 @@ constexpr auto iterateSelection(studio::Selection const&sel, auto const&cb) { } }; +constexpr auto iterateSelectionRows(studio::Selection const&sel, auto const&cb) { + constexpr auto retErr = ox::is_same_v; + for (auto y = sel.a.y; y <= sel.b.y; ++y) { + for (auto x = sel.a.x; x <= sel.b.x; ++x) { + if constexpr(retErr) { + oxReturnError(cb(x, y)); + } else { + cb(x, y); + } + } + } + if constexpr(retErr) { + return ox::Error{}; + } +}; + class SelectionTracker { private: bool m_selectionOngoing{}; @@ -53,14 +74,23 @@ class SelectionTracker { m_selectionOngoing = true; } - constexpr void updateCursorPoint(ox::Point cursor, bool allowStart = true) noexcept { + /** + * + * @param cursor + * @param allowStart + * @return true if changed, false otherwise + */ + constexpr bool updateCursorPoint(ox::Point cursor, bool allowStart = true) noexcept { + auto changed = false; if (!m_selectionOngoing && allowStart) { m_pointA = cursor; m_selectionOngoing = true; } if (m_selectionOngoing) { m_pointB = cursor; + changed = true; } + return changed; } constexpr void updateCursorPoint(ox::Vec2 cursor, bool allowStart = true) noexcept {