diff --git a/src/nostalgia/core/gfx.hpp b/src/nostalgia/core/gfx.hpp index 42dad3dd..b0e3a7a2 100644 --- a/src/nostalgia/core/gfx.hpp +++ b/src/nostalgia/core/gfx.hpp @@ -109,6 +109,10 @@ struct TileSheet { return *this; } + auto idx(const geo::Point &pt) const noexcept { + return ptToIdx(pt, columns); + } + /** * Reads all pixels of this sheet or its children into the given pixel list * @param pixels diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp index 89f59bd9..0cc38513 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp @@ -54,15 +54,28 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept { ImGui::SameLine(); ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true); { + ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true); + { + const auto btnSz = ImVec2(40, 14); + if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) { + m_tool = Tool::Draw; + } + ImGui::SameLine(); + if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) { + m_tool = Tool::Select; + } + } + ImGui::EndChild(); + const auto ySize = paneSize.y - 36; // draw palette/color picker - ImGui::BeginChild("child1", ImVec2(m_palViewWidth - 24, paneSize.y / 2.07f), true); + ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.07f), true); { drawPalettePicker(); } ImGui::EndChild(); - ImGui::BeginChild("child2", ImVec2(m_palViewWidth - 24, paneSize.y / 2.07f), true); + ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.07f), true); { - constexpr auto btnHeight = 18; + static constexpr auto btnHeight = 18; const auto btnSize = ImVec2(18, btnHeight); if (ImGui::Button("+", btnSize)) { auto insertOnIdx = model()->activeSubSheetIdx(); @@ -88,7 +101,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept { } } TileSheet::SubSheetIdx path; - constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; + static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; if (ImGui::BeginTable("Subsheets", 3, flags)) { ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide); ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50); @@ -193,7 +206,16 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept { const auto &winPos = ImGui::GetWindowPos(); clickPos.x -= winPos.x + 10; clickPos.y -= winPos.y + 10; - m_tileSheetEditor.click(fbSize, clickPos); + switch (m_tool) { + case Tool::Draw: + m_tileSheetEditor.clickDraw(fbSize, clickPos); + break; + case Tool::Fill: + case Tool::None: + case Tool::Select: + m_tileSheetEditor.clickSelect(fbSize, clickPos); + break; + } } } if (io.MouseReleased[0]) { diff --git a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp index b53926f2..93013af8 100644 --- a/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor-imgui.hpp @@ -18,6 +18,13 @@ namespace nostalgia::core { +enum class Tool { + None, + Draw, + Fill, + Select, +}; + class TileSheetEditorImGui: public studio::Editor { private: @@ -43,6 +50,7 @@ class TileSheetEditorImGui: public studio::Editor { TileSheetEditorView m_tileSheetEditor; float m_palViewWidth = 300; geo::Vec2 m_prevMouseDownPos; + Tool m_tool = Tool::Draw; public: TileSheetEditorImGui(Context *ctx, const ox::String &path); diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.cpp b/src/nostalgia/core/studio/tilesheeteditormodel.cpp index 7507744c..d5a5fe76 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.cpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.cpp @@ -65,18 +65,48 @@ void TileSheetEditorModel::setActiveSubsheet(const TileSheet::SubSheetIdx &idx) this->activeSubsheetChanged.emit(m_activeSubsSheetIdx); } +void TileSheetEditorModel::select(const geo::Point &pt) noexcept { + if (!m_selectionOngoing) { + m_selectionPt1 = pt; + m_selectionOngoing = true; + } + if (m_selectionPt2 != pt) { + m_selectionPt2 = pt; + m_selectionBounds = {m_selectionPt1, m_selectionPt2}; + m_updated = true; + } +} + +void TileSheetEditorModel::completeSelection() noexcept { + m_selectionOngoing = false; +} + +void TileSheetEditorModel::clearSelection() noexcept { + m_updated = true; + m_selectionPt1 = {-1, -1}; + m_selectionPt2 = {-1, -1}; + m_selectionBounds = {m_selectionPt1, m_selectionPt2}; +} + bool TileSheetEditorModel::updated() const noexcept { return m_updated; } ox::Error TileSheetEditorModel::markUpdated(int cmdId) noexcept { m_updated = true; - if (cmdId == CommandId::AddSubSheet || cmdId == CommandId::RmSubSheet) { - // make sure the current active SubSheet is still valid - auto idx = m_img.validateSubSheetIdx(m_activeSubsSheetIdx); - if (idx != m_activeSubsSheetIdx) { - setActiveSubsheet(idx); + switch (static_cast(cmdId)) { + case CommandId::AddSubSheet: + case CommandId::RmSubSheet: { + // make sure the current active SubSheet is still valid + auto idx = m_img.validateSubSheetIdx(m_activeSubsSheetIdx); + if (idx != m_activeSubsSheetIdx) { + setActiveSubsheet(idx); + } + break; } + case CommandId::Draw: + case CommandId::UpdateSubSheet: + break; } return OxError(0); } @@ -91,6 +121,12 @@ ox::Error TileSheetEditorModel::saveFile() noexcept { return m_ctx->assetManager.setAsset(m_path, m_img).error; } +bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept { + auto s = activeSubSheet(); + auto pt = idxToPt(idx, s->columns); + return m_selectionBounds.contains(pt); +} + void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int oldColor) const noexcept { auto &activeSubSheet = *this->activeSubSheet(); const auto tileIdx = [activeSubSheet](const geo::Point &pt) noexcept { diff --git a/src/nostalgia/core/studio/tilesheeteditormodel.hpp b/src/nostalgia/core/studio/tilesheeteditormodel.hpp index c4d35d68..3db89294 100644 --- a/src/nostalgia/core/studio/tilesheeteditormodel.hpp +++ b/src/nostalgia/core/studio/tilesheeteditormodel.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -279,6 +280,11 @@ class TileSheetEditorModel: public ox::SignalHandler { bool m_updated = false; Context *m_ctx = nullptr; ox::String m_path; + ox::Vector m_selectedPixels; // pixel idx values + bool m_selectionOngoing = false; + geo::Point m_selectionPt1 = {-1, -1}; + geo::Point m_selectionPt2 = {-1, -1}; + geo::Bounds m_selectionBounds; public: TileSheetEditorModel(Context *ctx, const ox::String &path); @@ -326,6 +332,12 @@ class TileSheetEditorModel: public ox::SignalHandler { return m_activeSubsSheetIdx; } + void select(const geo::Point &pt) noexcept; + + void completeSelection() noexcept; + + void clearSelection() noexcept; + [[nodiscard]] bool updated() const noexcept; @@ -337,6 +349,8 @@ class TileSheetEditorModel: public ox::SignalHandler { constexpr studio::UndoStack *undoStack() noexcept; + bool pixelSelected(std::size_t idx) const noexcept; + protected: void saveItem(); diff --git a/src/nostalgia/core/studio/tilesheeteditorview.cpp b/src/nostalgia/core/studio/tilesheeteditorview.cpp index 08981198..8bb2edb8 100644 --- a/src/nostalgia/core/studio/tilesheeteditorview.cpp +++ b/src/nostalgia/core/studio/tilesheeteditorview.cpp @@ -10,7 +10,7 @@ namespace nostalgia::core { -TileSheetEditorView::TileSheetEditorView(Context *ctx, const ox::String &path): m_model(ctx, path) { +TileSheetEditorView::TileSheetEditorView(Context *ctx, const ox::String &path): m_model(ctx, path), m_pixelsDrawer(&m_model) { // build shaders oxThrowError(m_pixelsDrawer.buildShader()); oxThrowError(m_pixelGridDrawer.buildShader()); @@ -50,21 +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::click(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { - auto [x, y] = clickPos; - const auto pixDrawSz = m_pixelsDrawer.pixelSize(paneSize); - x /= paneSize.x; - y /= paneSize.y; - x += -m_scrollOffset.x / 2; - y += m_scrollOffset.y / 2; - x /= pixDrawSz.x; - y /= pixDrawSz.y; - const auto pt = geo::Point(static_cast(x * 2), static_cast(y * 2)); +void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { + const auto pt = clickPoint(paneSize, clickPos); m_model.drawCommand(pt, m_palIdx); + m_model.clearSelection(); +} + +void TileSheetEditorView::clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { + const auto pt = clickPoint(paneSize, clickPos); + m_model.select(pt); } void TileSheetEditorView::releaseMouseButton() noexcept { m_model.endDrawCommand(); + m_model.completeSelection(); } void TileSheetEditorView::resizeView(const geo::Vec2 &sz) noexcept { @@ -82,10 +81,22 @@ void TileSheetEditorView::ackUpdate() noexcept { } void TileSheetEditorView::initView() noexcept { - m_pixelsDrawer.initBufferSet(m_viewSize, img(), *m_model.activeSubSheet(), pal()); + m_pixelsDrawer.initBufferSet(m_viewSize); m_pixelGridDrawer.initBufferSet(m_viewSize, *m_model.activeSubSheet()); } +geo::Point TileSheetEditorView::clickPoint(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) const noexcept { + auto [x, y] = clickPos; + const auto pixDrawSz = m_pixelsDrawer.pixelSize(paneSize); + x /= paneSize.x; + y /= paneSize.y; + x += -m_scrollOffset.x / 2; + y += m_scrollOffset.y / 2; + x /= pixDrawSz.x; + y /= pixDrawSz.y; + return {static_cast(x * 2), static_cast(y * 2)}; +} + ox::Error TileSheetEditorView::setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept { initView(); return OxError(0); diff --git a/src/nostalgia/core/studio/tilesheeteditorview.hpp b/src/nostalgia/core/studio/tilesheeteditorview.hpp index a96dd227..f54d798a 100644 --- a/src/nostalgia/core/studio/tilesheeteditorview.hpp +++ b/src/nostalgia/core/studio/tilesheeteditorview.hpp @@ -55,7 +55,9 @@ class TileSheetEditorView: public ox::SignalHandler { void draw() noexcept; - void click(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 releaseMouseButton() noexcept; @@ -113,6 +115,8 @@ class TileSheetEditorView: public ox::SignalHandler { [[nodiscard]] ox::String palettePath(const ox::String &palettePath) const; + geo::Point clickPoint(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) const noexcept; + // slots ox::Error setActiveSubsheet(const TileSheet::SubSheetIdx &idx) noexcept; diff --git a/src/nostalgia/core/studio/tilesheetpixels.cpp b/src/nostalgia/core/studio/tilesheetpixels.cpp index 3fa7c427..6c224296 100644 --- a/src/nostalgia/core/studio/tilesheetpixels.cpp +++ b/src/nostalgia/core/studio/tilesheetpixels.cpp @@ -4,10 +4,14 @@ #include #include +#include "tilesheeteditormodel.hpp" #include "tilesheetpixels.hpp" namespace nostalgia::core { +TileSheetPixels::TileSheetPixels(TileSheetEditorModel *model) noexcept: m_model(model) { +} + void TileSheetPixels::setPixelSizeMod(float sm) noexcept { m_pixelSizeMod = sm; } @@ -29,14 +33,14 @@ void TileSheetPixels::draw(bool update, const geo::Vec2 &scroll) noexcept { glDrawElements(GL_TRIANGLES, static_cast(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr); } -void TileSheetPixels::initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept { +void TileSheetPixels::initBufferSet(const geo::Vec2 &paneSize) noexcept { // vao m_bufferSet.vao = glutils::generateVertexArrayObject(); glBindVertexArray(m_bufferSet.vao); // vbo & ebo m_bufferSet.vbo = glutils::generateBuffer(); m_bufferSet.ebo = glutils::generateBuffer(); - setBufferObjects(paneSize, img, subSheet, pal); + setBufferObjects(paneSize); glutils::sendVbo(m_bufferSet); glutils::sendEbo(m_bufferSet); // vbo layout @@ -78,21 +82,29 @@ void TileSheetPixels::setPixelBufferObject(const geo::Vec2 &paneSize, unsigned v memcpy(ebo, elms, sizeof(elms)); } -void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept { +void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize) noexcept { // set buffer lengths - const auto width = subSheet.columns * TileWidth; - const auto height = subSheet.rows * TileHeight; + const auto subSheet = m_model->activeSubSheet(); + const auto &pal = m_model->pal(); + const auto width = subSheet->columns * TileWidth; + const auto height = subSheet->rows * TileHeight; const auto pixels = static_cast(width * height); m_bufferSet.vertices.resize(pixels * VertexVboLength); m_bufferSet.elements.resize(pixels * VertexEboLength); // set pixels - subSheet.walkPixels(img.bpp, [&](std::size_t i, uint8_t p) { - const auto color = pal.colors[p]; - const auto pt = idxToPt(static_cast(i), subSheet.columns); + subSheet->walkPixels(m_model->img().bpp, [&](std::size_t i, uint8_t p) { + auto color = pal.colors[p]; + const auto pt = idxToPt(static_cast(i), subSheet->columns); const auto fx = static_cast(pt.x); const auto fy = static_cast(pt.y); const auto vbo = &m_bufferSet.vertices[i * VertexVboLength]; const auto ebo = &m_bufferSet.elements[i * VertexEboLength]; + if (m_model->pixelSelected(i)) { + const auto r = red16(color) / 2; + const auto g = (green16(color) + 20) / 2; + const auto b = (blue16(color) + 31) / 2; + color = color16(r, g, b); + } setPixelBufferObject(paneSize, i * VertexVboRows, fx, fy, color, vbo, ebo); }); } diff --git a/src/nostalgia/core/studio/tilesheetpixels.hpp b/src/nostalgia/core/studio/tilesheetpixels.hpp index 4e776093..738c9eb9 100644 --- a/src/nostalgia/core/studio/tilesheetpixels.hpp +++ b/src/nostalgia/core/studio/tilesheetpixels.hpp @@ -40,15 +40,18 @@ class TileSheetPixels { float m_pixelSizeMod = 1; glutils::GLProgram m_shader; glutils::BufferSet m_bufferSet; + const class TileSheetEditorModel *m_model = nullptr; public: + explicit TileSheetPixels(class TileSheetEditorModel *model) noexcept; + void setPixelSizeMod(float sm) noexcept; ox::Error buildShader() noexcept; void draw(bool update, const geo::Vec2 &scroll) noexcept; - void initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept; + void initBufferSet(const geo::Vec2 &paneSize) noexcept; [[nodiscard]] geo::Vec2 pixelSize(const geo::Vec2 &paneSize) const noexcept; @@ -56,7 +59,7 @@ class TileSheetPixels { private: void setPixelBufferObject(const geo::Vec2 &paneS, unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) const noexcept; - void setBufferObjects(const geo::Vec2 &paneS, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept; + void setBufferObjects(const geo::Vec2 &paneS) noexcept; };