Adding tooltip and TileMetaDialog to TilesetView

This commit is contained in:
2025-06-26 21:49:57 -05:00
parent 27349b2ee1
commit ba3d88004f
10 changed files with 227 additions and 41 deletions

View File

@@ -46,7 +46,7 @@ CPMAddPackage(NAME JGL
URL https://git.redacted.cc/josh/JGL/archive/Prerelease-58.zip)
CPMAddPackage(NAME JUI
URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-6.3.zip)
URL https://git.redacted.cc/josh/ReJUI/archive/Prerelease-6.4.zip)
if (UNIX)

View File

@@ -1,25 +1,17 @@
{
"author":"",
"cols":80.000000,
"cols":50.000000,
"file-path":"tileset.json",
"first-gid":0.000000,
"name":"tileset",
"rows":64.000000,
"texture-height":1280.000000,
"texture-height":800.000000,
"texture-path":"assets/megacommando.png",
"texture-width":1024.000000,
"tile-height":16.000000,
"tile-spacing":0.000000,
"tile-width":16.000000,
"tiles":[
{
"id":5000.000000,
"metadata":{
"color":"#FFAAFF"
},
"name":"Special Tile",
"quad":[0.000000, 0.000000, 16.000000, 16.000000]
},
{
"id":896,
"collides": false,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -135,8 +135,6 @@ public:
//std::vector<Quad> quads;
/// 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);
@@ -164,6 +162,8 @@ public:
/// @note Placeholder until FileDialogs are added.
void SaveCurrentLevel() const;
void SaveTileset() const;
void SaveCurrentLevelAs(const std::string &filename) const;
void LoadLevel(const std::filesystem::path& level_meta_path);

View File

@@ -0,0 +1,38 @@
#pragma once
#include <Data/Tileset.hpp>
#include <JUI/Base/Widget.hpp>
#include <JUI/Widgets/Window.hpp>
#include <JUI/Widgets/Checkbox.hpp>
#include <JUI/Widgets/TextInputForm.hpp>
class TileMetaDialog : public JUI::Window, public JUI::Hoverable {
public:
Event<bool> CollidesChanged;
Event<std::string> NameChanged;
TileMetaDialog();
explicit TileMetaDialog(JUI::Widget* parent);
void SetTileMetadata(const Tile& tile);
[[nodiscard]] int CurrentTileID() const;
[[nodiscard]] std::string CurrentTileName() const;
void OnExit(const Vector2 &MousePos) override;
JUI::TextRect* name_label;
JUI::TextInputForm* name_form;
JUI::TextRect* collides_label;
JUI::Checkbox* collides_form;
int cur_tile_id;
std::string cur_tile_name;
protected:
private:
};

View File

@@ -2,11 +2,17 @@
#include <App/Grid.hpp>
#include "TileMetaDialog.hpp"
class TilesetView : public JUI::Window {
public:
Tileset* stored_tileset;
Texture* stored_texture;
Event<int> TileSelected;
Event<int, std::string> TileNameChanged;
Event<int, bool> TileCollidesChanged;
bool is_focusing;
TilesetView() : JUI::Window() {
Title("Tileset View");
@@ -50,6 +56,19 @@ public:
cell_indicator->BGColor(Colors::Transparent);
SetCellSizeIndicatorToTilesetSize();
tile_info_tooltip = new JUI::Tooltip(this->Content());
tile_meta_dialog = new TileMetaDialog(this->Content());
tile_meta_dialog->CollidesChanged += [&] (bool collides) {
TileCollidesChanged(tile_meta_dialog->CurrentTileID(), collides);
};
tile_meta_dialog->NameChanged += [&] (const std::string& new_name) {
TileNameChanged(tile_meta_dialog->CurrentTileID(), new_name);
};
}
TilesetView(Widget* parent) : TilesetView() {
Parent(parent);
@@ -90,8 +109,17 @@ public:
rel.x *= stored_tileset->tile_width;
rel.y *= stored_tileset->tile_height;
Vector2i cell = GetTilesetCellFromMouse(last_known_mouse_pos);
int index = CellToIndex(cell, stored_tileset->rows);
auto tile = stored_tileset->tiles[index];
cell_indicator->Position(JUI::UDim2::FromPixels(rel.x, rel.y));
tile_info_tooltip->Content(std::format("#{}: {}", tile.id, tile.name));
tile_info_tooltip->AnchorPoint({0, 0.5f});
tile_info_tooltip->Position(cell_indicator->Position());
} else {
cell_indicator->Visible(false);
is_focusing = false;
@@ -107,6 +135,29 @@ public:
int index = CellToIndex(cell, stored_tileset->rows);
TileSelected.Invoke(index);
}
if (Content()->IsMouseInside() && pressed == false && btn == JUI::MouseButton::Right) {
auto ipair = InputService::GetMousePosition();
Vector2 mpos(ipair.x, ipair.y);
Vector2i cell = GetTilesetCellFromMouse(last_known_mouse_pos);
int index = CellToIndex(cell, stored_tileset->rows);
auto tile = stored_tileset->tiles[index];
Vector2 rel = Vector2(GetTilesetCellFromMouse(mpos));
rel.x *= stored_tileset->tile_width;
rel.y *= stored_tileset->tile_height;
tile_meta_dialog->SetTileMetadata(tile);
tile_meta_dialog->Position(JUI::UDim2::FromPixels(rel.x, rel.y));
tile_meta_dialog->Open();
return true;
}
return true;
}
return false;
@@ -126,9 +177,11 @@ protected:
JUI::Image* tilesheet = nullptr;
JGL::Texture* grid_overlay = nullptr;
JUI::Rect* cell_indicator = nullptr;
JUI::Tooltip* tile_info_tooltip = nullptr;
TileMetaDialog* tile_meta_dialog = nullptr;
protected:
Color4 grid_overlay_color = {255, 255, 255, 64};
Color4 cell_pointer_outline_color = Colors::Blues::LightSteelBlue;
private:
};
};

View File

@@ -26,6 +26,8 @@ struct Tile
Quad quad;
json::value metadata;
bool collides;
bool persistent = false;
};
/// TODO: Only generate tile entry for tiles that are used in the level, or have been assigned custom metadata.
@@ -88,11 +90,11 @@ public:
tileset.author = "J. O'Leary";
tileset.name = "Sample Tileset";
tileset.file_path = "tileset.json";
tileset.cols = 80;
tileset.cols = 50;
tileset.rows = 64;
tileset.texture_path = "assets/megacommando.png";
tileset.tex_width = 1024;
tileset.tex_height = 1280;
tileset.tex_height = 800;
return tileset;
}
@@ -136,6 +138,7 @@ protected:
void FillTiles() {
// Generate some tiles
tiles.reserve(rows*cols);
tile_count = rows*cols;
for (int i = 0 ; i < this->rows*this->cols; i++)
{
Tile tile;

View File

@@ -210,6 +210,10 @@ void EditorApp::SaveCurrentLevel() const
write_file_contents("level.json", json::deparse(data));
}
void EditorApp::SaveTileset() const {
write_file_contents("tileset.json", json::deparse(loaded_tileset->Serialize()));
}
void EditorApp::SaveCurrentLevelAs(const std::string& filename) const {
// TODO: Is it decent for the layers to be written to file separate?
@@ -236,12 +240,36 @@ void EditorApp::LoadLevel(const std::filesystem::path& level_meta_path)
for (auto layer : loaded_level->layers)
layer->Load();
// Check layers for tiles that are too large
for (auto layer : loaded_level->layers) {
for (int x = 0; x < layer->rows; x++) {
for (int y = 0; y < layer->cols; y++) {
int id = layer->cells[x][y];
if (id >= loaded_tileset->tile_count)
layer->cells[x][y] = -1;
}
}
}
tileset_view = new TilesetView(loaded_tileset, loaded_tilesheet, scene);
tileset_view->TileSelected += [this](int id) mutable {
selected_quad = id;
};
tileset_view->TileCollidesChanged += [this](int id, bool collides) mutable {
loaded_tileset->tiles[id].collides = collides;
loaded_tileset->tiles[id].persistent = true;
SaveTileset();
};
tileset_view->TileNameChanged += [this](int id, const std::string& new_name) mutable {
loaded_tileset->tiles[id].name = new_name;
loaded_tileset->tiles[id].persistent = true;
SaveTileset();
};
layer_view->UpdateComponents(loaded_level);
layer_view->Open();
@@ -268,13 +296,13 @@ void EditorApp::CreateTestLevel() {
loaded_tileset = new Tileset();
loaded_tileset->name = "MegaCommando";
loaded_tileset->author = "dawsh";
loaded_tileset->tex_height = 1280;
loaded_tileset->tex_height = 800;
loaded_tileset->tex_width = 1024;
loaded_tileset->tile_width = 16;
loaded_tileset->tile_height = 16;
loaded_tileset->tile_spacing = 0;
loaded_tileset->cols = 80;
loaded_tileset->rows = 60;
loaded_tileset->cols = 50;
loaded_tileset->rows = 64;
loaded_tileset->file_path = "tileset.json";
loaded_tileset->texture_path = "assets/megacommando.png";
loaded_tilesheet = new JGL::Texture("assets/megacommando.png");
@@ -474,7 +502,7 @@ void EditorApp::CreateWidgets()
BindConsoleCallbacks();
nmd = new NewMapDialog(scene);
nmd->Open();
nmd->Close();
layer_view = new LayerView(scene);
layer_view->Close();
@@ -953,9 +981,6 @@ void EditorApp::Draw()
}
J2D::End();
scene->Draw();
}

View File

@@ -0,0 +1,66 @@
#include <App/TileMetaDialog.hpp>
TileMetaDialog::TileMetaDialog(): JUI::Window() {
Name("TileMetaDialog");
Title("Tile Metadata: #0");
Size({150_px, 60_px});
AnchorPoint({0.5, 0.5});
name_label = new JUI::TextRect(this->Content());
name_label->Size({50_px, 20_px});
name_label->Center();
name_label->Content("Tile Name:");
name_form = new JUI::TextInputForm(this->Content());
name_form->Size({70_px, 20_px});
name_form->Position({50_px, 0_px});
//name_form->SetInputBuffer("MyTile");
// TODO: autocomplete_text_enabled does nothing.
name_form->SetAutocompleteTextEnabled(false);
name_form->SetAutoCompleteText("");
name_form->Content("Another One");
name_form->ClearTextOnReturn(false);
collides_label = new JUI::TextRect(this->Content());
collides_label->Size({50_px, 20_px});
collides_label->Position({0_px, 20_px});
collides_label->Center();
collides_label->Content("Collides:");
collides_form = new JUI::Checkbox(this->Content());
collides_form->Size({20_px, 20_px});
collides_form->Position({50_px, 20_px});
name_form->OnReturn += [&] (const std::string& new_name) {
NameChanged(new_name);
};
collides_form->OnReleaseEvent += [&](auto pos, auto btn, bool value) mutable {
CollidesChanged(!collides_form->IsChecked());
};
}
TileMetaDialog::TileMetaDialog(JUI::Widget *parent): TileMetaDialog() {
this->Parent(parent);
}
void TileMetaDialog::SetTileMetadata(const Tile &tile) {
cur_tile_id = tile.id;
cur_tile_name = tile.name;
name_form->Content(tile.name);
collides_form->SetChecked(tile.collides);
Title(std::format("Tile Metadata: {} #{}", tile.name, tile.id));
}
int TileMetaDialog::CurrentTileID() const { return cur_tile_id;}
std::string TileMetaDialog::CurrentTileName() const { return cur_tile_name;}
void TileMetaDialog::OnExit(const Vector2 &MousePos) {
this->Close();
}

View File

@@ -27,8 +27,6 @@ Tileset::Tileset(const std::string& name, const std::filesystem::path& texture_p
this->rows = J3ML::Math::Floor(this->tex_width / this->tile_width);
this->cols = J3ML::Math::Floor(this->tex_height / this->tile_height);
ComputeQuads();
FillTiles();
}
@@ -71,9 +69,18 @@ void Tileset::Deserialize(const json::value& json)
for (json::value& v : tilemeta)
{
int id = v["id"].number.value();
tiles[id].name = v["name"].as_string();
if (v.contains("collides"))
tiles[id].collides = v["collides"].boolean.value_or(true);
if (id < this->tile_count) {
tiles[id].persistent = true;
// TODO: Warning, tile out of tileset ID bounds!
tiles[id].name = v["name"].as_string();
if (v.contains("collides"))
tiles[id].collides = v["collides"].boolean.value_or(true);
}
// this->tiles.push_back(v);
}
@@ -81,6 +88,7 @@ void Tileset::Deserialize(const json::value& json)
}
json::value Tileset::Serialize() const
{
json::object tileset;
@@ -89,7 +97,6 @@ json::value Tileset::Serialize() const
tileset["texture-path"] = this->texture_path;
tileset["author"] = this->author;
tileset["tile-width"] = (float) this->tile_width;
tileset["tile-height"] = (float) this->tile_height;
tileset["tile-spacing"] = (float)this->tile_spacing;
@@ -116,18 +123,20 @@ json::value Tileset::Serialize() const
json::array tiles;
for (auto& tile : this->tiles)
{
json::object tile_json;
tile_json["name"] = tile.name;
tile_json["id"] = (float)tile.id;
tile_json["metadata"] = tile.metadata;
tile_json["collides"] = json::boolean(tile.collides);
json::array quad;
quad.push_back((float)tile.quad.x);
quad.push_back((float)tile.quad.y);
quad.push_back((float)tile.quad.w);
quad.push_back((float)tile.quad.h);
tile_json["quad"] = quad;
tiles.push_back(tile_json);
if (tile.persistent) {
json::object tile_json;
tile_json["name"] = tile.name;
tile_json["id"] = (float)tile.id;
tile_json["metadata"] = tile.metadata;
tile_json["collides"] = json::boolean(tile.collides);
//json::array quad;
//quad.push_back((float)tile.quad.x);
//quad.push_back((float)tile.quad.y);
//quad.push_back((float)tile.quad.w);
//quad.push_back((float)tile.quad.h);
//tile_json["quad"] = quad;
tiles.push_back(tile_json);
}
}
tileset["tiles"] = tiles;