Files
ox/src/nostalgia/core/studio/tilesheeteditor-imgui.cpp
T

371 lines
12 KiB
C++

/*
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <imgui.h>
#include <nostalgia/core/media.hpp>
#include <nostalgia/geo/point.hpp>
#include "tilesheeteditor-imgui.hpp"
namespace nostalgia::core {
TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, const ox::String &path): m_tileSheetEditor(ctx, path) {
m_ctx = ctx;
m_itemPath = path;
const auto lastSlash = std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset();
m_itemName = m_itemPath.substr(lastSlash + 1);
// init palette idx
const auto &palPath = model()->palPath();
auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto &palList = sctx->project->fileList(core::FileExt_npal + 1);
for (std::size_t i = 0; const auto &pal : palList) {
if (palPath == pal) {
m_selectedPaletteIdx = i;
break;
}
++i;
}
// connect signal/slots
undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
}
const ox::String &TileSheetEditorImGui::itemName() const noexcept {
return m_itemPath;
}
const ox::String &TileSheetEditorImGui::itemDisplayName() const noexcept {
return m_itemName;
}
void TileSheetEditorImGui::exportFile() {
}
void TileSheetEditorImGui::cut() {
model()->cut();
}
void TileSheetEditorImGui::copy() {
model()->copy();
}
void TileSheetEditorImGui::paste() {
model()->paste();
}
void TileSheetEditorImGui::keyStateChanged(core::Key key, bool down) {
if (!down) {
return;
}
const auto colorCnt = model()->pal().colors.size();
if (key >= core::Key::Num_1 && key <= core::Key::Num_0 + colorCnt) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx);
} else if (key == core::Key::Num_0 && colorCnt >= 10) {
auto idx = ox::min<std::size_t>(static_cast<uint32_t>(key - core::Key::Num_1 + 9), colorCnt - 1);
m_tileSheetEditor.setPalIdx(idx);
}
}
void TileSheetEditorImGui::draw(core::Context*) noexcept {
const auto paneSize = ImGui::GetContentRegionAvail();
const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
const auto fbSize = geo::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
{
drawTileSheet(fbSize);
}
ImGui::EndChild();
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(45, 14);
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
m_tool = Tool::Draw;
model()->clearSelection();
}
ImGui::SameLine();
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
m_tool = Tool::Select;
}
ImGui::SameLine();
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
m_tool = Tool::Fill;
model()->clearSelection();
}
}
ImGui::EndChild();
const auto ySize = paneSize.y - 36;
// draw palette/color picker
ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.07f), true);
{
drawPaletteSelector();
}
ImGui::EndChild();
ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.03f), true);
{
static constexpr auto btnHeight = 18;
const auto btnSize = ImVec2(18, btnHeight);
if (ImGui::Button("+", btnSize)) {
auto insertOnIdx = model()->activeSubSheetIdx();
const auto &parent = *model()->activeSubSheet();
model()->addSubsheet(insertOnIdx);
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
model()->setActiveSubsheet(insertOnIdx);
}
ImGui::SameLine();
if (ImGui::Button("-", btnSize)) {
const auto &activeSubsheetIdx = model()->activeSubSheetIdx();
if (activeSubsheetIdx.size() > 0) {
model()->rmSubsheet(activeSubsheetIdx);
}
}
ImGui::SameLine();
if (ImGui::Button("Edit", ImVec2(36, btnHeight))) {
showSubsheetEditor();
}
TileSheet::SubSheetIdx path;
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);
ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableHeadersRow();
drawSubsheetSelector(&m_tileSheetEditor.img().subsheet, &path);
ImGui::EndTable();
}
}
ImGui::EndChild();
}
ImGui::EndChild();
m_subsheetEditor.draw();
}
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
ImGui::TableNextRow(0, 5);
using Str = ox::BasicString<100>;
auto pathStr = ox::join<Str>("##", *path).value;
auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
const auto rowSelected = *path == model()->activeSubSheetIdx();
const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
| ImGuiTreeNodeFlags_OpenOnArrow
| ImGuiTreeNodeFlags_DefaultOpen
| (subsheet->subsheets.size() ? 0 : ImGuiTreeNodeFlags_Leaf)
| (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
ImGui::TableNextColumn();
const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
Str newName = subsheet->name.c_str();
ImGui::SameLine();
if (ImGui::IsItemClicked()) {
model()->setActiveSubsheet(*path);
}
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
showSubsheetEditor();
}
if (subsheet->subsheets.size()) {
ImGui::TableNextColumn();
ImGui::Text("--");
ImGui::TableNextColumn();
ImGui::Text("--");
} else {
ImGui::TableNextColumn();
ImGui::Text("%d", subsheet->columns);
ImGui::TableNextColumn();
ImGui::Text("%d", subsheet->rows);
}
if (open) {
for (auto i = 0ul; auto &child : subsheet->subsheets) {
path->push_back(i);
ImGui::PushID(static_cast<int>(i));
drawSubsheetSelector(&child, path);
ImGui::PopID();
path->pop_back();
++i;
}
ImGui::TreePop();
}
}
studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept {
return model()->undoStack();
}
[[nodiscard]]
geo::Vec2 TileSheetEditorImGui::clickPos(const ImVec2 &winPos, geo::Vec2 clickPos) noexcept {
clickPos.x -= winPos.x + 10;
clickPos.y -= winPos.y + 10;
return clickPos;
}
ox::Error TileSheetEditorImGui::saveItem() noexcept {
return model()->saveFile();
}
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
const auto sheet = model()->activeSubSheet();
if (sheet->subsheets.size()) {
m_subsheetEditor.show(sheet->name, -1, -1);
} else {
m_subsheetEditor.show(sheet->name, sheet->columns, sheet->rows);
}
}
void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
const auto winPos = ImGui::GetWindowPos();
const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
m_framebuffer = glutils::generateFrameBuffer(fbSizei.width, fbSizei.height);
m_tileSheetEditor.resizeView(fbSize);
} else if (m_tileSheetEditor.updated()) {
m_tileSheetEditor.resizeView(fbSize);
m_tileSheetEditor.ackUpdate();
}
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
// clear screen and draw
glViewport(0, 0, fbSizei.width, fbSizei.height);
m_tileSheetEditor.draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ImGui::Image(
reinterpret_cast<void*>(m_framebuffer.color.id),
static_cast<ImVec2>(fbSize),
ImVec2(0, 1),
ImVec2(1, 0));
// handle input, this must come after drawing
const auto &io = ImGui::GetIO();
const auto mousePos = geo::Vec2(io.MousePos);
if (ImGui::IsItemHovered()) {
const auto wheel = io.MouseWheel;
const auto wheelh = io.MouseWheelH;
if (wheel != 0) {
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
}
if (wheelh != 0) {
m_tileSheetEditor.scrollH(fbSize, wheelh);
}
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
m_prevMouseDownPos = mousePos;
switch (m_tool) {
case Tool::Draw:
m_tileSheetEditor.clickDraw(fbSize, clickPos(winPos, mousePos));
break;
case Tool::Fill:
m_tileSheetEditor.clickFill(fbSize, clickPos(winPos, mousePos));
break;
case Tool::Select:
m_tileSheetEditor.clickSelect(fbSize, clickPos(winPos, mousePos));
break;
case Tool::None:
break;
}
}
}
if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
const auto popupPos = geo::Vec2(ImGui::GetWindowPos());
if (ImGui::MenuItem("Insert Tile")) {
m_tileSheetEditor.insertTile(fbSize, clickPos(winPos, popupPos));
}
if (ImGui::MenuItem("Delete Tile")) {
m_tileSheetEditor.deleteTile(fbSize, clickPos(winPos, popupPos));
}
ImGui::EndPopup();
}
if (io.MouseReleased[0]) {
m_prevMouseDownPos = {-1, -1};
m_tileSheetEditor.releaseMouseButton();
}
}
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
auto sctx = applicationData<studio::StudioContext>(m_ctx);
const auto &files = sctx->project->fileList(core::FileExt_npal + 1);
const auto first = m_selectedPaletteIdx < files.size() ?
files[m_selectedPaletteIdx].c_str() : "";
if (ImGui::BeginCombo("Palette", first, 0)) {
for (auto n = 0u; n < files.size(); n++) {
const auto selected = (m_selectedPaletteIdx == n);
if (ImGui::Selectable(files[n].c_str(), selected) && m_selectedPaletteIdx != n) {
m_selectedPaletteIdx = n;
oxLogError(model()->setPalette(files[n]));
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
// header
if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("No.", 0, 0.45);
ImGui::TableSetupColumn("", 0, 0.22);
ImGui::TableSetupColumn("Color16", 0, 3);
ImGui::TableHeadersRow();
for (auto i = 0u; auto c: m_tileSheetEditor.pal().colors) {
ImGui::PushID(static_cast<int>(i));
// Column: color idx
ImGui::TableNextColumn();
const auto label = ox::BString<8>() + (i + 1);
const auto rowSelected = i == m_tileSheetEditor.palIdx();
if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
m_tileSheetEditor.setPalIdx(i);
}
// Column: color RGB
ImGui::TableNextColumn();
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
ImGui::TableNextRow();
ImGui::PopID();
++i;
}
ImGui::EndTable();
}
}
ox::Error TileSheetEditorImGui::updateActiveSubsheet(const ox::String &name, int cols, int rows) noexcept {
return model()->updateSubsheet(model()->activeSubSheetIdx(), name, cols, rows);
}
ox::Error TileSheetEditorImGui::markUnsavedChanges(const studio::UndoCommand*) noexcept {
setUnsavedChanges(true);
return OxError(0);
}
void TileSheetEditorImGui::SubSheetEditor::draw() noexcept {
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
constexpr auto popupName = "Edit Subsheet";
if (!m_show) {
return;
}
ImGui::OpenPopup(popupName);
const auto modSize = m_cols > 0;
const auto popupHeight = modSize ? 125.f : 80.f;
ImGui::SetNextWindowSize(ImVec2(235, popupHeight));
if (ImGui::BeginPopupModal(popupName, &m_show, modalFlags)) {
ImGui::InputText("Name", m_name.data(), m_name.cap());
if (modSize) {
ImGui::InputInt("Columns", &m_cols);
ImGui::InputInt("Rows", &m_rows);
}
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();
m_show = false;
inputSubmitted.emit(m_name.c_str(), m_cols, m_rows);
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
m_show = false;
}
ImGui::EndPopup();
}
}
}