[nostalgia/core/studio] Add ability to select pixels for copy/paste
This commit is contained in:
parent
3486734b50
commit
b502b8cc30
@ -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
|
||||
|
@ -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]) {
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user