From 8daabe42590183c39887dbc49b31e19f177b0dc0 Mon Sep 17 00:00:00 2001 From: josh Date: Tue, 20 May 2025 03:26:29 -0500 Subject: [PATCH] Fleshing out code generally. --- CMakeLists.txt | 7 +- include/Editor.hpp | 2 +- include/EditorApp.hpp | 397 +++++++++---------------------------- include/EditorCamera.hpp | 72 +++++++ include/Level.hpp | 4 + include/Preferences.hpp | 29 +++ include/TileLayer.hpp | 6 +- include/Utils.hpp | 17 ++ src/EditorApp.cpp | 411 ++++++++++++++++++++++++++++++++++++++- src/EditorCamera.cpp | 28 +++ 10 files changed, 657 insertions(+), 316 deletions(-) create mode 100644 include/EditorCamera.hpp create mode 100644 include/Preferences.hpp create mode 100644 include/Utils.hpp create mode 100644 src/EditorCamera.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b01a219..7321e2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,12 @@ file(GLOB_RECURSE EDITOR_SRC "src/*.cpp") include_directories("include") +CPMAddPackage(NAME mcolor + URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-7.3.zip) CPMAddPackage(NAME jlog URL https://git.redacted.cc/josh/jlog/archive/Prerelease-18.zip) -CPMAddPackage(NAME mcolor - URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-7.2.zip) CPMAddPackage(NAME Event URL https://git.redacted.cc/josh/Event/archive/Release-12.zip) @@ -51,7 +51,8 @@ if (UNIX) endif() if (WIN32) - ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC}) + ADD_LIBRARY(Editor2D STATIC ${EDITOR_SRC} + include/Preferences.hpp) endif() set_target_properties(Editor2D PROPERTIES LINKER_LANGUAGE CXX) diff --git a/include/Editor.hpp b/include/Editor.hpp index 92bd7de..3490958 100644 --- a/include/Editor.hpp +++ b/include/Editor.hpp @@ -6,4 +6,4 @@ protected: private: -} \ No newline at end of file +}; \ No newline at end of file diff --git a/include/EditorApp.hpp b/include/EditorApp.hpp index c2c8719..2b2e07b 100644 --- a/include/EditorApp.hpp +++ b/include/EditorApp.hpp @@ -16,10 +16,44 @@ using namespace ReWindow; using namespace JUI::UDimLiterals; -struct EditorCamera { - Vector2 translation; - float rotation; - float scale; +#include + +class Action { +public: + std::string label; +}; + +class SetTileAction: public Action { +public: + int tile_id; + int prior_tile_id; + Vector2i cell; + + SetTileAction(const Vector2i& cell, int tileID, int priorTileID); +}; + +class BucketFillAction: public Action { +public: + int tile_id; + Vector2i cell; + std::vector effected_tiles; + + BucketFillAction(); +}; + +class UndoAction : public Action {}; +class RedoAction : public Action {}; + +class EditHistory { +public: + void Undo(int index = 0); + void Redo(int index = 0); + + std::vector actions; +}; + +class EditHistoryLogWindow : public JUI::Window { +public: }; class EditorApp : public OpenGLWindow { @@ -39,6 +73,11 @@ public: 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; @@ -61,336 +100,75 @@ public: std::vector quads; - void PopulateQuads() { - quads.reserve(tileset_width*tileset_height); - for (int i = 0; i < tileset_width*tileset_height; i++) { - Vector2i cell = IndexToCell(i, tileset_width); - quads[i] = Quad{cell.x*grid_pixel_width, cell.y*grid_pixel_height, grid_pixel_width, grid_pixel_height}; - } - } + /// Fill the tile-id lookup table with their respective partial-sprite bounding boxes. + void PopulateQuads(); + /// This provides a mapping from a 1D flat array to a 2D grid. + /// @return The {x,y} coordinate of the grid-cell that maps to the given index. + Vector2i IndexToCell(int index, int width); - Vector2i IndexToCell(int index, int width) { - int x = index % width; - int y = index / width; - return {x, y}; - } + /// This provides a mapping from a 2D grid to a 1D flat array. + /// @return The {x,y} coordinate of the grid-cell that maps to the given index. + int CellToIndex(Vector2i cell, int width); - int CellToIndex(Vector2i cell, int width) { - return cell.y*width + cell.x; - } + EditorApp(); - EditorApp() : OpenGLWindow("Editor App", 1776, 1000, GL_VER_MAJOR, GL_VER_MINOR) { - camera.rotation = 0.f; - camera.translation = {0.f, 0.f}; - camera.scale = 1.f; - } + /// Loads the test.lvl file into the editor. + /// @note Placeholder until FileDialogs are added. + void LoadTestFile(); - void LoadTestFile() { - std::ifstream input; - input.open("test.lvl", std::ios::binary | std::ios::in); - input.seekg(0, std::ios::end); - int data_length = input.tellg(); - input.seekg(0, std::ios::beg); + /// Saves the current editor's data to test.lvl. + /// @note Placeholder until FileDialogs are added. + void SaveTestFile(); - char* buffer = new char[data_length]; - input.read(buffer, data_length); - input.close(); + /// Loads test-data, including a tilesheet, and a binary level-data file. + /// @note Placeholder until FileDialogs are added. + /// @note Placeholder until Tileset json is added. + void LoadMisc(); - auto* data = reinterpret_cast(buffer); - memcpy(grid, data, grid_rows * grid_cols * sizeof(int)); - } - void SaveTestFile() { - std::ofstream output; - output.open("test.lvl", std::ios::out | std::ios::binary); - output.write(reinterpret_cast(&grid[0][0]), grid_rows * grid_cols * sizeof(int)); - output.close(); - } + /// Creates a JUI widget that displays the Tileset, and lets you select a tile from it. + JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent); - void LoadMisc() { - test_tilesheet = new JGL::Texture("../megacommando.png"); + /// Create all JUI elements required for this program. + void CreateWidgets(); - auto texture_size = test_tilesheet->GetDimensions(); - tileset_width = texture_size.x / grid_pixel_width; - tileset_height = texture_size.y / grid_pixel_height; + /// Toggles the level grid. + void ToggleGrid(); - PopulateQuads(); + bool Open() override; - for (int x = 0; x < grid_rows; x++) { - for (int y = 0; y < grid_cols; y++) { - grid[x][y] = -1; - } - } + Vector2i GetTilesetCellFromMouse(); - if (std::filesystem::exists("test.lvl")) - LoadTestFile(); - } + Vector2i GetGridCellFromMouse(); + void CameraUpdate(float elapsed); - JUI::Window* CreateTilesetViewerWindow(JUI::Widget* parent) { - using namespace JUI; + void SetTile(const Vector2i& cell, int tileID); - auto* wind = new Window(parent); - wind->SetTitle("Tileset Viewer"); - wind->Size(JUI::UDim2::FromPixels(test_tilesheet->GetDimensions().x, test_tilesheet->GetDimensions().y+20)); - wind->SetResizable(false); + void PlaceTileBrush(const Vector2i& cell); - auto* img = new Image(wind->Content()); - img->Content(test_tilesheet); - img->FitImageToParent(false); + void EraseTile(const Vector2i& cell); - auto* grid_overlay = new JGL::RenderTarget(test_tilesheet->GetDimensions()); - // TODO: supplying a translucent grid_overlay_color seems to not work? - J2D::Begin(grid_overlay); - DrawGrid(AABB2D({0,0}, Vector2(test_tilesheet->GetDimensions())), grid_overlay_color); - J2D::End(); + void Update(float elapsed); - auto* grid_overlay_tex = new JGL::Texture(*grid_overlay->GetTexture()); + /// Draws a uniform grid of dashes lines at the boundaries of each tile. + /// @param bounds The viewport in which to draw. Grid lines will not be drawn beyond this box. + /// @param color The color with which to draw the grid lines. + void DrawGrid(const AABB2D& bounds, const Color4& color); + void DrawGrid(const Color4& color = {255,255,255,128}); - auto* overlay = new JUI::Image(wind->Content()); - overlay->Content(grid_overlay_tex); - overlay->ZIndex(2); + /// Draws a colored outline on the cell that the mouse-pointer is over. + void DrawCellPointerOutline(); - cell_indicator = new JUI::Rect(wind->Content()); - cell_indicator->Size(UDim2::FromPixels(grid_pixel_width, grid_pixel_height)); - cell_indicator->BorderMode(JUI::BorderMode::Outline); - cell_indicator->BGColor(Colors::Transparent); - cell_indicator->BorderColor(Colors::Blues::CornflowerBlue); - cell_indicator->BorderWidth(2); + void DrawTiles(); - return wind; - } + void Draw(); - void CreateWidgets() { - scene = new JUI::Scene(); - - auto* topbar = new JUI::UtilityBar(scene); - - auto* file = topbar->AddSubmenu("File"); - file->SetFont(JGL::Fonts::Jupiteroid); - file->AddButton("New"); - file->AddButton("Open"); - file->AddButton("Save", [this]{SaveTestFile();}); - file->AddButton("Save As"); - file->AddSeparator(2_px); - file->AddButton("About"); - file->AddButton("Preferences"); - - auto* edit = topbar->AddSubmenu("Edit"); - edit->AddButton("Undo"); - edit->AddButton("Redo"); - edit->AddButton("Copy"); - edit->AddSeparator(2_px); - edit->AddButton("Paste"); - edit->AddButton("Cut Selection"); - - auto* view = topbar->AddSubmenu("View"); - view->AddButton("Zoom In"); - view->AddButton("Zoom Out"); - view->AddSeparator(2_px); - view->AddButton("Toggle Grid", [this]{ToggleGrid();}); - view->AddButton("Set Background Color", [this]{bg_color_tool_window->Toggle();}); - - auto* level = topbar->AddSubmenu("Level"); - auto* layer = topbar->AddSubmenu("Layer"); - layer->AddButton("New"); - layer->AddButton("Open from File"); - layer->AddButton("Duplicate Selected"); - layer->AddButton("Delete Selected"); - layer->AddButton("Edit Selected"); - layer->AddButton("Export Layer"); - - - tileset_viewer = CreateTilesetViewerWindow(scene); - - bg_color_tool_window = new JUI::Window(scene); - bg_color_tool_window->Close(); - - bg_color_tool = new JUI::ColorPicker(bg_color_tool_window->Content()); - bg_color_tool->OnColorValueChanged += [this] (Color4 new_color) mutable { bg_color = new_color; }; - - } - - void ToggleGrid() { - grid_overlay_enabled = !grid_overlay_enabled; - } - - bool Open() override { - if (!OpenGLWindow::Open()) return false; - - auto size = GetSize(); - auto vec_size = Vector2i(size.x, size.y); - if (!JGL::Init(vec_size, 0.f, 1.f)) - return false; - JGL::Update(vec_size); - - glClearColor(0.f, 0.f, 0.f, 0.f); - - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - glDepthMask(GL_TRUE); - // TODO: Delete when we update to the next release of JGL - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!! - - LoadMisc(); - CreateWidgets(); - - return true; - } - - Vector2i GetTilesetCellFromMouse() { - auto maus = GetMouseCoordinates(); - - Vector2 mouse_v2(maus.x, maus.y); - - - Vector2 rel = tileset_viewer->Content()->GetAbsolutePosition(); - - Vector2 rel_mouse = mouse_v2 - rel; - - return Vector2i( - Math::Floor(rel_mouse.x/grid_pixel_width), - Math::Floor(rel_mouse.y/grid_pixel_height)); - } - - Vector2i GetGridCellFromMouse() { - auto maus = GetMouseCoordinates(); - - Vector2 mouse_v2(maus.x, maus.y); - - return Vector2i( - Math::Floor(mouse_v2.x/grid_pixel_width), - Math::Floor(mouse_v2.y/grid_pixel_height)); - } - - void CameraUpdate(float elapsed) { - float move_rate = 10; - float zoom_rate = 0.05f; - - if (IsKeyDown(Keys::LeftArrow)) - camera.translation.x -= move_rate*elapsed; - if (IsKeyDown(Keys::RightArrow)) - camera.translation.x += move_rate*elapsed; - if (IsKeyDown(Keys::UpArrow)) - camera.translation.y -= move_rate*elapsed; - if (IsKeyDown(Keys::DownArrow)) - camera.translation.y += move_rate*elapsed; - - if (IsKeyDown(Keys::Minus)) - camera.scale -= zoom_rate*elapsed; - if (IsKeyDown(Keys::Equals)) - camera.scale += zoom_rate*elapsed; - - } - - void Update(float elapsed) { - CameraUpdate(elapsed); - auto size = GetSize(); - Vector2i vSize = Vector2i(size.x, size.y); - - - JGL::Update(vSize); - scene->SetViewportSize(Vector2(vSize)); - scene->Update(elapsed); - - if (tileset_viewer->IsMouseInside()) { - if (tileset_viewer->Content()->IsMouseInside()) { - cell_indicator->Visible(true); - - Vector2 rel_mouse = Vector2(GetTilesetCellFromMouse()); - - rel_mouse.x *= grid_pixel_width; - rel_mouse.y *= grid_pixel_height; - - cell_indicator->Position(JUI::UDim2::FromPixels(rel_mouse.x, rel_mouse.y)); - } - } else { - cell_indicator->Visible(false); - - if (IsMouseButtonDown(MouseButtons::Left)) { - Vector2i grid_cell = GetGridCellFromMouse(); - grid[grid_cell.x][grid_cell.y] = selected_quad; - } - - if (IsMouseButtonDown(MouseButtons::Right)) { - Vector2i grid_cell = GetGridCellFromMouse(); - grid[grid_cell.x][grid_cell.y] = -1; - } - } - } - - void DrawGrid(const AABB2D& bounds, const Color4& color) { - Vector2 viewport_topleft = bounds.minPoint; - Vector2 viewport_bottomright = bounds.maxPoint; - - int nearest_grid_left = Math::Floor(viewport_topleft.x / grid_pixel_width); - int nearest_grid_right = Math::Floor(viewport_bottomright.x / grid_pixel_width); - for (int x = nearest_grid_left; x <= nearest_grid_right; x++) { - auto top = Vector2(x * grid_pixel_width, viewport_topleft.y); - auto bottom = Vector2(x * grid_pixel_width, viewport_bottomright.y); - JGL::J2D::DrawDashedLine(color, top, bottom, 4, 4, 1 / bounds.Width()); - } - int nearest_grid_top = Math::Floor(viewport_topleft.y / grid_pixel_height); - int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / grid_pixel_height); - for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) { - auto left = Vector2(viewport_topleft.x, y * grid_pixel_height); - auto right = Vector2(viewport_bottomright.x, y * grid_pixel_height); - JGL::J2D::DrawDashedLine(color, left, right, 4, 4, 1 / bounds.Height()); - } - } - void DrawGrid(const Color4& color = {255,255,255,128}) { - - float sw = GetWidth(); - float sh = GetHeight(); - - DrawGrid({{0,0}, {sw, sh}}, color); - } - - void Draw() { - glClearColor(bg_color.RN(), bg_color.GN(), bg_color.BN(), 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - - - J2D::Begin(); - glPushMatrix(); - glTranslatef(-camera.translation.x, -camera.translation.y, 0); - glRotatef(camera.rotation, 0, 0, 1); - glScalef(camera.scale, camera.scale, 1); - - if (grid_overlay_enabled) - DrawGrid(grid_overlay_color); - //J2D::DrawSprite(test_tilesheet, {32, 32}, 0, Vector2::Zero, Vector2::One, Colors::White); - for (int gx = 0; gx < grid_rows; gx++) { - for (int gy = 0; gy < grid_cols; gy++) { - - auto quad_idx = grid[gx][gy]; - - if (quad_idx < 0) - continue; - - auto quad = quads[quad_idx]; - Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height); - J2D::DrawPartialSprite(test_tilesheet, pos, - Vector2(quad.x, quad.y), Vector2(quad.w, quad.h)); - } - } - - - glPopMatrix(); - J2D::End(); - - - - scene->Draw(); - } - - void OnClosing() override { - SaveTestFile(); - } + // TODO: Closing the app nominally doesn't work on Linux. + void OnClosing() override; void OnRefresh(float elapsed) override { Update(elapsed); @@ -430,7 +208,6 @@ public: Vector2 mposv2(e.Position.x, e.Position.y); if (scene->ObserveMouseMovement(mposv2)) return; - } void OnKeyDown(const KeyDownEvent &e) override { diff --git a/include/EditorCamera.hpp b/include/EditorCamera.hpp new file mode 100644 index 0000000..654b494 --- /dev/null +++ b/include/EditorCamera.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +struct EditorCamera { + Lerped translation; + Lerped rotation; + Lerped scale; + + Vector2 origin{0,0}; + + Vector2 screenSize{1024,768}; + + [[nodiscard]] Matrix4x4 CurrentTranslationMatrix() const; + + [[nodiscard]] Matrix4x4 GoalTranslationMatrix() const; + + Matrix4x4 CurrentRotationMatrix() const; + + Matrix4x4 GoalRotationMatrix() const; + + Matrix4x4 CurrentScaleMatrix() const; + + Matrix4x4 GoalScaleMatrix() const + { + return Matrix4x4::Scale({scale.goal, scale.goal, 1}); + } + + Matrix4x4 CurrentViewMatrix() const + { + return CurrentTranslationMatrix() * CurrentScaleMatrix(); + } + Matrix4x4 GoalViewMatrix() const + { + return GoalTranslationMatrix() * GoalScaleMatrix(); + } + + void DefaultState() + { + this->rotation.goal = 0.f; + this->rotation.current = 0.f; + this->rotation.rate = 5.f; + this->translation.goal = {0.f, 0.f}; + this->translation.current = {0.f, 0.f}; + this->translation.rate = 5.f; + this->scale.goal = 1.f; + this->scale.current = 1.f; + this->scale.rate = 5.f; + } + + Vector2 ScreenToCameraSpace(const Vector2& ssv) const { + return { + (ssv.x + translation.current.x) / scale.current, + (ssv.y + translation.current.y) / scale.current + }; + } + Vector2 CameraToScreenSpace(const Vector2& csv) const{ + + return { + (csv.x * scale.current) - translation.current.x, + (csv.y * scale.current) - translation.current.y + }; + } + + void Update(float elapsed) + { + translation.Step(elapsed); + rotation.Step(elapsed); + scale.Step(elapsed); + } +}; \ No newline at end of file diff --git a/include/Level.hpp b/include/Level.hpp index 7045e26..cdf243d 100644 --- a/include/Level.hpp +++ b/include/Level.hpp @@ -1,5 +1,7 @@ #pragma once #include +#include +#include class Level { public: @@ -9,6 +11,8 @@ public: std::vector tags; std::vector tile_layers; + + protected: private: }; diff --git a/include/Preferences.hpp b/include/Preferences.hpp new file mode 100644 index 0000000..2ffee51 --- /dev/null +++ b/include/Preferences.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +using namespace JJX; + +class Preferences { +public: + explicit Preferences(const json::value& json) + { + + } + + json::value Serialize() const + { + json::value json; + + return json; + } + +public: + Color4 GridColor() const; + + bool ShowBackgroundGrid() const; + bool ShowForegroundGrid() const; + + bool AutoSave() const; +}; \ No newline at end of file diff --git a/include/TileLayer.hpp b/include/TileLayer.hpp index 23943cd..8057505 100644 --- a/include/TileLayer.hpp +++ b/include/TileLayer.hpp @@ -1,7 +1,9 @@ #pragma once struct TileCell { - + int ID; + bool flipH; + bool flipV; }; class TileLayer { @@ -11,6 +13,8 @@ public: int cell_width; int cell_height; TileCell cells[][]; + + protected: private: }; \ No newline at end of file diff --git a/include/Utils.hpp b/include/Utils.hpp new file mode 100644 index 0000000..fdcb5e5 --- /dev/null +++ b/include/Utils.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +template +struct Lerped +{ + T goal; + T current; + float rate; + + void Step(float elapsed); + +}; + +template <> inline void Lerped::Step(float elapsed) { current = Math::Lerp(current, goal, rate*elapsed);} +template <> inline void Lerped::Step(float elapsed) { current = Vector2::Lerp(current, goal, rate*elapsed);} \ No newline at end of file diff --git a/src/EditorApp.cpp b/src/EditorApp.cpp index 1c72fc8..5c18f1b 100644 --- a/src/EditorApp.cpp +++ b/src/EditorApp.cpp @@ -1 +1,410 @@ -#include \ No newline at end of file +#include + +void EditorApp::PopulateQuads() +{ + quads.reserve(tileset_width*tileset_height); + for (int i = 0; i < tileset_width*tileset_height; i++) { + Vector2i cell = IndexToCell(i, tileset_width); + quads.push_back(Quad{cell.x*grid_pixel_width, cell.y*grid_pixel_height, grid_pixel_width, grid_pixel_height}); + } +} + +Vector2i EditorApp::IndexToCell(int index, int width) +{ + int x = index % width; + int y = index / width; + return {x, y}; +} + +int EditorApp::CellToIndex(Vector2i cell, int width) +{ + return cell.y*width + cell.x; +} + +EditorApp::EditorApp(): OpenGLWindow("Editor App", 1776, 1000, GL_VER_MAJOR, GL_VER_MINOR) +{ + camera.DefaultState(); +} + +void EditorApp::LoadTestFile() +{ + std::ifstream input; + input.open("test.lvl", 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(); + + auto* data = reinterpret_cast(buffer); + memcpy(grid, data, grid_rows * grid_cols * sizeof(int)); +} + +void EditorApp::SaveTestFile() +{ + std::ofstream output; + output.open("test.lvl", std::ios::out | std::ios::binary); + output.write(reinterpret_cast(&grid[0][0]), grid_rows * grid_cols * sizeof(int)); + output.close(); +} + +void EditorApp::LoadMisc() +{ + test_tilesheet = new JGL::Texture("../megacommando.png"); + + auto texture_size = test_tilesheet->GetDimensions(); + tileset_width = texture_size.x / grid_pixel_width; + tileset_height = texture_size.y / grid_pixel_height; + + PopulateQuads(); + + for (int x = 0; x < grid_rows; x++) { + for (int y = 0; y < grid_cols; y++) { + grid[x][y] = -1; + } + } + + if (std::filesystem::exists("test.lvl")) + LoadTestFile(); +} + +JUI::Window* EditorApp::CreateTilesetViewerWindow(JUI::Widget* parent) +{ + using namespace JUI; + + auto* wind = new Window(parent); + wind->SetTitle("Tileset Viewer"); + wind->Size(JUI::UDim2::FromPixels(test_tilesheet->GetDimensions().x, test_tilesheet->GetDimensions().y+20)); + wind->SetResizable(false); + + auto* img = new Image(wind->Content()); + img->Content(test_tilesheet); + img->FitImageToParent(false); + + auto* grid_overlay = new JGL::RenderTarget(test_tilesheet->GetDimensions()); + // TODO: supplying a translucent grid_overlay_color seems to not work? + J2D::Begin(grid_overlay); + DrawGrid(AABB2D({0,0}, Vector2(test_tilesheet->GetDimensions())), grid_overlay_color); + J2D::End(); + + auto* grid_overlay_tex = new JGL::Texture(*grid_overlay->GetTexture()); + + + auto* overlay = new JUI::Image(wind->Content()); + overlay->Content(grid_overlay_tex); + overlay->ZIndex(2); + + cell_indicator = new JUI::Rect(wind->Content()); + cell_indicator->Size(UDim2::FromPixels(grid_pixel_width, grid_pixel_height)); + cell_indicator->BorderMode(JUI::BorderMode::Outline); + cell_indicator->BGColor(Colors::Transparent); + cell_indicator->BorderColor(Colors::Blues::CornflowerBlue); + cell_indicator->BorderWidth(2); + + + return wind; +} + +void EditorApp::CreateWidgets() +{ + scene = new JUI::Scene(); + + auto* topbar = new JUI::UtilityBar(scene); + + auto* file = topbar->AddSubmenu("File"); + file->SetFont(JGL::Fonts::Jupiteroid); + file->AddButton("New"); + file->AddButton("Open"); + file->AddButton("Save", [this]{SaveTestFile();}); + file->AddButton("Save As"); + file->AddSeparator(2_px); + file->AddButton("About"); + file->AddButton("Preferences"); + + auto* edit = topbar->AddSubmenu("Edit"); + edit->AddButton("Undo"); + edit->AddButton("Redo"); + edit->AddButton("Copy"); + edit->AddSeparator(2_px); + edit->AddButton("Paste"); + edit->AddButton("Cut Selection"); + + auto* view = topbar->AddSubmenu("View"); + view->AddButton("Zoom In"); + view->AddButton("Zoom Out"); + view->AddSeparator(2_px); + view->AddButton("Toggle Grid", [this]{ToggleGrid();}); + view->AddButton("Set Background Color", [this]{bg_color_tool_window->Toggle();}); + + auto* level = topbar->AddSubmenu("Level"); + auto* layer = topbar->AddSubmenu("Layer"); + layer->AddButton("New"); + layer->AddButton("Open from File"); + layer->AddButton("Duplicate Selected"); + layer->AddButton("Delete Selected"); + layer->AddButton("Edit Selected"); + layer->AddButton("Export Layer"); + + + tileset_viewer = CreateTilesetViewerWindow(scene); + + bg_color_tool_window = new JUI::Window(scene); + bg_color_tool_window->Close(); + + bg_color_tool = new JUI::ColorPicker(bg_color_tool_window->Content()); + bg_color_tool->OnColorValueChanged += [this] (Color4 new_color) mutable { bg_color = new_color; }; + +} + +void EditorApp::ToggleGrid() +{ + grid_overlay_enabled = !grid_overlay_enabled; +} + +bool EditorApp::Open() +{ + if (!OpenGLWindow::Open()) return false; + + auto size = GetSize(); + auto vec_size = Vector2i(size.x, size.y); + if (!JGL::Init(vec_size, 0.f, 1.f)) + return false; + JGL::Update(vec_size); + + glClearColor(0.f, 0.f, 0.f, 0.f); + + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + // TODO: Delete when we update to the next release of JGL + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // NOTE: This MUST be called for text rendering to work properly!!! + + LoadMisc(); + CreateWidgets(); + + return true; +} + +Vector2i EditorApp::GetTilesetCellFromMouse() +{ + auto maus = GetMouseCoordinates(); + + Vector2 mouse_v2(maus.x, maus.y); + + + Vector2 rel = tileset_viewer->Content()->GetAbsolutePosition(); + + Vector2 rel_mouse = mouse_v2 - rel; + + return Vector2i( + Math::Floor(rel_mouse.x/grid_pixel_width), + Math::Floor(rel_mouse.y/grid_pixel_height)); +} + +Vector2i EditorApp::GetGridCellFromMouse() +{ + auto maus = GetMouseCoordinates(); + + Vector2 mouse_v2(maus.x, maus.y); + + + mouse_v2 = camera.ScreenToCameraSpace(mouse_v2); + + return Vector2i( + Math::Floor(mouse_v2.x/grid_pixel_width), + Math::Floor(mouse_v2.y/grid_pixel_height)); +} + +void EditorApp::CameraUpdate(float elapsed) +{ + float move_rate = 40; + float zoom_rate = 0.1f; + + if (IsKeyDown(Keys::LeftArrow)) + camera.translation.goal.x -= move_rate*elapsed; + if (IsKeyDown(Keys::RightArrow)) + camera.translation.goal.x += move_rate*elapsed; + if (IsKeyDown(Keys::UpArrow)) + camera.translation.goal.y -= move_rate*elapsed; + if (IsKeyDown(Keys::DownArrow)) + camera.translation.goal.y += move_rate*elapsed; + + if (IsKeyDown(Keys::Minus)) + camera.scale.goal -= zoom_rate*elapsed; + if (IsKeyDown(Keys::Equals)) + camera.scale.goal += zoom_rate*elapsed; + + camera.Update(elapsed); + +} + +void EditorApp::SetTile(const Vector2i& cell, int tileID) +{ + // out of bounds horizontally + if (cell.x > grid_rows) return; + + // out of bounds vertically + if (cell.y > grid_cols) return; + + grid[cell.x][cell.y] = tileID; +} + +void EditorApp::PlaceTileBrush(const Vector2i& cell) +{ + + // out of bounds horizontally + if (cell.x > grid_rows) return; + + // out of bounds vertically + if (cell.y > grid_cols) return; + + SetTile(cell, selected_quad); +} + +void EditorApp::EraseTile(const Vector2i& cell) +{ + // out of bounds horizontally + if (cell.x > grid_rows) return; + + // out of bounds vertically + if (cell.y > grid_cols) return; + + SetTile(cell, -1); +} + +void EditorApp::Update(float elapsed) +{ + CameraUpdate(elapsed); + auto size = GetSize(); + Vector2i vSize = Vector2i(size.x, size.y); + + camera.screenSize.x = vSize.x; + camera.screenSize.y = vSize.y; + + JGL::Update(vSize); + scene->SetViewportSize(Vector2(vSize)); + scene->Update(elapsed); + + if (tileset_viewer->IsMouseInside()) { + if (tileset_viewer->Content()->IsMouseInside()) { + cell_indicator->Visible(true); + + Vector2 rel_mouse = Vector2(GetTilesetCellFromMouse()); + + rel_mouse.x *= grid_pixel_width; + rel_mouse.y *= grid_pixel_height; + + cell_indicator->Position(JUI::UDim2::FromPixels(rel_mouse.x, rel_mouse.y)); + } + } else { + cell_indicator->Visible(false); + + if (IsMouseButtonDown(MouseButtons::Left)) + PlaceTileBrush(GetGridCellFromMouse()); + + if (IsMouseButtonDown(MouseButtons::Right)) + EraseTile(GetGridCellFromMouse()); + } +} + +void EditorApp::DrawGrid(const AABB2D& bounds, const Color4& color) +{ + Vector2 viewport_topleft = bounds.minPoint; + Vector2 viewport_bottomright = bounds.maxPoint; + + int nearest_grid_left = Math::Floor(viewport_topleft.x / grid_pixel_width); + int nearest_grid_right = Math::Floor(viewport_bottomright.x / grid_pixel_width); + for (int x = nearest_grid_left; x <= nearest_grid_right; x++) { + auto top = Vector2(x * grid_pixel_width, viewport_topleft.y); + auto bottom = Vector2(x * grid_pixel_width, viewport_bottomright.y); + JGL::J2D::DrawDashedLine(color, top, bottom, 4, 4, 1 / bounds.Width()); + } + int nearest_grid_top = Math::Floor(viewport_topleft.y / grid_pixel_height); + int nearest_grid_bottom = Math::Floor(viewport_bottomright.y / grid_pixel_height); + for (int y = nearest_grid_top; y <= nearest_grid_bottom; y++) { + auto left = Vector2(viewport_topleft.x, y * grid_pixel_height); + auto right = Vector2(viewport_bottomright.x, y * grid_pixel_height); + JGL::J2D::DrawDashedLine(color, left, right, 4, 4, 1 / bounds.Height()); + } +} + +void EditorApp::DrawGrid(const Color4& color) +{ + + float sw = GetWidth(); + float sh = GetHeight(); + + DrawGrid({{0,0}, {sw, sh}}, color); +} + +void EditorApp::DrawCellPointerOutline() +{ + Vector2 rel_mouse = Vector2(GetGridCellFromMouse()); + + rel_mouse.x *= grid_pixel_width; + rel_mouse.y *= grid_pixel_height; + + J2D::OutlineRect(cell_pointer_outline_color, rel_mouse, Vector2(grid_pixel_width, grid_pixel_height), cell_pointer_outline_width); +} + +void EditorApp::DrawTiles() +{ + for (int gx = 0; gx < grid_rows; gx++) { + for (int gy = 0; gy < grid_cols; gy++) { + + auto quad_idx = grid[gx][gy]; + + if (quad_idx < 0) + continue; + + auto quad = quads[quad_idx]; + Vector2 pos(gx*grid_pixel_width, gy*grid_pixel_height); + J2D::DrawPartialSprite(test_tilesheet, pos, + Vector2(quad.x, quad.y), Vector2(quad.w, quad.h)); + } + } +} + +void EditorApp::Draw() +{ + glClearColor(bg_color.RN(), bg_color.GN(), bg_color.BN(), 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + J2D::Begin(); + glPushMatrix(); + glTranslatef(-camera.translation.current.x, -camera.translation.current.y, 0); + glRotatef(camera.rotation.current, 0, 0, 1); + glScalef(camera.scale.current, camera.scale.current, 1); + + if (grid_overlay_enabled) + DrawGrid(grid_overlay_color); + + DrawTiles(); + + if (grid_overlay_enabled) + DrawGrid(grid_overlay_color); + + DrawCellPointerOutline(); + + glPopMatrix(); + + auto maus = GetMouseCoordinates(); + + Vector2 mouse_v2(maus.x, maus.y); + + auto bruhbruh = camera.CameraToScreenSpace(camera.ScreenToCameraSpace(mouse_v2)); + + J2D::DrawPoint(Colors::White, bruhbruh, 4); + J2D::End(); + + + + scene->Draw(); +} + +void EditorApp::OnClosing() +{ + SaveTestFile(); +} diff --git a/src/EditorCamera.cpp b/src/EditorCamera.cpp new file mode 100644 index 0000000..4d80379 --- /dev/null +++ b/src/EditorCamera.cpp @@ -0,0 +1,28 @@ +#include + +Matrix4x4 EditorCamera::CurrentTranslationMatrix() const +{ + return Matrix4x4::FromTranslation({translation.current.x, translation.current.y, 0}); +} + +Matrix4x4 EditorCamera::GoalTranslationMatrix() const +{ + return Matrix4x4::FromTranslation({translation.goal.x, translation.goal.y, 0}); +} + +Matrix4x4 EditorCamera::CurrentRotationMatrix() const +{ + // TODO: Which axis? + return Matrix4x4::RotateX(rotation.current); +} + +Matrix4x4 EditorCamera::GoalRotationMatrix() const +{ + // TODO: Which axis? + return Matrix4x4::RotateX(rotation.goal); +} + +Matrix4x4 EditorCamera::CurrentScaleMatrix() const +{ + return Matrix4x4::Scale({scale.current, scale.current, 1}); +}