Refactoring **everything**

This commit is contained in:
2025-05-29 02:56:51 -05:00
parent 391dcd32ad
commit 74b1d0d1fc
26 changed files with 629 additions and 331 deletions

8
include/App/Editor.hpp Normal file
View File

@@ -0,0 +1,8 @@
//
// Created by josh on 5/29/2025.
//
#ifndef EDITOR_HPP
#define EDITOR_HPP
#endif //EDITOR_HPP

View File

@@ -13,7 +13,11 @@
#include "NewMapDialog.hpp"
#include "JUI/Widgets/ColorPicker.hpp"
#include "Preferences.hpp"
#include <Level.hpp>
#include <Data/Level.hpp>
#include <Data/Tileset.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <App/LayerView.hpp>
#include <App/TilesetView.hpp>
#define GL_VER_MAJOR 2
#define GL_VER_MINOR 1
@@ -21,7 +25,10 @@
using namespace ReWindow;
using namespace JUI::UDimLiterals;
#include <EditorCamera.hpp>
#include <App/EditorCamera.hpp>
// TODO: Support loading an entity_prefab.json file which defines template entities to copy.
// TODO: Support sprites for these?
class Action {
public:
@@ -61,85 +68,55 @@ class EditHistoryLogWindow : public JUI::Window {
public:
};
class LayerView : public JUI::Window
{
public:
LayerView() : JUI::Window()
{
this->SetTitle("Layers");
Name("LayerView");
}
explicit LayerView(Widget* parent) : LayerView()
{
Parent(parent);
}
};
class EditorApp : public OpenGLWindow {
public:
#pragma region GUI Elements
JUI::Scene* scene = nullptr;
JUI::CommandLine* console = nullptr;
JUI::Window* tileset_viewer = nullptr;
JUI::Rect* cell_indicator = nullptr;
NewMapDialog* nmd = nullptr;
LayerView* layer_view = nullptr;
JUI::Window* bg_color_tool_window = nullptr;
JUI::ColorPicker* bg_color_tool = nullptr;
#pragma endregion
#pragma region Map Data
Level* loaded_level;
Tileset* loaded_tileset;
JGL::Texture* loaded_tilesheet;
#pragma endregion
EditorCamera camera;
JUI::Scene* scene;
JUI::CommandLine* console;
JUI::Window* tileset_viewer;
JUI::Rect* cell_indicator;
JGL::Texture* test_tilesheet;
LayerView* layer_view;
NewMapDialog* nmd = nullptr;
Level loaded_level;
Preferences preferences;
JUI::Window* bg_color_tool_window = nullptr;
JUI::ColorPicker* bg_color_tool = nullptr;
int focus_layer_index = 0;
Layer* GetLayer(int index)
{
return loaded_level->layers[index];
}
Layer* GetFocusLayer()
{
return loaded_level->layers[focus_layer_index];
}
bool grid_overlay_enabled = true;
Color4 grid_overlay_color = {255, 255, 255, 64};
Color4 bg_color = Colors::Black;
bool show_cell_pointer_outline = true;
Color4 cell_pointer_outline_color = Colors::Blues::LightSteelBlue;
float cell_pointer_outline_width = 1.f;
int grid_pixel_width = 16;
int grid_pixel_height = 16;
int tileset_width = 420;
int tileset_height = 420;
int grid_rows = 128;
int grid_cols = 128;
int** grid;
void InitGrid()
{
grid = new int*[grid_rows];
for (int i = 0; i < grid_rows; i++)
grid[i] = new int[grid_cols];
for (int x = 0; x < grid_rows; x++) {
for (int y = 0; y < grid_cols; y++) {
grid[x][y] = -1;
}
}
}
void DeleteGrid()
{
for (int i = 0; i < grid_rows; i++)
delete[] grid[i];
delete[] grid;
}
int selected_quad = 0;
bool data_ready = false;
struct Quad {
int x;
int y;
@@ -147,7 +124,7 @@ public:
int h;
};
std::vector<Quad> quads;
//std::vector<Quad> quads;
/// Fill the tile-id lookup table with their respective partial-sprite bounding boxes.
void PopulateQuads();
@@ -165,7 +142,7 @@ public:
EditorApp();
~EditorApp() override
{
DeleteGrid();
//DeleteGrid();
}
void LoadPreferences();
@@ -185,6 +162,7 @@ public:
/// @note Placeholder until FileDialogs are added.
/// @note Placeholder until Tileset json is added.
void LoadMisc();
void SizeCellIndicatorToTilesetSize();
/// Creates a JUI widget that displays the Tileset, and lets you select a tile from it.
JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent);
@@ -229,6 +207,47 @@ public:
void DrawTiles();
void DrawLayer(const Layer* layer) const
{
for (int gx = 0; gx < layer->rows; gx++) {
for (int gy = 0; gy < layer->cols; gy++) {
auto quad_idx = layer->cells[gx][gy];
Vector2 pos(gx*layer->cell_width, gy*layer->cell_height);
//if ((gx+gy) % 2 == 0)
//J2D::FillRect(Colors::Purples::Fuchsia, pos, {16, 16});
if (quad_idx < 0)
continue;
auto quad = loaded_tileset->quads[quad_idx];
J2D::DrawPartialSprite(loaded_tilesheet, pos,
Vector2(quad.x, quad.y), Vector2(quad.w, quad.h));
}
}
}
void DrawLevel(const Level* level)
{
// TODO: Draw Per-Level Background Texture.
// TODO: Draw Per-Layer Background Texture.
for (const auto* layer : level->layers)
{
DrawLayer(layer);
}
// TODO: Support a handful of presets to represent entity shape types.
for (auto entity : level->entities)
{
J2D::DrawPoint(entity->overlay_color, entity->x, entity->y, 1);
J2D::DrawString(Colors::White, std::format("{} - {}", entity->type, entity->name), entity->x, entity->y, 1.f, 12);
}
}
void Draw();
// TODO: Closing the app nominally doesn't work on Linux.
@@ -259,7 +278,7 @@ public:
if (btn == JUI::MouseButton::Middle)
{
Vector2i cell = GetGridCellFromMouse();
selected_quad = grid[cell.x][cell.y];
selected_quad = loaded_level->layers[0]->cells[cell.x][cell.y];
}
}
@@ -269,7 +288,7 @@ public:
if (tileset_viewer->Content()->IsMouseInside()) {
Vector2i cell = GetTilesetCellFromMouse();
int index = CellToIndex(cell, tileset_width);
int index = CellToIndex(cell, loaded_tileset->rows);
selected_quad = index;
}

39
include/App/LayerView.hpp Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/Button.hpp>
#include <JUI/Widgets/ScrollingRect.hpp>
#include <JUI/Widgets/ListLayout.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <Data/Level.hpp>
class LayerView : public JUI::Window
{
public:
LayerView() : JUI::Window()
{
this->SetTitle("Layers");
Name("LayerView");
scroller = new JUI::ScrollingRect(this->Content());
layout = new JUI::VerticalListLayout(scroller);
}
explicit LayerView(Widget* parent) : LayerView()
{
Parent(parent);
}
void UpdateComponents(Level* level)
{
for (auto layer : level->layers)
{
//auto* rect = new JUI::Rect(layout);
//auto* vis_chk = new JUI::Checkbox(rect);
}
}
JUI::ScrollingRect* scroller = nullptr;
JUI::VerticalListLayout* layout = nullptr;
};

View File

@@ -0,0 +1,2 @@
#pragma once

61
include/Data/Entity.hpp Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include <string>
#include <JJX/json.hpp>
#include <Color4.hpp>
using namespace JJX;
enum class EntityRepresentativeShape {
Point, AABB, Sphere, OBB
};
/// @class Entity
/// @brief Represents an object or interactive element placed on the map, not necessarily tied to the tile grid.
/// This can include plater spawn points, enemies, pickups, triggers, boxes (my favorite), or any other
/// game specific object that needs to be placed and configured in the level.
class Entity
{
public:
#pragma region json properties
std::string name;
std::string type;
float x;
float y;
float width;
float height;
float rotation;
float flip_h;
float flip_v;
int z_index;
Color4 overlay_color;
json::value metadata;
#pragma endregion
Entity();
Entity(const json::value& json)
{
Deserialize(json);
}
json::value Serialize() const
{
}
void Deserialize(const json::value& json)
{
name = json["name"].as_string();
type = json["type"].as_string();
x = json["x"].number.value();
y = json["y"].number.value();
width = json["width"].number.value_or(0);
height = json["height"].number.value_or(0);
}
};

7
include/Data/Etc.hpp Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
namespace LevelFormat
{
};

5
include/Data/Format.hpp Normal file
View File

@@ -0,0 +1,5 @@
#include <Data/Entity.hpp>
#include <Data/Layer.hpp>
#include <Data/Tileset.hpp>
#include <Data/Level.hpp>
#include <Data/Tile.hpp>

View File

@@ -1,7 +1,7 @@
#pragma once
#include <string>
#include <vector>
#include <Layer.hpp>
#include <Data/Layer.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
@@ -41,7 +41,10 @@ public:
int cell_width;
/// The height of the individual cells of this layer, measured in pixels.
int cell_height;
/// The horizontal parallax scrolling factor for this layer.
float parallax_x;
/// The vertical parallax scrolling factor for this layer.
float parallax_y;
bool visible;
@@ -51,7 +54,7 @@ public:
#pragma endregion
#pragma region binary data storage
int** cells;
int** cells = nullptr;
#pragma endregion
@@ -69,10 +72,7 @@ public:
/// Destructor for Layer cleans up all dynamically allocated memory for the tile grid.
~Layer()
{
DeleteGrid();
}
~Layer();
/// Constructs a layer object from the JSON contained in the file at the given path.
@@ -104,58 +104,26 @@ public:
void LoadFromDataBuffer(const int* buffer);
void WriteToDataBuffer(int* buffer)
{
//int* buffer = new int[rows*cols];
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < cols; y++)
{
int index = (x*cols + y);
buffer[index] = cells[x][y];
}
}
}
void WriteToDataBuffer(int* buffer) const;
/// Writes the current tile grid data to a specified raw binary file.
/// The file path should typically be stored in the layer's JSON metadata.
/// @param path
/// @return True if the binary data was written successfully, false otherwise.
/// @see Save().
bool WriteBinaryData(const std::filesystem::path& path) const;
bool Save();
bool Load();
/// Reads tile grid data from a specified raw binary file.
/// The file path should typically be stored in the layer's JSON metadata.
/// @param path
/// @return True if the binary data was loaded successfully, false otherwise.
bool WriteBinaryData(const std::filesystem::path& path)
{
int* buffer = new int[rows*cols];
WriteToDataBuffer(buffer);
std::ofstream output;
output.open(path, std::ios::out | std::ios::binary);
output.write(reinterpret_cast<const char*>(buffer), rows * cols * sizeof(int));
output.close();
/// @see Load().
bool ReadBinaryData(const std::filesystem::path& path);
delete[] buffer;
return true;
}
bool Save() { return WriteBinaryData(binary_path); }
bool Load() { return ReadBinaryData(binary_path); }
bool ReadBinaryData(const std::filesystem::path& path)
{
std::ifstream input;
input.open(path, std::ios::binary | std::ios::in);
input.seekg(0, std::ios::end);
int data_length = input.tellg();
input.seekg(0, std::ios::beg);
char* buffer = new char[data_length];
input.read(buffer, data_length);
input.close();
InitGrid();
LoadFromDataBuffer(reinterpret_cast<int*>(buffer));
delete[] buffer;
return true;
}
protected:
bool grid_allocated = false;
};

127
include/Data/Level.hpp Normal file
View File

@@ -0,0 +1,127 @@
#pragma once
#include <string>
#include <vector>
#include <Data/Layer.hpp>
#include <Data/Entity.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
#include "Tileset.hpp"
using namespace JJX;
const float REDACTED_EDITOR_LIB_VERSION = 1;
/// @class Level
/// @brief Represents the complete game level, containing its metadata, tile layers, and entites.
///
/// This class serves as the primary data structure for loading, managing, and saving
/// all components that constitute the playable game map. It centralizes information
/// about the level's dimensions, graphical assets (tilesets), stuctural layers,
/// and interactive objects (entities).
class Level {
public:
/// The human-readable name of the level.
/// This is typically displayed in level selection menus or editors.
std::string name;
/// A brief description of the level's content or purpose.
/// Useful for level browsers or internal documentation.
std::string description;
/// The name or identifier of the author who created or last modified the level.
std::string author;
/// A collection of keywords or categories associated with the level.
/// Can be used for filtering, searching, or thematic grouping (e.g., "forest", "puzzle").
std::vector<std::string> tags;
/// The height of the level grid in number of tiles (rows).
// TODO: I mixed up rows and columns. I'm just going to leave it as the convention this system follows for now. -josh
int rows;
/// The width of the level grid in number of tiles (columns).
int cols;
/// A collection of pointers to the tile layers that compose the level's tile grid.
/// Each layer typically represents a distinct plane of tiles (e.g., background, foreground, collisions).
/// @warning The `Level` class is responsible for the ownership and lifecycle of these `Layer` objects
/// if they are allocated dynamically. Consider using smart pointers like `std::unique_ptr<Layer>
/// for automatic memory management,
std::vector<Layer*> layers;
/// A collection of pointers to the entities placed within the level.
/// Entities represent interactive objects, characters, spawn points, or other dynamic elements.
/// @warning Similar to `layers`, the `Level` class is responsible for the ownership and lifecycle
/// of these `Entity` objects if they are dynamically allocated. Consider `std::unique_ptr<Entity>`.
std::vector<Entity*> entities;
/// The filesystem path to the primary tileset image or definition file used by this level.
/// This path is typically relative to the level's main JSON file.
std::string tileset_path;
/// A generic JSON value for storing arbitrary, unstructured level metdata.
/// This can include game-specific settings, editor-specific flags, or any data
/// not explicitly covered by other `Level` members.
json::value metadata;
#pragma region Member Functions
/// The default constructor initializes all member variables to their default or empty states.
/// The level will have zero dimensions, no layers, no entities, and empty metadata.
Level();
/// Destructor for the Level class.
/// Responsible for deallocating all dynamically allocated `Layer` and `Entity` objects
/// pointed to by the `layers` and `entities` vectors to prevent memory leaks.
~Level();
/// Constructs a Level object by loading and parsing a level definition file from the given path.
/// This constructor typically reads a JSON file containing the level's metadata, and
/// may also initiate the loading of associated binary tile data or other assets. NOTE: Currently, we do not.
/// @param path
/// @throws std::runtime_error If the file cannot be opened, is malformed, or if associated data (like binary layers) is missing / invalid.
explicit Level(const std::filesystem::path& path);
/// Constructs a Level object by deserializing its state from a pre-existing JSON value.
/// This constructor assumes the JSON value is already loaded in memory and correct.
/// It will parse the JSON to populate the level's members, including layers and entities.
/// @param json
/// @throws json::exception If the JSON structure is malformed.
/// @see Deserialize() for the underlying parsing logic.
explicit Level(const json::value& json);
/// Deserializes the Level object's state from a provided JSON value.
/// This method takes a JSON object (typically parsed from a file) and populates
/// the current `Level` instance's member variables, including its layers and entities.
/// This is the core parsing logic for loading level data into memory.
/// @param json
/// @throws json::exception If the JSON structure is malformed.
void Deserialize(const json::value& json);
/// Serializes the current Level object's state into a JSON value.
/// This method converts all the level's metadata, layer properties, and entity data
/// into a json object, ready for saving to a file or transmitting.
/// @return a `json` value representing the complete level data.
/// @see Level::Save() for writing the serialized JSON to a file.
json::value Serialize() const;
bool Load(const std::filesystem::path& path);
bool Save(const std::filesystem::path& path);
Layer* CreateLayer(const std::string& name);
Layer* GetLayer(int index);
const Layer* GetLayer(int index) const;
Layer* FindLayer(const std::string& name);
const Layer* FindLayer(const std::string& name) const;
bool RemoveLayer(const std::string& name);
bool RemoveLayer(int index);
bool RemoveLayer(Layer* layer);
bool RemoveAllLayers();
Entity* CreateEntity(const std::string& name);
Entity* GetEntity(const std::string& name);
const Entity* GetEntity(const std::string& name) const;
bool RemoveEntity(Entity* entity);
Tile GetTileData(int gid) const;
Quad GetTileQuad(int gid) const;
#pragma endregion
protected:
private:
};

3
include/Data/Tile.hpp Normal file
View File

@@ -0,0 +1,3 @@
#pragma once

View File

@@ -79,6 +79,22 @@ public:
#pragma endregion
public:
static Tileset Template()
{
Tileset tileset;
tileset.author = "J. O'Leary";
tileset.name = "Sample Tileset";
tileset.file_path = "tileset.json";
tileset.cols = 80;
tileset.rows = 64;
tileset.texture_path = "../megacommando.png";
tileset.tex_width = 1024;
tileset.tex_height = 1280;
return tileset;
}
/// Default constructor for Tileset .
/// Initializes member variables to default or zero values.
/// @see CreateDefault().

View File

@@ -1,11 +0,0 @@
#pragma once
class Editor {
public:
protected:
private:
};

View File

@@ -1,22 +0,0 @@
#pragma once
/// @class Entity
/// @brief Represents an object or interactive element placed on the map, not necessarily tied to the tile grid.
/// This can include plater spawn points, enemies, pickups, triggers, boxes (my favorite), or any other
/// game specific object that needs to be placed and configured in the level.
class Entity
{
public:
#pragma region json properties
std::string name;
std::string type;
float x;
float y;
#pragma endregion
Entity();
Entity(const json::value& json);
};

View File

@@ -1,85 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include <Layer.hpp>
#include <JJX/JSON.hpp>
#include <Colors.hpp>
#include <Utils.hpp>
using namespace JJX;
const float REDACTED_EDITOR_LIB_VERSION = 1;
class Level {
public:
std::string name;
std::string description;
std::string author;
std::vector<std::string> tags;
int rows;
int cols;
std::vector<Layer> layers;
std::string tileset_path;
json::value metadata;
Level() {}
explicit Level(const std::filesystem::path& path)
{
auto [json, err] = json::parse(read_file_contents(path));
Deserialize(json);
}
explicit Level(const json::value& json) {
Deserialize(json);
}
void Deserialize(const json::value& json)
{
name = json["name"].as_string();
description = json["description"].as_string();
author = json["author"].as_string();
tileset_path = json["tileset-path"].as_string();
auto layer_json_array = json["layers"].as_array();
for (auto& layer_json : layer_json_array)
{
layers.push_back(Layer(layer_json));
}
}
json::value Serialize() const
{
json::object data;
data["name"] = name;
data["description"] = description;
data["author"] = author;
data["tileset-path"] = tileset_path;
data["editor-version"] = REDACTED_EDITOR_LIB_VERSION;
data["map-type"] = "OrthogonalTileMap";
data["map-rows"] = (double)rows;
data["map-cols"] = (double)cols;
data["tile-width"] = 16.f;
data["tile-height"] = 16.f;
data["bgcolor"] = JsonConversions::deparse_color_to_hex(Colors::Black);
data["tags"] = JsonConversions::deparse_string_list(tags);
json::array layers;
for (auto& layer : this->layers)
{
layers.push_back(layer.Serialize());
}
data["layers"] = layers;
return data;
}
void Deserialize(const json::value& json) const {
}
protected:
private:
};