[nostalgia/core/studio] Cleanup TileSheet selection, fix copy/paste bug
This commit is contained in:
parent
9d2fe0e814
commit
5a426829f2
@ -118,18 +118,18 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
|||||||
if (!popupOpen) {
|
if (!popupOpen) {
|
||||||
auto const colorCnt = pal.pages[m_model.palettePage()].size();
|
auto const colorCnt = pal.pages[m_model.palettePage()].size();
|
||||||
if (key == turbine::Key::Alpha_D) {
|
if (key == turbine::Key::Alpha_D) {
|
||||||
m_tool = Tool::Draw;
|
m_tool = TileSheetTool::Draw;
|
||||||
setCopyEnabled(false);
|
setCopyEnabled(false);
|
||||||
setCutEnabled(false);
|
setCutEnabled(false);
|
||||||
setPasteEnabled(false);
|
setPasteEnabled(false);
|
||||||
m_model.clearSelection();
|
m_model.clearSelection();
|
||||||
} else if (key == turbine::Key::Alpha_S) {
|
} else if (key == turbine::Key::Alpha_S) {
|
||||||
m_tool = Tool::Select;
|
m_tool = TileSheetTool::Select;
|
||||||
setCopyEnabled(true);
|
setCopyEnabled(true);
|
||||||
setCutEnabled(true);
|
setCutEnabled(true);
|
||||||
setPasteEnabled(true);
|
setPasteEnabled(true);
|
||||||
} else if (key == turbine::Key::Alpha_F) {
|
} else if (key == turbine::Key::Alpha_F) {
|
||||||
m_tool = Tool::Fill;
|
m_tool = TileSheetTool::Fill;
|
||||||
setCopyEnabled(false);
|
setCopyEnabled(false);
|
||||||
setCutEnabled(false);
|
setCutEnabled(false);
|
||||||
setPasteEnabled(false);
|
setPasteEnabled(false);
|
||||||
@ -173,17 +173,17 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
|
|||||||
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
|
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
|
||||||
{
|
{
|
||||||
auto const btnSz = ImVec2(45, 14);
|
auto const btnSz = ImVec2(45, 14);
|
||||||
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
|
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
|
||||||
m_tool = Tool::Select;
|
m_tool = TileSheetTool::Select;
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
|
if (ImGui::Selectable("Draw", m_tool == TileSheetTool::Draw, 0, btnSz)) {
|
||||||
m_tool = Tool::Draw;
|
m_tool = TileSheetTool::Draw;
|
||||||
m_model.clearSelection();
|
m_model.clearSelection();
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
|
if (ImGui::Selectable("Fill", m_tool == TileSheetTool::Fill, 0, btnSz)) {
|
||||||
m_tool = Tool::Fill;
|
m_tool = TileSheetTool::Fill;
|
||||||
m_model.clearSelection();
|
m_model.clearSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -370,16 +370,16 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
|||||||
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
|
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
|
||||||
m_prevMouseDownPos = mousePos;
|
m_prevMouseDownPos = mousePos;
|
||||||
switch (m_tool) {
|
switch (m_tool) {
|
||||||
case Tool::Draw:
|
case TileSheetTool::Draw:
|
||||||
m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
|
m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
|
||||||
break;
|
break;
|
||||||
case Tool::Fill:
|
case TileSheetTool::Fill:
|
||||||
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
||||||
break;
|
break;
|
||||||
case Tool::Select:
|
case TileSheetTool::Select:
|
||||||
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
||||||
break;
|
break;
|
||||||
case Tool::None:
|
case TileSheetTool::None:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,7 +396,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
|||||||
}
|
}
|
||||||
if (io.MouseReleased[0]) {
|
if (io.MouseReleased[0]) {
|
||||||
m_prevMouseDownPos = {-1, -1};
|
m_prevMouseDownPos = {-1, -1};
|
||||||
m_view.releaseMouseButton();
|
m_view.releaseMouseButton(m_tool);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,13 +16,6 @@
|
|||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
enum class Tool {
|
|
||||||
None,
|
|
||||||
Draw,
|
|
||||||
Fill,
|
|
||||||
Select,
|
|
||||||
};
|
|
||||||
|
|
||||||
class TileSheetEditorImGui: public studio::Editor {
|
class TileSheetEditorImGui: public studio::Editor {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -71,7 +64,7 @@ class TileSheetEditorImGui: public studio::Editor {
|
|||||||
TileSheetEditorModel &m_model;
|
TileSheetEditorModel &m_model;
|
||||||
float m_palViewWidth = 300;
|
float m_palViewWidth = 300;
|
||||||
ox::Vec2 m_prevMouseDownPos;
|
ox::Vec2 m_prevMouseDownPos;
|
||||||
Tool m_tool = Tool::Draw;
|
TileSheetTool m_tool = TileSheetTool::Draw;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
|
TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
|
||||||
|
@ -55,52 +55,55 @@ TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::cut() {
|
void TileSheetEditorModel::cut() {
|
||||||
|
if (!m_selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
TileSheetClipboard blankCb;
|
TileSheetClipboard blankCb;
|
||||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||||
const auto &s = activeSubSheet();
|
auto const&s = activeSubSheet();
|
||||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
iterateSelectionRows(*m_selection, [&](int x, int y) {
|
||||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
auto pt = ox::Point{x, y};
|
||||||
auto pt = ox::Point(x, y);
|
auto const idx = core::idx(s, pt);
|
||||||
const auto idx = core::idx(s, pt);
|
auto const c = getPixel(s, m_img.bpp, idx);
|
||||||
const auto c = getPixel(s, m_img.bpp, idx);
|
pt -= m_selection->a;
|
||||||
pt.x -= m_selectionBounds.x;
|
|
||||||
pt.y -= m_selectionBounds.y;
|
|
||||||
cb->addPixel(pt, c);
|
cb->addPixel(pt, c);
|
||||||
blankCb.addPixel(pt, 0);
|
blankCb.addPixel(pt, 0);
|
||||||
}
|
});
|
||||||
}
|
auto const pt1 = m_selection->a;
|
||||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||||
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
|
|
||||||
turbine::setClipboardObject(m_tctx, std::move(cb));
|
turbine::setClipboardObject(m_tctx, std::move(cb));
|
||||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::copy() {
|
void TileSheetEditorModel::copy() {
|
||||||
|
if (!m_selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
iterateSelectionRows(*m_selection, [&](int x, int y) {
|
||||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
auto pt = ox::Point{x, y};
|
||||||
auto pt = ox::Point(x, y);
|
const auto&s = activeSubSheet();
|
||||||
const auto &s = activeSubSheet();
|
|
||||||
const auto idx = core::idx(s, pt);
|
const auto idx = core::idx(s, pt);
|
||||||
const auto c = getPixel(s, m_img.bpp, idx);
|
const auto c = getPixel(s, m_img.bpp, idx);
|
||||||
pt.x -= m_selectionBounds.x;
|
pt -= m_selection->a;
|
||||||
pt.y -= m_selectionBounds.y;
|
|
||||||
cb->addPixel(pt, c);
|
cb->addPixel(pt, c);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
turbine::setClipboardObject(m_tctx, std::move(cb));
|
turbine::setClipboardObject(m_tctx, std::move(cb));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::paste() {
|
void TileSheetEditorModel::paste() {
|
||||||
|
if (!m_selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_tctx);
|
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_tctx);
|
||||||
if (err) {
|
if (err) {
|
||||||
oxLogError(err);
|
oxLogError(err);
|
||||||
oxErrf("Could not read clipboard: {}", toStr(err));
|
oxErrf("Could not read clipboard: {}", toStr(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto &s = activeSubSheet();
|
auto const&s = activeSubSheet();
|
||||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
auto const pt1 = m_selection->a;
|
||||||
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
|
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
pushCommand(ox::make<CutPasteCommand>(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 {
|
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
|
||||||
if (!m_selectionOngoing) {
|
if (m_selTracker.updateCursorPoint(pt)) {
|
||||||
m_selectionOrigin = pt;
|
m_selection.emplace(m_selTracker.selection());
|
||||||
m_selectionOngoing = true;
|
|
||||||
m_selectionBounds = {pt, pt};
|
|
||||||
m_updated = true;
|
|
||||||
} else if (m_selectionBounds.pt2() != pt) {
|
|
||||||
m_selectionBounds = {m_selectionOrigin, pt};
|
|
||||||
m_updated = true;
|
m_updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::completeSelection() noexcept {
|
void TileSheetEditorModel::completeSelection() noexcept {
|
||||||
m_selectionOngoing = false;
|
|
||||||
auto &s = activeSubSheet();
|
auto &s = activeSubSheet();
|
||||||
auto pt = m_selectionBounds.pt2();
|
m_selTracker.finishSelection();
|
||||||
pt.x = ox::min(s.columns * TileWidth, pt.x);
|
m_selection.emplace(m_selTracker.selection());
|
||||||
pt.y = ox::min(s.rows * TileHeight, pt.y);
|
auto &pt = m_selection->b;
|
||||||
m_selectionBounds.setPt2(pt);
|
pt.x = ox::min(s.columns * TileWidth - 1, pt.x);
|
||||||
|
pt.y = ox::min(s.rows * TileHeight - 1, pt.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::clearSelection() noexcept {
|
void TileSheetEditorModel::clearSelection() noexcept {
|
||||||
m_updated = true;
|
m_updated = true;
|
||||||
m_selectionOrigin = {-1, -1};
|
m_selection.reset();
|
||||||
m_selectionBounds = {{-1, -1}, {-1, -1}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TileSheetEditorModel::updated() const noexcept {
|
bool TileSheetEditorModel::updated() const noexcept {
|
||||||
@ -266,9 +263,9 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
||||||
const auto &s = activeSubSheet();
|
auto const&s = activeSubSheet();
|
||||||
const auto pt = idxToPt(static_cast<int>(idx), s.columns);
|
auto const pt = idxToPt(static_cast<int>(idx), s.columns);
|
||||||
return m_selectionBounds.contains(pt);
|
return m_selection && m_selection->contains(pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
||||||
|
@ -33,10 +33,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
|||||||
size_t m_palettePage{};
|
size_t m_palettePage{};
|
||||||
studio::UndoStack &m_undoStack;
|
studio::UndoStack &m_undoStack;
|
||||||
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
||||||
|
studio::SelectionTracker m_selTracker;
|
||||||
|
ox::Optional<studio::Selection> m_selection;
|
||||||
bool m_updated = false;
|
bool m_updated = false;
|
||||||
bool m_selectionOngoing = false;
|
|
||||||
ox::Point m_selectionOrigin = {-1, -1};
|
|
||||||
ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
|
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
|
||||||
|
@ -84,9 +84,18 @@ void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clic
|
|||||||
m_model.fill(pt, static_cast<int>(m_palIdx));
|
m_model.fill(pt, static_cast<int>(m_palIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorView::releaseMouseButton() noexcept {
|
void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
|
||||||
|
switch (tool) {
|
||||||
|
case TileSheetTool::Draw:
|
||||||
m_model.endDrawCommand();
|
m_model.endDrawCommand();
|
||||||
|
break;
|
||||||
|
case TileSheetTool::Select:
|
||||||
m_model.completeSelection();
|
m_model.completeSelection();
|
||||||
|
break;
|
||||||
|
case TileSheetTool::Fill:
|
||||||
|
case TileSheetTool::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
|
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
enum class TileSheetTool: int {
|
enum class TileSheetTool {
|
||||||
|
None,
|
||||||
Select,
|
Select,
|
||||||
Draw,
|
Draw,
|
||||||
Fill,
|
Fill,
|
||||||
@ -33,6 +34,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
|
|||||||
return "Draw";
|
return "Draw";
|
||||||
case TileSheetTool::Fill:
|
case TileSheetTool::Fill:
|
||||||
return "Fill";
|
return "Fill";
|
||||||
|
case TileSheetTool::None:
|
||||||
|
return "None";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -66,7 +69,7 @@ class TileSheetEditorView: public ox::SignalHandler {
|
|||||||
|
|
||||||
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
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;
|
void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;
|
||||||
|
|
||||||
|
@ -18,6 +18,11 @@ struct Selection {
|
|||||||
constexpr ox::Size size() const noexcept {
|
constexpr ox::Size size() const noexcept {
|
||||||
return {b.x - a.x, b.y - a.y};
|
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) {
|
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<decltype(cb(0, 0)), ox::Error>;
|
||||||
|
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 {
|
class SelectionTracker {
|
||||||
private:
|
private:
|
||||||
bool m_selectionOngoing{};
|
bool m_selectionOngoing{};
|
||||||
@ -53,14 +74,23 @@ class SelectionTracker {
|
|||||||
m_selectionOngoing = true;
|
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) {
|
if (!m_selectionOngoing && allowStart) {
|
||||||
m_pointA = cursor;
|
m_pointA = cursor;
|
||||||
m_selectionOngoing = true;
|
m_selectionOngoing = true;
|
||||||
}
|
}
|
||||||
if (m_selectionOngoing) {
|
if (m_selectionOngoing) {
|
||||||
m_pointB = cursor;
|
m_pointB = cursor;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void updateCursorPoint(ox::Vec2 cursor, bool allowStart = true) noexcept {
|
constexpr void updateCursorPoint(ox::Vec2 cursor, bool allowStart = true) noexcept {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user