[jasper/world] Add WorldObjectEditor
All checks were successful
Build / build (push) Successful in 2m44s

This commit is contained in:
Gary Talent 2024-01-28 00:00:22 -06:00
parent ec099aa386
commit aa1f063a6d
36 changed files with 1375 additions and 235 deletions

View File

@ -15,7 +15,7 @@
namespace jasper::world {
struct PrefabDoc {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.PrefabDoc";
constexpr static auto TypeName = "net.drinkingtea.jasper.world.PrefabDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;

View File

@ -8,7 +8,7 @@
#include "consts.hpp"
#include "prefab.hpp"
#include "worldobjectset.hpp"
#include "worldobject.hpp"
#include "worldstatic.hpp"
namespace jasper::world {

View File

@ -0,0 +1,68 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/core/context.hpp>
#include <nostalgia/core/tilesheet.hpp>
namespace jasper::world {
namespace ncore = nostalgia::core;
struct PaletteCycle {
static constexpr auto TypeName = "net.drinkingtea.jasper.world.PaletteCycle";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
ox::FileAddress palette;
uint16_t intervalMs = 1000;
};
oxModelBegin(PaletteCycle)
oxModelField(palette)
oxModelField(intervalMs)
oxModelEnd()
struct WorldObject {
static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObject";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
int id{};
ox::String name;
uint16_t palBank{};
ncore::SubSheetId subsheetId{};
uint64_t collisionMap{};
};
oxModelBegin(WorldObject)
oxModelField(id)
oxModelField(name)
oxModelField(palBank)
oxModelField(subsheetId)
oxModelField(collisionMap)
oxModelEnd()
struct WorldObjectSet {
static constexpr auto TypeName = "net.drinkingtea.jasper.world.WorldObjectSet";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
int objIdIdx = 0;
ox::FileAddress tilesheet;
ox::Vector<PaletteCycle> palettes;
ox::Vector<WorldObject> objects;
};
oxModelBegin(WorldObjectSet)
oxModelField(objIdIdx)
oxModelField(tilesheet)
oxModelField(palettes)
oxModelField(objects)
oxModelEnd()
ox::Error loadObjectSet(WorldObjectSet const&os) noexcept;
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <nostalgia/core/context.hpp>
#include "consts.hpp"
#include "prefab.hpp"
#include "worldstatic.hpp"
namespace jasper::world {
namespace ncore = nostalgia::core;
struct WorldObject {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.world.WorldObject";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
ox::FileAddress tilesheet;
ncore::SubSheetId subSheetId;
};
oxModelBegin(WorldObject)
oxModelField(tilesheet)
oxModelField(subsheetId)
oxModelEnd()
struct WorldObjectSet {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.world.WorldObjectSet";
static constexpr auto TypeVersion = 1;
static constexpr auto Preloadable = true;
ox::Vector<WorldObject> objects;
};
oxModelBegin(WorldObjectSet)
oxModelField(objects)
oxModelEnd()
}

View File

@ -18,7 +18,7 @@ namespace ncore = nostalgia::core;
struct SpriteDoc {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.SpriteDoc";
constexpr static auto TypeName = "net.drinkingtea.jasper.world.SpriteDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
@ -29,7 +29,7 @@ struct SpriteDoc {
struct TileDoc {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.TileDoc";
constexpr static auto TypeName = "net.drinkingtea.jasper.world.TileDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
@ -72,7 +72,7 @@ struct WorldDoc {
using TileMapLayer = ox::Vector<TileMapRow>;
using TileMap = ox::Vector<TileMapLayer>;
constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.WorldDoc";
constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldDoc";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;
@ -141,7 +141,7 @@ constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
struct WorldStatic {
constexpr static auto TypeName = "net.drinkingtea.nostalgia.world.WorldStatic";
constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldStatic";
constexpr static auto TypeVersion = 1;
constexpr static auto Preloadable = true;

View File

@ -4,6 +4,7 @@
#include <keel/module.hpp>
#include <jasper/world/worldobject.hpp>
#include <jasper/world/worldstatic.hpp>
#include "typeconv.hpp"
@ -23,13 +24,15 @@ class WorldModule: public keel::Module {
[[nodiscard]]
ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
return {
keel::generateTypeDesc<PaletteCycle>,
keel::generateTypeDesc<WorldObjectSet>,
keel::generateTypeDesc<WorldDoc>,
keel::generateTypeDesc<WorldStatic>,
};
}
[[nodiscard]]
ox::Vector<const keel::BaseConverter*> converters() const noexcept override {
ox::Vector<keel::BaseConverter const*> converters() const noexcept override {
return {
&m_worldDocToWorldStaticConverter,
};

View File

@ -16,5 +16,5 @@ install(
${NOSTALGIA_DIST_MODULE}
)
add_subdirectory(worldobjecteditor)
add_subdirectory(worldobjectseteditor)
add_subdirectory(worldeditor)

View File

@ -6,22 +6,22 @@
#include <jasper/world/consts.hpp>
#include "worldobjecteditor/worldobjecteditor-imgui.hpp"
#include "worldobjectseteditor/worldobjectseteditor-imgui.hpp"
#include "worldeditor/worldeditor-imgui.hpp"
namespace jasper::world {
class StudioModule: public studio::Module {
public:
ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept override {
ox::Vector<studio::EditorMaker> editors(studio::StudioContext &ctx) const noexcept override {
return {
studio::editorMaker<WorldObjectEditorImGui>(ctx, FileExt_jwob),
studio::editorMaker<WorldObjectSetEditorImGui>(ctx, FileExt_jwob),
studio::editorMaker<WorldEditorImGui>(ctx, FileExt_jwld),
};
}
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(turbine::Context&) const noexcept override {
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(studio::StudioContext&) const noexcept override {
ox::Vector<ox::UPtr<studio::ItemMaker>> out;
out.emplace_back(ox::make<studio::ItemMakerT<PrefabDoc>>("World Object", "WorldObjects", FileExt_jwob, ox::ClawFormat::Organic));
out.emplace_back(ox::make<studio::ItemMakerT<WorldObjectSet>>("World Object Set", "WorldObjectSets", FileExt_jwob, ox::ClawFormat::Organic));
out.emplace_back(ox::make<studio::ItemMakerT<WorldDoc>>("World", "Worlds", FileExt_jwld, ox::ClawFormat::Organic));
return out;
}

View File

@ -10,9 +10,9 @@
namespace jasper::world {
WorldEditorImGui::WorldEditorImGui(turbine::Context &ctx, ox::StringView path):
WorldEditorImGui::WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path):
Editor(path),
m_ctx(ctx),
m_ctx(ctx.tctx),
m_editor(m_ctx, path),
m_view(m_ctx, m_editor.world()) {
setRequiresConstantRefresh(false);

View File

@ -21,7 +21,7 @@ class WorldEditorImGui: public studio::Editor {
WorldEditorView m_view;
public:
WorldEditorImGui(turbine::Context &ctx, ox::StringView path);
WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path);
void draw(turbine::Context&) noexcept final;

View File

@ -1,5 +0,0 @@
target_sources(
JasperWorld-Studio PRIVATE
worldobjecteditor.cpp
worldobjecteditor-imgui.cpp
)

View File

@ -1,69 +0,0 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "worldobjecteditor-imgui.hpp"
namespace jasper::world {
WorldObjectEditorImGui::WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path):
Editor(path),
m_sctx(*applicationData<studio::StudioContext>(ctx)),
m_editor(ctx, path) {
}
void WorldObjectEditorImGui::draw(turbine::Context&) noexcept {
const auto paneSize = ImGui::GetContentRegionAvail();
constexpr auto attrEditorWidth = 400.f;
ImGui::BeginChild("Preview", ImVec2(paneSize.x - attrEditorWidth, 0));
ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("AttrEditor", ImVec2(attrEditorWidth, 0), true);
drawAttrEditor();
drawTileSheetSelector();
ImGui::EndChild();
}
void WorldObjectEditorImGui::onActivated() noexcept {
}
void WorldObjectEditorImGui::drawAttrEditor() noexcept {
static constexpr auto boundDimension = [](int &v) noexcept {
v = ox::clamp(v, 1, 4);
};
auto &doc = m_editor.doc();
ImGui::InputInt("Footprint Width", &doc.footprint.width);
ImGui::InputInt("Footprint Height", &doc.footprint.height);
ImGui::InputInt("Visible Width", &doc.visibleSz.width);
ImGui::InputInt("Visible Height", &doc.visibleSz.height);
boundDimension(doc.footprint.width);
boundDimension(doc.footprint.height);
boundDimension(doc.visibleSz.width);
boundDimension(doc.visibleSz.height);
}
void WorldObjectEditorImGui::drawTileSheetSelector() noexcept {
auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng);
auto const first = m_selectedTilesheetIdx < tilesheetList.size() ?
tilesheetList[m_selectedTilesheetIdx].c_str() : "";
if (ImGui::BeginCombo("Tile Sheet", first, 0)) {
for (auto i = 0u; i < tilesheetList.size(); ++i) {
auto const selected = (m_selectedTilesheetIdx == i);
if (ImGui::Selectable(tilesheetList[i].c_str(), selected) &&
m_selectedTilesheetIdx != i) {
m_selectedTilesheetIdx = i;
//oxLogError(m_model.setPalette(tilesheetList[n]));
}
if (selected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
}
ox::Error WorldObjectEditorImGui::saveItem() noexcept {
return m_editor.saveItem();
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <studio/studio.hpp>
#include <turbine/context.hpp>
#include "worldobjecteditor.hpp"
namespace jasper::world {
class WorldObjectEditorImGui: public studio::Editor {
private:
studio::StudioContext &m_sctx;
PrefabEditor m_editor;
uint_t m_selectedTilesheetIdx{};
public:
WorldObjectEditorImGui(turbine::Context &ctx, ox::StringView path);
void draw(turbine::Context&) noexcept final;
void onActivated() noexcept override;
protected:
void drawAttrEditor() noexcept;
void drawTileSheetSelector() noexcept;
ox::Error saveItem() noexcept final;
};
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <turbine/turbine.hpp>
#include "worldobjecteditor.hpp"
namespace jasper::world {
PrefabEditor::PrefabEditor(turbine::Context &ctx, ox::StringView path):
m_ctx(*applicationData<studio::StudioContext>(ctx)),
m_itemPath(path),
m_doc(*readObj<PrefabDoc>(keelCtx(ctx), path).unwrapThrow()) {
}
PrefabDoc const&PrefabEditor::doc() const noexcept {
return m_doc;
}
PrefabDoc &PrefabEditor::doc() noexcept {
return m_doc;
}
ox::Error PrefabEditor::saveItem() noexcept {
return m_ctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic);
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/context.hpp>
#include <studio/context.hpp>
#include <jasper/world/world.hpp>
namespace jasper::world {
class PrefabEditor {
private:
studio::StudioContext &m_ctx;
ox::String m_itemPath;
PrefabDoc m_doc;
public:
PrefabEditor(turbine::Context &ctx, ox::StringView path);
[[nodiscard]]
PrefabDoc const&doc() const noexcept;
[[nodiscard]]
PrefabDoc &doc() noexcept;
ox::Error saveItem() noexcept;
};
}

View File

@ -0,0 +1,12 @@
target_sources(
JasperWorld-Studio PRIVATE
commands/addobject.cpp
commands/rmobject.cpp
commands/editobject.cpp
commands/addpalette.cpp
commands/rmpalette.cpp
commands/editpalette.cpp
commands/changetilesheet.cpp
collisionmapview.cpp
worldobjectseteditor-imgui.cpp
)

View File

@ -0,0 +1,175 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "collisionmapview.hpp"
namespace jasper::world {
uint64_t mapIdx(auto x, auto y) noexcept {
return static_cast<uint64_t>(y) * 8 + static_cast<uint64_t>(x);
}
const glutils::ProgramSource CollisionView::s_programSrc = {
.shaderParams = {
{
.len = 2,
.name = ox::String("vPosition"),
},
{
.len = 1,
.name = ox::String("vSelection"),
},
},
.vertShader = ox::sfmt(R"(
{}
in vec2 vPosition;
in float vSelection;
out float fSelection;
void main() {
gl_Position = vec4(vPosition, 0.0, 1.0);
fSelection = vSelection;
})", ncore::gl::GlslVersion),
.fragShader = ox::sfmt(R"(
{}
in float fSelection;
out vec4 outColor;
void main() {
outColor = vec4(0.0, 0.7, 1.0, 0.4) * fSelection;
})", ncore::gl::GlslVersion),
};
CollisionView::CollisionView(studio::StudioContext &sctx):
m_nctx(ncore::init(sctx.tctx, {
.glInstallDrawer = false,
.glSpriteCount = 0,
}).unwrapThrow()),
m_frameBuffer(glutils::generateFrameBuffer(240 * s_scale, 160 * s_scale)) {
m_bufferSet.vao = glutils::generateVertexArrayObject();
m_bufferSet.vbo = glutils::generateBuffer();
m_bufferSet.ebo = glutils::generateBuffer();
glBindVertexArray(m_bufferSet.vao);
sendVbo(m_bufferSet);
sendEbo(m_bufferSet);
m_shader = glutils::buildShaderProgram(s_programSrc).unwrapThrow();
glBindVertexArray(0);
}
ox::Error CollisionView::setup(
ox::FileAddress const&tsAddr,
ox::FileAddress const&palAddr,
int const w,
int h,
uint_t tile,
uint64_t colMap) noexcept {
m_subsheetTilesWidth = w;
m_subsheetTilesHeight = h;
m_sheetTileStart = tile;
ncore::setBgStatus(*m_nctx, 0, true);
oxReturnError(ncore::loadBgTileSheet(*m_nctx, 0, tsAddr));
oxReturnError(ncore::loadBgPalette(*m_nctx, 0, palAddr));
buildGlBuffers(colMap);
return {};
}
void CollisionView::draw() noexcept {
glutils::FrameBufferBind const frameBufferBind(m_frameBuffer);
ncore::gl::draw(*m_nctx, s_scale);
//glViewport(0, 0, m_frameBuffer.width, m_frameBuffer.height);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(m_shader);
glBindVertexArray(m_bufferSet.vao);
sendVbo(m_bufferSet);
sendEbo(m_bufferSet);
auto const elmCnt = static_cast<GLsizei>(m_bufferSet.elements.size());
glDrawElements(GL_TRIANGLES, elmCnt, GL_UNSIGNED_INT, nullptr);
glBindVertexArray(0);
glUseProgram(0);
glDisable(GL_BLEND);
}
int CollisionView::scale() const noexcept {
return s_scale;
}
glutils::FrameBuffer const&CollisionView::framebuffer() const noexcept {
return m_frameBuffer;
}
void CollisionView::setPixelBufferObject(
unsigned vertexRow,
float x, float y,
bool const selected,
float *vbo,
GLuint *ebo) const noexcept {
auto constexpr xmod = static_cast<float>(ncore::TileWidth) / 240.f * 2;
auto constexpr ymod = static_cast<float>(ncore::TileHeight) / 160.f * 2;
x *= xmod;
y *= -ymod;
x -= 1.0f;
y += 1.0f - ymod;
//x = -1;
//y = -1;
auto const selection = 1.f * static_cast<float>(selected);
// don't worry, these memcpys gets optimized to something much more ideal
std::array const vertices{
x, y, selection, // bottom left
x + xmod, y, selection, // bottom right
x + xmod, y + ymod, selection, // top right
x, y + ymod, selection, // top left
};
memcpy(vbo, vertices.data(), sizeof(vertices));
ox::Array<GLuint, 6> const elms{
vertexRow + 0, vertexRow + 1, vertexRow + 2,
vertexRow + 2, vertexRow + 3, vertexRow + 0,
};
memcpy(ebo, elms.data(), sizeof(elms));
}
bool CollisionView::click(ox::Vec2 const&pos, uint64_t &colMap) noexcept {
auto const inMap = colMap;
auto const x = static_cast<uint64_t>(pos.x * static_cast<float>(m_subsheetTilesWidth));
auto const y = static_cast<uint64_t>(pos.y * static_cast<float>(m_subsheetTilesHeight));
uint64_t const idx = mapIdx(x, y);
uint64_t const colOn = (colMap >> idx) & 1;
colMap ^= uint64_t{1} << idx;
colMap |= static_cast<uint64_t>(!colOn) << idx;
buildGlBuffers(colMap);
return inMap != colMap;
}
void CollisionView::buildGlBuffers(uint64_t colMap) noexcept {
auto const vboLength = static_cast<size_t>(s_programSrc.rowLen) * 4;
auto constexpr eboLength = 6;
auto const tileCnt =
static_cast<size_t>(m_subsheetTilesWidth) * static_cast<size_t>(m_subsheetTilesHeight);
m_bufferSet.vertices.resize(tileCnt * vboLength);
m_bufferSet.elements.resize(tileCnt * eboLength);
size_t i = 0;
auto tile = m_sheetTileStart;
for (auto y = 0; y < m_subsheetTilesHeight; ++y) {
for (auto x = 0; x < m_subsheetTilesWidth; ++x) {
ncore::setBgTile(*m_nctx, 0, x, y, tile);
auto const vbo = &m_bufferSet.vertices[i * vboLength];
auto const ebo = &m_bufferSet.elements[i * eboLength];
setPixelBufferObject(
static_cast<uint_t>(i * 4),
static_cast<float>(x),
static_cast<float>(y),
(colMap >> mapIdx(x, y)) & 1,
vbo,
ebo);
++i;
++tile;
}
}
glUseProgram(m_shader);
glBindVertexArray(m_bufferSet.vao);
sendVbo(m_bufferSet);
sendEbo(m_bufferSet);
glBindVertexArray(0);
glUseProgram(0);
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <glutils/glutils.hpp>
#include <studio/context.hpp>
#include <nostalgia/core/core.hpp>
namespace jasper::world {
namespace ncore = nostalgia::core;
class CollisionView {
private:
static const glutils::ProgramSource s_programSrc;
static constexpr int s_scale = 10;
ncore::ContextUPtr m_nctx;
glutils::FrameBuffer m_frameBuffer;
glutils::GLProgram m_shader;
glutils::BufferSet m_bufferSet;
uint_t m_sheetTileStart{};
int m_subsheetTilesWidth{};
int m_subsheetTilesHeight{};
public:
CollisionView(studio::StudioContext &sctx);
ox::Error setup(
ox::FileAddress const&tsAddr,
ox::FileAddress const&palAddr,
int w,
int h,
uint_t tile,
uint64_t colMap) noexcept;
void draw() noexcept;
[[nodiscard]]
int scale() const noexcept;
[[nodiscard]]
glutils::FrameBuffer const&framebuffer() const noexcept;
void setPixelBufferObject(
unsigned vertexRow,
float x, float y,
bool selected,
float *vbo,
GLuint *ebo) const noexcept;
/**
* @return true if colMap changes
*/
bool click(ox::Vec2 const&pos, uint64_t &colMap) noexcept;
private:
void buildGlBuffers(uint64_t colMap) noexcept;
};
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "addobject.hpp"
namespace jasper::world {
AddObject::AddObject(WorldObjectSet &doc) noexcept:
m_doc(doc) {
m_obj.id = m_doc.objIdIdx + 1;
m_obj.name = ox::sfmt("Object {}", m_obj.id);
for (auto const&o : m_doc.objects) {
if (o.name > m_obj.name) {
break;
}
++m_insertIdx;
}
}
void AddObject::redo() noexcept {
++m_doc.objIdIdx;
m_doc.objects.emplace(m_insertIdx, std::move(m_obj));
}
void AddObject::undo() noexcept {
m_obj = std::move(m_doc.objects[m_insertIdx]);
oxIgnoreError(m_doc.objects.erase(m_insertIdx));
--m_doc.objIdIdx;
}
int AddObject::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::AddObject);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class AddObject: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t m_insertIdx{};
WorldObject m_obj{};
public:
AddObject(WorldObjectSet &doc) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
[[nodiscard]]
inline size_t insertIdx() const noexcept {
return m_insertIdx;
}
};
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <algorithm>
#include "commands.hpp"
#include "addpalette.hpp"
namespace jasper::world {
AddPalette::AddPalette(WorldObjectSet &doc, ox::FileAddress palAddr) noexcept:
m_doc(doc),
m_palAddr(std::move(palAddr)) {
}
void AddPalette::redo() noexcept {
m_doc.palettes.emplace_back(PaletteCycle{
.palette = m_palAddr
});
std::sort(m_doc.palettes.begin(), m_doc.palettes.end(), [](PaletteCycle const&a, PaletteCycle const&b) {
return a.palette.getPath().or_value("") < b.palette.getPath().or_value("");
});
}
void AddPalette::undo() noexcept {
auto const idx = std::find_if(m_doc.palettes.begin(), m_doc.palettes.end(), [this](PaletteCycle const&v) {
return v.palette == m_palAddr;
});
oxIgnoreError(m_doc.palettes.erase(idx));
}
int AddPalette::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::AddPalette);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class AddPalette: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
ox::FileAddress m_palAddr;
public:
AddPalette(WorldObjectSet &doc, ox::FileAddress palAddr) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "changetilesheet.hpp"
namespace jasper::world {
ChangeTileSheet::ChangeTileSheet(
WorldObjectSet &doc,
ox::FileAddress newVal,
size_t newIdx,
size_t &selectedTilesheetIdx) noexcept:
m_doc(doc),
m_newVal(std::move(newVal)),
m_oldIdx(selectedTilesheetIdx),
m_newIdx(newIdx),
m_selectedTilesheetIdx(selectedTilesheetIdx) {
}
void ChangeTileSheet::redo() noexcept {
m_oldVal = std::move(m_doc.tilesheet);
m_doc.tilesheet = std::move(m_newVal);
m_selectedTilesheetIdx = m_newIdx;
}
void ChangeTileSheet::undo() noexcept {
m_newVal = std::move(m_doc.tilesheet);
m_doc.tilesheet = std::move(m_oldVal);
m_selectedTilesheetIdx = m_oldIdx;
}
int ChangeTileSheet::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::ChangeTileSheet);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class ChangeTileSheet: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
ox::FileAddress m_newVal;
ox::FileAddress m_oldVal;
size_t m_oldIdx{};
size_t m_newIdx{};
size_t &m_selectedTilesheetIdx;
public:
ChangeTileSheet(
WorldObjectSet &doc,
ox::FileAddress newVal,
size_t newIdx,
size_t &selectedTilesheetIdx) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
namespace jasper::world {
enum class WorldObjCommand {
None,
AddObject,
RmObject,
EditObjectName,
EditObjectPalette,
EditObjectSubSheet,
EditObjectCollisionMap,
AddPalette,
RmPalette,
EditPalette,
ChangeTileSheet,
};
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "editobject.hpp"
#include <utility>
namespace jasper::world {
EditObjectName::EditObjectName(WorldObjectSet &doc, size_t objIdx, ox::String newName) noexcept:
m_doc(doc),
m_objIdx(objIdx),
m_oldVal(m_doc.objects[objIdx].name),
m_newVal(std::move(newName)) {
}
void EditObjectName::redo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.name = std::move(m_newVal);
}
void EditObjectName::undo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
m_newVal = std::move(obj.name);
obj.name = m_oldVal;
}
int EditObjectName::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::EditObjectName);
}
bool EditObjectName::mergeWith(UndoCommand const*cmd) noexcept {
auto const en = dynamic_cast<EditObjectName const*>(cmd);
if (en && m_objIdx == en->m_objIdx) {
m_newVal = en->m_newVal;
return true;
}
return false;
}
EditObjectSubSheet::EditObjectSubSheet(
WorldObjectSet &doc,
size_t objIdx,
ncore::SubSheetId subSheetId) noexcept:
m_doc(doc),
m_objIdx(objIdx),
m_newVal(subSheetId),
m_oldVal(m_doc.objects[m_objIdx].subsheetId) {
}
void EditObjectSubSheet::redo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.subsheetId = m_newVal;
}
void EditObjectSubSheet::undo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.subsheetId = m_oldVal;
}
int EditObjectSubSheet::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::EditObjectSubSheet);
}
EditObjectPalette::EditObjectPalette(WorldObjectSet &doc, size_t objIdx, uint16_t palBank) noexcept:
m_doc(doc),
m_objIdx(objIdx),
m_oldVal(m_doc.objects[m_objIdx].palBank),
m_newVal(palBank) {
}
void EditObjectPalette::redo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.palBank = m_newVal;
}
void EditObjectPalette::undo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.palBank = m_oldVal;
}
[[nodiscard]]
int EditObjectPalette::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::EditObjectPalette);
}
EditObjectCollisionMap::EditObjectCollisionMap(WorldObjectSet &doc, size_t objIdx, uint64_t colMap) noexcept:
m_doc(doc),
m_objIdx(objIdx),
m_oldVal(m_doc.objects[m_objIdx].collisionMap),
m_newVal(colMap) {
}
void EditObjectCollisionMap::redo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.collisionMap = m_newVal;
}
void EditObjectCollisionMap::undo() noexcept {
auto &obj = m_doc.objects[m_objIdx];
obj.collisionMap = m_oldVal;
}
[[nodiscard]]
int EditObjectCollisionMap::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::EditObjectCollisionMap);
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class EditObjectName: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t const m_objIdx{};
ox::String const m_oldVal{};
ox::String m_newVal{};
public:
EditObjectName(WorldObjectSet &doc, size_t objIdx, ox::String newName) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
bool mergeWith(UndoCommand const*cmd) noexcept override;
};
class EditObjectSubSheet: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t const m_objIdx{};
ncore::SubSheetId const m_newVal{};
ncore::SubSheetId const m_oldVal{};
public:
EditObjectSubSheet(WorldObjectSet &doc, size_t objIdx, ncore::SubSheetId subSheetId) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
class EditObjectPalette: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t const m_objIdx{};
uint16_t const m_oldVal{};
uint16_t const m_newVal{};
public:
EditObjectPalette(WorldObjectSet &doc, size_t objIdx, uint16_t palBank) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
class EditObjectCollisionMap: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t const m_objIdx{};
uint64_t const m_oldVal{};
uint64_t const m_newVal{};
public:
EditObjectCollisionMap(WorldObjectSet &doc, size_t objIdx, uint64_t colMap) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "editpalette.hpp"
namespace jasper::world {
EditPalette::EditPalette(PaletteCycle &doc, uint16_t interval) noexcept:
m_doc(doc),
m_oldVal(doc.intervalMs),
m_newVal(interval) {
}
void EditPalette::redo() noexcept {
m_doc.intervalMs = m_newVal;
}
void EditPalette::undo() noexcept {
m_doc.intervalMs = m_oldVal;
}
int EditPalette::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::EditPalette);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class EditPalette: public studio::UndoCommand {
private:
PaletteCycle &m_doc;
uint16_t const m_oldVal{};
uint16_t const m_newVal{};
public:
EditPalette(PaletteCycle &doc, uint16_t interval) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include "commands.hpp"
#include "rmobject.hpp"
namespace jasper::world {
RmObject::RmObject(WorldObjectSet &doc, size_t const rmIdx) noexcept:
m_doc(doc),
m_rmIdx(rmIdx) {
}
void RmObject::redo() noexcept {
m_obj = std::move(m_doc.objects[m_rmIdx]);
oxIgnoreError(m_doc.objects.erase(m_rmIdx));
}
void RmObject::undo() noexcept {
m_doc.objects.emplace(m_rmIdx, std::move(m_obj));
}
int RmObject::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::RmObject);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class RmObject: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
size_t const m_rmIdx{};
WorldObject m_obj{};
public:
RmObject(WorldObjectSet &doc, size_t rmIdx) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,33 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <algorithm>
#include "commands.hpp"
#include "rmpalette.hpp"
namespace jasper::world {
RmPalette::RmPalette(WorldObjectSet &doc, size_t rmIdx) noexcept:
m_doc(doc),
m_rmIdx(rmIdx) {
}
void RmPalette::redo() noexcept {
m_pal = std::move(m_doc.palettes[m_rmIdx]);
oxIgnoreError(m_doc.palettes.erase(m_rmIdx));
}
void RmPalette::undo() noexcept {
m_doc.palettes.emplace_back(std::move(m_pal));
std::sort(m_doc.palettes.begin(), m_doc.palettes.end(), [](PaletteCycle const&a, PaletteCycle const&b) {
return a.palette.getPath().or_value("") < b.palette.getPath().or_value("");
});
}
int RmPalette::commandId() const noexcept {
return static_cast<int>(WorldObjCommand::RmPalette);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/fs/fs.hpp>
#include <studio/undostack.hpp>
#include <jasper/world/worldobject.hpp>
namespace jasper::world {
class RmPalette: public studio::UndoCommand {
private:
WorldObjectSet &m_doc;
PaletteCycle m_pal;
size_t const m_rmIdx = 0;
public:
RmPalette(WorldObjectSet &doc, size_t rmIdx) noexcept;
void redo() noexcept override;
void undo() noexcept override;
[[nodiscard]]
int commandId() const noexcept override;
};
}

View File

@ -0,0 +1,360 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#include "commands/addobject.hpp"
#include "commands/rmobject.hpp"
#include "commands/editobject.hpp"
#include "commands/addpalette.hpp"
#include "commands/rmpalette.hpp"
#include "commands/editpalette.hpp"
#include "commands/changetilesheet.hpp"
#include "worldobjectseteditor-imgui.hpp"
namespace jasper::world {
namespace ig = studio::ig;
constexpr auto btnSize = ImVec2(22, ig::BtnSz.y);
WorldObjectSetEditorImGui::WorldObjectSetEditorImGui(
studio::StudioContext &ctx,
ox::StringView path):
Editor(path),
m_sctx(ctx),
m_itemPath(path),
m_doc(*readObj<WorldObjectSet>(keelCtx(ctx.tctx), path).unwrapThrow()),
m_tileSheet(readObj<ncore::TileSheet>(keelCtx(m_sctx.tctx), m_doc.tilesheet).unwrapThrow()) {
auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng);
auto const tsPath = m_doc.tilesheet.getPath().or_value("");
m_selectedTilesheetIdx = std::find(tilesheetList.begin(), tilesheetList.end(), tsPath).offset();
loadObj();
undoStack()->changeTriggered.connect(this, &WorldObjectSetEditorImGui::handleCmd);
buildPaletteDisplayNameList();
}
void WorldObjectSetEditorImGui::draw(turbine::Context&) noexcept {
const auto paneSize = ImGui::GetContentRegionAvail();
constexpr auto resourcesWidth = 300.f;
ImGui::BeginChild("ObjEditor", ImVec2(paneSize.x - resourcesWidth, 0));
if (m_selectedObj < m_doc.objects.size()) {
drawObjEditor();
}
ImGui::EndChild();
ImGui::SameLine();
ImGui::BeginChild("Resources", ImVec2(0, 0));
{
{
ig::IDStackItem const idStackItem("TileSheetSelector");
drawTileSheetSelector();
}
ImGui::Separator();
{
ig::IDStackItem const idStackItem("ObjSelector");
drawObjSelector();
}
ImGui::Separator();
{
ig::IDStackItem const idStackItem("PaletteList");
drawPaletteList();
}
}
ImGui::EndChild();
drawAddPalettePopup();
drawEditPalettePopup();
}
void WorldObjectSetEditorImGui::onActivated() noexcept {
}
WorldObject const&WorldObjectSetEditorImGui::activeObj() const noexcept {
return m_doc.objects[m_selectedObj];
}
WorldObject &WorldObjectSetEditorImGui::activeObj() noexcept {
return m_doc.objects[m_selectedObj];
}
void WorldObjectSetEditorImGui::buildPaletteDisplayNameList() noexcept {
m_paletteDisplayNames.clear();
for (auto i = 1u; auto const&pal : m_doc.palettes) {
auto path = pal.palette.getPath().or_value("n/a");
m_paletteDisplayNames.emplace_back(ox::sfmt("{}: {} - {} ms", i, path, pal.intervalMs));
++i;
}
}
void WorldObjectSetEditorImGui::drawTileSheetSelector() noexcept {
auto const&tilesheetList = m_sctx.project->fileList(ncore::FileExt_ng);
auto sel = m_selectedTilesheetIdx;
if (ig::ComboBox("Tile Sheet", tilesheetList, sel)) {
undoStack()->push(ox::make_unique<ChangeTileSheet>
(m_doc, ox::FileAddress(tilesheetList[sel]), sel, m_selectedTilesheetIdx));
loadObj();
}
}
void WorldObjectSetEditorImGui::drawObjSelector() noexcept {
ig::IDStackItem const idStackItem1("ObjSelector");
if (ig::PushButton("+", btnSize)) {
auto cmd = ox::make_unique<AddObject>(m_doc);
m_selectedObj = cmd->insertIdx();
undoStack()->push(std::move(cmd));
loadObj();
}
ImGui::SameLine();
if (ig::PushButton("-", btnSize)) {
undoStack()->push(ox::make_unique<RmObject>(m_doc, m_selectedObj));
}
if (ImGui::BeginListBox("Objects")) {
for (auto i = 0u; auto const&obj : m_doc.objects) {
ig::IDStackItem const idStackItem2(static_cast<int>(i));
if (ImGui::Selectable(obj.name.c_str(), m_selectedObj == i)) {
if (i != m_selectedObj) {
m_selectedObj = i;
loadObj();
}
}
++i;
}
ImGui::EndListBox();
}
}
void WorldObjectSetEditorImGui::loadObj() noexcept {
if (m_selectedObj >= m_doc.objects.size()) {
return;
}
auto const&obj = activeObj();
auto &nameBuff = m_objEditor.nameBuff;
ox_strncpy(nameBuff.data(), obj.name.data(), nameBuff.size());
m_objEditor.palette = obj.palBank;
m_subsheet = getSubsheet(*m_tileSheet, obj.subsheetId);
int w = 0;
int h = 0;
if (m_subsheet) {
w = m_subsheet->columns;
h = m_subsheet->rows;
}
auto const idx = getTileIdx(*m_tileSheet, obj.subsheetId);
oxLogError(m_colView.setup(
m_doc.tilesheet,
m_doc.palettes[obj.palBank].palette,
w,
h,
static_cast<uint_t>(idx),
obj.collisionMap));
}
[[nodiscard]]
static ox::Vec2 clickPos(ImVec2 const&imgSz, ImVec2 const&offset, ox::Vec2 clickPos) noexcept {
clickPos.x -= offset.x;
clickPos.y -= offset.y;
clickPos.x /= imgSz.x;
clickPos.y /= imgSz.y;
return clickPos;
}
void WorldObjectSetEditorImGui::drawObjEditor() noexcept {
ig::IDStackItem const idStackItem("ObjEditor");
ig::IndentStackItem const indent1(10);
ImGui::NewLine();
auto &nameBuff = m_objEditor.nameBuff;
if (ImGui::InputText("Name", nameBuff.data(), nameBuff.size())) {
undoStack()->push(ox::make_unique<EditObjectName>(
m_doc,
m_selectedObj,
ox::String(nameBuff.data(), ox_strnlen(nameBuff.data(), nameBuff.size()))));
}
// SubSheet Selector
{
ig::IDStackItem const subsheetSelectorStackItem("SubsheetSelector");
if (ig::ComboBox("Palette", m_paletteDisplayNames, m_objEditor.palette)) {
undoStack()->push(ox::make_unique<EditObjectPalette>(
m_doc, m_selectedObj, static_cast<uint16_t>(m_objEditor.palette)));
}
ImGui::Separator();
ImGui::Text("Subsheet:");
ImGui::BeginChild("SubsheetSelector", ImVec2{300, 300});
{
ImGui::SetNextItemOpen(true);
if (m_tileSheet) {
drawSubSheetNode(m_tileSheet->subsheet);
}
}
ImGui::EndChild();
}
// collision map
if (m_subsheet) {
ImGui::NewLine();
ImGui::Text("Collision Map:");
ig::IndentStackItem const indent2(30);
m_colView.draw();
auto const&fb = m_colView.framebuffer();
uintptr_t const buffId = fb.color.id;
auto const scale = static_cast<float>(m_colView.scale());
auto const width = static_cast<float>(m_subsheet->columns * ncore::TileWidth);
auto const height = static_cast<float>(m_subsheet->rows * ncore::TileHeight);
auto const horzPct = width / 240.f;
auto const vertPct = height / 160.f;
auto const imageSz = ImVec2(width * scale, height * scale);
auto const imagePos = ImGui::GetCursorScreenPos();
ImGui::Image(
std::bit_cast<void*>(buffId),
imageSz,
ImVec2(0, 1.0),
ImVec2(horzPct, 1 - vertPct));
auto const&io = ImGui::GetIO();
if (ImGui::IsItemClicked()) {
auto const mousePos = clickPos(imageSz, imagePos, ox::Vec2(io.MousePos));
auto &obj = activeObj();
auto intermediate = obj.collisionMap;
if (m_colView.click(mousePos, intermediate)) {
undoStack()->push(ox::make_unique<EditObjectCollisionMap>(m_doc, m_selectedObj, intermediate));
}
}
}
}
void WorldObjectSetEditorImGui::drawSubSheetNode(ncore::TileSheet::SubSheet const&ss) noexcept {
auto constexpr dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
auto &obj = activeObj();
auto const selected = ss.id == obj.subsheetId ? ImGuiTreeNodeFlags_Selected : 0;
ig::IDStackItem const idStackItem(ss.name);
if (!ss.subsheets.empty()) {
if (ImGui::TreeNodeEx(ss.name.c_str(), dirFlags)) {
for (auto const&child : ss.subsheets) {
drawSubSheetNode(child);
}
ImGui::TreePop();
}
} else if (ImGui::TreeNodeEx(ss.name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
if (ImGui::IsItemClicked()) {
undoStack()->push(ox::make_unique<EditObjectSubSheet>(m_doc, m_selectedObj, ss.id));
}
ImGui::TreePop();
}
}
void WorldObjectSetEditorImGui::drawPaletteList() noexcept {
ig::IDStackItem const idStackItem("PaletteList");
if (ig::PushButton("+", btnSize)) {
addPalette();
}
ImGui::SameLine();
if (ig::PushButton("-", btnSize)) {
rmPalette();
}
ImGui::SameLine();
if (ig::PushButton("Edit")) {
editPalette();
}
constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
if (ImGui::BeginTable("Subsheets", 2, flags)) {
ImGui::TableSetupColumn("Palette", ImGuiTableColumnFlags_NoHide);
ImGui::TableSetupColumn("Interval", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableHeadersRow();
drawPaletteListItems();
ImGui::EndTable();
}
}
void WorldObjectSetEditorImGui::drawPaletteListItems() noexcept {
for (auto i = 0u; auto const&pal : m_doc.palettes) {
ig::IDStackItem const idStackItem(static_cast<int>(i));
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%s", pal.palette.getPath().value.c_str());
ImGui::TableNextColumn();
ImGui::Text("%d ms", pal.intervalMs);
ImGui::SameLine();
if (ImGui::Selectable("##PaletteSelection", i == m_selectedPal, ImGuiSelectableFlags_SpanAllColumns)) {
m_selectedPal = i;
}
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
editPalette();
}
++i;
}
}
void WorldObjectSetEditorImGui::drawAddPalettePopup() noexcept {
if (!m_addPalPopup.show) {
return;
}
auto constexpr popupName = ox::CStringView("Add Palette");
auto constexpr popupSz = ImVec2{285.f, 0};
ig::IDStackItem const idStackItem("AddPalette");
if (ig::BeginPopup(m_sctx.tctx, popupName, m_addPalPopup.show, popupSz)) {
auto const&palettes = m_sctx.project->fileList(ncore::FileExt_npal);
ig::ComboBox("Palette", palettes, m_addPalPopup.selectedIdx);
if (ig::PopupControlsOkCancel(popupSz.x, m_addPalPopup.show) == ig::PopupResponse::OK) {
undoStack()->push(ox::make_unique<AddPalette>(
m_doc,
ox::FileAddress(palettes[m_addPalPopup.selectedIdx])));
}
ImGui::EndPopup();
}
}
void WorldObjectSetEditorImGui::drawEditPalettePopup() noexcept {
if (!m_editPalPopup.show) {
return;
}
auto constexpr popupName = ox::CStringView("Edit Palette");
auto constexpr popupSz = ImVec2{300.f, 0};
ig::IDStackItem const idStackItem("EditPalette");
if (ig::BeginPopup(m_sctx.tctx, popupName, m_editPalPopup.show, popupSz)) {
auto &pal = m_doc.palettes[m_selectedPal];
ImGui::InputInt("Interval (ms)", &m_editPalPopup.intervalInputVal, 1000);
m_editPalPopup.intervalInputVal =
static_cast<uint16_t>(ox::clamp(m_editPalPopup.intervalInputVal, 0, 60000));
if (ig::PopupControlsOkCancel(popupSz.x, m_editPalPopup.show) == ig::PopupResponse::OK) {
undoStack()->push(ox::make_unique<EditPalette>(
pal, static_cast<uint16_t>(m_editPalPopup.intervalInputVal)));
}
ImGui::EndPopup();
}
}
void WorldObjectSetEditorImGui::addPalette() noexcept {
m_addPalPopup.show = true;
m_addPalPopup.selectedIdx = 0;
}
void WorldObjectSetEditorImGui::rmPalette() noexcept {
undoStack()->push(ox::make_unique<RmPalette>(
m_doc, m_selectedPal));
}
void WorldObjectSetEditorImGui::editPalette() noexcept {
auto &pal = m_doc.palettes[m_selectedPal];
m_editPalPopup.show = true;
m_editPalPopup.intervalInputVal = pal.intervalMs;
}
ox::Error WorldObjectSetEditorImGui::handleCmd(studio::UndoCommand const*cmd) noexcept {
if (dynamic_cast<ChangeTileSheet const*>(cmd)) {
oxLogError(readObj<ncore::TileSheet>(keelCtx(m_sctx.tctx), m_doc.tilesheet).moveTo(m_tileSheet));
}
if (dynamic_cast<AddPalette const*>(cmd) ||
dynamic_cast<RmPalette const*>(cmd) ||
dynamic_cast<EditPalette const*>(cmd)) {
buildPaletteDisplayNameList();
}
if (dynamic_cast<EditObjectPalette const*>(cmd)) {
auto const&obj = activeObj();
m_objEditor.palette = obj.palBank;
}
loadObj();
return {};
}
ox::Error WorldObjectSetEditorImGui::saveItem() noexcept {
return m_sctx.project->writeObj(m_itemPath, m_doc, ox::ClawFormat::Organic);
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2023 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <turbine/context.hpp>
#include <studio/studio.hpp>
#include <jasper/world/worldobject.hpp>
#include "collisionmapview.hpp"
namespace jasper::world {
class WorldObjectSetEditorImGui: public studio::Editor {
private:
studio::StudioContext &m_sctx;
ox::String m_itemPath;
WorldObjectSet m_doc;
keel::AssetRef<ncore::TileSheet> m_tileSheet;
ncore::TileSheet::SubSheet const*m_subsheet = nullptr;
ox::Vector<ox::String> m_paletteDisplayNames;
size_t m_selectedObj{};
size_t m_selectedPal{};
size_t m_selectedTilesheetIdx{};
struct {
bool show{};
size_t selectedIdx{};
} m_addPalPopup;
struct {
bool show{};
int intervalInputVal{};
} m_editPalPopup;
CollisionView m_colView{m_sctx};
struct {
ox::Buffer nameBuff = ox::Buffer(256);
size_t palette{};
} m_objEditor;
public:
WorldObjectSetEditorImGui(studio::StudioContext &ctx, ox::StringView path);
void draw(turbine::Context&) noexcept final;
void onActivated() noexcept override;
private:
WorldObject const&activeObj() const noexcept;
WorldObject &activeObj() noexcept;
void buildPaletteDisplayNameList() noexcept;
void drawTileSheetSelector() noexcept;
void drawObjSelector() noexcept;
void loadObj() noexcept;
void drawObjEditor() noexcept;
void drawSubSheetNode(ncore::TileSheet::SubSheet const&ss) noexcept;
void drawPaletteList() noexcept;
void drawPaletteListItems() noexcept;
void drawAddPalettePopup() noexcept;
void drawEditPalettePopup() noexcept;
void addPalette() noexcept;
void rmPalette() noexcept;
void editPalette() noexcept;
ox::Error handleCmd(studio::UndoCommand const*cmd) noexcept;
ox::Error saveItem() noexcept final;
};
}

View File

@ -42,9 +42,9 @@ void World::setupLayer(
const auto width = m_worldStatic.rows[layerNo];
for (auto const&tile : layer) {
const auto tile8 = static_cast<uint8_t>(tile);
ncore::setBgTile(ctx, layerNo, x, y, tile8);
ncore::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1);
ncore::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2);
ncore::setBgTile(ctx, layerNo, x + 0, y + 0, tile8 + 0);
ncore::setBgTile(ctx, layerNo, x + 1, y + 0, tile8 + 1);
ncore::setBgTile(ctx, layerNo, x + 0, y + 1, tile8 + 2);
ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
x += 2;
if (x >= width * 2) {