diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.cpp index b359e70a..8f6c46ef 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.cpp @@ -2,10 +2,64 @@ * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. */ +#include +#include + #include "drawcommand.hpp" 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(rise) / static_cast(run); + auto y = static_cast(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(ceil(y + slope - y)); + f(a.x + i * xmod, static_cast(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(floor(y + slope - y)); + f(a.x + i * xmod, static_cast(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(floor(y + slope - y)); + f(a.x + i * xmod, static_cast(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(ceil(y + slope - y)); + f(a.x + i * xmod, static_cast(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(y), 1); + } + } +} + + DrawCommand::DrawCommand( TileSheet &img, TileSheet::SubSheetIdx subSheetIdx, @@ -21,7 +75,7 @@ DrawCommand::DrawCommand( DrawCommand::DrawCommand( TileSheet &img, TileSheet::SubSheetIdx subSheetIdx, - ox::Vector const&idxList, + ox::SpanView const&idxList, int palIdx) noexcept: m_img(img), m_subSheetIdx(std::move(subSheetIdx)), @@ -32,11 +86,12 @@ DrawCommand::DrawCommand( } } -bool DrawCommand::append(std::size_t idx) noexcept { +bool DrawCommand::append(std::size_t const idx) noexcept { auto &subsheet = getSubSheet(m_img, m_subSheetIdx); if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) { // duplicate entries are bad - auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) { + auto existing = find_if( + m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) { return c.idx == idx; }); if (existing == m_changes.cend()) { @@ -48,7 +103,7 @@ bool DrawCommand::append(std::size_t idx) noexcept { return false; } -bool DrawCommand::append(ox::Vector const&idxList) noexcept { +bool DrawCommand::append(ox::SpanView const&idxList) noexcept { auto out = false; for (auto idx : idxList) { out = append(idx) || out; @@ -56,6 +111,25 @@ bool DrawCommand::append(ox::Vector const&idxList) noexcept { 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; + for (int32_t i{}; i < abs(h); ++i) { + auto const idx = ptToIdx(x, y + i * mod, ss.columns * TileWidth); + if (idx < ss.pixels.size()) { + m_changes.emplace_back(static_cast(idx), getPixel(ss, idx)); + } + } + }); + std::ignore = redo(); +} + ox::Error DrawCommand::redo() noexcept { auto &subsheet = getSubSheet(m_img, m_subSheetIdx); for (auto const&c : m_changes) { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.hpp index 21cf845a..a9945e72 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/drawcommand.hpp @@ -33,12 +33,14 @@ class DrawCommand: public TileSheetCommand { DrawCommand( TileSheet &img, TileSheet::SubSheetIdx subSheetIdx, - ox::Vector const&idxList, + ox::SpanView const&idxList, int palIdx) noexcept; bool append(std::size_t idx) noexcept; - bool append(ox::Vector const&idxList) noexcept; + bool append(ox::SpanView const&idxList) noexcept; + + void lineUpdate(ox::Point a, ox::Point b) noexcept; ox::Error redo() noexcept final; @@ -52,4 +54,4 @@ class DrawCommand: public TileSheetCommand { }; -} \ No newline at end of file +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index ba6838ab..fe4bece9 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -152,6 +152,12 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do setCutEnabled(false); setPasteEnabled(false); 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) { m_tool = TileSheetTool::Select; setCopyEnabled(true); @@ -214,7 +220,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true); { auto const controlsSize = ImGui::GetContentRegionAvail(); - ImGui::BeginChild("ToolBox", {168, 32}, true); + ImGui::BeginChild("ToolBox", {0, 32}, true); { auto const btnSz = ImVec2{45, 14}; if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) { @@ -230,9 +236,16 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { m_tool = TileSheetTool::Fill; 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{"Operations"}, i); } ImGui::EndChild(); - ImGui::SameLine(); ImGui::BeginChild("OperationsBox", {0, 32}, true); { auto constexpr btnSz = ImVec2{55, 16}; @@ -448,6 +461,9 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept { case TileSheetTool::Fill: m_view.clickFill(fbSize, clickPos(winPos, mousePos)); break; + case TileSheetTool::Line: + m_view.clickLine(fbSize, clickPos(winPos, mousePos)); + break; case TileSheetTool::Select: m_view.clickSelect(fbSize, clickPos(winPos, mousePos)); break; diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index 788c5f3f..954ef891 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -140,11 +140,15 @@ size_t TileSheetEditorModel::palettePage() const noexcept { 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); 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_updated = m_updated || m_ongoingDrawCommand->append(idx); @@ -154,6 +158,26 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const pal } } +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 if (getPixel(activeSubSheet, idx) != palIdx) { + std::ignore = pushCommand(ox::make( + m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); + m_lineStartPt = pt; + } +} + void TileSheetEditorModel::endDrawCommand() noexcept { m_ongoingDrawCommand = nullptr; } diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index c6f930e4..95cab4f4 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -34,6 +34,8 @@ class TileSheetEditorModel: public ox::SignalHandler { class DrawCommand *m_ongoingDrawCommand = nullptr; studio::SelectionTracker m_selTracker; ox::Optional m_selection; + ox::Point m_lineStartPt; + ox::Point m_lastDrawUpdatePt; bool m_updated = false; public: @@ -71,6 +73,8 @@ class TileSheetEditorModel: public ox::SignalHandler { 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 addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept; diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.cpp index 03fdaee6..0776a9a8 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.cpp @@ -11,7 +11,8 @@ namespace nostalgia::gfx { -TileSheetEditorView::TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack): +TileSheetEditorView::TileSheetEditorView( + studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack): m_model(sctx, path, undoStack), m_pixelsDrawer(m_model) { glBindVertexArray(0); @@ -76,6 +77,11 @@ void TileSheetEditorView::clickDraw(ox::Vec2 const&paneSize, ox::Vec2 const&clic 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 { auto const pt = clickPoint(paneSize, clickPos); m_model.select(pt); @@ -90,6 +96,7 @@ void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept { switch (tool) { case TileSheetTool::Draw: case TileSheetTool::Fill: + case TileSheetTool::Line: m_model.endDrawCommand(); break; case TileSheetTool::Select: diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.hpp index a3198151..b0dbab27 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditorview.hpp @@ -23,6 +23,7 @@ enum class TileSheetTool { Select, Draw, Fill, + Line, }; [[nodiscard]] @@ -34,6 +35,8 @@ constexpr auto toString(TileSheetTool t) noexcept { return "Draw"; case TileSheetTool::Fill: return "Fill"; + case TileSheetTool::Line: + return "Line"; case TileSheetTool::None: return "None"; } @@ -53,7 +56,7 @@ class TileSheetEditorView: public ox::SignalHandler { std::size_t m_palIdx = 0; public: - TileSheetEditorView(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack); + TileSheetEditorView(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack); ~TileSheetEditorView() override = default; @@ -65,6 +68,8 @@ class TileSheetEditorView: public ox::SignalHandler { 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 clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;