[jasper/world] Add WorldObjectEditor
All checks were successful
Build / build (push) Successful in 2m44s
All checks were successful
Build / build (push) Successful in 2m44s
This commit is contained in:
parent
ec099aa386
commit
aa1f063a6d
@ -15,7 +15,7 @@
|
|||||||
namespace jasper::world {
|
namespace jasper::world {
|
||||||
|
|
||||||
struct PrefabDoc {
|
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 TypeVersion = 1;
|
||||||
constexpr static auto Preloadable = true;
|
constexpr static auto Preloadable = true;
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "consts.hpp"
|
#include "consts.hpp"
|
||||||
#include "prefab.hpp"
|
#include "prefab.hpp"
|
||||||
#include "worldobjectset.hpp"
|
#include "worldobject.hpp"
|
||||||
#include "worldstatic.hpp"
|
#include "worldstatic.hpp"
|
||||||
|
|
||||||
namespace jasper::world {
|
namespace jasper::world {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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()
|
|
||||||
|
|
||||||
}
|
|
@ -18,7 +18,7 @@ namespace ncore = nostalgia::core;
|
|||||||
|
|
||||||
struct SpriteDoc {
|
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 TypeVersion = 1;
|
||||||
constexpr static auto Preloadable = true;
|
constexpr static auto Preloadable = true;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ struct SpriteDoc {
|
|||||||
|
|
||||||
struct TileDoc {
|
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 TypeVersion = 1;
|
||||||
constexpr static auto Preloadable = true;
|
constexpr static auto Preloadable = true;
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ struct WorldDoc {
|
|||||||
using TileMapLayer = ox::Vector<TileMapRow>;
|
using TileMapLayer = ox::Vector<TileMapRow>;
|
||||||
using TileMap = ox::Vector<TileMapLayer>;
|
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 TypeVersion = 1;
|
||||||
constexpr static auto Preloadable = true;
|
constexpr static auto Preloadable = true;
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
|
|||||||
|
|
||||||
struct WorldStatic {
|
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 TypeVersion = 1;
|
||||||
constexpr static auto Preloadable = true;
|
constexpr static auto Preloadable = true;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <keel/module.hpp>
|
#include <keel/module.hpp>
|
||||||
|
|
||||||
|
#include <jasper/world/worldobject.hpp>
|
||||||
#include <jasper/world/worldstatic.hpp>
|
#include <jasper/world/worldstatic.hpp>
|
||||||
|
|
||||||
#include "typeconv.hpp"
|
#include "typeconv.hpp"
|
||||||
@ -23,13 +24,15 @@ class WorldModule: public keel::Module {
|
|||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
|
ox::Vector<keel::TypeDescGenerator> types() const noexcept override {
|
||||||
return {
|
return {
|
||||||
|
keel::generateTypeDesc<PaletteCycle>,
|
||||||
|
keel::generateTypeDesc<WorldObjectSet>,
|
||||||
keel::generateTypeDesc<WorldDoc>,
|
keel::generateTypeDesc<WorldDoc>,
|
||||||
keel::generateTypeDesc<WorldStatic>,
|
keel::generateTypeDesc<WorldStatic>,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
ox::Vector<const keel::BaseConverter*> converters() const noexcept override {
|
ox::Vector<keel::BaseConverter const*> converters() const noexcept override {
|
||||||
return {
|
return {
|
||||||
&m_worldDocToWorldStaticConverter,
|
&m_worldDocToWorldStaticConverter,
|
||||||
};
|
};
|
||||||
|
@ -16,5 +16,5 @@ install(
|
|||||||
${NOSTALGIA_DIST_MODULE}
|
${NOSTALGIA_DIST_MODULE}
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(worldobjecteditor)
|
add_subdirectory(worldobjectseteditor)
|
||||||
add_subdirectory(worldeditor)
|
add_subdirectory(worldeditor)
|
||||||
|
@ -6,22 +6,22 @@
|
|||||||
|
|
||||||
#include <jasper/world/consts.hpp>
|
#include <jasper/world/consts.hpp>
|
||||||
|
|
||||||
#include "worldobjecteditor/worldobjecteditor-imgui.hpp"
|
#include "worldobjectseteditor/worldobjectseteditor-imgui.hpp"
|
||||||
#include "worldeditor/worldeditor-imgui.hpp"
|
#include "worldeditor/worldeditor-imgui.hpp"
|
||||||
|
|
||||||
namespace jasper::world {
|
namespace jasper::world {
|
||||||
|
|
||||||
class StudioModule: public studio::Module {
|
class StudioModule: public studio::Module {
|
||||||
public:
|
public:
|
||||||
ox::Vector<studio::EditorMaker> editors(turbine::Context &ctx) const noexcept override {
|
ox::Vector<studio::EditorMaker> editors(studio::StudioContext &ctx) const noexcept override {
|
||||||
return {
|
return {
|
||||||
studio::editorMaker<WorldObjectEditorImGui>(ctx, FileExt_jwob),
|
studio::editorMaker<WorldObjectSetEditorImGui>(ctx, FileExt_jwob),
|
||||||
studio::editorMaker<WorldEditorImGui>(ctx, FileExt_jwld),
|
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;
|
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));
|
out.emplace_back(ox::make<studio::ItemMakerT<WorldDoc>>("World", "Worlds", FileExt_jwld, ox::ClawFormat::Organic));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
|
|
||||||
namespace jasper::world {
|
namespace jasper::world {
|
||||||
|
|
||||||
WorldEditorImGui::WorldEditorImGui(turbine::Context &ctx, ox::StringView path):
|
WorldEditorImGui::WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path):
|
||||||
Editor(path),
|
Editor(path),
|
||||||
m_ctx(ctx),
|
m_ctx(ctx.tctx),
|
||||||
m_editor(m_ctx, path),
|
m_editor(m_ctx, path),
|
||||||
m_view(m_ctx, m_editor.world()) {
|
m_view(m_ctx, m_editor.world()) {
|
||||||
setRequiresConstantRefresh(false);
|
setRequiresConstantRefresh(false);
|
||||||
|
@ -21,7 +21,7 @@ class WorldEditorImGui: public studio::Editor {
|
|||||||
WorldEditorView m_view;
|
WorldEditorView m_view;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WorldEditorImGui(turbine::Context &ctx, ox::StringView path);
|
WorldEditorImGui(studio::StudioContext &ctx, ox::StringView path);
|
||||||
|
|
||||||
void draw(turbine::Context&) noexcept final;
|
void draw(turbine::Context&) noexcept final;
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
target_sources(
|
|
||||||
JasperWorld-Studio PRIVATE
|
|
||||||
worldobjecteditor.cpp
|
|
||||||
worldobjecteditor-imgui.cpp
|
|
||||||
)
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
||||||
|
)
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -42,9 +42,9 @@ void World::setupLayer(
|
|||||||
const auto width = m_worldStatic.rows[layerNo];
|
const auto width = m_worldStatic.rows[layerNo];
|
||||||
for (auto const&tile : layer) {
|
for (auto const&tile : layer) {
|
||||||
const auto tile8 = static_cast<uint8_t>(tile);
|
const auto tile8 = static_cast<uint8_t>(tile);
|
||||||
ncore::setBgTile(ctx, layerNo, x, y, tile8);
|
ncore::setBgTile(ctx, layerNo, x + 0, y + 0, tile8 + 0);
|
||||||
ncore::setBgTile(ctx, layerNo, x + 1, y, tile8 + 1);
|
ncore::setBgTile(ctx, layerNo, x + 1, y + 0, tile8 + 1);
|
||||||
ncore::setBgTile(ctx, layerNo, x, y + 1, tile8 + 2);
|
ncore::setBgTile(ctx, layerNo, x + 0, y + 1, tile8 + 2);
|
||||||
ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
|
ncore::setBgTile(ctx, layerNo, x + 1, y + 1, tile8 + 3);
|
||||||
x += 2;
|
x += 2;
|
||||||
if (x >= width * 2) {
|
if (x >= width * 2) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user