[nostalgia/core/studio] Add ability to select pixels for copy/paste

This commit is contained in:
Gary Talent 2022-03-10 02:39:22 -06:00
parent 3486734b50
commit b502b8cc30
9 changed files with 147 additions and 33 deletions

View File

@ -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

View File

@ -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]) {

View File

@ -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);

View File

@ -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<CommandId>(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 {

View File

@ -8,6 +8,7 @@
#include <ox/std/string.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/geo/bounds.hpp>
#include <nostalgia/geo/point.hpp>
#include <nostalgia/geo/vec.hpp>
#include <nostalgia/studio/studio.hpp>
@ -279,6 +280,11 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool m_updated = false;
Context *m_ctx = nullptr;
ox::String m_path;
ox::Vector<std::size_t> 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();

View File

@ -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<int>(x * 2), static_cast<int>(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<int>(x * 2), static_cast<int>(y * 2)};
}
ox::Error TileSheetEditorView::setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept {
initView();
return OxError(0);

View File

@ -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;

View File

@ -4,10 +4,14 @@
#include <nostalgia/core/consts.hpp>
#include <nostalgia/core/ptidxconv.hpp>
#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<GLsizei>(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<unsigned>(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<int>(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<int>(i), subSheet->columns);
const auto fx = static_cast<float>(pt.x);
const auto fy = static_cast<float>(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);
});
}

View File

@ -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;
};