backend-infra-engineer: Release v0.3.0 snapshot

This commit is contained in:
scawful
2025-09-27 00:25:45 -04:00
parent 8ce29e1436
commit e32ac75b9c
346 changed files with 55946 additions and 11764 deletions

View File

@@ -1,12 +1,15 @@
#include "assembly_editor.h"
#include <fstream>
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "app/core/platform/file_dialog.h"
#include "app/gui/icons.h"
#include "app/gui/modules/text_editor.h"
namespace yaze {
namespace editor {
namespace yaze::editor {
using core::FileDialogWrapper;
@@ -18,22 +21,21 @@ std::vector<std::string> RemoveIgnoredFiles(
std::vector<std::string> filtered_files;
for (const auto& file : files) {
// Remove subdirectory files
if (file.find('/') != std::string::npos) {
if (file.contains('/')) {
continue;
}
// Make sure the file has an extension
if (file.find('.') == std::string::npos) {
if (!file.contains('.')) {
continue;
}
if (std::find(ignored_files.begin(), ignored_files.end(), file) ==
ignored_files.end()) {
if (std::ranges::find(ignored_files, file) == ignored_files.end()) {
filtered_files.push_back(file);
}
}
return filtered_files;
}
core::FolderItem LoadFolder(const std::string& folder) {
FolderItem LoadFolder(const std::string& folder) {
// Check if .gitignore exists in the folder
std::ifstream gitignore(folder + "/.gitignore");
std::vector<std::string> ignored_files;
@@ -51,28 +53,27 @@ core::FolderItem LoadFolder(const std::string& folder) {
}
}
core::FolderItem current_folder;
FolderItem current_folder;
current_folder.name = folder;
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
for (const auto& subfolder :
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
core::FolderItem folder_item;
FolderItem folder_item;
folder_item.name = subfolder;
std::string full_folder = current_folder.name + "/" + subfolder;
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
for (const auto& files : folder_files) {
// Remove subdirectory files
if (files.find('/') != std::string::npos) {
if (files.contains('/')) {
continue;
}
// Make sure the file has an extension
if (files.find('.') == std::string::npos) {
if (!files.contains('.')) {
continue;
}
if (std::find(ignored_files.begin(), ignored_files.end(), files) !=
ignored_files.end()) {
if (std::ranges::find(ignored_files, files) != ignored_files.end()) {
continue;
}
folder_item.files.push_back(files);
@@ -80,7 +81,7 @@ core::FolderItem LoadFolder(const std::string& folder) {
for (const auto& subdir :
FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) {
core::FolderItem subfolder_item;
FolderItem subfolder_item;
subfolder_item.name = subdir;
subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
folder_item.subfolders.push_back(subfolder_item);
@@ -93,6 +94,12 @@ core::FolderItem LoadFolder(const std::string& folder) {
} // namespace
void AssemblyEditor::Initialize() {
// Set the language definition
}
absl::Status AssemblyEditor::Load() { return absl::OkStatus(); }
void AssemblyEditor::OpenFolder(const std::string& folder_path) {
current_folder_ = LoadFolder(folder_path);
}
@@ -119,7 +126,6 @@ void AssemblyEditor::Update(bool& is_loaded) {
}
void AssemblyEditor::InlineUpdate() {
ChangeActiveFile("assets/asm/template_song.asm");
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
@@ -225,8 +231,8 @@ void AssemblyEditor::DrawFileTabView() {
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
if (std::find(active_files_.begin(), active_files_.end(),
current_file_id_) != active_files_.end()) {
if (std::ranges::find(active_files_, current_file_id_) !=
active_files_.end()) {
// Room is already open
next_tab_id++;
}
@@ -354,5 +360,4 @@ absl::Status AssemblyEditor::Redo() {
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
} // namespace editor
} // namespace yaze
} // namespace yaze::editor

View File

@@ -3,21 +3,27 @@
#include <string>
#include "app/core/common.h"
#include "app/editor/editor.h"
#include "app/gui/modules/text_editor.h"
#include "app/gui/style.h"
#include "app/rom.h"
namespace yaze {
namespace editor {
struct FolderItem {
std::string name;
std::vector<FolderItem> subfolders;
std::vector<std::string> files;
};
/**
* @class AssemblyEditor
* @brief Text editor for modifying assembly code.
*/
class AssemblyEditor : public Editor {
public:
AssemblyEditor() {
explicit AssemblyEditor(Rom* rom = nullptr) : rom_(rom) {
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette());
text_editor_.SetShowWhitespaces(false);
@@ -28,6 +34,8 @@ class AssemblyEditor : public Editor {
file_is_loaded_ = false;
}
void Initialize() override;
absl::Status Load() override;
void Update(bool &is_loaded);
void InlineUpdate();
@@ -43,28 +51,32 @@ class AssemblyEditor : public Editor {
absl::Status Update() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
void OpenFolder(const std::string &folder_path);
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private:
void DrawFileMenu();
void DrawEditMenu();
void SetEditorText();
void DrawCurrentFolder();
void DrawFileTabView();
bool file_is_loaded_ = false;
int current_file_id_ = 0;
std::vector<std::string> files_;
std::vector<TextEditor> open_files_;
ImVector<int> active_files_;
int current_file_id_ = 0;
std::string current_file_;
core::FolderItem current_folder_;
FolderItem current_folder_;
TextEditor text_editor_;
Rom* rom_;
};
} // namespace editor

View File

@@ -1,31 +1,13 @@
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/snes.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "util/macro.h"
namespace yaze {
namespace editor {
@@ -33,7 +15,9 @@ namespace editor {
using ImGui::SameLine;
using ImGui::Text;
struct MemoryEditorWithDiffChecker : public SharedRom {
struct MemoryEditorWithDiffChecker {
explicit MemoryEditorWithDiffChecker(Rom* rom = nullptr) : rom_(rom) {}
void Update(bool &show_memory_editor) {
static MemoryEditor mem_edit;
static MemoryEditor comp_edit;
@@ -49,7 +33,7 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
static uint64_t convert_address = 0;
gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f);
SameLine();
Text("%x", core::SnesToPc(convert_address));
Text("%x", SnesToPc(convert_address));
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
@@ -74,6 +58,15 @@ struct MemoryEditorWithDiffChecker : public SharedRom {
ImGui::End();
}
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
private:
Rom* rom_;
};
} // namespace editor

View File

@@ -0,0 +1,706 @@
#include "dungeon_canvas_viewer.h"
#include "absl/strings/str_format.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/sprite/sprite.h"
#include "imgui/imgui.h"
namespace yaze::editor {
using ImGui::Button;
using ImGui::Separator;
void DungeonCanvasViewer::DrawDungeonTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("MyTabBar", ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_TabListPopupButton)) {
if (ImGui::TabItemButton("+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
if (std::find(active_rooms_.begin(), active_rooms_.end(), current_active_room_tab_) != active_rooms_.end()) {
next_tab_id++;
}
active_rooms_.push_back(next_tab_id++);
}
// Submit our regular tabs
for (int n = 0; n < active_rooms_.Size;) {
bool open = true;
if (active_rooms_[n] > sizeof(zelda3::kRoomNames) / 4) {
active_rooms_.erase(active_rooms_.Data + n);
continue;
}
if (ImGui::BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(), &open, ImGuiTabItemFlags_None)) {
current_active_room_tab_ = n;
DrawDungeonCanvas(active_rooms_[n]);
ImGui::EndTabItem();
}
if (!open)
active_rooms_.erase(active_rooms_.Data + n);
else
n++;
}
ImGui::EndTabBar();
}
Separator();
}
void DungeonCanvasViewer::Draw(int room_id) {
DrawDungeonCanvas(room_id);
}
void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
// Validate room_id and ROM
if (room_id < 0 || room_id >= 128) {
ImGui::Text("Invalid room ID: %d", room_id);
return;
}
if (!rom_ || !rom_->is_loaded()) {
ImGui::Text("ROM not loaded");
return;
}
ImGui::BeginGroup();
if (rooms_) {
gui::InputHexByte("Layout", &(*rooms_)[room_id].layout);
ImGui::SameLine();
gui::InputHexByte("Blockset", &(*rooms_)[room_id].blockset);
ImGui::SameLine();
gui::InputHexByte("Spriteset", &(*rooms_)[room_id].spriteset);
ImGui::SameLine();
gui::InputHexByte("Palette", &(*rooms_)[room_id].palette);
gui::InputHexByte("Floor1", &(*rooms_)[room_id].floor1);
ImGui::SameLine();
gui::InputHexByte("Floor2", &(*rooms_)[room_id].floor2);
ImGui::SameLine();
gui::InputHexWord("Message ID", &(*rooms_)[room_id].message_id_);
ImGui::SameLine();
if (Button("Load Room Graphics")) {
(void)LoadAndRenderRoomGraphics(room_id);
}
}
ImGui::EndGroup();
canvas_.DrawBackground();
canvas_.DrawContextMenu();
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
// Automatically load room graphics if not already loaded
if (room.blocks().empty()) {
(void)LoadAndRenderRoomGraphics(room_id);
}
// Load room objects if not already loaded
if (room.GetTileObjects().empty()) {
room.LoadObjects();
}
// Render background layers with proper positioning
RenderRoomBackgroundLayers(room_id);
// Render room objects with proper graphics
if (current_palette_id_ < current_palette_group_.size()) {
auto room_palette = current_palette_group_[current_palette_id_];
// Render regular objects with proper graphics
for (const auto& object : room.GetTileObjects()) {
RenderObjectInCanvas(object, room_palette);
}
// Render special objects with primitive shapes
RenderStairObjects(room, room_palette);
RenderChests(room);
RenderDoorObjects(room);
RenderWallObjects(room);
RenderPotObjects(room);
// Render sprites as simple 16x16 squares with labels
RenderSprites(room);
}
}
canvas_.DrawGrid();
canvas_.DrawOverlay();
// Draw layer information overlay
if (rooms_ && rom_->is_loaded()) {
auto& room = (*rooms_)[room_id];
std::string layer_info = absl::StrFormat(
"Room %03X - Objects: %zu, Sprites: %zu\n"
"Layers are game concept: Objects exist on different levels\n"
"connected by stair objects for player navigation",
room_id, room.GetTileObjects().size(), room.GetSprites().size());
canvas_.DrawText(layer_info, 10, canvas_.height() - 60);
}
}
void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette) {
// Validate ROM is loaded
if (!rom_ || !rom_->is_loaded()) {
return;
}
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Check if object is within canvas bounds
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
return; // Skip objects outside visible area
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
mutable_object.EnsureTilesLoaded();
// Try to render the object with proper graphics
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
if (render_result.ok()) {
auto object_bitmap = std::move(render_result.value());
// Ensure the bitmap is valid and has content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0) {
object_bitmap.SetPalette(palette);
core::Renderer::Get().RenderBitmap(&object_bitmap);
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Fallback: Draw object as colored rectangle with ID if rendering fails
ImVec4 object_color;
// Color-code objects based on layer
switch (object.layer_) {
case zelda3::RoomObject::LayerType::BG1:
object_color = ImVec4(0.8f, 0.4f, 0.4f, 0.8f); // Red-ish for BG1
break;
case zelda3::RoomObject::LayerType::BG2:
object_color = ImVec4(0.4f, 0.8f, 0.4f, 0.8f); // Green-ish for BG2
break;
case zelda3::RoomObject::LayerType::BG3:
object_color = ImVec4(0.4f, 0.4f, 0.8f, 0.8f); // Blue-ish for BG3
break;
default:
object_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f); // Gray for unknown
break;
}
// Calculate object size (16x16 is base, size affects width/height)
int object_width = 16 + (object.size_ & 0x0F) * 8;
int object_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height, object_color);
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height,
ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Black border
// Draw object ID
std::string object_text = absl::StrFormat("0x%X", object.id_);
canvas_.DrawText(object_text, canvas_x + object_width + 2, canvas_y);
}
void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object,
int canvas_x, int canvas_y) {
// Display object information as text overlay
std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_,
object.x_, object.y_, object.size_);
// Draw text at the object position
canvas_.DrawText(info_text, canvas_x, canvas_y - 12);
}
void DungeonCanvasViewer::RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette) {
// Render stair objects with special highlighting to show they enable layer transitions
// Stair object IDs from room.h: {0x139, 0x138, 0x13B, 0x12E, 0x12D}
constexpr uint16_t stair_ids[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
for (const auto& object : room.GetTileObjects()) {
bool is_stair = false;
for (uint16_t stair_id : stair_ids) {
if (object.id_ == stair_id) {
is_stair = true;
break;
}
}
if (is_stair) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Draw stair object with special highlighting
canvas_.DrawRect(canvas_x - 2, canvas_y - 2, 20, 20,
ImVec4(1.0f, 1.0f, 0.0f, 0.8f)); // Yellow highlight
// Draw text label
std::string stair_text = absl::StrFormat("STAIR\n0x%X", object.id_);
canvas_.DrawText(stair_text, canvas_x + 22, canvas_y);
}
}
}
}
void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) {
// Render sprites as simple 16x16 squares with sprite name/ID
for (const auto& sprite : room.GetSprites()) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(sprite.x(), sprite.y());
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw 16x16 square for sprite
ImVec4 sprite_color;
// Color-code sprites based on layer
if (sprite.layer() == 0) {
sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0
} else {
sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1
}
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, sprite_color);
// Draw sprite border
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw sprite ID and name
std::string sprite_text;
if (sprite.id() >= 0) { // sprite.id() is uint8_t so always < 256
// Extract just the sprite name part (remove ID prefix)
std::string full_name = zelda3::kSpriteDefaultNames[sprite.id()];
auto space_pos = full_name.find(' ');
if (space_pos != std::string::npos && space_pos < full_name.length() - 1) {
std::string sprite_name = full_name.substr(space_pos + 1);
// Truncate long names
if (sprite_name.length() > 8) {
sprite_name = sprite_name.substr(0, 8) + "...";
}
sprite_text = absl::StrFormat("%02X\n%s", sprite.id(), sprite_name.c_str());
} else {
sprite_text = absl::StrFormat("%02X", sprite.id());
}
} else {
sprite_text = absl::StrFormat("%02X", sprite.id());
}
canvas_.DrawText(sprite_text, canvas_x + 18, canvas_y);
}
}
}
void DungeonCanvasViewer::RenderChests(const zelda3::Room& room) {
// Render chest objects from tile objects - chests are objects with IDs 0xF9, 0xFA
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0xF9 || object.id_ == 0xFA) { // Chest object IDs
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Determine if it's a big chest based on object ID
bool is_big_chest = (object.id_ == 0xFA);
// Draw chest base
ImVec4 chest_color = is_big_chest ?
ImVec4(0.8f, 0.6f, 0.2f, 0.9f) : // Gold for big chest
ImVec4(0.6f, 0.4f, 0.2f, 0.9f); // Brown for small chest
int chest_size = is_big_chest ? 24 : 16; // Big chests are larger
canvas_.DrawRect(canvas_x, canvas_y + 8, chest_size, 8, chest_color);
// Draw chest lid (slightly lighter)
ImVec4 lid_color = is_big_chest ?
ImVec4(0.9f, 0.7f, 0.3f, 0.9f) :
ImVec4(0.7f, 0.5f, 0.3f, 0.9f);
canvas_.DrawRect(canvas_x, canvas_y + 4, chest_size, 6, lid_color);
// Draw chest borders
canvas_.DrawRect(canvas_x, canvas_y + 4, chest_size, 12, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string chest_text = is_big_chest ? "BIG\nCHEST" : "CHEST";
canvas_.DrawText(chest_text, canvas_x + chest_size + 2, canvas_y + 6);
}
}
}
}
void DungeonCanvasViewer::RenderDoorObjects(const zelda3::Room& room) {
// Render door objects from tile objects based on IDs from assembly constants
constexpr uint16_t door_ids[] = {0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E};
for (const auto& object : room.GetTileObjects()) {
bool is_door = false;
for (uint16_t door_id : door_ids) {
if (object.id_ == door_id) {
is_door = true;
break;
}
}
if (is_door) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Draw door frame
canvas_.DrawRect(canvas_x, canvas_y, 32, 32, ImVec4(0.5f, 0.3f, 0.2f, 0.8f)); // Brown frame
// Draw door opening (darker)
canvas_.DrawRect(canvas_x + 4, canvas_y + 4, 24, 24, ImVec4(0.1f, 0.1f, 0.1f, 0.9f));
// Draw door border
canvas_.DrawRect(canvas_x, canvas_y, 32, 32, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string door_text = absl::StrFormat("DOOR\n0x%X", object.id_);
canvas_.DrawText(door_text, canvas_x + 34, canvas_y + 8);
}
}
}
}
void DungeonCanvasViewer::RenderWallObjects(const zelda3::Room& room) {
// Render wall objects with proper dimensions based on properties
for (const auto& object : room.GetTileObjects()) {
if (object.id_ >= 0x10 && object.id_ <= 0x1F) { // Wall objects range
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Different wall types based on ID
ImVec4 wall_color;
std::string wall_type;
switch (object.id_) {
case 0x10: // Basic wall
wall_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f);
wall_type = "WALL";
break;
case 0x11: // Corner wall
wall_color = ImVec4(0.7f, 0.7f, 0.6f, 0.8f);
wall_type = "CORNER";
break;
case 0x12: // Decorative wall
wall_color = ImVec4(0.8f, 0.7f, 0.6f, 0.8f);
wall_type = "DEC_WALL";
break;
default:
wall_color = ImVec4(0.5f, 0.5f, 0.5f, 0.8f);
wall_type = "WALL";
break;
}
// Calculate wall size with proper length handling
int wall_width, wall_height;
// For walls, use the size field to determine length
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
uint8_t size_x = object.size_ & 0x0F;
uint8_t size_y = (object.size_ >> 4) & 0x0F;
if (size_x > size_y) {
// Horizontal wall
wall_width = 16 + size_x * 16;
wall_height = 16;
} else if (size_y > size_x) {
// Vertical wall
wall_width = 16;
wall_height = 16 + size_y * 16;
} else {
// Square wall or corner
wall_width = 16 + size_x * 8;
wall_height = 16 + size_y * 8;
}
} else {
wall_width = 16 + (object.size_ & 0x0F) * 8;
wall_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
}
wall_width = std::min(wall_width, 256);
wall_height = std::min(wall_height, 256);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, wall_color);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Add stone block pattern
for (int i = 0; i < wall_width; i += 8) {
for (int j = 0; j < wall_height; j += 8) {
canvas_.DrawRect(canvas_x + i, canvas_y + j, 6, 6,
ImVec4(wall_color.x * 0.9f, wall_color.y * 0.9f, wall_color.z * 0.9f, wall_color.w));
}
}
// Draw text label
std::string wall_text = absl::StrFormat("%s\n0x%X\n%dx%d", wall_type.c_str(), object.id_, wall_width/16, wall_height/16);
canvas_.DrawText(wall_text, canvas_x + wall_width + 2, canvas_y + 4);
}
}
}
}
void DungeonCanvasViewer::RenderPotObjects(const zelda3::Room& room) {
// Render pot objects based on assembly constants - Object_Pot is 0x2F
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0x2F || object.id_ == 0x2B) { // Pot objects from assembly
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw pot base (wider at bottom)
canvas_.DrawRect(canvas_x + 2, canvas_y + 10, 12, 6, ImVec4(0.7f, 0.5f, 0.3f, 0.8f)); // Brown base
// Draw pot middle
canvas_.DrawRect(canvas_x + 3, canvas_y + 6, 10, 6, ImVec4(0.8f, 0.6f, 0.4f, 0.8f)); // Lighter middle
// Draw pot rim
canvas_.DrawRect(canvas_x + 4, canvas_y + 4, 8, 4, ImVec4(0.9f, 0.7f, 0.5f, 0.8f)); // Lightest top
// Draw pot outline
canvas_.DrawRect(canvas_x + 2, canvas_y + 4, 12, 12, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string pot_text = absl::StrFormat("POT\n0x%X", object.id_);
canvas_.DrawText(pot_text, canvas_x + 18, canvas_y + 6);
}
}
}
}
// Coordinate conversion helper functions
std::pair<int, int> DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x,
int room_y) const {
// Convert room coordinates (tile units) to canvas coordinates (pixels)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
return {static_cast<int>((room_x * 16 + offset_x) * scale),
static_cast<int>((room_y * 16 + offset_y) * scale)};
}
std::pair<int, int> DungeonCanvasViewer::CanvasToRoomCoordinates(int canvas_x,
int canvas_y) const {
// Convert canvas coordinates (pixels) to room coordinates (tile units)
// Account for canvas scaling and offset
float scale = canvas_.global_scale();
int offset_x = static_cast<int>(canvas_.drawn_tile_position().x);
int offset_y = static_cast<int>(canvas_.drawn_tile_position().y);
if (scale <= 0.0f) scale = 1.0f; // Prevent division by zero
return {static_cast<int>((canvas_x / scale - offset_x) / 16),
static_cast<int>((canvas_y / scale - offset_y) / 16)};
}
bool DungeonCanvasViewer::IsWithinCanvasBounds(int canvas_x, int canvas_y,
int margin) const {
// Check if coordinates are within canvas bounds with optional margin
auto canvas_width = canvas_.width();
auto canvas_height = canvas_.height();
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= canvas_width + margin &&
canvas_y <= canvas_height + margin);
}
void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height) {
// Default base size
width = 16;
height = 16;
// For walls, use the size field to determine length and orientation
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
// Wall objects: size determines length and orientation
uint8_t size_x = object.size_ & 0x0F;
uint8_t size_y = (object.size_ >> 4) & 0x0F;
// Walls can be horizontal or vertical based on size parameters
if (size_x > size_y) {
// Horizontal wall
width = 16 + size_x * 16; // Each unit adds 16 pixels
height = 16;
} else if (size_y > size_x) {
// Vertical wall
width = 16;
height = 16 + size_y * 16;
} else {
// Square wall or corner
width = 16 + size_x * 8;
height = 16 + size_y * 8;
}
} else {
// For other objects, use standard size calculation
width = 16 + (object.size_ & 0x0F) * 8;
height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
}
// Clamp to reasonable limits
width = std::min(width, 256);
height = std::min(height, 256);
}
// Room graphics management methods
absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) {
if (room_id < 0 || room_id >= 128) {
return absl::InvalidArgumentError("Invalid room ID");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (!rooms_) {
return absl::FailedPreconditionError("Room data not available");
}
auto& room = (*rooms_)[room_id];
// Load room graphics with proper blockset
room.LoadRoomGraphics(room.blockset);
// Load the room's palette with bounds checking
if (room.palette < rom_->paletteset_ids.size() &&
!rom_->paletteset_ids[room.palette].empty()) {
auto dungeon_palette_ptr = rom_->paletteset_ids[room.palette][0];
auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
if (palette_id.ok()) {
current_palette_group_id_ = palette_id.value() / 180;
if (current_palette_group_id_ < rom_->palette_group().dungeon_main.size()) {
auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
ASSIGN_OR_RETURN(current_palette_group_,
gfx::CreatePaletteGroupFromLargePalette(full_palette));
}
}
}
// Render the room graphics to the graphics arena
room.RenderRoomGraphics();
// Update the background layers with proper palette
RETURN_IF_ERROR(UpdateRoomBackgroundLayers(room_id));
return absl::OkStatus();
}
absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
if (room_id < 0 || room_id >= 128) {
return absl::InvalidArgumentError("Invalid room ID");
}
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (!rooms_) {
return absl::FailedPreconditionError("Room data not available");
}
auto& room = (*rooms_)[room_id];
// Validate palette group access
if (current_palette_group_id_ >= rom_->palette_group().dungeon_main.size()) {
return absl::FailedPreconditionError("Invalid palette group ID");
}
// Get the current room's palette
auto current_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
// Update BG1 (background layer 1) with proper palette
if (room.blocks().size() >= 8) {
for (int i = 0; i < 8; i++) {
int block = room.blocks()[i];
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
if (current_palette_id_ < current_palette_group_.size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
current_palette_group_[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
}
// Update BG2 (background layer 2) with sprite auxiliary palette
if (room.blocks().size() >= 16) {
auto sprites_aux1_pal_group = rom_->palette_group().sprites_aux1;
if (current_palette_id_ < sprites_aux1_pal_group.size()) {
for (int i = 8; i < 16; i++) {
int block = room.blocks()[i];
if (block >= 0 && block < gfx::Arena::Get().gfx_sheets().size()) {
gfx::Arena::Get().gfx_sheets()[block].SetPaletteWithTransparent(
sprites_aux1_pal_group[current_palette_id_], 0);
core::Renderer::Get().UpdateBitmap(&gfx::Arena::Get().gfx_sheets()[block]);
}
}
}
}
return absl::OkStatus();
}
void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
if (room_id < 0 || room_id >= 128) {
return;
}
if (!rom_ || !rom_->is_loaded()) {
return;
}
if (!rooms_) {
return;
}
// Get canvas dimensions to limit rendering
int canvas_width = canvas_.width();
int canvas_height = canvas_.height();
// Validate canvas dimensions
if (canvas_width <= 0 || canvas_height <= 0) {
return;
}
// Render the room's background layers using the graphics arena
// BG1 (background layer 1) - main room graphics
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
// Scale the background to fit the canvas
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
}
// BG2 (background layer 2) - sprite graphics (overlay)
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
// Scale the background to fit the canvas
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200); // Semi-transparent overlay
}
}
} // namespace yaze::editor

View File

@@ -0,0 +1,105 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_CANVAS_VIEWER_H
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/gfx/snes_palette.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
/**
* @brief Handles the main dungeon canvas rendering and interaction
*
* In Link to the Past, dungeon "layers" are not separate visual layers
* but a game concept where objects exist on different logical levels.
* Players move between these levels using stair objects that act as
* transitions between the different object planes.
*/
class DungeonCanvasViewer {
public:
explicit DungeonCanvasViewer(Rom* rom = nullptr) : rom_(rom), object_renderer_(rom) {}
void DrawDungeonTabView();
void DrawDungeonCanvas(int room_id);
void Draw(int room_id);
void SetRom(Rom* rom) {
rom_ = rom;
object_renderer_.SetROM(rom);
}
Rom* rom() const { return rom_; }
// Room data access
void SetRooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; }
// Palette access
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; }
void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { current_palette_group_ = group; }
// Canvas access
gui::Canvas& canvas() { return canvas_; }
const gui::Canvas& canvas() const { return canvas_; }
private:
void RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette);
void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x,
int canvas_y);
void RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette);
void RenderSprites(const zelda3::Room& room);
void RenderChests(const zelda3::Room& room);
void RenderDoorObjects(const zelda3::Room& room);
void RenderWallObjects(const zelda3::Room& room);
void RenderPotObjects(const zelda3::Room& room);
// Coordinate conversion helpers
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
// Object dimension calculation
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
// Room graphics management
absl::Status LoadAndRenderRoomGraphics(int room_id);
absl::Status UpdateRoomBackgroundLayers(int room_id);
void RenderRoomBackgroundLayers(int room_id);
Rom* rom_ = nullptr;
gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
zelda3::ObjectRenderer object_renderer_;
// Room data
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
ImVector<int> active_rooms_;
int current_active_room_tab_ = 0;
// Palette data
uint64_t current_palette_group_id_ = 0;
uint64_t current_palette_id_ = 0;
gfx::PaletteGroup current_palette_group_;
// Object rendering cache
struct ObjectRenderCache {
int object_id;
int object_x, object_y, object_size;
uint64_t palette_hash;
gfx::Bitmap rendered_bitmap;
bool is_valid;
};
std::vector<ObjectRenderCache> object_render_cache_;
uint64_t last_palette_hash_ = 0;
};
} // namespace editor
} // namespace yaze
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -2,16 +2,26 @@
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
#include "absl/container/flat_hash_map.h"
#include "app/core/common.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "imgui/imgui.h"
#include "zelda3/dungeon/object_renderer.h"
#include "zelda3/dungeon/dungeon_editor_system.h"
#include "zelda3/dungeon/dungeon_object_editor.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_entrance.h"
#include "zelda3/dungeon/room_object.h"
#include "dungeon_room_selector.h"
#include "dungeon_canvas_viewer.h"
#include "dungeon_object_selector.h"
#include "dungeon_toolset.h"
#include "dungeon_object_interaction.h"
#include "dungeon_renderer.h"
#include "dungeon_room_loader.h"
#include "dungeon_usage_tracker.h"
namespace yaze {
namespace editor {
@@ -32,69 +42,100 @@ constexpr ImGuiTableFlags kDungeonTableFlags =
/**
* @brief DungeonEditor class for editing dungeons.
*
* This class is currently a work in progress and is used for editing dungeons.
* It provides various functions for updating, cutting, copying, pasting,
* undoing, and redoing. It also includes methods for drawing the toolset, room
* selector, entrance selector, dungeon tab view, dungeon canvas, room graphics,
* tile selector, and object renderer. Additionally, it handles loading room
* entrances, calculating usage statistics, and rendering set usage.
* This class provides a comprehensive dungeon editing interface that integrates
* with the new unified dungeon editing system. It includes object editing with
* scroll wheel support, sprite management, item placement, entrance/exit editing,
* and advanced dungeon features.
*/
class DungeonEditor : public Editor, public SharedRom {
class DungeonEditor : public Editor {
public:
DungeonEditor() { type_ = EditorType::kDungeon; }
explicit DungeonEditor(Rom* rom = nullptr)
: rom_(rom), object_renderer_(rom), preview_object_(0, 0, 0, 0, 0),
room_selector_(rom), canvas_viewer_(rom), object_selector_(rom),
object_interaction_(&canvas_), renderer_(&canvas_, rom), room_loader_(rom) {
type_ = EditorType::kDungeon;
// Initialize the new dungeon editor system
if (rom) {
dungeon_editor_system_ = std::make_unique<zelda3::DungeonEditorSystem>(rom);
object_editor_ = std::make_shared<zelda3::DungeonObjectEditor>(rom);
}
}
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Undo() override;
absl::Status Redo() override;
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status Save() override;
void add_room(int i) { active_rooms_.push_back(i); }
void set_rom(Rom* rom) {
rom_ = rom;
// Update the new UI components with the new ROM
room_selector_.set_rom(rom_);
canvas_viewer_.SetRom(rom_);
object_selector_.SetRom(rom_);
}
Rom* rom() const { return rom_; }
// ROM state methods (from Editor base class)
bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
std::string GetRomStatus() const override {
if (!rom_) return "No ROM loaded";
if (!rom_->is_loaded()) return "ROM failed to load";
return absl::StrFormat("ROM loaded: %s", rom_->title());
}
private:
absl::Status Initialize();
absl::Status RefreshGraphics();
void LoadDungeonRoomSize();
absl::Status UpdateDungeonRoomView();
void DrawToolset();
void DrawRoomSelector();
void DrawEntranceSelector();
void DrawDungeonTabView();
void DrawDungeonCanvas(int room_id);
// Enhanced UI methods
void DrawCanvasAndPropertiesPanel();
void DrawRoomPropertiesDebugPopup();
// Room selection management
void OnRoomSelected(int room_id);
void DrawRoomGraphics();
void DrawTileSelector();
void DrawObjectRenderer();
// Legacy methods (delegated to components)
absl::Status LoadAndRenderRoomGraphics(int room_id);
absl::Status ReloadAllRoomGraphics();
absl::Status UpdateRoomBackgroundLayers(int room_id);
void CalculateUsageStats();
void DrawUsageStats();
void DrawUsageGrid();
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
uint16_t& selected_set, int spriteset_offset = 0x00);
enum BackgroundType {
kNoBackground,
kBackground1,
kBackground2,
kBackground3,
kBackgroundAny,
};
enum PlacementType { kNoType, kSprite, kItem, kDoor, kBlock };
int background_type_ = kNoBackground;
int placement_type_ = kNoType;
int current_object_ = 0;
// Object preview system
zelda3::RoomObject preview_object_;
gfx::SnesPalette preview_palette_;
bool is_loaded_ = false;
bool object_loaded_ = false;
bool palette_showing_ = false;
bool refresh_graphics_ = false;
// New editor system integration
std::unique_ptr<zelda3::DungeonEditorSystem> dungeon_editor_system_;
std::shared_ptr<zelda3::DungeonObjectEditor> object_editor_;
bool show_object_editor_ = false;
bool show_sprite_editor_ = false;
bool show_item_editor_ = false;
bool show_entrance_editor_ = false;
bool show_door_editor_ = false;
bool show_chest_editor_ = false;
bool show_properties_editor_ = false;
uint16_t current_entrance_id_ = 0;
uint16_t current_room_id_ = 0;
@@ -102,6 +143,7 @@ class DungeonEditor : public Editor, public SharedRom {
uint64_t current_palette_group_id_ = 0;
ImVector<int> active_rooms_;
int current_active_room_tab_ = 0; // Track which room tab is currently active
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_;
@@ -109,34 +151,32 @@ class DungeonEditor : public Editor, public SharedRom {
gfx::SnesPalette full_palette_;
gfx::PaletteGroup current_palette_group_;
gui::Canvas canvas_;
gui::Canvas room_gfx_canvas_;
gui::Canvas canvas_{"##DungeonCanvas", ImVec2(0x200, 0x200)};
gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas",
ImVec2(0x100 + 1, 0x10 * 0x40 + 1)};
gui::Canvas object_canvas_;
gfx::Bitmap room_gfx_bmp_;
std::array<gfx::Bitmap, kNumGfxSheets> graphics_bin_;
std::vector<gfx::Bitmap*> room_gfx_sheets_;
std::vector<zelda3::Room> rooms_;
std::vector<zelda3::RoomEntrance> entrances_;
zelda3::DungeonObjectRenderer object_renderer_;
std::array<zelda3::Room, 0x128> rooms_ = {};
std::array<zelda3::RoomEntrance, 0x8C> entrances_ = {};
zelda3::ObjectRenderer object_renderer_;
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
absl::flat_hash_map<uint16_t, int> blockset_usage_;
absl::flat_hash_map<uint16_t, int> palette_usage_;
std::vector<int64_t> room_size_pointers_;
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
uint16_t selected_spriteset_ = 0xFFFF;
uint16_t selected_palette_ = 0xFFFF;
uint64_t total_room_size_ = 0;
std::unordered_map<int, int> room_size_addresses_;
std::unordered_map<int, ImVec4> room_palette_;
// UI components
DungeonRoomSelector room_selector_;
DungeonCanvasViewer canvas_viewer_;
DungeonObjectSelector object_selector_;
// Refactored components
DungeonToolset toolset_;
DungeonObjectInteraction object_interaction_;
DungeonRenderer renderer_;
DungeonRoomLoader room_loader_;
DungeonUsageTracker usage_tracker_;
absl::Status status_;
Rom* rom_;
};
} // namespace editor

View File

@@ -0,0 +1,308 @@
#include "dungeon_object_interaction.h"
#include "app/gui/color.h"
#include "imgui/imgui.h"
namespace yaze::editor {
void DungeonObjectInteraction::HandleCanvasMouseInput() {
const ImGuiIO& io = ImGui::GetIO();
// Check if mouse is over the canvas
if (!canvas_->IsMouseHovering()) {
return;
}
// Get mouse position relative to canvas
ImVec2 mouse_pos = io.MousePos;
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 canvas_size = canvas_->canvas_size();
// Convert to canvas coordinates
ImVec2 canvas_mouse_pos =
ImVec2(mouse_pos.x - canvas_pos.x, mouse_pos.y - canvas_pos.y);
// Handle mouse clicks
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
ImGui::IsKeyDown(ImGuiKey_RightCtrl)) {
// Start selection box
is_selecting_ = true;
select_start_pos_ = canvas_mouse_pos;
select_current_pos_ = canvas_mouse_pos;
selected_objects_.clear();
} else {
// Start dragging or place object
if (object_loaded_) {
// Convert canvas coordinates to room coordinates
auto [room_x, room_y] =
CanvasToRoomCoordinates(static_cast<int>(canvas_mouse_pos.x),
static_cast<int>(canvas_mouse_pos.y));
PlaceObjectAtPosition(room_x, room_y);
} else {
// Start dragging existing objects
is_dragging_ = true;
drag_start_pos_ = canvas_mouse_pos;
drag_current_pos_ = canvas_mouse_pos;
}
}
}
// Handle mouse drag
if (is_selecting_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
select_current_pos_ = canvas_mouse_pos;
UpdateSelectedObjects();
}
if (is_dragging_ && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
drag_current_pos_ = canvas_mouse_pos;
DrawDragPreview();
}
// Handle mouse release
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
if (is_selecting_) {
is_selecting_ = false;
UpdateSelectedObjects();
}
if (is_dragging_) {
is_dragging_ = false;
// TODO: Apply drag transformation to selected objects
}
}
}
void DungeonObjectInteraction::CheckForObjectSelection() {
// Draw object selection rectangle similar to OverworldEditor
DrawObjectSelectRect();
// Handle object selection when rectangle is active
if (object_select_active_) {
SelectObjectsInRect();
}
}
void DungeonObjectInteraction::DrawObjectSelectRect() {
if (!canvas_->IsMouseHovering()) return;
const ImGuiIO& io = ImGui::GetIO();
const ImVec2 canvas_pos = canvas_->zero_point();
const ImVec2 mouse_pos =
ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
static bool dragging = false;
static ImVec2 drag_start_pos;
// Right click to start object selection
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !object_loaded_) {
drag_start_pos = mouse_pos;
object_select_start_ = mouse_pos;
selected_object_indices_.clear();
object_select_active_ = false;
dragging = false;
}
// Right drag to create selection rectangle
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && !object_loaded_) {
object_select_end_ = mouse_pos;
dragging = true;
// Draw selection rectangle
ImVec2 start =
ImVec2(canvas_pos.x + std::min(drag_start_pos.x, mouse_pos.x),
canvas_pos.y + std::min(drag_start_pos.y, mouse_pos.y));
ImVec2 end = ImVec2(canvas_pos.x + std::max(drag_start_pos.x, mouse_pos.x),
canvas_pos.y + std::max(drag_start_pos.y, mouse_pos.y));
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
}
// Complete selection on mouse release
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
dragging = false;
object_select_active_ = true;
SelectObjectsInRect();
}
}
void DungeonObjectInteraction::SelectObjectsInRect() {
if (!rooms_ || current_room_id_ < 0 || current_room_id_ >= 296) return;
auto& room = (*rooms_)[current_room_id_];
selected_object_indices_.clear();
// Calculate selection bounds in room coordinates
auto [start_room_x, start_room_y] = CanvasToRoomCoordinates(
static_cast<int>(std::min(object_select_start_.x, object_select_end_.x)),
static_cast<int>(std::min(object_select_start_.y, object_select_end_.y)));
auto [end_room_x, end_room_y] = CanvasToRoomCoordinates(
static_cast<int>(std::max(object_select_start_.x, object_select_end_.x)),
static_cast<int>(std::max(object_select_start_.y, object_select_end_.y)));
// Find objects within selection rectangle
const auto& objects = room.GetTileObjects();
for (size_t i = 0; i < objects.size(); ++i) {
const auto& object = objects[i];
if (object.x_ >= start_room_x && object.x_ <= end_room_x &&
object.y_ >= start_room_y && object.y_ <= end_room_y) {
selected_object_indices_.push_back(i);
}
}
// Highlight selected objects
if (!selected_object_indices_.empty()) {
for (size_t index : selected_object_indices_) {
if (index < objects.size()) {
const auto& object = objects[index];
auto [canvas_x, canvas_y] =
RoomToCanvasCoordinates(object.x_, object.y_);
// Draw selection highlight
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 obj_start(canvas_pos.x + canvas_x - 2,
canvas_pos.y + canvas_y - 2);
ImVec2 obj_end(canvas_pos.x + canvas_x + 18,
canvas_pos.y + canvas_y + 18);
draw_list->AddRect(obj_start, obj_end, IM_COL32(0, 255, 255, 255), 0.0f,
0, 2.0f);
}
}
}
}
void DungeonObjectInteraction::PlaceObjectAtPosition(int room_x, int room_y) {
if (!object_loaded_ || preview_object_.id_ < 0 || !rooms_) return;
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
// Create new object at the specified position
auto new_object = preview_object_;
new_object.x_ = room_x;
new_object.y_ = room_y;
// Add object to room
auto& room = (*rooms_)[current_room_id_];
room.AddTileObject(new_object);
// Notify callback if set
if (object_placed_callback_) {
object_placed_callback_(new_object);
}
// Trigger cache invalidation
if (cache_invalidation_callback_) {
cache_invalidation_callback_();
}
}
void DungeonObjectInteraction::DrawSelectBox() {
if (!is_selecting_) return;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = canvas_->zero_point();
// Calculate select box bounds
ImVec2 start = ImVec2(
canvas_pos.x + std::min(select_start_pos_.x, select_current_pos_.x),
canvas_pos.y + std::min(select_start_pos_.y, select_current_pos_.y));
ImVec2 end = ImVec2(
canvas_pos.x + std::max(select_start_pos_.x, select_current_pos_.x),
canvas_pos.y + std::max(select_start_pos_.y, select_current_pos_.y));
// Draw selection box
draw_list->AddRect(start, end, IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
draw_list->AddRectFilled(start, end, IM_COL32(255, 255, 0, 32));
}
void DungeonObjectInteraction::DrawDragPreview() {
if (!is_dragging_) return;
// Draw drag preview for selected objects
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 canvas_pos = canvas_->zero_point();
ImVec2 drag_delta = ImVec2(drag_current_pos_.x - drag_start_pos_.x,
drag_current_pos_.y - drag_start_pos_.y);
// Draw preview of where objects would be moved
for (int obj_id : selected_objects_) {
// TODO: Draw preview of object at new position
// This would require getting the object's current position and drawing it
// offset by drag_delta
}
}
void DungeonObjectInteraction::UpdateSelectedObjects() {
if (!is_selecting_ || !rooms_) return;
selected_objects_.clear();
if (current_room_id_ < 0 || current_room_id_ >= 296) return;
auto& room = (*rooms_)[current_room_id_];
// Check each object in the room
for (const auto& object : room.GetTileObjects()) {
if (IsObjectInSelectBox(object)) {
selected_objects_.push_back(object.id_);
}
}
}
bool DungeonObjectInteraction::IsObjectInSelectBox(
const zelda3::RoomObject& object) const {
if (!is_selecting_) return false;
// Convert object position to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Calculate select box bounds
float min_x = std::min(select_start_pos_.x, select_current_pos_.x);
float max_x = std::max(select_start_pos_.x, select_current_pos_.x);
float min_y = std::min(select_start_pos_.y, select_current_pos_.y);
float max_y = std::max(select_start_pos_.y, select_current_pos_.y);
// Check if object is within select box
return (canvas_x >= min_x && canvas_x <= max_x && canvas_y >= min_y &&
canvas_y <= max_y);
}
std::pair<int, int> DungeonObjectInteraction::RoomToCanvasCoordinates(int room_x, int room_y) const {
return {room_x * 16, room_y * 16};
}
std::pair<int, int> DungeonObjectInteraction::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
return {canvas_x / 16, canvas_y / 16};
}
bool DungeonObjectInteraction::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const {
auto canvas_size = canvas_->canvas_size();
auto global_scale = canvas_->global_scale();
int scaled_width = static_cast<int>(canvas_size.x * global_scale);
int scaled_height = static_cast<int>(canvas_size.y * global_scale);
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= scaled_width + margin &&
canvas_y <= scaled_height + margin);
}
void DungeonObjectInteraction::SetCurrentRoom(std::array<zelda3::Room, 0x128>* rooms, int room_id) {
rooms_ = rooms;
current_room_id_ = room_id;
}
void DungeonObjectInteraction::SetPreviewObject(const zelda3::RoomObject& object, bool loaded) {
preview_object_ = object;
object_loaded_ = loaded;
}
void DungeonObjectInteraction::ClearSelection() {
selected_object_indices_.clear();
object_select_active_ = false;
is_selecting_ = false;
is_dragging_ = false;
}
} // namespace yaze::editor

View File

@@ -0,0 +1,94 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H
#include <vector>
#include <functional>
#include "imgui/imgui.h"
#include "app/gui/canvas.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_object.h"
namespace yaze {
namespace editor {
/**
* @brief Handles object selection, placement, and interaction within the dungeon canvas
*
* This component manages mouse interactions for object selection (similar to OverworldEditor),
* object placement, drag operations, and multi-object selection.
*/
class DungeonObjectInteraction {
public:
explicit DungeonObjectInteraction(gui::Canvas* canvas) : canvas_(canvas) {}
// Main interaction handling
void HandleCanvasMouseInput();
void CheckForObjectSelection();
void PlaceObjectAtPosition(int room_x, int room_y);
// Selection rectangle (like OverworldEditor)
void DrawObjectSelectRect();
void SelectObjectsInRect();
// Drag and select box functionality
void DrawSelectBox();
void DrawDragPreview();
void UpdateSelectedObjects();
bool IsObjectInSelectBox(const zelda3::RoomObject& object) const;
// Coordinate conversion
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
// State management
void SetCurrentRoom(std::array<zelda3::Room, 0x128>* rooms, int room_id);
void SetPreviewObject(const zelda3::RoomObject& object, bool loaded);
// Selection state
const std::vector<size_t>& GetSelectedObjectIndices() const { return selected_object_indices_; }
bool IsObjectSelectActive() const { return object_select_active_; }
void ClearSelection();
// Callbacks
void SetObjectPlacedCallback(std::function<void(const zelda3::RoomObject&)> callback) {
object_placed_callback_ = callback;
}
void SetCacheInvalidationCallback(std::function<void()> callback) {
cache_invalidation_callback_ = callback;
}
private:
gui::Canvas* canvas_;
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
int current_room_id_ = 0;
// Preview object state
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
bool object_loaded_ = false;
// Drag and select infrastructure
bool is_dragging_ = false;
bool is_selecting_ = false;
ImVec2 drag_start_pos_;
ImVec2 drag_current_pos_;
ImVec2 select_start_pos_;
ImVec2 select_current_pos_;
std::vector<int> selected_objects_;
// Object selection rectangle (like OverworldEditor)
bool object_select_active_ = false;
ImVec2 object_select_start_;
ImVec2 object_select_end_;
std::vector<size_t> selected_object_indices_;
// Callbacks
std::function<void(const zelda3::RoomObject&)> object_placed_callback_;
std::function<void()> cache_invalidation_callback_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_INTERACTION_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_SELECTOR_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_OBJECT_SELECTOR_H
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/dungeon_object_editor.h"
#include "app/zelda3/dungeon/dungeon_editor_system.h"
#include "app/gfx/snes_palette.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
/**
* @brief Handles object selection, preview, and editing UI
*/
class DungeonObjectSelector {
public:
explicit DungeonObjectSelector(Rom* rom = nullptr) : rom_(rom), object_renderer_(rom) {}
void DrawTileSelector();
void DrawObjectRenderer();
void DrawIntegratedEditingPanels();
void Draw();
void set_rom(Rom* rom) {
rom_ = rom;
object_renderer_.SetROM(rom);
}
void SetRom(Rom* rom) {
rom_ = rom;
object_renderer_.SetROM(rom);
}
Rom* rom() const { return rom_; }
// Editor system access
void set_dungeon_editor_system(std::unique_ptr<zelda3::DungeonEditorSystem>* system) {
dungeon_editor_system_ = system;
}
void set_object_editor(std::shared_ptr<zelda3::DungeonObjectEditor>* editor) {
object_editor_ = editor;
}
// Room data access
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
void set_current_room_id(int room_id) { current_room_id_ = room_id; }
// Palette access
void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; }
void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { current_palette_group_ = palette_group; }
void SetCurrentPaletteId(uint64_t palette_id) { current_palette_id_ = palette_id; }
// Object selection callbacks
void SetObjectSelectedCallback(std::function<void(const zelda3::RoomObject&)> callback) {
object_selected_callback_ = callback;
}
void SetObjectPlacementCallback(std::function<void(const zelda3::RoomObject&)> callback) {
object_placement_callback_ = callback;
}
// Get current preview object for placement
const zelda3::RoomObject& GetPreviewObject() const { return preview_object_; }
bool IsObjectLoaded() const { return object_loaded_; }
private:
void DrawRoomGraphics();
void DrawObjectBrowser();
void DrawCompactObjectEditor();
void DrawCompactSpriteEditor();
// Helper methods for primitive object rendering
ImU32 GetObjectTypeColor(int object_id);
std::string GetObjectTypeSymbol(int object_id);
void RenderObjectPrimitive(const zelda3::RoomObject& object, int x, int y);
// AssetBrowser-style object selection
void DrawObjectAssetBrowser();
bool MatchesObjectFilter(int obj_id, int filter_type);
void CalculateObjectDimensions(const zelda3::RoomObject& object, int& width, int& height);
void PlaceObjectAtPosition(int x, int y);
void DrawCompactItemEditor();
void DrawCompactEntranceEditor();
void DrawCompactDoorEditor();
void DrawCompactChestEditor();
void DrawCompactPropertiesEditor();
Rom* rom_ = nullptr;
gui::Canvas room_gfx_canvas_{"##RoomGfxCanvas", ImVec2(0x100 + 1, 0x10 * 0x40 + 1)};
gui::Canvas object_canvas_;
zelda3::ObjectRenderer object_renderer_;
// Editor systems
std::unique_ptr<zelda3::DungeonEditorSystem>* dungeon_editor_system_ = nullptr;
std::shared_ptr<zelda3::DungeonObjectEditor>* object_editor_ = nullptr;
// Room data
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
int current_room_id_ = 0;
// Palette data
uint64_t current_palette_group_id_ = 0;
uint64_t current_palette_id_ = 0;
gfx::PaletteGroup current_palette_group_;
// Object preview system
zelda3::RoomObject preview_object_{0, 0, 0, 0, 0};
gfx::SnesPalette preview_palette_;
bool object_loaded_ = false;
// Callback for object selection
std::function<void(const zelda3::RoomObject&)> object_selected_callback_;
std::function<void(const zelda3::RoomObject&)> object_placement_callback_;
// Object selection state
int selected_object_id_ = -1;
};
} // namespace editor
} // namespace yaze
#endif

View File

@@ -0,0 +1,208 @@
#include "dungeon_renderer.h"
#include "absl/strings/str_format.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gui/color.h"
namespace yaze::editor {
using core::Renderer;
void DungeonRenderer::RenderObjectInCanvas(const zelda3::RoomObject& object,
const gfx::SnesPalette& palette) {
// Validate ROM is loaded
if (!rom_ || !rom_->is_loaded()) {
return;
}
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Check if object is within canvas bounds
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
return; // Skip objects outside visible area
}
// Calculate palette hash for caching
uint64_t palette_hash = 0;
for (size_t i = 0; i < palette.size() && i < 16; ++i) {
palette_hash ^= std::hash<uint16_t>{}(palette[i].snes()) + 0x9e3779b9 +
(palette_hash << 6) + (palette_hash >> 2);
}
// Check cache first
for (auto& cached : object_render_cache_) {
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
cached.object_y == object.y_ && cached.object_size == object.size_ &&
cached.palette_hash == palette_hash && cached.is_valid) {
canvas_->DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
mutable_object.EnsureTilesLoaded();
// Try to render the object with proper graphics
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
if (render_result.ok()) {
auto object_bitmap = std::move(render_result.value());
// Ensure the bitmap is valid and has meaningful content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0 &&
object_bitmap.data() != nullptr) {
object_bitmap.SetPalette(palette);
core::Renderer::Get().RenderBitmap(&object_bitmap);
canvas_->DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
// Cache the successfully rendered bitmap
ObjectRenderCache cache_entry;
cache_entry.object_id = object.id_;
cache_entry.object_x = object.x_;
cache_entry.object_y = object.y_;
cache_entry.object_size = object.size_;
cache_entry.palette_hash = palette_hash;
cache_entry.rendered_bitmap = object_bitmap;
cache_entry.is_valid = true;
// Add to cache (limit cache size)
if (object_render_cache_.size() >= 100) {
object_render_cache_.erase(object_render_cache_.begin());
}
object_render_cache_.push_back(std::move(cache_entry));
return;
}
}
// Fallback: Draw object as colored rectangle with ID if rendering fails
ImVec4 object_color;
// Color-code objects based on layer for better identification
switch (object.layer_) {
case zelda3::RoomObject::LayerType::BG1:
object_color = ImVec4(0.8f, 0.4f, 0.4f, 0.8f); // Red-ish for BG1
break;
case zelda3::RoomObject::LayerType::BG2:
object_color = ImVec4(0.4f, 0.8f, 0.4f, 0.8f); // Green-ish for BG2
break;
case zelda3::RoomObject::LayerType::BG3:
object_color = ImVec4(0.4f, 0.4f, 0.8f, 0.8f); // Blue-ish for BG3
break;
default:
object_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f); // Gray for unknown
break;
}
// Calculate object size (16x16 is base, size affects width/height)
int object_width = 16 + (object.size_ & 0x0F) * 8;
int object_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
canvas_->DrawRect(canvas_x, canvas_y, object_width, object_height, object_color);
canvas_->DrawRect(canvas_x, canvas_y, object_width, object_height,
ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Black border
}
void DungeonRenderer::DisplayObjectInfo(const zelda3::RoomObject& object,
int canvas_x, int canvas_y) {
std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_,
object.x_, object.y_, object.size_);
canvas_->DrawText(info_text, canvas_x, canvas_y - 12);
}
void DungeonRenderer::RenderSprites(const zelda3::Room& room) {
// Render sprites as simple 16x16 squares with sprite name/ID
for (const auto& sprite : room.GetSprites()) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(sprite.x(), sprite.y());
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw 16x16 square for sprite
ImVec4 sprite_color;
// Color-code sprites based on layer for identification
if (sprite.layer() == 0) {
sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0
} else {
sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1
}
canvas_->DrawRect(canvas_x, canvas_y, 16, 16, sprite_color);
canvas_->DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Border
}
}
}
void DungeonRenderer::RenderRoomBackgroundLayers(int room_id) {
// Get canvas dimensions to limit rendering
int canvas_width = canvas_->width();
int canvas_height = canvas_->height();
// Validate canvas dimensions
if (canvas_width <= 0 || canvas_height <= 0) {
return;
}
// BG1 (background layer 1) - main room graphics
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 &&
bg1_bitmap.height() > 0) {
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_->DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
}
// BG2 (background layer 2) - sprite graphics (overlay)
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 &&
bg2_bitmap.height() > 0) {
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_->DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200);
}
}
absl::Status DungeonRenderer::RefreshGraphics(int room_id, uint64_t palette_id,
const gfx::PaletteGroup& palette_group) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// This would need access to room data - will be called from main editor
return absl::OkStatus();
}
std::pair<int, int> DungeonRenderer::RoomToCanvasCoordinates(int room_x, int room_y) const {
return {room_x * 16, room_y * 16};
}
std::pair<int, int> DungeonRenderer::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
return {canvas_x / 16, canvas_y / 16};
}
bool DungeonRenderer::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const {
auto canvas_size = canvas_->canvas_size();
auto global_scale = canvas_->global_scale();
int scaled_width = static_cast<int>(canvas_size.x * global_scale);
int scaled_height = static_cast<int>(canvas_size.y * global_scale);
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= scaled_width + margin &&
canvas_y <= scaled_height + margin);
}
} // namespace yaze::editor

View File

@@ -0,0 +1,75 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_layout.h"
#include "app/zelda3/dungeon/room_object.h"
namespace yaze {
namespace editor {
/**
* @brief Handles rendering of dungeon objects, layouts, and backgrounds
*
* This component manages all rendering operations for the dungeon editor,
* including object caching, background layers, and layout visualization.
*/
class DungeonRenderer {
public:
explicit DungeonRenderer(gui::Canvas* canvas, Rom* rom)
: canvas_(canvas), rom_(rom), object_renderer_(rom) {}
// Object rendering
void RenderObjectInCanvas(const zelda3::RoomObject& object,
const gfx::SnesPalette& palette);
void DisplayObjectInfo(const zelda3::RoomObject& object, int canvas_x, int canvas_y);
void RenderSprites(const zelda3::Room& room);
// Background rendering
void RenderRoomBackgroundLayers(int room_id);
absl::Status RefreshGraphics(int room_id, uint64_t palette_id,
const gfx::PaletteGroup& palette_group);
// Graphics management
absl::Status LoadAndRenderRoomGraphics(int room_id,
std::array<zelda3::Room, 0x128>& rooms);
absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms);
// Cache management
void ClearObjectCache() { object_render_cache_.clear(); }
size_t GetCacheSize() const { return object_render_cache_.size(); }
// Coordinate conversion helpers
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
std::pair<int, int> CanvasToRoomCoordinates(int canvas_x, int canvas_y) const;
bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const;
private:
gui::Canvas* canvas_;
Rom* rom_;
zelda3::ObjectRenderer object_renderer_;
// Object rendering cache
struct ObjectRenderCache {
int object_id;
int object_x, object_y, object_size;
uint64_t palette_hash;
gfx::Bitmap rendered_bitmap;
bool is_valid;
};
std::vector<ObjectRenderCache> object_render_cache_;
uint64_t last_palette_hash_ = 0;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H

View File

@@ -0,0 +1,127 @@
#include "dungeon_room_loader.h"
#include <algorithm>
#include <map>
#include "app/gfx/snes_palette.h"
#include "app/zelda3/dungeon/room.h"
namespace yaze::editor {
absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
auto dungeon_man_pal_group = rom_->palette_group().dungeon_main;
for (int i = 0; i < 0x100 + 40; i++) {
rooms[i] = zelda3::LoadRoomFromRom(rom_, i);
auto room_size = zelda3::CalculateRoomSize(rom_, i);
room_size_pointers_.push_back(room_size.room_size_pointer);
room_sizes_.push_back(room_size.room_size);
if (room_size.room_size_pointer != 0x0A8000) {
room_size_addresses_[i] = room_size.room_size_pointer;
}
rooms[i].LoadObjects();
auto dungeon_palette_ptr = rom_->paletteset_ids[rooms[i].palette][0];
auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
if (palette_id.status() != absl::OkStatus()) {
continue;
}
int p_id = palette_id.value() / 180;
auto color = dungeon_man_pal_group[p_id][3];
room_palette_[rooms[i].palette] = color.rgb();
}
LoadDungeonRoomSize();
return absl::OkStatus();
}
absl::Status DungeonRoomLoader::LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Load entrances
for (int i = 0; i < 0x07; ++i) {
entrances[i] = zelda3::RoomEntrance(rom_, i, true);
}
for (int i = 0; i < 0x85; ++i) {
entrances[i + 0x07] = zelda3::RoomEntrance(rom_, i, false);
}
return absl::OkStatus();
}
void DungeonRoomLoader::LoadDungeonRoomSize() {
std::map<int, std::vector<int>> rooms_by_bank;
for (const auto& room : room_size_addresses_) {
int bank = room.second >> 16;
rooms_by_bank[bank].push_back(room.second);
}
// Process and calculate room sizes within each bank
for (auto& bank_rooms : rooms_by_bank) {
std::ranges::sort(bank_rooms.second);
for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
int room_ptr = bank_rooms.second[i];
// Identify the room ID for the current room pointer
int room_id =
std::ranges::find_if(room_size_addresses_, [room_ptr](
const auto& entry) {
return entry.second == room_ptr;
})->first;
if (room_ptr != 0x0A8000) {
if (i < bank_rooms.second.size() - 1) {
room_sizes_[room_id] = bank_rooms.second[i + 1] - room_ptr;
} else {
int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
room_sizes_[room_id] = bank_end_address - room_ptr + 1;
}
total_room_size_ += room_sizes_[room_id];
} else {
room_sizes_[room_id] = 0x00;
}
}
}
}
absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Load room graphics with proper blockset
room.LoadRoomGraphics(room.blockset);
// Render the room graphics to the graphics arena
room.RenderRoomGraphics();
return absl::OkStatus();
}
absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// Reload graphics for all rooms
for (size_t i = 0; i < rooms.size(); ++i) {
auto status = LoadAndRenderRoomGraphics(static_cast<int>(i), rooms[i]);
if (!status.ok()) {
continue; // Log error but continue with other rooms
}
}
return absl::OkStatus();
}
} // namespace yaze::editor

View File

@@ -0,0 +1,56 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H
#include <vector>
#include <unordered_map>
#include "absl/status/status.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_entrance.h"
namespace yaze {
namespace editor {
/**
* @brief Manages loading and saving of dungeon room data
*
* This component handles all ROM-related operations for loading room data,
* calculating room sizes, and managing room graphics.
*/
class DungeonRoomLoader {
public:
explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {}
// Room loading
absl::Status LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms);
absl::Status LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances);
// Room size management
void LoadDungeonRoomSize();
uint64_t GetTotalRoomSize() const { return total_room_size_; }
// Room graphics
absl::Status LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room);
absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms);
// Data access
const std::vector<int64_t>& GetRoomSizePointers() const { return room_size_pointers_; }
const std::vector<int64_t>& GetRoomSizes() const { return room_sizes_; }
const std::unordered_map<int, int>& GetRoomSizeAddresses() const { return room_size_addresses_; }
const std::unordered_map<int, ImVec4>& GetRoomPalette() const { return room_palette_; }
private:
Rom* rom_;
std::vector<int64_t> room_size_pointers_;
std::vector<int64_t> room_sizes_;
std::unordered_map<int, int> room_size_addresses_;
std::unordered_map<int, ImVec4> room_palette_;
uint64_t total_room_size_ = 0;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_LOADER_H

View File

@@ -0,0 +1,146 @@
#include "dungeon_room_selector.h"
#include "app/gui/input.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_entrance.h"
#include "imgui/imgui.h"
#include "util/hex.h"
namespace yaze::editor {
using ImGui::BeginChild;
using ImGui::EndChild;
using ImGui::SameLine;
void DungeonRoomSelector::Draw() {
if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
if (ImGui::BeginTabItem("Rooms")) {
DrawRoomSelector();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Entrances")) {
DrawEntranceSelector();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void DungeonRoomSelector::DrawRoomSelector() {
if (!rom_ || !rom_->is_loaded()) {
ImGui::Text("ROM not loaded");
return;
}
gui::InputHexWord("Room ID", &current_room_id_, 50.f, true);
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
int i = 0;
for (const auto each_room_name : zelda3::kRoomNames) {
rom_->resource_label()->SelectableLabelWithNameEdit(
current_room_id_ == i, "Dungeon Room Names", util::HexByte(i),
each_room_name.data());
if (ImGui::IsItemClicked()) {
current_room_id_ = i;
// Notify the dungeon editor about room selection
if (room_selected_callback_) {
room_selected_callback_(i);
}
}
i += 1;
}
}
EndChild();
}
void DungeonRoomSelector::DrawEntranceSelector() {
if (!rom_ || !rom_->is_loaded()) {
ImGui::Text("ROM not loaded");
return;
}
if (!entrances_) {
ImGui::Text("Entrances not loaded");
return;
}
auto current_entrance = (*entrances_)[current_entrance_id_];
gui::InputHexWord("Entrance ID", &current_entrance.entrance_id_);
gui::InputHexWord("Room ID", &current_entrance.room_);
SameLine();
gui::InputHexByte("Dungeon ID", &current_entrance.dungeon_id_, 50.f, true);
gui::InputHexByte("Blockset", &current_entrance.blockset_, 50.f, true);
SameLine();
gui::InputHexByte("Music", &current_entrance.music_, 50.f, true);
SameLine();
gui::InputHexByte("Floor", &current_entrance.floor_);
ImGui::Separator();
gui::InputHexWord("Player X ", &current_entrance.x_position_);
SameLine();
gui::InputHexWord("Player Y ", &current_entrance.y_position_);
gui::InputHexWord("Camera X", &current_entrance.camera_trigger_x_);
SameLine();
gui::InputHexWord("Camera Y", &current_entrance.camera_trigger_y_);
gui::InputHexWord("Scroll X ", &current_entrance.camera_x_);
SameLine();
gui::InputHexWord("Scroll Y ", &current_entrance.camera_y_);
gui::InputHexWord("Exit", &current_entrance.exit_, 50.f, true);
ImGui::Separator();
ImGui::Text("Camera Boundaries");
ImGui::Separator();
ImGui::Text("\t\t\t\t\tNorth East South West");
gui::InputHexByte("Quadrant", &current_entrance.camera_boundary_qn_, 50.f,
true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qe_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qw_, 50.f, true);
gui::InputHexByte("Full room", &current_entrance.camera_boundary_fn_, 50.f,
true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fe_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fw_, 50.f, true);
if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (int i = 0; i < 0x8C; i++) {
// The last seven are the spawn points
auto entrance_name = absl::StrFormat("Spawn Point %d", i - 0x85);
if (i < 0x85) {
entrance_name = std::string(zelda3::kEntranceNames[i]);
}
rom_->resource_label()->SelectableLabelWithNameEdit(
current_entrance_id_ == i, "Dungeon Entrance Names",
util::HexByte(i), entrance_name);
if (ImGui::IsItemClicked()) {
current_entrance_id_ = i;
if (i < entrances_->size()) {
int room_id = (*entrances_)[i].room_;
// Notify the dungeon editor about room selection
if (room_selected_callback_) {
room_selected_callback_(room_id);
}
}
}
}
}
EndChild();
}
} // namespace yaze::editor

View File

@@ -0,0 +1,64 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_ROOM_SELECTOR_H
#include <functional>
#include "imgui/imgui.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/room_entrance.h"
#include "zelda3/dungeon/room.h"
namespace yaze {
namespace editor {
/**
* @brief Handles room and entrance selection UI
*/
class DungeonRoomSelector {
public:
explicit DungeonRoomSelector(Rom* rom = nullptr) : rom_(rom) {}
void Draw();
void DrawRoomSelector();
void DrawEntranceSelector();
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
// Room selection
void set_current_room_id(uint16_t room_id) { current_room_id_ = room_id; }
int current_room_id() const { return current_room_id_; }
void set_active_rooms(const ImVector<int>& rooms) { active_rooms_ = rooms; }
const ImVector<int>& active_rooms() const { return active_rooms_; }
ImVector<int>& mutable_active_rooms() { return active_rooms_; }
// Entrance selection
void set_current_entrance_id(int entrance_id) { current_entrance_id_ = entrance_id; }
int current_entrance_id() const { return current_entrance_id_; }
// Room data access
void set_rooms(std::array<zelda3::Room, 0x128>* rooms) { rooms_ = rooms; }
void set_entrances(std::array<zelda3::RoomEntrance, 0x8C>* entrances) { entrances_ = entrances; }
// Callback for room selection events
void set_room_selected_callback(std::function<void(int)> callback) {
room_selected_callback_ = callback;
}
private:
Rom* rom_ = nullptr;
uint16_t current_room_id_ = 0;
int current_entrance_id_ = 0;
ImVector<int> active_rooms_;
std::array<zelda3::Room, 0x128>* rooms_ = nullptr;
std::array<zelda3::RoomEntrance, 0x8C>* entrances_ = nullptr;
// Callback for room selection events
std::function<void(int)> room_selected_callback_;
};
} // namespace editor
} // namespace yaze
#endif

View File

@@ -0,0 +1,151 @@
#include "dungeon_toolset.h"
#include <algorithm>
#include <array>
#include "app/gui/icons.h"
#include "imgui/imgui.h"
namespace yaze::editor {
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::EndTable;
using ImGui::RadioButton;
using ImGui::TableNextColumn;
using ImGui::TableSetupColumn;
using ImGui::Text;
void DungeonToolset::Draw() {
if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) {
static std::array<const char*, 16> tool_names = {
"Undo", "Redo", "Separator", "All", "BG1", "BG2",
"BG3", "Separator", "Object", "Sprite", "Item", "Entrance",
"Door", "Chest", "Block", "Palette"};
std::ranges::for_each(tool_names,
[](const char* name) { TableSetupColumn(name); });
// Undo button
TableNextColumn();
if (Button(ICON_MD_UNDO)) {
if (undo_callback_) undo_callback_();
}
// Redo button
TableNextColumn();
if (Button(ICON_MD_REDO)) {
if (redo_callback_) redo_callback_();
}
// Separator
TableNextColumn();
Text(ICON_MD_MORE_VERT);
// Background layer selection
TableNextColumn();
if (RadioButton("All", background_type_ == kBackgroundAny)) {
background_type_ = kBackgroundAny;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show all background layers");
}
TableNextColumn();
if (RadioButton("BG1", background_type_ == kBackground1)) {
background_type_ = kBackground1;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show background layer 1 only");
}
TableNextColumn();
if (RadioButton("BG2", background_type_ == kBackground2)) {
background_type_ = kBackground2;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show background layer 2 only");
}
TableNextColumn();
if (RadioButton("BG3", background_type_ == kBackground3)) {
background_type_ = kBackground3;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Show background layer 3 only");
}
// Separator
TableNextColumn();
Text(ICON_MD_MORE_VERT);
// Placement mode selection
TableNextColumn();
if (RadioButton(ICON_MD_SQUARE, placement_type_ == kObject)) {
placement_type_ = kObject;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Objects");
}
TableNextColumn();
if (RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
placement_type_ = kSprite;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Sprites");
}
TableNextColumn();
if (RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
placement_type_ = kItem;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Items");
}
TableNextColumn();
if (RadioButton(ICON_MD_NAVIGATION, placement_type_ == kEntrance)) {
placement_type_ = kEntrance;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Entrances");
}
TableNextColumn();
if (RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
placement_type_ = kDoor;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Doors");
}
TableNextColumn();
if (RadioButton(ICON_MD_INVENTORY, placement_type_ == kChest)) {
placement_type_ = kChest;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Chests");
}
TableNextColumn();
if (RadioButton(ICON_MD_VIEW_MODULE, placement_type_ == kBlock)) {
placement_type_ = kBlock;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Blocks");
}
// Palette button
TableNextColumn();
if (Button(ICON_MD_PALETTE)) {
if (palette_toggle_callback_) palette_toggle_callback_();
}
ImGui::EndTable();
}
ImGui::Separator();
ImGui::Text("Instructions: Click to place objects, Ctrl+Click to select, drag to move");
}
} // namespace yaze::editor

View File

@@ -0,0 +1,69 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H
#include <functional>
#include <array>
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
/**
* @brief Handles the dungeon editor toolset UI
*
* This component manages the toolbar with placement modes, background layer
* selection, and other editing tools.
*/
class DungeonToolset {
public:
enum BackgroundType {
kNoBackground,
kBackground1,
kBackground2,
kBackground3,
kBackgroundAny,
};
enum PlacementType {
kNoType,
kObject, // Object editing mode
kSprite, // Sprite editing mode
kItem, // Item placement mode
kEntrance, // Entrance/exit editing mode
kDoor, // Door configuration mode
kChest, // Chest management mode
kBlock // Legacy block mode
};
DungeonToolset() = default;
void Draw();
// Getters
BackgroundType background_type() const { return background_type_; }
PlacementType placement_type() const { return placement_type_; }
// Setters
void set_background_type(BackgroundType type) { background_type_ = type; }
void set_placement_type(PlacementType type) { placement_type_ = type; }
// Callbacks
void SetUndoCallback(std::function<void()> callback) { undo_callback_ = callback; }
void SetRedoCallback(std::function<void()> callback) { redo_callback_ = callback; }
void SetPaletteToggleCallback(std::function<void()> callback) { palette_toggle_callback_ = callback; }
private:
BackgroundType background_type_ = kBackgroundAny;
PlacementType placement_type_ = kNoType;
// Callbacks for editor actions
std::function<void()> undo_callback_;
std::function<void()> redo_callback_;
std::function<void()> palette_toggle_callback_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_TOOLSET_H

View File

@@ -0,0 +1,87 @@
#include "dungeon_usage_tracker.h"
#include "imgui/imgui.h"
namespace yaze::editor {
void DungeonUsageTracker::CalculateUsageStats(const std::array<zelda3::Room, 0x128>& rooms) {
blockset_usage_.clear();
spriteset_usage_.clear();
palette_usage_.clear();
for (const auto& room : rooms) {
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
blockset_usage_[room.blockset] = 1;
} else {
blockset_usage_[room.blockset] += 1;
}
if (spriteset_usage_.find(room.spriteset) == spriteset_usage_.end()) {
spriteset_usage_[room.spriteset] = 1;
} else {
spriteset_usage_[room.spriteset] += 1;
}
if (palette_usage_.find(room.palette) == palette_usage_.end()) {
palette_usage_[room.palette] = 1;
} else {
palette_usage_[room.palette] += 1;
}
}
}
void DungeonUsageTracker::DrawUsageStats() {
if (ImGui::Button("Refresh")) {
ClearUsageStats();
}
ImGui::Text("Usage Statistics");
ImGui::Separator();
ImGui::Text("Blocksets: %zu used", blockset_usage_.size());
ImGui::Text("Spritesets: %zu used", spriteset_usage_.size());
ImGui::Text("Palettes: %zu used", palette_usage_.size());
ImGui::Separator();
// Detailed usage breakdown
if (ImGui::CollapsingHeader("Blockset Usage")) {
for (const auto& [blockset, count] : blockset_usage_) {
ImGui::Text("Blockset 0x%02X: %d rooms", blockset, count);
}
}
if (ImGui::CollapsingHeader("Spriteset Usage")) {
for (const auto& [spriteset, count] : spriteset_usage_) {
ImGui::Text("Spriteset 0x%02X: %d rooms", spriteset, count);
}
}
if (ImGui::CollapsingHeader("Palette Usage")) {
for (const auto& [palette, count] : palette_usage_) {
ImGui::Text("Palette 0x%02X: %d rooms", palette, count);
}
}
}
void DungeonUsageTracker::DrawUsageGrid() {
// TODO: Implement usage grid visualization
ImGui::Text("Usage grid visualization not yet implemented");
}
void DungeonUsageTracker::RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
uint16_t& selected_set, int spriteset_offset) {
// TODO: Implement set usage rendering
ImGui::Text("Set usage rendering not yet implemented");
}
void DungeonUsageTracker::ClearUsageStats() {
selected_blockset_ = 0xFFFF;
selected_spriteset_ = 0xFFFF;
selected_palette_ = 0xFFFF;
spriteset_usage_.clear();
blockset_usage_.clear();
palette_usage_.clear();
}
} // namespace yaze::editor

View File

@@ -0,0 +1,57 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_USAGE_TRACKER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_USAGE_TRACKER_H
#include "absl/container/flat_hash_map.h"
#include "app/zelda3/dungeon/room.h"
namespace yaze {
namespace editor {
/**
* @brief Tracks and analyzes usage statistics for dungeon resources
*
* This component manages blockset, spriteset, and palette usage statistics
* across all dungeon rooms, providing insights for optimization.
*/
class DungeonUsageTracker {
public:
DungeonUsageTracker() = default;
// Statistics calculation
void CalculateUsageStats(const std::array<zelda3::Room, 0x128>& rooms);
void DrawUsageStats();
void DrawUsageGrid();
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
uint16_t& selected_set, int spriteset_offset = 0x00);
// Data access
const absl::flat_hash_map<uint16_t, int>& GetBlocksetUsage() const { return blockset_usage_; }
const absl::flat_hash_map<uint16_t, int>& GetSpritesetUsage() const { return spriteset_usage_; }
const absl::flat_hash_map<uint16_t, int>& GetPaletteUsage() const { return palette_usage_; }
// Selection state
uint16_t GetSelectedBlockset() const { return selected_blockset_; }
uint16_t GetSelectedSpriteset() const { return selected_spriteset_; }
uint16_t GetSelectedPalette() const { return selected_palette_; }
void SetSelectedBlockset(uint16_t blockset) { selected_blockset_ = blockset; }
void SetSelectedSpriteset(uint16_t spriteset) { selected_spriteset_ = spriteset; }
void SetSelectedPalette(uint16_t palette) { selected_palette_ = palette; }
// Clear data
void ClearUsageStats();
private:
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
absl::flat_hash_map<uint16_t, int> blockset_usage_;
absl::flat_hash_map<uint16_t, int> palette_usage_;
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
uint16_t selected_spriteset_ = 0xFFFF;
uint16_t selected_palette_ = 0xFFFF;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_USAGE_TRACKER_H

View File

@@ -1,7 +0,0 @@
#include "editor.h"
namespace yaze {
namespace editor {
} // namespace editor
} // namespace yaze

View File

@@ -1,21 +1,36 @@
set(
YAZE_APP_EDITOR_SRC
app/editor/editor.cc
app/editor/editor_manager.cc
app/editor/dungeon/dungeon_editor.cc
app/editor/dungeon/dungeon_room_selector.cc
app/editor/dungeon/dungeon_canvas_viewer.cc
app/editor/dungeon/dungeon_object_selector.cc
app/editor/dungeon/dungeon_toolset.cc
app/editor/dungeon/dungeon_object_interaction.cc
app/editor/dungeon/dungeon_renderer.cc
app/editor/dungeon/dungeon_room_loader.cc
app/editor/dungeon/dungeon_usage_tracker.cc
app/editor/overworld/overworld_editor.cc
app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc
app/editor/message/message_editor.cc
app/editor/message/message_data.cc
app/editor/message/message_preview.cc
app/editor/code/assembly_editor.cc
app/editor/graphics/screen_editor.cc
app/editor/graphics/graphics_editor.cc
app/editor/graphics/palette_editor.cc
app/editor/graphics/tile16_editor.cc
app/editor/overworld/tile16_editor.cc
app/editor/overworld/map_properties.cc
app/editor/graphics/gfx_group_editor.cc
app/editor/overworld/entity.cc
app/editor/system/settings_editor.cc
app/editor/system/command_manager.cc
app/editor/system/extension_manager.cc
app/editor/system/shortcut_manager.cc
app/editor/system/popup_manager.cc
app/test/test_manager.cc
app/test/integrated_test_suite.h
app/test/rom_dependent_test_suite.h
app/test/unit_test_suite.h
)

View File

@@ -2,13 +2,17 @@
#define YAZE_APP_CORE_EDITOR_H
#include <array>
#include <vector>
#include <functional>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/editor/system/command_manager.h"
#include "app/editor/system/constant_manager.h"
#include "app/editor/system/extension_manager.h"
#include "app/editor/system/history_manager.h"
#include "app/editor/system/resource_manager.h"
#include "app/editor/system/popup_manager.h"
#include "app/editor/system/shortcut_manager.h"
namespace yaze {
@@ -19,11 +23,26 @@ namespace yaze {
namespace editor {
struct EditorContext {
ConstantManager constant_manager;
CommandManager command_manager;
ExtensionManager extension_manager;
HistoryManager history_manager;
ResourceManager resource_manager;
PopupManager* popup_manager = nullptr;
ShortcutManager shortcut_manager;
// Cross-session shared clipboard for editor data transfers
struct SharedClipboard {
// Overworld tile16 selection payload
bool has_overworld_tile16 = false;
std::vector<int> overworld_tile16_ids;
int overworld_width = 0; // in tile16 units
int overworld_height = 0; // in tile16 units
void Clear() {
has_overworld_tile16 = false;
overworld_tile16_ids.clear();
overworld_width = 0;
overworld_height = 0;
}
} shared_clipboard;
};
enum class EditorType {
@@ -39,7 +58,7 @@ enum class EditorType {
kSettings,
};
constexpr std::array<const char *, 10> kEditorNames = {
constexpr std::array<const char*, 10> kEditorNames = {
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
"Palette", "Screen", "Sprite", "Message", "Settings",
};
@@ -55,6 +74,18 @@ class Editor {
Editor() = default;
virtual ~Editor() = default;
// Initialization of the editor, no ROM assets.
virtual void Initialize() = 0;
// Initialization of ROM assets.
virtual absl::Status Load() = 0;
// Save the editor state.
virtual absl::Status Save() = 0;
// Update the editor state, ran every frame.
virtual absl::Status Update() = 0;
virtual absl::Status Cut() = 0;
virtual absl::Status Copy() = 0;
virtual absl::Status Paste() = 0;
@@ -62,15 +93,41 @@ class Editor {
virtual absl::Status Undo() = 0;
virtual absl::Status Redo() = 0;
virtual absl::Status Update() = 0;
virtual absl::Status Find() = 0;
virtual absl::Status Clear() { return absl::OkStatus(); }
EditorType type() const { return type_; }
void set_context(EditorContext* context) { context_ = context; }
bool* active() { return &active_; }
void set_active(bool active) { active_ = active; }
// ROM loading state helpers (default implementations)
virtual bool IsRomLoaded() const { return false; }
virtual std::string GetRomStatus() const { return "ROM state not implemented"; }
protected:
bool active_ = false;
EditorType type_;
EditorContext context_;
EditorContext* context_ = nullptr;
// Helper method for ROM access with safety check
template<typename T>
absl::StatusOr<T> SafeRomAccess(std::function<T()> accessor, const std::string& operation = "") const {
if (!IsRomLoaded()) {
return absl::FailedPreconditionError(
operation.empty() ? "ROM not loaded" :
absl::StrFormat("%s: ROM not loaded", operation));
}
try {
return accessor();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrFormat(
"%s: %s", operation.empty() ? "ROM access failed" : operation, e.what()));
}
}
};
} // namespace editor

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,9 @@
#define IMGUI_DEFINE_MATH_OPERATORS
#include <deque>
#include <vector>
#include "absl/status/status.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
@@ -15,15 +18,56 @@
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/system/popup_manager.h"
#include "app/editor/system/toast_manager.h"
#include "app/editor/system/settings_editor.h"
#include "app/emu/emulator.h"
#include "app/gui/input.h"
#include "app/core/features.h"
#include "app/rom.h"
#include "yaze_config.h"
namespace yaze {
namespace editor {
/**
* @class EditorSet
* @brief Contains a complete set of editors for a single ROM instance
*/
class EditorSet {
public:
explicit EditorSet(Rom* rom = nullptr)
: assembly_editor_(rom),
dungeon_editor_(rom),
graphics_editor_(rom),
music_editor_(rom),
overworld_editor_(rom),
palette_editor_(rom),
screen_editor_(rom),
sprite_editor_(rom),
settings_editor_(rom),
message_editor_(rom),
memory_editor_(rom) {
active_editors_ = {&overworld_editor_, &dungeon_editor_, &graphics_editor_,
&palette_editor_, &sprite_editor_, &message_editor_,
&music_editor_, &screen_editor_, &settings_editor_,
&assembly_editor_};
}
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
MusicEditor music_editor_;
OverworldEditor overworld_editor_;
PaletteEditor palette_editor_;
ScreenEditor screen_editor_;
SpriteEditor sprite_editor_;
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
std::vector<Editor*> active_editors_;
};
/**
* @class EditorManager
* @brief The EditorManager controls the main editor window and manages the
@@ -35,75 +79,174 @@ namespace editor {
* variable points to the currently active editor in the tab view.
*
*/
class EditorManager : public SharedRom {
class EditorManager {
public:
EditorManager() {
current_editor_ = &overworld_editor_;
active_editors_.push_back(&overworld_editor_);
active_editors_.push_back(&dungeon_editor_);
active_editors_.push_back(&graphics_editor_);
active_editors_.push_back(&palette_editor_);
active_editors_.push_back(&sprite_editor_);
active_editors_.push_back(&message_editor_);
active_editors_.push_back(&screen_editor_);
std::stringstream ss;
ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
<< YAZE_VERSION_PATCH;
ss >> version_;
context_.popup_manager = popup_manager_.get();
}
void Initialize(std::string filename = "");
void Initialize(const std::string& filename = "");
absl::Status Update();
void DrawMenuBar();
auto emulator() -> emu::Emulator & { return emulator_; }
auto quit() { return quit_; }
auto emulator() -> emu::Emulator& { return emulator_; }
auto quit() const { return quit_; }
auto version() const { return version_; }
absl::Status SetCurrentRom(Rom* rom);
auto GetCurrentRom() -> Rom* { return current_rom_; }
auto GetCurrentEditorSet() -> EditorSet* { return current_editor_set_; }
// Get current session's feature flags (falls back to global if no session)
core::FeatureFlags::Flags* GetCurrentFeatureFlags() {
size_t current_index = GetCurrentSessionIndex();
if (current_index < sessions_.size()) {
return &sessions_[current_index].feature_flags;
}
return &core::FeatureFlags::get(); // Fallback to global
}
private:
void ManageActiveEditors();
void ManageKeyboardShortcuts();
void DrawStatusPopup();
void DrawAboutPopup();
void DrawInfoPopup();
void DrawYazeMenu();
void DrawYazeMenuBar();
void LoadRom();
void SaveRom();
void OpenRomOrProject(const std::string &filename);
void DrawHomepage();
void DrawWelcomeScreen();
absl::Status DrawRomSelector();
absl::Status LoadRom();
absl::Status LoadAssets();
absl::Status SaveRom();
absl::Status OpenRomOrProject(const std::string& filename);
// Enhanced project management
absl::Status CreateNewProject(const std::string& template_name = "Basic ROM Hack");
absl::Status OpenProject();
absl::Status SaveProject();
absl::Status SaveProjectAs();
absl::Status ImportProject(const std::string& project_path);
absl::Status RepairCurrentProject();
void ShowProjectHelp();
// Testing system
void InitializeTestSuites();
bool quit_ = false;
bool about_ = false;
bool rom_info_ = false;
bool backup_rom_ = false;
bool save_new_auto_ = true;
bool show_status_ = false;
bool rom_assets_loaded_ = false;
bool autosave_enabled_ = false;
float autosave_interval_secs_ = 120.0f;
float autosave_timer_ = 0.0f;
bool new_project_menu = false;
bool show_emulator_ = false;
bool show_memory_editor_ = false;
bool show_asm_editor_ = false;
bool show_imgui_metrics_ = false;
bool show_imgui_demo_ = false;
bool show_palette_editor_ = false;
bool show_resource_label_manager = false;
bool show_workspace_layout = false;
bool show_save_workspace_preset_ = false;
bool show_load_workspace_preset_ = false;
bool show_session_switcher_ = false;
bool show_session_manager_ = false;
bool show_layout_presets_ = false;
bool show_homepage_ = true;
bool show_command_palette_ = false;
bool show_global_search_ = false;
bool show_session_rename_dialog_ = false;
bool show_welcome_screen_ = false;
size_t session_to_rename_ = 0;
char session_rename_buffer_[256] = {};
// Testing interface
bool show_test_dashboard_ = false;
std::string version_ = "";
std::string settings_filename_ = "settings.ini";
float font_global_scale_ = 1.0f;
std::vector<std::string> workspace_presets_;
std::string last_workspace_preset_ = "";
std::string status_message_ = "";
bool workspace_presets_loaded_ = false;
absl::Status status_;
emu::Emulator emulator_;
std::vector<Editor *> active_editors_;
Project current_project_;
EditorContext editor_context_;
struct RomSession {
Rom rom;
EditorSet editors;
std::string custom_name; // User-defined session name
std::string filepath; // ROM filepath for duplicate detection
core::FeatureFlags::Flags feature_flags; // Per-session feature flags
Editor *current_editor_ = nullptr;
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
MusicEditor music_editor_;
OverworldEditor overworld_editor_{*rom()};
PaletteEditor palette_editor_;
ScreenEditor screen_editor_;
SpriteEditor sprite_editor_;
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
RomSession() = default;
explicit RomSession(Rom&& r)
: rom(std::move(r)), editors(&rom) {
filepath = rom.filename();
// Initialize with default feature flags
feature_flags = core::FeatureFlags::Flags{};
}
// Get display name (custom name or ROM title)
std::string GetDisplayName() const {
if (!custom_name.empty()) {
return custom_name;
}
return rom.title().empty() ? "Untitled Session" : rom.title();
}
};
std::deque<RomSession> sessions_;
Rom* current_rom_ = nullptr;
EditorSet* current_editor_set_ = nullptr;
Editor* current_editor_ = nullptr;
EditorSet blank_editor_set_{};
core::YazeProject current_project_;
EditorContext context_;
std::unique_ptr<PopupManager> popup_manager_;
ToastManager toast_manager_;
// Settings helpers
void LoadUserSettings();
void SaveUserSettings();
void RefreshWorkspacePresets();
void SaveWorkspacePreset(const std::string& name);
void LoadWorkspacePreset(const std::string& name);
// Workspace management
void CreateNewSession();
void DuplicateCurrentSession();
void CloseCurrentSession();
void SwitchToSession(size_t index);
size_t GetCurrentSessionIndex() const;
void ResetWorkspaceLayout();
// Multi-session editor management
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index) const;
void SaveWorkspaceLayout();
void LoadWorkspaceLayout();
void ShowAllWindows();
void HideAllWindows();
void MaximizeCurrentWindow();
void RestoreAllWindows();
void CloseAllFloatingWindows();
void LoadDeveloperLayout();
void LoadDesignerLayout();
void LoadModderLayout();
// Session management helpers
bool HasDuplicateSession(const std::string& filepath);
void RenameSession(size_t index, const std::string& new_name);
std::string GenerateUniqueEditorTitle(EditorType type, size_t session_index);
// UI drawing helpers
void DrawSessionSwitcher();
void DrawSessionManager();
void DrawLayoutPresets();
void DrawSessionRenameDialog();
};
} // namespace editor

View File

@@ -0,0 +1,43 @@
#ifndef YAZE_APP_EDITOR_SAFEGUARDS_H
#define YAZE_APP_EDITOR_SAFEGUARDS_H
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/rom.h"
namespace yaze {
namespace editor {
// Macro for checking ROM loading state in editor methods
#define REQUIRE_ROM_LOADED(rom_ptr, operation) \
do { \
if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \
return absl::FailedPreconditionError( \
absl::StrFormat("%s: ROM not loaded", (operation))); \
} \
} while (0)
// Macro for ROM state checking with custom error message
#define CHECK_ROM_STATE(rom_ptr, message) \
do { \
if (!(rom_ptr) || !(rom_ptr)->is_loaded()) { \
return absl::FailedPreconditionError(message); \
} \
} while (0)
// Helper function for generating consistent ROM status messages
inline std::string GetRomStatusMessage(const Rom* rom) {
if (!rom) return "No ROM loaded";
if (!rom->is_loaded()) return "ROM failed to load";
return absl::StrFormat("ROM loaded: %s", rom->title());
}
// Helper function to check if ROM is in a valid state for editing
inline bool IsRomReadyForEditing(const Rom* rom) {
return rom && rom->is_loaded() && !rom->title().empty();
}
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SAFEGUARDS_H

View File

@@ -2,7 +2,7 @@
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/arena.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
@@ -112,7 +112,7 @@ void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
BeginGroup();
for (int i = 0; i < 8; i++) {
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
auto sheet = rom()->gfx_sheets().at(sheet_id);
auto &sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 22);
}
@@ -165,7 +165,7 @@ void GfxGroupEditor::DrawRoomsetViewer() {
BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
auto sheet = rom()->gfx_sheets().at(sheet_id);
auto &sheet = gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 23);
}
@@ -203,7 +203,8 @@ void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
BeginGroup();
for (int i = 0; i < 4; i++) {
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
auto sheet = rom()->gfx_sheets().at(115 + sheet_id);
auto &sheet =
gfx::Arena::Get().mutable_gfx_sheets()->at(115 + sheet_id);
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
0x20, true, false, 24);
}
@@ -235,6 +236,9 @@ void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
} // namespace
void GfxGroupEditor::DrawPaletteViewer() {
if (!rom()->is_loaded()) {
return;
}
gui::InputHexByte("Selected Paletteset", &selected_paletteset_);
if (selected_paletteset_ >= 71) {
selected_paletteset_ = 71;

View File

@@ -13,7 +13,7 @@ namespace editor {
* @class GfxGroupEditor
* @brief Manage graphics group configurations in a Rom.
*/
class GfxGroupEditor : public SharedRom {
class GfxGroupEditor {
public:
absl::Status Update();
@@ -27,6 +27,8 @@ class GfxGroupEditor : public SharedRom {
void SetSelectedSpriteset(uint8_t spriteset) {
selected_spriteset_ = spriteset;
}
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private:
uint8_t selected_blockset_ = 0;
@@ -39,6 +41,7 @@ class GfxGroupEditor : public SharedRom {
gui::Canvas spriteset_canvas_;
gfx::SnesPalette palette_;
Rom* rom_ = nullptr;
};
} // namespace editor

View File

@@ -4,10 +4,11 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "app/core/platform/clipboard.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/compression.h"
#include "app/gfx/scad_format.h"
@@ -41,15 +42,20 @@ constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_SizingFixedFit;
void GraphicsEditor::Initialize() {}
absl::Status GraphicsEditor::Load() { return absl::OkStatus(); }
absl::Status GraphicsEditor::Update() {
if (ImGui::BeginTabBar("##TabBar")) {
status_ = UpdateGfxEdit();
TAB_ITEM("Sheet Browser")
if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(rom()->gfx_sheets());
if (ImGui::BeginTabItem("Sheet Browser")) {
if (asset_browser_.Initialized == false) {
asset_browser_.Initialize(gfx::Arena::Get().gfx_sheets());
}
asset_browser_.Draw(gfx::Arena::Get().gfx_sheets());
ImGui::EndTabItem();
}
asset_browser_.Draw(rom()->gfx_sheets());
END_TAB_ITEM()
status_ = UpdateScadView();
status_ = UpdateLinkGfxView();
ImGui::EndTabBar();
@@ -67,17 +73,16 @@ absl::Status GraphicsEditor::UpdateGfxEdit() {
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
NEXT_COLUMN();
ImGui::TableNextColumn();
status_ = UpdateGfxSheetList();
NEXT_COLUMN();
ImGui::TableNextColumn();
if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
}
NEXT_COLUMN();
ImGui::TableNextColumn();
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
@@ -116,25 +121,35 @@ void GraphicsEditor::DrawGfxEditToolset() {
TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) {
#if YAZE_LIB_PNG == 1
std::vector<uint8_t> png_data =
rom()->gfx_sheets().at(current_sheet_).GetPngData();
gfx::Arena::Get().gfx_sheets().at(current_sheet_).GetPngData();
core::CopyImageToClipboard(png_data);
#else
// PNG support disabled - show message or alternative action
status_ = absl::UnimplementedError("PNG export not available in this build");
#endif
}
HOVER_HINT("Copy to Clipboard");
TableNextColumn();
if (Button(ICON_MD_CONTENT_PASTE)) {
#if YAZE_LIB_PNG == 1
std::vector<uint8_t> png_data;
int width, height;
core::GetImageFromClipboard(png_data, width, height);
if (png_data.size() > 0) {
rom()
->mutable_gfx_sheets()
gfx::Arena::Get()
.mutable_gfx_sheets()
->at(current_sheet_)
.Create(width, height, 8, png_data);
Renderer::GetInstance().UpdateBitmap(
&rom()->mutable_gfx_sheets()->at(current_sheet_));
Renderer::Get().UpdateBitmap(
&gfx::Arena::Get().mutable_gfx_sheets()->at(current_sheet_));
}
#else
// PNG support disabled - show message or alternative action
status_ = absl::UnimplementedError("PNG import not available in this build");
#endif
}
HOVER_HINT("Paste from Clipboard");
@@ -153,9 +168,9 @@ void GraphicsEditor::DrawGfxEditToolset() {
}
TableNextColumn();
auto bitmap = rom()->gfx_sheets()[current_sheet_];
auto bitmap = gfx::Arena::Get().gfx_sheets()[current_sheet_];
auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) {
for (int i = 0; i < palette.size(); i++) {
ImGui::SameLine();
auto color =
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
@@ -192,7 +207,7 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
int key = 0;
for (auto& value : rom()->gfx_sheets()) {
for (auto& value : gfx::Arena::Get().gfx_sheets()) {
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
ImVec2(0x100 + 1, 0x40 + 1), true,
ImGuiWindowFlags_NoDecoration);
@@ -281,17 +296,18 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap = rom()->mutable_gfx_sheets()->at(sheet_id);
gfx::Bitmap& current_bitmap =
gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id);
auto draw_tile_event = [&]() {
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
current_color_);
Renderer::GetInstance().UpdateBitmap(&current_bitmap);
Renderer::Get().UpdateBitmap(&current_bitmap);
};
current_sheet_canvas_.UpdateColorPainter(
rom()->mutable_gfx_sheets()->at(sheet_id), current_color_,
draw_tile_event, tile_size_, current_scale_);
gfx::Arena::Get().mutable_gfx_sheets()->at(sheet_id),
current_color_, draw_tile_event, tile_size_, current_scale_);
ImGui::EndChild();
ImGui::EndTabItem();
@@ -323,7 +339,7 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
current_sheet_ = id;
// ImVec2(0x100, 0x40),
current_sheet_canvas_.UpdateColorPainter(
rom()->mutable_gfx_sheets()->at(id), current_color_,
gfx::Arena::Get().mutable_gfx_sheets()->at(id), current_color_,
[&]() {
},
@@ -359,13 +375,12 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
palette);
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()
->mutable_gfx_sheets()
->data()[current_sheet_]
.ApplyPaletteWithTransparent(palette, edit_palette_sub_index_));
Renderer::GetInstance().UpdateBitmap(
&rom()->mutable_gfx_sheets()->data()[current_sheet_]);
gfx::Arena::Get()
.mutable_gfx_sheets()
->data()[current_sheet_]
.SetPaletteWithTransparent(palette, edit_palette_sub_index_);
Renderer::Get().UpdateBitmap(
&gfx::Arena::Get().mutable_gfx_sheets()->data()[current_sheet_]);
refresh_graphics_ = false;
}
}
@@ -382,29 +397,29 @@ absl::Status GraphicsEditor::UpdateLinkGfxView() {
ImGui::TableHeadersRow();
NEXT_COLUMN();
ImGui::TableNextColumn();
link_canvas_.DrawBackground();
link_canvas_.DrawGrid(16.0f);
int i = 0;
for (auto link_sheet : *rom()->mutable_link_graphics()) {
for (auto& link_sheet : link_sheets_) {
int x_offset = 0;
int y_offset = gfx::kTilesheetHeight * i * 4;
link_canvas_.DrawContextMenu(&link_sheet);
link_canvas_.DrawContextMenu();
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
i++;
}
link_canvas_.DrawOverlay();
link_canvas_.DrawGrid();
NEXT_COLUMN();
ImGui::TableNextColumn();
ImGui::Text("Placeholder");
NEXT_COLUMN();
ImGui::TableNextColumn();
if (ImGui::Button("Load Link Graphics (Experimental)")) {
if (rom()->is_loaded()) {
// Load Links graphics from the ROM
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
ASSIGN_OR_RETURN(link_sheets_, LoadLinkGraphics(*rom()));
// Split it into the pose data frames
// Create an animation step display for the poses
@@ -461,9 +476,9 @@ absl::Status GraphicsEditor::UpdateScadView() {
// TODO: Implement the Super Donkey 1 graphics decompression
// if (refresh_graphics_) {
// for (int i = 0; i < kNumGfxSheets; i++) {
// status_ = graphics_bin_[i].ApplyPalette(
// status_ = graphics_bin_[i].SetPalette(
// col_file_palette_group_[current_palette_index_]);
// Renderer::GetInstance().UpdateBitmap(&graphics_bin_[i]);
// Renderer::Get().UpdateBitmap(&graphics_bin_[i]);
// }
// refresh_graphics_ = false;
// }
@@ -488,16 +503,15 @@ absl::Status GraphicsEditor::UpdateScadView() {
absl::Status GraphicsEditor::DrawToolset() {
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
if (ImGui::BeginTable("GraphicsToolset", 1, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) {
for (const auto& name : kGfxToolsetColumnNames)
ImGui::TableSetupColumn(name.data());
TableNextColumn();
if (Button(ICON_MD_MEMORY)) {
if (Button(absl::StrCat(ICON_MD_MEMORY, "Open Memory Editor").c_str())) {
if (!open_memory_editor_) {
open_memory_editor_ = true;
} else {
@@ -505,8 +519,6 @@ absl::Status GraphicsEditor::DrawToolset() {
}
}
TEXT_COLUMN("Open Memory Editor") // Separator
ImGui::EndTable();
}
return absl::OkStatus();
@@ -532,13 +544,13 @@ absl::Status GraphicsEditor::DrawCgxImport() {
}
if (ImGui::Button("Load CGX Data")) {
status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
decoded_cgx_, extra_cgx_data_);
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
decoded_cgx_, extra_cgx_data_);
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
if (col_file_) {
cgx_bitmap_.ApplyPalette(decoded_col_);
Renderer::GetInstance().RenderBitmap(&cgx_bitmap_);
cgx_bitmap_.SetPalette(decoded_col_);
Renderer::Get().RenderBitmap(&cgx_bitmap_);
}
}
@@ -559,17 +571,16 @@ absl::Status GraphicsEditor::DrawScrImport() {
InputInt("SCR Mod", &scr_mod_value_);
if (ImGui::Button("Load Scr Data")) {
status_ =
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
decoded_scr_data_.resize(0x100 * 0x100);
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
decoded_scr_data_, decoded_cgx_);
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
decoded_cgx_);
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
if (scr_loaded_) {
scr_bitmap_.ApplyPalette(decoded_col_);
Renderer::GetInstance().RenderBitmap(&scr_bitmap_);
scr_bitmap_.SetPalette(decoded_col_);
Renderer::Get().RenderBitmap(&scr_bitmap_);
}
}
@@ -599,7 +610,7 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
col_file_palette_ = gfx::SnesPalette(col_data_);
// gigaleak dev format based code
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
decoded_col_ = gfx::DecodeColFile(col_file_path_);
col_file_ = true;
is_open_ = true;
}
@@ -705,7 +716,7 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
const auto clipboard_data =
std::vector<uint8_t>(text, text + strlen(text));
ImGui::MemFree((void*)text);
status_ = temp_rom_.LoadFromBytes(clipboard_data);
status_ = temp_rom_.LoadFromData(clipboard_data);
is_open_ = true;
open_memory_editor_ = true;
}
@@ -755,7 +766,7 @@ absl::Status GraphicsEditor::DrawMemoryEditor() {
absl::Status GraphicsEditor::DecompressImportData(int size) {
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
temp_rom_.data(), current_offset_, size))
temp_rom_.data(), current_offset_, size));
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
bin_bitmap_.Create(gfx::kTilesheetWidth, 0x2000, gfx::kTilesheetDepth,
@@ -765,13 +776,13 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) {
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
bin_bitmap_.SetPalette(col_file_palette_);
} else {
status_ = bin_bitmap_.ApplyPalette(z3_rom_palette_);
bin_bitmap_.SetPalette(z3_rom_palette_);
}
}
Renderer::GetInstance().RenderBitmap(&bin_bitmap_);
Renderer::Get().RenderBitmap(&bin_bitmap_);
gfx_loaded_ = true;
return absl::OkStatus();
@@ -784,12 +795,12 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
std::stoi(offset, nullptr, 16); // convert hex string to int
ASSIGN_OR_RETURN(
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000));
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = gfx_sheets_[i].ApplyPalette(
gfx_sheets_[i].SetPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
@@ -797,10 +808,10 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
gfx_sheets_[i].SetPalette(z3_rom_palette_);
}
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
Renderer::Get().RenderBitmap(&gfx_sheets_[i]);
i++;
}
@@ -809,22 +820,22 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
std::stoi(offset, nullptr, 16); // convert hex string to int
ASSIGN_OR_RETURN(
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000));
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
gfx::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = gfx_sheets_[i].ApplyPalette(
gfx_sheets_[i].SetPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
gfx_sheets_[i].SetPalette(z3_rom_palette_);
}
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
Renderer::Get().RenderBitmap(&gfx_sheets_[i]);
i++;
}
super_donkey_ = true;

View File

@@ -54,18 +54,28 @@ const std::string kSuperDonkeySprites[] = {
* drawing toolsets, palette controls, clipboard imports, experimental features,
* and memory editor.
*/
class GraphicsEditor : public SharedRom, public Editor {
class GraphicsEditor : public Editor {
public:
GraphicsEditor() { type_ = EditorType::kGraphics; }
explicit GraphicsEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kGraphics;
}
void Initialize() override;
absl::Status Load() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
private:
enum class GfxEditMode {
@@ -159,7 +169,7 @@ class GraphicsEditor : public SharedRom, public Editor {
Rom temp_rom_;
Rom tilemap_rom_;
zelda3::Overworld overworld_;
zelda3::Overworld overworld_{&temp_rom_};
MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_;
@@ -176,6 +186,7 @@ class GraphicsEditor : public SharedRom, public Editor {
gfx::Bitmap bin_bitmap_;
gfx::Bitmap link_full_sheet_;
std::array<gfx::Bitmap, kNumGfxSheets> gfx_sheets_;
std::array<gfx::Bitmap, kNumLinkSheets> link_sheets_;
gfx::PaletteGroup col_file_palette_group_;
gfx::SnesPalette z3_rom_palette_;
@@ -192,6 +203,8 @@ class GraphicsEditor : public SharedRom, public Editor {
ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k16x16};
absl::Status status_;
Rom* rom_;
};
} // namespace editor

View File

@@ -4,7 +4,6 @@
#include "absl/strings/str_cat.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/color.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -37,11 +36,8 @@ using ImGui::SetClipboardText;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetColumnIndex;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TreeNode;
using ImGui::TreePop;
using namespace gfx;
@@ -51,12 +47,14 @@ constexpr ImGuiTableFlags kPaletteTableFlags =
constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
constexpr ImGuiColorEditFlags kPalButtonFlags2 = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
constexpr ImGuiColorEditFlags kPalButtonFlags = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
constexpr ImGuiColorEditFlags kColorPopupFlags =
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV |
ImGuiColorEditFlags_DisplayHex;
namespace {
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
@@ -94,7 +92,7 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
static bool init = false;
if (loaded && !init) {
for (int n = 0; n < palette.size(); n++) {
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
auto color = palette[n];
current_palette[n].x = color.rgb().x / 255;
current_palette[n].y = color.rgb().y / 255;
current_palette[n].z = color.rgb().z / 255;
@@ -146,7 +144,7 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
PushID(n);
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
if (ColorButton("##palette", current_palette[n], kPalButtonFlags2,
if (ColorButton("##palette", current_palette[n], kPalButtonFlags,
ImVec2(20, 20)))
color = ImVec4(current_palette[n].x, current_palette[n].y,
current_palette[n].z, color.w); // Preserve alpha!
@@ -170,7 +168,9 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
return absl::OkStatus();
}
absl::Status PaletteEditor::Update() {
void PaletteEditor::Initialize() {}
absl::Status PaletteEditor::Load() {
if (rom()->is_loaded()) {
// Initialize the labels
for (int i = 0; i < kNumPalettes; i++) {
@@ -181,71 +181,166 @@ absl::Status PaletteEditor::Update() {
} else {
return absl::NotFoundError("ROM not open, no palettes to display");
}
return absl::OkStatus();
}
if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableSetupColumn("Palette Sets and Metadata",
ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
absl::Status PaletteEditor::Update() {
static int current_palette_group = 0;
if (BeginTable("paletteGroupsTable", 3, kPaletteTableFlags)) {
TableSetupColumn("Categories", ImGuiTableColumnFlags_WidthFixed, 200);
TableSetupColumn("Palette Editor", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Quick Access", ImGuiTableColumnFlags_WidthStretch);
TableHeadersRow();
TableNextRow();
TableNextColumn();
DrawModifiedColors();
DrawCustomPalette();
Separator();
gui::SnesColorEdit4("Current Color Picker", &current_color_,
ImGuiColorEditFlags_NoAlpha);
Separator();
DisplayCategoryTable();
static int selected_category = 0;
BeginChild("CategoryList", ImVec2(0, GetContentRegionAvail().y), true);
for (int i = 0; i < kNumPalettes; i++) {
const bool is_selected = (selected_category == i);
if (Selectable(std::string(kPaletteCategoryNames[i]).c_str(),
is_selected)) {
selected_category = i;
}
}
EndChild();
TableNextColumn();
gfx_group_editor_.DrawPaletteViewer();
BeginChild("PaletteEditor", ImVec2(0, 0), true);
Text("%s", std::string(kPaletteCategoryNames[selected_category]).c_str());
Separator();
static bool in_use = false;
ImGui::Checkbox("Palette in use? ", &in_use);
Separator();
static std::string palette_notes = "Notes about the palette";
ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
ImVec2(-1, ImGui::GetTextLineHeight() * 4),
ImGuiInputTextFlags_AllowTabInput);
if (rom()->is_loaded()) {
status_ = DrawPaletteGroup(selected_category, true);
}
EndChild();
TableNextColumn();
DrawQuickAccessTab();
EndTable();
}
CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus();
}
void PaletteEditor::DrawQuickAccessTab() {
BeginChild("QuickAccessPalettes", ImVec2(0, 0), true);
Text("Custom Palette");
DrawCustomPalette();
Separator();
// Current color picker with more options
BeginGroup();
Text("Current Color");
gui::SnesColorEdit4("##CurrentColorPicker", &current_color_,
kColorPopupFlags);
char buf[64];
auto col = current_color_.rgb();
int cr = F32_TO_INT8_SAT(col.x / 255.0f);
int cg = F32_TO_INT8_SAT(col.y / 255.0f);
int cb = F32_TO_INT8_SAT(col.z / 255.0f);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "RGB: %d, %d, %d", cr, cg, cb);
Text("%s", buf);
CustomFormatString(buf, IM_ARRAYSIZE(buf), "SNES: $%04X",
current_color_.snes());
Text("%s", buf);
if (Button("Copy to Clipboard")) {
SetClipboardText(buf);
}
EndGroup();
Separator();
// Recently used colors
Text("Recently Used Colors");
for (int i = 0; i < recently_used_colors_.size(); i++) {
PushID(i);
if (i % 8 != 0) SameLine();
ImVec4 displayColor =
gui::ConvertSnesColorToImVec4(recently_used_colors_[i]);
if (ImGui::ColorButton("##recent", displayColor)) {
// Set as current color
current_color_ = recently_used_colors_[i];
}
PopID();
}
EndChild();
}
void PaletteEditor::DrawCustomPalette() {
if (BeginChild("ColorPalette", ImVec2(0, 40), true,
if (BeginChild("ColorPalette", ImVec2(0, 40), ImGuiChildFlags_None,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (int i = 0; i < custom_palette_.size(); i++) {
PushID(i);
SameLine(0.0f, GetStyle().ItemSpacing.y);
gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
ImGuiColorEditFlags_NoInputs);
// Accept a drag drop target which adds a color to the custom_palette_
if (i > 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
// Add a context menu to each color
ImVec4 displayColor = gui::ConvertSnesColorToImVec4(custom_palette_[i]);
bool open_color_picker = ImGui::ColorButton(
absl::StrFormat("##customPal%d", i).c_str(), displayColor);
if (open_color_picker) {
current_color_ = custom_palette_[i];
edit_palette_index_ = i;
ImGui::OpenPopup("CustomPaletteColorEdit");
}
if (BeginPopupContextItem()) {
// Edit color directly in the popup
SnesColor original_color = custom_palette_[i];
if (gui::SnesColorEdit4("Edit Color", &custom_palette_[i],
kColorPopupFlags)) {
// Color was changed, add to recently used
AddRecentlyUsedColor(custom_palette_[i]);
}
if (Button("Delete", ImVec2(-1, 0))) {
custom_palette_.erase(custom_palette_.begin() + i);
}
}
// Handle drag/drop for palette rearrangement
if (BeginDragDropTarget()) {
if (const ImGuiPayload* payload =
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
ImVec4 color = ImVec4(0, 0, 0, 1.0f);
memcpy((float*)&color, payload->Data, sizeof(float));
custom_palette_.push_back(SnesColor(color));
ImVec4 color;
memcpy((float*)&color, payload->Data, sizeof(float) * 3);
color.w = 1.0f; // Set alpha to 1.0
custom_palette_[i] = SnesColor(color);
AddRecentlyUsedColor(custom_palette_[i]);
}
EndDragDropTarget();
}
PopID();
}
SameLine();
if (ImGui::Button("Add Color")) {
if (ImGui::Button("+")) {
custom_palette_.push_back(SnesColor(0x7FFF));
}
SameLine();
if (ImGui::Button("Export to Clipboard")) {
if (ImGui::Button("Clear")) {
custom_palette_.clear();
}
SameLine();
if (ImGui::Button("Export")) {
std::string clipboard;
for (const auto& color : custom_palette_) {
clipboard += absl::StrFormat("$%04X,", color.snes());
@@ -254,89 +349,20 @@ void PaletteEditor::DrawCustomPalette() {
}
}
EndChild();
}
void PaletteEditor::DisplayCategoryTable() {
if (BeginTable("Category Table", 8,
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_SizingStretchSame |
ImGuiTableFlags_Hideable,
ImVec2(0, 0))) {
TableSetupColumn("Weapons and Gear");
TableSetupColumn("Overworld and Area Colors");
TableSetupColumn("Global Sprites");
TableSetupColumn("Sprites Aux1");
TableSetupColumn("Sprites Aux2");
TableSetupColumn("Sprites Aux3");
TableSetupColumn("Maps and Items");
TableSetupColumn("Dungeons");
TableHeadersRow();
TableNextRow();
TableSetColumnIndex(0);
if (TreeNode("Sword")) {
status_ = DrawPaletteGroup(PaletteCategory::kSword);
TreePop();
// Color picker popup for custom palette editing
if (ImGui::BeginPopup("CustomPaletteColorEdit")) {
if (edit_palette_index_ >= 0 &&
edit_palette_index_ < custom_palette_.size()) {
SnesColor original_color = custom_palette_[edit_palette_index_];
if (gui::SnesColorEdit4(
"Edit Color", &custom_palette_[edit_palette_index_],
kColorPopupFlags | ImGuiColorEditFlags_PickerHueWheel)) {
// Color was changed, add to recently used
AddRecentlyUsedColor(custom_palette_[edit_palette_index_]);
}
}
if (TreeNode("Shield")) {
status_ = DrawPaletteGroup(PaletteCategory::kShield);
TreePop();
}
if (TreeNode("Clothes")) {
status_ = DrawPaletteGroup(PaletteCategory::kClothes, true);
TreePop();
}
TableSetColumnIndex(1);
gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
if (TreeNode("World Colors")) {
status_ = DrawPaletteGroup(PaletteCategory::kWorldColors);
TreePop();
}
if (TreeNode("Area Colors")) {
status_ = DrawPaletteGroup(PaletteCategory::kAreaColors);
TreePop();
}
EndChild();
TableSetColumnIndex(2);
status_ = DrawPaletteGroup(PaletteCategory::kGlobalSprites, true);
TableSetColumnIndex(3);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux1);
TableSetColumnIndex(4);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux2);
TableSetColumnIndex(5);
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux3);
TableSetColumnIndex(6);
gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
if (TreeNode("World Map")) {
status_ = DrawPaletteGroup(PaletteCategory::kWorldMap, true);
TreePop();
}
if (TreeNode("Dungeon Map")) {
status_ = DrawPaletteGroup(PaletteCategory::kDungeonMap);
TreePop();
}
if (TreeNode("Triforce")) {
status_ = DrawPaletteGroup(PaletteCategory::kTriforce);
TreePop();
}
if (TreeNode("Crystal")) {
status_ = DrawPaletteGroup(PaletteCategory::kCrystal);
TreePop();
}
EndChild();
TableSetColumnIndex(7);
gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
status_ = DrawPaletteGroup(PaletteCategory::kDungeons, true);
EndChild();
EndTable();
ImGui::EndPopup();
}
}
@@ -350,26 +376,30 @@ absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
rom()->mutable_palette_group()->get_group(palette_group_name.data());
const auto size = palette_group->size();
static bool edit_color = false;
for (int j = 0; j < size; j++) {
gfx::SnesPalette* palette = palette_group->mutable_palette(j);
auto pal_size = palette->size();
BeginGroup();
PushID(j);
BeginGroup();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, palette_group_name.data(), /*key=*/std::to_string(j),
"Unnamed Palette");
EndGroup();
for (int n = 0; n < pal_size; n++) {
PushID(n);
if (!right_side) {
if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
} else {
if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
}
if (n > 0 && n % 8 != 0) SameLine(0.0f, 2.0f);
auto popup_id =
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
// Small icon of the color in the palette
if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
kPalNoAlpha)) {
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
ImVec4 displayColor = gui::ConvertSnesColorToImVec4((*palette)[n]);
if (ImGui::ColorButton(popup_id.c_str(), displayColor)) {
current_color_ = (*palette)[n];
AddRecentlyUsedColor(current_color_);
}
if (BeginPopupContextItem(popup_id.c_str())) {
@@ -377,49 +407,64 @@ absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
}
PopID();
}
SameLine();
rom()->resource_label()->SelectableLabelWithNameEdit(
false, palette_group_name.data(), /*key=*/std::to_string(j),
"Unnamed Palette");
if (right_side) Separator();
PopID();
EndGroup();
if (j < size - 1) {
Separator();
}
}
return absl::OkStatus();
}
void PaletteEditor::DrawModifiedColors() {
if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
ImGuiWindowFlags_HorizontalScrollbar)) {
for (int i = 0; i < history_.size(); i++) {
PushID(i);
gui::SnesColorEdit4("Original ", &history_.GetOriginalColor(i),
ImGuiColorEditFlags_NoInputs);
SameLine(0.0f, GetStyle().ItemSpacing.y);
gui::SnesColorEdit4("Modified ", &history_.GetModifiedColor(i),
ImGuiColorEditFlags_NoInputs);
PopID();
}
void PaletteEditor::AddRecentlyUsedColor(const SnesColor& color) {
// Check if color already exists in recently used
auto it = std::find_if(
recently_used_colors_.begin(), recently_used_colors_.end(),
[&color](const SnesColor& c) { return c.snes() == color.snes(); });
// If found, remove it to re-add at front
if (it != recently_used_colors_.end()) {
recently_used_colors_.erase(it);
}
// Add at front
recently_used_colors_.insert(recently_used_colors_.begin(), color);
// Limit size
if (recently_used_colors_.size() > 16) {
recently_used_colors_.pop_back();
}
EndChild();
}
absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
int j, int n) {
auto col = gfx::ToFloatArray(palette[n]);
auto original_color = palette[n];
if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
/*palette_index=*/j, /*color_index=*/n,
original_color, palette[n]);
palette[n].set_modified(true);
// Add to recently used colors
AddRecentlyUsedColor(palette[n]);
}
// Color information display
char buf[64];
int cr = F32_TO_INT8_SAT(col[0]);
int cg = F32_TO_INT8_SAT(col[1]);
int cb = F32_TO_INT8_SAT(col[2]);
Text("RGB: %d, %d, %d", cr, cg, cb);
Text("SNES: $%04X", palette[n].snes());
Separator();
if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
if (BeginPopup("Copy")) {
int cr = F32_TO_INT8_SAT(col[0]);
int cg = F32_TO_INT8_SAT(col[1]);
int cb = F32_TO_INT8_SAT(col[2]);
char buf[64];
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
col[1], col[2]);
if (Selectable(buf)) SetClipboardText(buf);
@@ -438,6 +483,11 @@ absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
EndPopup();
}
// Add a button to add this color to custom palette
if (Button("Add to Custom Palette", ImVec2(-1, 0))) {
custom_palette_.push_back(palette[n]);
}
EndPopup();
return absl::OkStatus();
}
@@ -449,11 +499,14 @@ absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
}
// Get the current color
ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
auto color = palette[index];
auto currentColor = color.rgb();
if (ColorPicker4("Color Picker", (float*)&palette[index])) {
// The color was modified, update it in the palette
palette(index, currentColor);
palette[index] = gui::ConvertImVec4ToSnesColor(currentColor);
// Add to recently used colors
AddRecentlyUsedColor(palette[index]);
}
return absl::OkStatus();
}
@@ -464,9 +517,9 @@ absl::Status PaletteEditor::ResetColorToOriginal(
if (index >= palette.size() || index >= originalPalette.size()) {
return absl::InvalidArgumentError("Index out of bounds");
}
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
auto color = originalPalette[index];
auto originalColor = color.rgb();
palette(index, originalColor);
palette[index] = gui::ConvertImVec4ToSnesColor(originalColor);
return absl::OkStatus();
}

View File

@@ -6,10 +6,10 @@
#include <vector>
#include "absl/status/status.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/editor.h"
#include "app/gfx/snes_palette.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
@@ -17,6 +17,7 @@ namespace yaze {
namespace editor {
namespace palette_internal {
struct PaletteChange {
std::string group_name;
size_t palette_index;
@@ -76,32 +77,36 @@ absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded);
* @class PaletteEditor
* @brief Allows the user to view and edit in game palettes.
*/
class PaletteEditor : public SharedRom, public Editor {
class PaletteEditor : public Editor {
public:
PaletteEditor() {
explicit PaletteEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kPalette;
custom_palette_.push_back(gfx::SnesColor(0x7FFF));
}
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
absl::Status Cut() override { return absl::OkStatus(); }
absl::Status Copy() override { return absl::OkStatus(); }
absl::Status Paste() override { return absl::OkStatus(); }
absl::Status Undo() override { return absl::OkStatus(); }
absl::Status Redo() override { return absl::OkStatus(); }
absl::Status Find() override { return absl::OkStatus(); }
absl::Status Save() override { return absl::UnimplementedError("Save"); }
void DisplayCategoryTable();
void DrawQuickAccessTab();
void DrawCustomPalette();
absl::Status DrawPaletteGroup(int category, bool right_side = false);
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
const gfx::SnesPalette& originalPalette);
absl::Status DrawPaletteGroup(int category, bool right_side = false);
void DrawCustomPalette();
void AddRecentlyUsedColor(const gfx::SnesColor& color);
void DrawModifiedColors();
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private:
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
@@ -112,10 +117,15 @@ class PaletteEditor : public SharedRom, public Editor {
GfxGroupEditor gfx_group_editor_;
std::vector<gfx::SnesColor> custom_palette_;
std::vector<gfx::SnesColor> recently_used_colors_;
int edit_palette_index_ = -1;
ImVec4 saved_palette_[256] = {};
palette_internal::PaletteEditorHistory history_;
Rom* rom_;
};
} // namespace editor

View File

@@ -6,17 +6,18 @@
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "app/core/constants.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/core/window.h"
#include "app/gfx/arena.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "imgui/imgui.h"
#include "util/hex.h"
#include "util/macro.h"
namespace yaze {
namespace editor {
@@ -25,12 +26,42 @@ using core::Renderer;
constexpr uint32_t kRedPen = 0xFF0000FF;
void ScreenEditor::Initialize() {}
absl::Status ScreenEditor::Load() {
ASSIGN_OR_RETURN(dungeon_maps_,
zelda3::LoadDungeonMaps(*rom(), dungeon_map_labels_));
RETURN_IF_ERROR(zelda3::LoadDungeonMapTile16(
tile16_blockset_, *rom(), rom()->graphics_buffer(), false));
// TODO: Load roomset gfx based on dungeon ID
sheets_.try_emplace(0, gfx::Arena::Get().gfx_sheets()[212]);
sheets_.try_emplace(1, gfx::Arena::Get().gfx_sheets()[213]);
sheets_.try_emplace(2, gfx::Arena::Get().gfx_sheets()[214]);
sheets_.try_emplace(3, gfx::Arena::Get().gfx_sheets()[215]);
/**
int current_tile8 = 0;
int tile_data_offset = 0;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 32; j++) {
std::vector<uint8_t> tile_data(64, 0); // 8x8 tile (64 bytes
int tile_index = current_tile8 + j;
int x = (j % 8) * 8;
int y = (j / 8) * 8;
sheets_[i].Get8x8Tile(tile_index, x, y, tile_data, tile_data_offset);
tile8_individual_.emplace_back(gfx::Bitmap(8, 8, 4, tile_data));
tile8_individual_.back().SetPalette(*rom()->mutable_dungeon_palette(3));
Renderer::Get().RenderBitmap(&tile8_individual_.back());
}
tile_data_offset = 0;
}
*/
return absl::OkStatus();
}
absl::Status ScreenEditor::Update() {
if (ImGui::BeginTabBar("##ScreenEditorTabBar")) {
if (ImGui::BeginTabItem("Dungeon Maps")) {
if (rom()->is_loaded()) {
DrawDungeonMapsEditor();
}
DrawDungeonMapsEditor();
ImGui::EndTabItem();
}
DrawInventoryMenuEditor();
@@ -43,44 +74,44 @@ absl::Status ScreenEditor::Update() {
}
void ScreenEditor::DrawInventoryMenuEditor() {
TAB_ITEM("Inventory Menu")
if (ImGui::BeginTabItem("Inventory Menu")) {
static bool create = false;
if (!create && rom()->is_loaded()) {
status_ = inventory_.Create();
palette_ = inventory_.palette();
create = true;
}
static bool create = false;
if (!create && rom()->is_loaded()) {
status_ = inventory_.Create();
palette_ = inventory_.Palette();
create = true;
DrawInventoryToolset();
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Canvas");
ImGui::TableSetupColumn("Tiles");
ImGui::TableSetupColumn("Palette");
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
screen_canvas_.DrawBackground();
screen_canvas_.DrawContextMenu();
screen_canvas_.DrawBitmap(inventory_.bitmap(), 2, create);
screen_canvas_.DrawGrid(32.0f);
screen_canvas_.DrawOverlay();
ImGui::TableNextColumn();
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
tilesheet_canvas_.DrawContextMenu();
tilesheet_canvas_.DrawBitmap(inventory_.tilesheet(), 2, create);
tilesheet_canvas_.DrawGrid(16.0f);
tilesheet_canvas_.DrawOverlay();
ImGui::TableNextColumn();
gui::DisplayPalette(palette_, create);
ImGui::EndTable();
}
ImGui::Separator();
ImGui::EndTabItem();
}
DrawInventoryToolset();
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Canvas");
ImGui::TableSetupColumn("Tiles");
ImGui::TableSetupColumn("Palette");
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
screen_canvas_.DrawBackground();
screen_canvas_.DrawContextMenu();
screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
screen_canvas_.DrawGrid(32.0f);
screen_canvas_.DrawOverlay();
ImGui::TableNextColumn();
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
tilesheet_canvas_.DrawContextMenu();
tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
tilesheet_canvas_.DrawGrid(16.0f);
tilesheet_canvas_.DrawOverlay();
ImGui::TableNextColumn();
status_ = gui::DisplayPalette(palette_, create);
ImGui::EndTable();
}
ImGui::Separator();
END_TAB_ITEM()
}
void ScreenEditor::DrawInventoryToolset() {
@@ -95,189 +126,82 @@ void ScreenEditor::DrawInventoryToolset() {
ImGui::TableSetupColumn("#bg3Tool");
ImGui::TableSetupColumn("#itemTool");
BUTTON_COLUMN(ICON_MD_UNDO)
BUTTON_COLUMN(ICON_MD_REDO)
TEXT_COLUMN(ICON_MD_MORE_VERT)
BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
BUTTON_COLUMN(ICON_MD_ZOOM_IN)
TEXT_COLUMN(ICON_MD_MORE_VERT)
BUTTON_COLUMN(ICON_MD_DRAW)
BUTTON_COLUMN(ICON_MD_BUILD)
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_UNDO)) {
// status_ = inventory_.Undo();
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_REDO)) {
// status_ = inventory_.Redo();
}
ImGui::TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT);
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_ZOOM_OUT)) {
screen_canvas_.ZoomOut();
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_ZOOM_IN)) {
screen_canvas_.ZoomIn();
}
ImGui::TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT);
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_DRAW)) {
current_mode_ = EditingMode::DRAW;
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_BUILD)) {
// current_mode_ = EditingMode::BUILD;
}
ImGui::EndTable();
}
}
absl::Status ScreenEditor::LoadDungeonMaps() {
std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
int total_floors_d;
uint8_t nbr_floor_d;
uint8_t nbr_basement_d;
void ScreenEditor::DrawDungeonMapScreen(int i) {
auto &current_dungeon = dungeon_maps_[selected_dungeon];
for (int d = 0; d < 14; d++) {
current_floor_rooms_d.clear();
current_floor_gfx_d.clear();
ASSIGN_OR_RETURN(
int ptr,
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
ASSIGN_OR_RETURN(
int ptr_gfx,
rom()->ReadWord(zelda3::screen::kDungeonMapGfxPtr + (d * 2)));
ptr |= 0x0A0000; // Add bank to the short ptr
ptr_gfx |= 0x0A0000; // Add bank to the short ptr
int pc_ptr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
int pc_ptr_gfx =
core::SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
floor_number = i;
screen_canvas_.DrawBackground(ImVec2(325, 325));
screen_canvas_.DrawTileSelector(64.f);
ASSIGN_OR_RETURN(
ushort boss_room_d,
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
auto boss_room = current_dungeon.boss_room;
for (int j = 0; j < zelda3::kNumRooms; j++) {
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
int tile16_id = current_dungeon.floor_gfx[floor_number][j];
int posX = ((j % 5) * 32);
int posY = ((j / 5) * 32);
ASSIGN_OR_RETURN(
nbr_basement_d,
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
nbr_basement_d &= 0x0F;
gfx::RenderTile16(tile16_blockset_, tile16_id);
screen_canvas_.DrawBitmap(tile16_blockset_.tile_bitmaps[tile16_id],
(posX * 2), (posY * 2), 4.0f);
ASSIGN_OR_RETURN(
nbr_floor_d,
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
nbr_floor_d &= 0xF0;
nbr_floor_d = nbr_floor_d >> 4;
total_floors_d = nbr_basement_d + nbr_floor_d;
dungeon_map_labels_.emplace_back();
// for each floor in the dungeon
for (int i = 0; i < total_floors_d; i++) {
dungeon_map_labels_[d].emplace_back();
std::array<uint8_t, 25> rdata;
std::array<uint8_t, 25> gdata;
// for each room on the floor
for (int j = 0; j < 25; j++) {
gdata[j] = 0xFF;
rdata[j] = rom()->data()[pc_ptr + j + (i * 25)]; // Set the rooms
if (rdata[j] == 0x0F) {
gdata[j] = 0xFF;
} else {
gdata[j] = rom()->data()[pc_ptr_gfx++];
}
std::string label = core::HexByte(rdata[j]);
dungeon_map_labels_[d][i][j] = label;
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64, 64,
kRedPen);
}
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
current_floor_rooms_d.push_back(rdata); // Add new floor data
}
dungeon_maps_.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
current_floor_rooms_d, current_floor_gfx_d);
}
return absl::OkStatus();
}
absl::Status ScreenEditor::SaveDungeonMaps() {
for (int d = 0; d < 14; d++) {
int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
int ptr_gfx = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
int pc_ptr = core::SnesToPc(ptr);
int pc_ptr_gfx = core::SnesToPc(ptr_gfx);
const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
for (int j = 0; j < 25; j++) {
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr + j + (i * 25),
dungeon_maps_[d].floor_rooms[i][j]));
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr_gfx + j + (i * 25),
dungeon_maps_[d].floor_gfx[i][j]));
pc_ptr_gfx++;
}
std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j];
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
std::string gfx_id = util::HexByte(tile16_id);
screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
}
}
return absl::OkStatus();
}
screen_canvas_.DrawGrid(64.f, 5);
screen_canvas_.DrawOverlay();
absl::Status ScreenEditor::LoadDungeonMapTile16(
const std::vector<uint8_t>& gfx_data, bool bin_mode) {
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
for (int i = 0; i < 186; i++) {
int addr = zelda3::screen::kDungeonMapTile16;
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
addr = zelda3::screen::kDungeonMapTile16Expanded;
}
ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
int sheet_offset = 212;
if (bin_mode) {
sheet_offset = 0;
}
tile16_sheet_.ComposeTile16(gfx_data, t1, t2, t3, t4, sheet_offset);
if (!screen_canvas_.points().empty()) {
int x = screen_canvas_.points().front().x / 64;
int y = screen_canvas_.points().front().y / 64;
selected_room = x + (y * 5);
}
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
auto tile = tile16_sheet_.GetTile16(i);
tile16_individual_[i] = tile;
RETURN_IF_ERROR(
tile16_individual_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(&tile16_individual_[i]);
}
return absl::OkStatus();
}
absl::Status ScreenEditor::SaveDungeonMapTile16() {
for (int i = 0; i < 186; i++) {
int addr = zelda3::screen::kDungeonMapTile16;
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
addr = zelda3::screen::kDungeonMapTile16Expanded;
}
gfx::TileInfo t1 = tile16_sheet_.tile_info()[i].tiles[0];
gfx::TileInfo t2 = tile16_sheet_.tile_info()[i].tiles[1];
gfx::TileInfo t3 = tile16_sheet_.tile_info()[i].tiles[2];
gfx::TileInfo t4 = tile16_sheet_.tile_info()[i].tiles[3];
auto tl = gfx::TileInfoToWord(t1);
RETURN_IF_ERROR(rom()->WriteWord(addr + (i * 8), tl));
auto tr = gfx::TileInfoToWord(t2);
RETURN_IF_ERROR(rom()->WriteWord(addr + 2 + (i * 8), tr));
auto bl = gfx::TileInfoToWord(t3);
RETURN_IF_ERROR(rom()->WriteWord(addr + 4 + (i * 8), bl));
auto br = gfx::TileInfoToWord(t4);
RETURN_IF_ERROR(rom()->WriteWord(addr + 6 + (i * 8), br));
}
return absl::OkStatus();
}
void ScreenEditor::DrawDungeonMapsTabs() {
auto& current_dungeon = dungeon_maps_[selected_dungeon];
auto &current_dungeon = dungeon_maps_[selected_dungeon];
if (ImGui::BeginTabBar("##DungeonMapTabs")) {
auto nbr_floors =
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
@@ -288,49 +212,8 @@ void ScreenEditor::DrawDungeonMapsTabs() {
tab_name = absl::StrFormat("Floor %d",
i - current_dungeon.nbr_of_basement + 1);
}
if (ImGui::BeginTabItem(tab_name.c_str())) {
floor_number = i;
screen_canvas_.DrawBackground(ImVec2(325, 325));
screen_canvas_.DrawTileSelector(64.f);
auto boss_room = current_dungeon.boss_room;
for (int j = 0; j < 25; j++) {
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
int tile16_id = current_dungeon.floor_gfx[floor_number][j];
int posX = ((j % 5) * 32);
int posY = ((j / 5) * 32);
if (tile16_individual_.count(tile16_id) == 0) {
tile16_individual_[tile16_id] =
tile16_sheet_.GetTile16(tile16_id);
Renderer::GetInstance().RenderBitmap(
&tile16_individual_[tile16_id]);
}
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
(posY * 2), 4.0f);
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
64, kRedPen);
}
std::string label =
dungeon_map_labels_[selected_dungeon][floor_number][j];
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
std::string gfx_id = core::HexByte(tile16_id);
screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
}
}
screen_canvas_.DrawGrid(64.f, 5);
screen_canvas_.DrawOverlay();
if (!screen_canvas_.points().empty()) {
int x = screen_canvas_.points().front().x / 64;
int y = screen_canvas_.points().front().y / 64;
selected_room = x + (y * 5);
}
if (ImGui::BeginTabItem(tab_name.data())) {
DrawDungeonMapScreen(i);
ImGui::EndTabItem();
}
}
@@ -343,9 +226,8 @@ void ScreenEditor::DrawDungeonMapsTabs() {
gui::InputHexWord("Boss Room", &current_dungeon.boss_room);
const ImVec2 button_size = ImVec2(130, 0);
const auto button_size = ImVec2(130, 0);
// Add Floor Button
if (ImGui::Button("Add Floor", button_size) &&
current_dungeon.nbr_of_floor < 8) {
current_dungeon.nbr_of_floor++;
@@ -358,7 +240,6 @@ void ScreenEditor::DrawDungeonMapsTabs() {
dungeon_map_labels_[selected_dungeon].pop_back();
}
// Add Basement Button
if (ImGui::Button("Add Basement", button_size) &&
current_dungeon.nbr_of_basement < 8) {
current_dungeon.nbr_of_basement++;
@@ -380,56 +261,74 @@ void ScreenEditor::DrawDungeonMapsTabs() {
}
}
void ScreenEditor::DrawDungeonMapsEditor() {
if (!dungeon_maps_loaded_) {
if (!LoadDungeonMaps().ok()) {
ImGui::Text("Failed to load dungeon maps");
void ScreenEditor::DrawDungeonMapsRoomGfx() {
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
tilesheet_canvas_.DrawContextMenu();
if (tilesheet_canvas_.DrawTileSelector(32.f)) {
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
(tilesheet_canvas_.points().front().y / 32) * 16;
gfx::RenderTile16(tile16_blockset_, selected_tile16_);
std::ranges::copy(tile16_blockset_.tile_info[selected_tile16_],
current_tile16_info.begin());
}
tilesheet_canvas_.DrawBitmap(tile16_blockset_.atlas, 1, 1, 2.0f);
tilesheet_canvas_.DrawGrid(32.f);
tilesheet_canvas_.DrawOverlay();
if (!tilesheet_canvas_.points().empty() &&
!screen_canvas_.points().empty()) {
dungeon_maps_[selected_dungeon].floor_gfx[floor_number][selected_room] =
selected_tile16_;
tilesheet_canvas_.mutable_points()->clear();
}
if (LoadDungeonMapTile16(rom()->graphics_buffer()).ok()) {
// TODO: Load roomset gfx based on dungeon ID
sheets_.emplace(0, rom()->gfx_sheets()[212]);
sheets_.emplace(1, rom()->gfx_sheets()[213]);
sheets_.emplace(2, rom()->gfx_sheets()[214]);
sheets_.emplace(3, rom()->gfx_sheets()[215]);
int current_tile8 = 0;
int tile_data_offset = 0;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 32; j++) {
std::vector<uint8_t> tile_data(64, 0); // 8x8 tile (64 bytes
int tile_index = current_tile8 + j;
int x = (j % 8) * 8;
int y = (j / 8) * 8;
sheets_[i].Get8x8Tile(tile_index, 0, 0, tile_data, tile_data_offset);
tile8_individual_.emplace_back(gfx::Bitmap(8, 8, 4, tile_data));
RETURN_VOID_IF_ERROR(tile8_individual_.back().ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(&tile8_individual_.back());
}
tile_data_offset = 0;
}
dungeon_maps_loaded_ = true;
} else {
ImGui::Text("Failed to load dungeon map tile16");
ImGui::Separator();
current_tile_canvas_.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
current_tile_canvas_.DrawContextMenu();
if (current_tile_canvas_.DrawTilePainter(tile8_individual_[selected_tile8_],
16)) {
// Modify the tile16 based on the selected tile and current_tile16_info
gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
current_tile16_info[0], current_tile16_info[1],
current_tile16_info[2], current_tile16_info[3], 212,
selected_tile16_);
gfx::UpdateTile16(tile16_blockset_, selected_tile16_);
}
current_tile_canvas_.DrawBitmap(
tile16_blockset_.tile_bitmaps[selected_tile16_], 2, 4.0f);
current_tile_canvas_.DrawGrid(16.f);
current_tile_canvas_.DrawOverlay();
gui::InputTileInfo("TL", &current_tile16_info[0]);
ImGui::SameLine();
gui::InputTileInfo("TR", &current_tile16_info[1]);
gui::InputTileInfo("BL", &current_tile16_info[2]);
ImGui::SameLine();
gui::InputTileInfo("BR", &current_tile16_info[3]);
if (ImGui::Button("Modify Tile16")) {
gfx::ModifyTile16(tile16_blockset_, rom()->graphics_buffer(),
current_tile16_info[0], current_tile16_info[1],
current_tile16_info[2], current_tile16_info[3], 212,
selected_tile16_);
gfx::UpdateTile16(tile16_blockset_, selected_tile16_);
}
}
ImGui::EndChild();
}
if (ImGui::BeginTable("##DungeonMapToolset", 2,
ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Draw Mode");
ImGui::TableSetupColumn("Edit Mode");
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_DRAW)) {
current_mode_ = EditingMode::DRAW;
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_EDIT)) {
current_mode_ = EditingMode::EDIT;
}
ImGui::EndTable();
void ScreenEditor::DrawDungeonMapsEditor() {
if (ImGui::Button(ICON_MD_DRAW)) {
current_mode_ = EditingMode::DRAW;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_EDIT)) {
current_mode_ = EditingMode::EDIT;
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_SAVE)) {
PRINT_IF_ERROR(zelda3::SaveDungeonMapTile16(tile16_blockset_, *rom()));
}
static std::vector<std::string> dungeon_names = {
@@ -449,7 +348,6 @@ void ScreenEditor::DrawDungeonMapsEditor() {
ImGui::TableSetupColumn("Tiles Gfx");
ImGui::TableHeadersRow();
// Dungeon column
ImGui::TableNextColumn();
for (int i = 0; i < dungeon_names.size(); i++) {
rom()->resource_label()->SelectableLabelWithNameEdit(
@@ -460,66 +358,11 @@ void ScreenEditor::DrawDungeonMapsEditor() {
}
}
// Map column
ImGui::TableNextColumn();
DrawDungeonMapsTabs();
ImGui::TableNextColumn();
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
tilesheet_canvas_.DrawContextMenu();
tilesheet_canvas_.DrawTileSelector(32.f);
tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true);
tilesheet_canvas_.DrawGrid(32.f);
tilesheet_canvas_.DrawOverlay();
if (!tilesheet_canvas_.points().empty()) {
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
(tilesheet_canvas_.points().front().y / 32) * 16;
current_tile16_info = tile16_sheet_.tile_info().at(selected_tile16_);
// Draw the selected tile
if (!screen_canvas_.points().empty()) {
dungeon_maps_[selected_dungeon]
.floor_gfx[floor_number][selected_room] = selected_tile16_;
tilesheet_canvas_.mutable_points()->clear();
}
}
ImGui::Separator();
current_tile_canvas_
.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
current_tile_canvas_.DrawContextMenu();
if (current_tile_canvas_.DrawTilePainter(
tile8_individual_[selected_tile8_], 16)) {
// Modify the tile16 based on the selected tile and current_tile16_info
}
current_tile_canvas_.DrawBitmap(tile16_individual_[selected_tile16_], 2,
4.0f);
current_tile_canvas_.DrawGrid(16.f);
current_tile_canvas_.DrawOverlay();
gui::InputTileInfo("TL", &current_tile16_info.tiles[0]);
ImGui::SameLine();
gui::InputTileInfo("TR", &current_tile16_info.tiles[1]);
gui::InputTileInfo("BL", &current_tile16_info.tiles[2]);
ImGui::SameLine();
gui::InputTileInfo("BR", &current_tile16_info.tiles[3]);
if (ImGui::Button("Modify Tile16")) {
tile16_sheet_.ModifyTile16(
rom()->graphics_buffer(), current_tile16_info.tiles[0],
current_tile16_info.tiles[1], current_tile16_info.tiles[2],
current_tile16_info.tiles[3], selected_tile16_, 212);
tile16_individual_[selected_tile16_] =
tile16_sheet_.GetTile16(selected_tile16_);
RETURN_VOID_IF_ERROR(tile16_individual_[selected_tile16_].ApplyPalette(
*rom()->mutable_dungeon_palette(3)));
Renderer::GetInstance().RenderBitmap(
&tile16_individual_[selected_tile16_]);
}
}
ImGui::EndChild();
DrawDungeonMapsRoomGfx();
ImGui::TableNextColumn();
tilemap_canvas_.DrawBackground();
@@ -533,7 +376,6 @@ void ScreenEditor::DrawDungeonMapsEditor() {
tilemap_canvas_.DrawOverlay();
ImGui::Text("Selected tile8: %d", selected_tile8_);
ImGui::Separator();
ImGui::Text("For use with custom inserted graphics assembly patches.");
if (ImGui::Button("Load GFX from BIN file")) LoadBinaryGfx();
@@ -550,20 +392,18 @@ void ScreenEditor::LoadBinaryGfx() {
// Read the gfx data into a buffer
std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
gfx_bin_data_ = converted_bin;
tile16_sheet_.clear();
if (LoadDungeonMapTile16(converted_bin, true).ok()) {
if (auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
zelda3::LoadDungeonMapTile16(tile16_blockset_, *rom(), converted_bin,
true)
.ok()) {
sheets_.clear();
std::vector<std::vector<uint8_t>> gfx_sheets;
for (int i = 0; i < 4; i++) {
gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
converted_bin.begin() + ((i + 1) * 0x1000));
sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
status_ = sheets_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3));
if (status_.ok()) {
Renderer::GetInstance().RenderBitmap(&sheets_[i]);
}
sheets_[i].SetPalette(*rom()->mutable_dungeon_palette(3));
Renderer::Get().RenderBitmap(&sheets_[i]);
}
binary_gfx_loaded_ = true;
} else {

View File

@@ -7,7 +7,7 @@
#include "app/editor/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/tilesheet.h"
#include "app/gfx/tilemap.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/screen/dungeon_map.h"
@@ -28,26 +28,28 @@ namespace editor {
*
* The screens that can be edited include the title screen, naming screen,
* overworld map, inventory menu, and more.
*
* The class inherits from the SharedRom class.
*/
class ScreenEditor : public SharedRom, public Editor {
class ScreenEditor : public Editor {
public:
ScreenEditor() {
explicit ScreenEditor(Rom* rom = nullptr) : rom_(rom) {
screen_canvas_.SetCanvasSize(ImVec2(512, 512));
type_ = EditorType::kScreen;
}
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status Save() override { return absl::UnimplementedError("Save"); }
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
absl::Status SaveDungeonMaps();
std::vector<zelda3::DungeonMap> dungeon_maps_;
private:
void DrawTitleScreenEditor();
@@ -58,12 +60,14 @@ class ScreenEditor : public SharedRom, public Editor {
void DrawToolset();
void DrawInventoryToolset();
absl::Status LoadDungeonMaps();
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t> &gfx_data,
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t>& gfx_data,
bool bin_mode = false);
absl::Status SaveDungeonMapTile16();
void DrawDungeonMapScreen(int i);
void DrawDungeonMapsTabs();
void DrawDungeonMapsEditor();
void DrawDungeonMapsRoomGfx();
void LoadBinaryGfx();
@@ -71,11 +75,9 @@ class ScreenEditor : public SharedRom, public Editor {
EditingMode current_mode_ = EditingMode::DRAW;
bool dungeon_maps_loaded_ = false;
bool binary_gfx_loaded_ = false;
uint8_t selected_room = 0;
uint8_t boss_room = 0;
int selected_tile16_ = 0;
int selected_tile8_ = 0;
@@ -85,20 +87,13 @@ class ScreenEditor : public SharedRom, public Editor {
bool copy_button_pressed = false;
bool paste_button_pressed = false;
std::array<uint16_t, 4> current_tile16_data_;
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
std::vector<gfx::Bitmap> tile8_individual_;
std::vector<uint8_t> all_gfx_;
std::vector<uint8_t> gfx_bin_data_;
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
absl::Status status_;
zelda3::DungeonMapLabels dungeon_map_labels_;
gfx::SnesPalette palette_;
gfx::BitmapTable sheets_;
gfx::Tilesheet tile16_sheet_;
gfx::InternalTile16 current_tile16_info;
gfx::Tilemap tile16_blockset_;
std::array<gfx::TileInfo, 4> current_tile16_info;
gui::Canvas current_tile_canvas_{"##CurrentTileCanvas", ImVec2(32, 32),
gui::CanvasGridSize::k16x16, 2.0f};
@@ -107,7 +102,9 @@ class ScreenEditor : public SharedRom, public Editor {
gui::Canvas tilemap_canvas_{"##TilemapCanvas", ImVec2(128 + 2, (192) + 4),
gui::CanvasGridSize::k8x8, 2.f};
zelda3::screen::Inventory inventory_;
zelda3::Inventory inventory_;
Rom* rom_;
absl::Status status_;
};
} // namespace editor

View File

@@ -1,396 +0,0 @@
#include "tile16_editor.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/platform/renderer.h"
#include "app/editor/editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gfx/tilesheet.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
using core::Renderer;
using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginMenuBar;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::Checkbox;
using ImGui::Combo;
using ImGui::EndChild;
using ImGui::EndMenu;
using ImGui::EndMenuBar;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::GetContentRegionAvail;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
absl::Status Tile16Editor::InitBlockset(
const gfx::Bitmap& tile16_blockset_bmp, const gfx::Bitmap& current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
std::array<uint8_t, 0x200>& all_tiles_types) {
all_tiles_types_ = all_tiles_types;
tile16_blockset_bmp_ = tile16_blockset_bmp;
tile16_individual_ = tile16_individual;
current_gfx_bmp_ = current_gfx_bmp;
RETURN_IF_ERROR(LoadTile8());
ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) {
std::string str = core::HexByte(all_tiles_types_[i]);
tile16_names.push_back(str);
}
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
*tile8_source_canvas_.custom_labels_enabled() = true;
return absl::OkStatus();
}
absl::Status Tile16Editor::Update() {
if (!map_blockset_loaded_) {
return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
}
RETURN_IF_ERROR(DrawMenu());
if (BeginTabBar("Tile16 Editor Tabs")) {
DrawTile16Editor();
RETURN_IF_ERROR(UpdateTile16Transfer());
EndTabBar();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawMenu() {
if (BeginMenuBar()) {
if (BeginMenu("View")) {
Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled());
EndMenu();
}
EndMenuBar();
}
return absl::OkStatus();
}
void Tile16Editor::DrawTile16Editor() {
if (BeginTabItem("Tile16 Editing")) {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x);
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableHeadersRow();
TableNextRow();
TableNextColumn();
status_ = UpdateBlockset();
if (!status_.ok()) {
EndTable();
}
TableNextColumn();
status_ = UpdateTile16Edit();
if (status_ != absl::OkStatus()) {
EndTable();
}
status_ = DrawTileEditControls();
EndTable();
}
EndTabItem();
}
}
absl::Status Tile16Editor::UpdateBlockset() {
gui::BeginPadding(2);
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
blockset_canvas_.DrawBackground();
gui::EndPadding();
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawTileSelector(32);
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
EndChild();
if (!blockset_canvas_.points().empty()) {
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
notify_tile16.apply_changes();
if (notify_tile16.modified()) {
current_tile16_ = notify_tile16.get();
current_tile16_bmp_ = tile16_individual_[notify_tile16];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
ow_main_pal_group[current_palette_]));
Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
}
}
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
constexpr int tile8_size = 8;
constexpr int tile16_size = 16;
// Calculate the tile index for x and y based on the click_position
// Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
std::cout << "Tile Index X: " << tile_index_x << std::endl;
std::cout << "Tile Index Y: " << tile_index_y << std::endl;
// Calculate the pixel start position within the Tile16
ImVec2 start_position;
start_position.x = tile_index_x * 0x40;
start_position.y = tile_index_y * 0x40;
std::cout << "Start Position X: " << start_position.x << std::endl;
std::cout << "Start Position Y: " << start_position.y << std::endl;
// Draw the Tile8 to the correct position within the Tile16
for (int y = 0; y < tile8_size; ++y) {
for (int x = 0; x < tile8_size; ++x) {
int pixel_index =
(start_position.y + y) * tile16_size + ((start_position.x) + x);
int gfx_pixel_index = y * tile8_size + x;
current_tile16_bmp_.WriteToPixel(
pixel_index,
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
}
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTile16Edit() {
auto ow_main_pal_group = rom()->palette_group().overworld_main;
if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
true)) {
tile8_source_canvas_.DrawBackground();
tile8_source_canvas_.DrawContextMenu(&current_gfx_bmp_);
if (tile8_source_canvas_.DrawTileSelector(32)) {
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
}
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
tile8_source_canvas_.DrawGrid();
tile8_source_canvas_.DrawOverlay();
}
EndChild();
// The user selected a tile8
if (!tile8_source_canvas_.points().empty()) {
uint16_t x = tile8_source_canvas_.points().front().x / 16;
uint16_t y = tile8_source_canvas_.points().front().y / 16;
current_tile8_ = x + (y * 8);
RETURN_IF_ERROR(
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
Renderer::GetInstance().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
}
if (BeginChild("Tile16 Editor Options",
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
tile16_edit_canvas_.DrawBackground();
tile16_edit_canvas_.DrawContextMenu(&current_tile16_bmp_);
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
if (!tile8_source_canvas_.points().empty()) {
if (tile16_edit_canvas_.DrawTilePainter(
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
RETURN_IF_ERROR(
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
}
}
tile16_edit_canvas_.DrawGrid();
tile16_edit_canvas_.DrawOverlay();
}
EndChild();
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawTileEditControls() {
Separator();
Text("Tile16 ID: %d", current_tile16_);
Text("Tile8 ID: %d", current_tile8_);
Text("Options:");
gui::InputHexByte("Palette", &notify_palette.mutable_get());
notify_palette.apply_changes();
if (notify_palette.modified()) {
auto palette = palettesets_[current_palette_].main_;
auto value = notify_palette.get();
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
palette = palettesets_[current_palette_].aux1;
value -= 0x04;
} else if (notify_palette.get() > 0x06) {
palette = palettesets_[current_palette_].aux2;
value -= 0x06;
}
if (value > 0x00) {
RETURN_IF_ERROR(
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
Renderer::GetInstance().UpdateBitmap(&current_gfx_bmp_);
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
Renderer::GetInstance().UpdateBitmap(&current_tile16_bmp_);
}
}
Checkbox("X Flip", &x_flip);
Checkbox("Y Flip", &y_flip);
Checkbox("Priority Tile", &priority_tile);
return absl::OkStatus();
}
absl::Status Tile16Editor::LoadTile8() {
auto ow_main_pal_group = rom()->palette_group().overworld_main;
current_gfx_individual_.reserve(1024);
for (int index = 0; index < 1024; index++) {
std::vector<uint8_t> tile_data(0x40, 0x00);
// Copy the pixel data for the current tile into the vector
for (int ty = 0; ty < 8; ty++) {
for (int tx = 0; tx < 8; tx++) {
// Current Gfx Data is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
// Calculate the position in the tile data vector
int position = tx + (ty * 0x08);
// Calculate the position in the current gfx data
int num_columns = current_gfx_bmp_.width() / 8;
int x = (index % num_columns) * 8 + tx;
int y = (index / num_columns) * 8 + ty;
int gfx_position = x + (y * 0x100);
// Get the pixel value from the current gfx data
uint8_t value = current_gfx_bmp_.data()[gfx_position];
if (value & 0x80) {
value -= 0x88;
}
tile_data[position] = value;
}
}
current_gfx_individual_.emplace_back();
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent(
ow_main_pal_group[0], current_palette_));
Renderer::GetInstance().RenderBitmap(&current_gfx_individual_[index]);
}
map_blockset_loaded_ = true;
return absl::OkStatus();
}
absl::Status Tile16Editor::SetCurrentTile(int id) {
current_tile16_ = id;
current_tile16_bmp_ = tile16_individual_[id];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
RETURN_IF_ERROR(
current_tile16_bmp_.ApplyPalette(ow_main_pal_group[current_palette_]));
Renderer::GetInstance().RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
#pragma mark - Tile16Transfer
absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTabItem("Tile16 Transfer")) {
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x / 2);
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x / 2);
TableHeadersRow();
TableNextRow();
TableNextColumn();
RETURN_IF_ERROR(UpdateBlockset());
TableNextColumn();
RETURN_IF_ERROR(UpdateTransferTileCanvas());
EndTable();
}
EndTabItem();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// Create a button for loading another ROM
if (Button("Load ROM")) {
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
transfer_status_ = transfer_rom_.LoadFromFile(file_name);
transfer_started_ = true;
}
// TODO: Implement tile16 transfer
if (transfer_started_ && !transfer_blockset_loaded_) {
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
// Load the Link to the Past overworld.
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
transfer_overworld_.set_current_map(0);
palette_ = transfer_overworld_.current_area_palette();
// Create the tile16 blockset image
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
transfer_blockset_bmp_, palette_));
transfer_blockset_loaded_ = true;
}
// Create a canvas for holding the tiles which will be exported
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
3);
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -1,6 +1,11 @@
#include "message_data.h"
#include "app/core/common.h"
#include <optional>
#include <string>
#include "app/snes.h"
#include "util/hex.h"
#include "util/log.h"
namespace yaze {
namespace editor {
@@ -14,45 +19,46 @@ uint8_t FindMatchingCharacter(char value) {
return 0xFF;
}
uint8_t FindDictionaryEntry(uint8_t value) {
int8_t FindDictionaryEntry(uint8_t value) {
if (value < DICTOFF || value == 0xFF) {
return -1;
}
return value - DICTOFF;
}
TextElement FindMatchingCommand(uint8_t b) {
TextElement empty_element;
std::optional<TextElement> FindMatchingCommand(uint8_t b) {
for (const auto& text_element : TextCommands) {
if (text_element.ID == b) {
return text_element;
}
}
return empty_element;
return std::nullopt;
}
TextElement FindMatchingSpecial(uint8_t value) {
auto it = std::find_if(SpecialChars.begin(), SpecialChars.end(),
[value](const TextElement& text_element) {
return text_element.ID == value;
});
std::optional<TextElement> FindMatchingSpecial(uint8_t value) {
auto it = std::ranges::find_if(SpecialChars,
[value](const TextElement& text_element) {
return text_element.ID == value;
});
if (it != SpecialChars.end()) {
return *it;
}
return TextElement();
return std::nullopt;
}
ParsedElement FindMatchingElement(const std::string& str) {
std::smatch match;
for (auto& textElement : TextCommands) {
match = textElement.MatchMe(str);
std::vector<TextElement> commands_and_chars = TextCommands;
commands_and_chars.insert(commands_and_chars.end(), SpecialChars.begin(),
SpecialChars.end());
for (auto& text_element : commands_and_chars) {
match = text_element.MatchMe(str);
if (match.size() > 0) {
if (textElement.HasArgument) {
return ParsedElement(textElement,
std::stoi(match[1].str(), nullptr, 16));
if (text_element.HasArgument) {
std::string arg = match[1].str().substr(1);
return ParsedElement(text_element, std::stoi(arg, nullptr, 16));
} else {
return ParsedElement(textElement, 0);
return ParsedElement(text_element, 0);
}
}
}
@@ -77,21 +83,21 @@ std::string ParseTextDataByte(uint8_t value) {
}
// Check for command.
TextElement textElement = FindMatchingCommand(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
if (auto text_element = FindMatchingCommand(value);
text_element != std::nullopt) {
return text_element->GenericToken;
}
// Check for special characters.
textElement = FindMatchingSpecial(value);
if (!textElement.Empty()) {
return textElement.GenericToken;
if (auto special_element = FindMatchingSpecial(value);
special_element != std::nullopt) {
return special_element->GenericToken;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(value);
if (dictionary >= 0) {
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
return absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN, dictionary);
}
return "";
@@ -101,7 +107,6 @@ std::vector<uint8_t> ParseMessageToData(std::string str) {
std::vector<uint8_t> bytes;
std::string temp_string = str;
int pos = 0;
while (pos < temp_string.size()) {
// Get next text fragment.
if (temp_string[pos] == '[') {
@@ -117,7 +122,7 @@ std::vector<uint8_t> ParseMessageToData(std::string str) {
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
if (!parsedElement.Active) {
core::logf("Error parsing message: %s", temp_string);
util::logf("Error parsing message: %s", temp_string);
break;
} else if (parsedElement.Parent == dictionary_element) {
bytes.push_back(parsedElement.Value);
@@ -135,7 +140,6 @@ std::vector<uint8_t> ParseMessageToData(std::string str) {
uint8_t bb = FindMatchingCharacter(temp_string[pos++]);
if (bb != 0xFF) {
core::logf("Error parsing message: %s", temp_string);
bytes.push_back(bb);
}
}
@@ -150,14 +154,14 @@ std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom) {
std::vector<uint8_t> bytes;
std::stringstream stringBuilder;
int address = core::SnesToPc(
int address = SnesToPc(
kTextData + (rom->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + (i * 2)]);
int temppush_backress = core::SnesToPc(
kTextData +
(rom->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + ((i + 1) * 2)]);
int temppush_backress =
SnesToPc(kTextData +
(rom->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
rom->data()[kPointersDictionaries + ((i + 1) * 2)]);
while (address < temppush_backress) {
uint8_t uint8_tDictionary = rom->data()[address++];
@@ -168,13 +172,271 @@ std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom) {
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
}
std::sort(AllDictionaries.begin(), AllDictionaries.end(),
[](const DictionaryEntry& a, const DictionaryEntry& b) {
return a.Contents.size() > b.Contents.size();
});
std::ranges::sort(AllDictionaries,
[](const DictionaryEntry& a, const DictionaryEntry& b) {
return a.Contents.size() > b.Contents.size();
});
return AllDictionaries;
}
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary) {
std::string temp = str;
for (const auto& entry : dictionary) {
if (entry.ContainedInString(temp)) {
temp = entry.ReplaceInstancesOfIn(temp);
}
}
return temp;
}
DictionaryEntry FindRealDictionaryEntry(
uint8_t value, std::vector<DictionaryEntry> dictionary) {
for (const auto& entry : dictionary) {
if (entry.ID + DICTOFF == value) {
return entry;
}
}
return DictionaryEntry();
}
absl::StatusOr<MessageData> ParseSingleMessage(
const std::vector<uint8_t>& rom_data, int* current_pos) {
MessageData message_data;
int pos = *current_pos;
uint8_t current_byte;
std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed;
std::string current_message_raw;
std::string current_message_parsed;
// Read the message data
while (true) {
current_byte = rom_data[pos++];
if (current_byte == kMessageTerminator) {
message_data.ID = message_data.ID + 1;
message_data.Address = pos;
message_data.RawString = current_message_raw;
message_data.Data = temp_bytes_raw;
message_data.DataParsed = temp_bytes_parsed;
message_data.ContentsParsed = current_message_parsed;
temp_bytes_raw.clear();
temp_bytes_parsed.clear();
current_message_raw.clear();
current_message_parsed.clear();
break;
} else if (current_byte == 0xFF) {
break;
}
temp_bytes_raw.push_back(current_byte);
// Check for command.
auto text_element = FindMatchingCommand(current_byte);
if (text_element != std::nullopt) {
current_message_raw.append(text_element->GetParamToken());
current_message_parsed.append(text_element->GetParamToken());
temp_bytes_parsed.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_message_raw.append("[");
current_message_raw.append(DICTIONARYTOKEN);
current_message_raw.append(":");
current_message_raw.append(util::HexWord(dictionary));
current_message_raw.append("]");
auto mutable_rom_data = const_cast<uint8_t*>(rom_data.data());
uint32_t address = Get24LocalFromPC(
mutable_rom_data, kPointersDictionaries + (dictionary * 2));
uint32_t address_end = Get24LocalFromPC(
mutable_rom_data, kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
temp_bytes_parsed.push_back(rom_data[i]);
current_message_parsed.append(ParseTextDataByte(rom_data[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_message_raw.append(str);
current_message_parsed.append(str);
temp_bytes_parsed.push_back(current_byte);
}
}
*current_pos = pos;
return message_data;
}
std::vector<std::string> ParseMessageData(
std::vector<MessageData>& message_data,
const std::vector<DictionaryEntry>& dictionary_entries) {
std::vector<std::string> parsed_messages;
for (auto& message : message_data) {
std::string parsed_message = "";
int pos = 0;
for (const uint8_t& byte : message.Data) {
if (CharEncoder.contains(byte)) {
parsed_message.push_back(CharEncoder.at(byte));
} else {
if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
DictionaryEntry dic_entry;
for (const auto& entry : dictionary_entries) {
if (entry.ID == byte - DICTOFF) {
dic_entry = entry;
break;
}
}
parsed_message.append(dic_entry.Contents);
} else {
auto text_element = FindMatchingCommand(byte);
if (text_element != std::nullopt) {
if (text_element->ID == kScrollVertical ||
text_element->ID == kLine2 || text_element->ID == kLine3) {
parsed_message.append("\n");
}
// If there is a param, add it to the message using GetParamToken.
if (text_element->HasArgument) {
// The next byte is the param.
parsed_message.append(
text_element->GetParamToken(message.Data[pos + 1]));
pos++;
} else {
parsed_message.append(text_element->GetParamToken());
}
}
auto special_element = FindMatchingSpecial(byte);
if (special_element != std::nullopt) {
parsed_message.append(special_element->GetParamToken());
}
}
}
pos++;
}
parsed_messages.push_back(parsed_message);
}
return parsed_messages;
}
std::vector<MessageData> ReadAllTextData(uint8_t* rom, int pos) {
std::vector<MessageData> list_of_texts;
int message_id = 0;
std::vector<uint8_t> raw_message;
std::vector<uint8_t> parsed_message;
std::string current_raw_message;
std::string current_parsed_message;
uint8_t current_byte = 0;
while (current_byte != 0xFF) {
current_byte = rom[pos++];
if (current_byte == kMessageTerminator) {
list_of_texts.push_back(
MessageData(message_id++, pos, current_raw_message, raw_message,
current_parsed_message, parsed_message));
raw_message.clear();
parsed_message.clear();
current_raw_message.clear();
current_parsed_message.clear();
continue;
} else if (current_byte == 0xFF) {
break;
}
raw_message.push_back(current_byte);
auto text_element = FindMatchingCommand(current_byte);
if (text_element != std::nullopt) {
parsed_message.push_back(current_byte);
if (text_element->HasArgument) {
current_byte = rom[pos++];
raw_message.push_back(current_byte);
parsed_message.push_back(current_byte);
}
current_raw_message.append(text_element->GetParamToken(current_byte));
current_parsed_message.append(text_element->GetParamToken(current_byte));
if (text_element->Token == kBankToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
auto special_element = FindMatchingSpecial(current_byte);
if (special_element != std::nullopt) {
current_raw_message.append(special_element->GetParamToken());
current_parsed_message.append(special_element->GetParamToken());
parsed_message.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_raw_message.append(absl::StrFormat("[%s:%s]", DICTIONARYTOKEN,
util::HexByte(dictionary)));
uint32_t address =
Get24LocalFromPC(rom, kPointersDictionaries + (dictionary * 2));
uint32_t address_end =
Get24LocalFromPC(rom, kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
parsed_message.push_back(rom[i]);
current_parsed_message.append(ParseTextDataByte(rom[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_raw_message.append(str);
current_parsed_message.append(str);
parsed_message.push_back(current_byte);
}
}
return list_of_texts;
}
absl::Status LoadExpandedMessages(std::string& expanded_message_path,
std::vector<std::string>& parsed_messages,
std::vector<MessageData>& expanded_messages,
std::vector<DictionaryEntry>& dictionary) {
static Rom expanded_message_rom;
if (!expanded_message_rom.LoadFromFile(expanded_message_path, false).ok()) {
return absl::InternalError("Failed to load expanded message ROM");
}
expanded_messages = ReadAllTextData(expanded_message_rom.mutable_data(), 0);
auto parsed_expanded_messages =
ParseMessageData(expanded_messages, dictionary);
// Insert into parsed_messages
for (const auto& expanded_message : expanded_messages) {
parsed_messages.push_back(parsed_expanded_messages[expanded_message.ID]);
}
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze
} // namespace yaze

View File

@@ -1,11 +1,12 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
#include <optional>
#include <regex>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "app/rom.h"
@@ -13,10 +14,11 @@
namespace yaze {
namespace editor {
const uint8_t kMessageTerminator = 0x7F;
const std::string BANKToken = "BANK";
const std::string kBankToken = "BANK";
const std::string DICTIONARYTOKEN = "D";
constexpr uint8_t kMessageTerminator = 0x7F;
constexpr uint8_t DICTOFF = 0x88;
constexpr uint8_t kWidthArraySize = 100;
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
{0x00, 'A'}, {0x01, 'B'}, {0x02, 'C'}, {0x03, 'D'}, {0x04, 'E'},
@@ -39,55 +41,59 @@ static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
};
uint8_t FindMatchingCharacter(char value);
uint8_t FindDictionaryEntry(uint8_t value);
int8_t FindDictionaryEntry(uint8_t value);
std::vector<uint8_t> ParseMessageToData(std::string str);
struct DictionaryEntry {
uint8_t ID;
std::string Contents;
uint8_t ID = 0;
std::string Contents = "";
std::vector<uint8_t> Data;
int Length;
std::string Token;
int Length = 0;
std::string Token = "";
DictionaryEntry() = default;
DictionaryEntry(uint8_t i, std::string s)
: Contents(s), ID(i), Length(s.length()) {
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
DictionaryEntry(uint8_t i, std::string_view s)
: ID(i), Contents(s), Length(s.length()) {
Token = absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN, ID);
Data = ParseMessageToData(Contents);
}
bool ContainedInString(std::string s) {
return s.find(Contents) != std::string::npos;
bool ContainedInString(std::string_view s) const {
return s.contains(Contents);
}
std::string ReplaceInstancesOfIn(std::string s) {
std::string replacedString = s;
size_t pos = replacedString.find(Contents);
std::string ReplaceInstancesOfIn(std::string_view s) const {
auto replaced_string = std::string(s);
size_t pos = replaced_string.find(Contents);
while (pos != std::string::npos) {
replacedString.replace(pos, Contents.length(), Token);
pos = replacedString.find(Contents, pos + Token.length());
replaced_string.replace(pos, Contents.length(), Token);
pos = replaced_string.find(Contents, pos + Token.length());
}
return replacedString;
return replaced_string;
}
};
constexpr int kTextData = 0xE0000;
constexpr int kTextDataEnd = 0xE7FFF;
constexpr int kNumDictionaryEntries = 97;
constexpr int kNumDictionaryEntries = 0x61;
constexpr int kPointersDictionaries = 0x74703;
constexpr uint8_t kScrollVertical = 0x73;
constexpr uint8_t kLine1 = 0x74;
constexpr uint8_t kLine2 = 0x75;
constexpr uint8_t kLine3 = 0x76;
std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom);
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary);
DictionaryEntry FindRealDictionaryEntry(
uint8_t value, std::vector<DictionaryEntry> dictionary);
// Inserted into commands to protect them from dictionary replacements.
const std::string CHEESE = "\uBEBE";
struct MessageData {
int ID;
int Address;
int ID = 0;
int Address = 0;
std::string RawString;
std::string ContentsParsed;
std::vector<uint8_t> Data;
@@ -101,9 +107,9 @@ struct MessageData {
: ID(id),
Address(address),
RawString(rawString),
ContentsParsed(parsedString),
Data(rawData),
DataParsed(parsedData),
ContentsParsed(parsedString) {}
DataParsed(parsedData) {}
// Copy constructor
MessageData(const MessageData& other) {
@@ -115,16 +121,12 @@ struct MessageData {
ContentsParsed = other.ContentsParsed;
}
std::string ToString() {
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
}
std::string OptimizeMessageForDictionary(
std::string messageString,
std::string_view message_string,
const std::vector<DictionaryEntry>& dictionary) {
std::stringstream protons;
bool command = false;
for (const auto& c : messageString) {
for (const auto& c : message_string) {
if (c == '[') {
command = true;
} else if (c == ']') {
@@ -137,13 +139,13 @@ struct MessageData {
}
}
std::string protonsString = protons.str();
std::string replacedString =
ReplaceAllDictionaryWords(protonsString, dictionary);
std::string finalString =
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
std::string protons_string = protons.str();
std::string replaced_string =
ReplaceAllDictionaryWords(protons_string, dictionary);
std::string final_string =
absl::StrReplaceAll(replaced_string, {{CHEESE, ""}});
return finalString;
return final_string;
}
void SetMessage(const std::string& message,
@@ -163,8 +165,8 @@ struct TextElement {
bool HasArgument;
TextElement() = default;
TextElement(uint8_t id, std::string token, bool arg,
std::string description) {
TextElement(uint8_t id, const std::string& token, bool arg,
const std::string& description) {
ID = id;
Token = token;
if (arg) {
@@ -174,14 +176,18 @@ struct TextElement {
}
HasArgument = arg;
Description = description;
Pattern =
arg ? "\\[" + Token + ":?([0-9A-F]{1,2})\\]" : "\\[" + Token + "\\]";
Pattern = absl::StrReplaceAll(Pattern, {{"[", "\\["}, {"]", "\\]"}});
StrictPattern = absl::StrCat("^", Pattern, "$");
StrictPattern = "^" + Pattern + "$";
if (arg) {
Pattern = absl::StrFormat(
"\\[%s(:[0-9A-F]{1,2})?\\]",
absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
} else {
Pattern = absl::StrFormat(
"\\[%s\\]", absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
}
StrictPattern = absl::StrFormat("^%s$", Pattern);
}
std::string GetParameterizedToken(uint8_t value = 0) {
std::string GetParamToken(uint8_t value = 0) const {
if (HasArgument) {
return absl::StrFormat("[%s:%02X]", Token, value);
} else {
@@ -189,10 +195,6 @@ struct TextElement {
}
}
std::string ToString() {
return absl::StrFormat("%s %s", GenericToken, Description);
}
std::smatch MatchMe(std::string dfrag) const {
std::regex pattern(StrictPattern);
std::smatch match;
@@ -200,38 +202,61 @@ struct TextElement {
return match;
}
bool Empty() { return ID == 0; }
bool Empty() const { return ID == 0; }
// Comparison operator
bool operator==(const TextElement& other) const { return ID == other.ID; }
};
const static std::string kWindowBorder = "Window border";
const static std::string kWindowPosition = "Window position";
const static std::string kScrollSpeed = "Scroll speed";
const static std::string kTextDrawSpeed = "Text draw speed";
const static std::string kTextColor = "Text color";
const static std::string kPlayerName = "Player name";
const static std::string kLine1Str = "Line 1";
const static std::string kLine2Str = "Line 2";
const static std::string kLine3Str = "Line 3";
const static std::string kWaitForKey = "Wait for key";
const static std::string kScrollText = "Scroll text";
const static std::string kDelayX = "Delay X";
const static std::string kBCDNumber = "BCD number";
const static std::string kSoundEffect = "Sound effect";
const static std::string kChoose3 = "Choose 3";
const static std::string kChoose2High = "Choose 2 high";
const static std::string kChoose2Low = "Choose 2 low";
const static std::string kChoose2Indented = "Choose 2 indented";
const static std::string kChooseItem = "Choose item";
const static std::string kNextAttractImage = "Next attract image";
const static std::string kBankMarker = "Bank marker (automatic)";
const static std::string kCrash = "Crash";
static const std::vector<TextElement> TextCommands = {
TextElement(0x6B, "W", true, "Window border"),
TextElement(0x6D, "P", true, "Window position"),
TextElement(0x6E, "SPD", true, "Scroll speed"),
TextElement(0x7A, "S", true, "Text draw speed"),
TextElement(0x77, "C", true, "Text color"),
TextElement(0x6A, "L", false, "Player name"),
TextElement(0x74, "1", false, "Line 1"),
TextElement(0x75, "2", false, "Line 2"),
TextElement(0x76, "3", false, "Line 3"),
TextElement(0x7E, "K", false, "Wait for key"),
TextElement(0x73, "V", false, "Scroll text"),
TextElement(0x78, "WT", true, "Delay X"),
TextElement(0x6C, "N", true, "BCD number"),
TextElement(0x79, "SFX", true, "Sound effect"),
TextElement(0x71, "CH3", false, "Choose 3"),
TextElement(0x72, "CH2", false, "Choose 2 high"),
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
TextElement(0x69, "CHI", false, "Choose item"),
TextElement(0x67, "IMG", false, "Next attract image"),
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
TextElement(0x70, "NONO", false, "Crash"),
TextElement(0x6B, "W", true, kWindowBorder),
TextElement(0x6D, "P", true, kWindowPosition),
TextElement(0x6E, "SPD", true, kScrollSpeed),
TextElement(0x7A, "S", true, kTextDrawSpeed),
TextElement(0x77, "C", true, kTextColor),
TextElement(0x6A, "L", false, kPlayerName),
TextElement(0x74, "1", false, kLine1Str),
TextElement(0x75, "2", false, kLine2Str),
TextElement(0x76, "3", false, kLine3Str),
TextElement(0x7E, "K", false, kWaitForKey),
TextElement(0x73, "V", false, kScrollText),
TextElement(0x78, "WT", true, kDelayX),
TextElement(0x6C, "N", true, kBCDNumber),
TextElement(0x79, "SFX", true, kSoundEffect),
TextElement(0x71, "CH3", false, kChoose3),
TextElement(0x72, "CH2", false, kChoose2High),
TextElement(0x6F, "CH2L", false, kChoose2Low),
TextElement(0x68, "CH2I", false, kChoose2Indented),
TextElement(0x69, "CHI", false, kChooseItem),
TextElement(0x67, "IMG", false, kNextAttractImage),
TextElement(0x80, kBankToken, false, kBankMarker),
TextElement(0x70, "NONO", false, kCrash),
};
TextElement FindMatchingCommand(uint8_t b);
std::optional<TextElement> FindMatchingCommand(uint8_t b);
static const std::vector<TextElement> SpecialChars = {
TextElement(0x43, "...", false, "Ellipsis …"),
@@ -257,7 +282,7 @@ static const std::vector<TextElement> SpecialChars = {
TextElement(0x4B, "LFR", false, "Link face right"),
};
TextElement FindMatchingSpecial(uint8_t b);
std::optional<TextElement> FindMatchingSpecial(uint8_t b);
struct ParsedElement {
TextElement Parent;
@@ -265,17 +290,33 @@ struct ParsedElement {
bool Active = false;
ParsedElement() = default;
ParsedElement(TextElement textElement, uint8_t value) {
Parent = textElement;
Value = value;
Active = true;
}
ParsedElement(const TextElement& textElement, uint8_t value)
: Parent(textElement), Value(value), Active(true) {}
};
ParsedElement FindMatchingElement(const std::string& str);
std::string ParseTextDataByte(uint8_t value);
absl::StatusOr<MessageData> ParseSingleMessage(
const std::vector<uint8_t>& rom_data, int* current_pos);
std::vector<std::string> ParseMessageData(
std::vector<MessageData>& message_data,
const std::vector<DictionaryEntry>& dictionary_entries);
constexpr int kTextData2 = 0x75F40;
constexpr int kTextData2End = 0x773FF;
// Reads all text data from the ROM and returns a vector of MessageData objects.
std::vector<MessageData> ReadAllTextData(uint8_t* rom, int pos = kTextData);
// Calls the file dialog and loads expanded messages from a BIN file.
absl::Status LoadExpandedMessages(std::string& expanded_message_path,
std::vector<std::string>& parsed_messages,
std::vector<MessageData>& expanded_messages,
std::vector<DictionaryEntry>& dictionary);
} // namespace editor
} // namespace yaze

View File

@@ -1,25 +1,42 @@
#include "message_editor.h"
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "app/core/platform/renderer.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/window.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "gui/input.h"
#include "imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "util/hex.h"
namespace yaze {
namespace editor {
namespace {
std::string DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
std::string bankSTR = bank ? "1st" : "2nd";
std::string posSTR =
bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s",
bankSTR, space, posSTR);
return message;
}
} // namespace
using core::Renderer;
using ImGui::BeginChild;
@@ -27,8 +44,9 @@ using ImGui::BeginTable;
using ImGui::Button;
using ImGui::EndChild;
using ImGui::EndTable;
using ImGui::InputText;
using ImGui::InputTextMultiline;
using ImGui::PopID;
using ImGui::PushID;
using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableHeadersRow;
@@ -41,106 +59,46 @@ constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
ImGuiTableFlags_Borders |
ImGuiTableFlags_Resizable;
constexpr ImGuiTableFlags kDictTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
absl::Status MessageEditor::Initialize() {
void MessageEditor::Initialize() {
for (int i = 0; i < kWidthArraySize; i++) {
width_array[i] = rom()->data()[kCharactersWidth + i];
message_preview_.width_array[i] = rom()->data()[kCharactersWidth + i];
}
all_dictionaries_ = BuildDictionaryEntries(rom());
ReadAllTextDataV2();
message_preview_.all_dictionaries_ = BuildDictionaryEntries(rom());
list_of_texts_ = ReadAllTextData(rom()->mutable_data());
font_preview_colors_ = rom()->palette_group().hud.palette(0);
font_preview_colors_.AddColor(0x7FFF); // White
font_preview_colors_.AddColor(0x7C00); // Red
font_preview_colors_.AddColor(0x03E0); // Green
font_preview_colors_.AddColor(0x001F); // Blue
std::vector<uint8_t> data(0x4000, 0);
for (int i = 0; i < 0x4000; i++) {
data[i] = rom()->data()[kGfxFont + i];
raw_font_gfx_data_[i] = rom()->data()[kGfxFont + i];
}
font_gfx16_data_ = gfx::SnesTo8bppSheet(data, /*bpp=*/2, /*num_sheets=*/2);
// 4bpp
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
message_preview_.font_gfx16_data_ =
gfx::SnesTo8bppSheet(raw_font_gfx_data_, /*bpp=*/2, /*num_sheets=*/2);
Renderer::Get().CreateAndRenderBitmap(
kFontGfxMessageSize, kFontGfxMessageSize, kFontGfxMessageDepth,
font_gfx16_data_, font_gfx_bitmap_, font_preview_colors_))
message_preview_.font_gfx16_data_, font_gfx_bitmap_,
font_preview_colors_);
*font_gfx_bitmap_.mutable_palette() = font_preview_colors_;
*current_font_gfx16_bitmap_.mutable_palette() = font_preview_colors_;
current_font_gfx16_data_.reserve(kCurrentMessageWidth *
kCurrentMessageHeight);
for (int i = 0; i < kCurrentMessageWidth * kCurrentMessageHeight; i++) {
current_font_gfx16_data_.push_back(0);
auto load_font = LoadFontGraphics(*rom());
if (load_font.ok()) {
message_preview_.font_gfx16_data_2_ = load_font.value().vector();
}
// 8bpp
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
kCurrentMessageWidth, kCurrentMessageHeight, 64, current_font_gfx16_data_,
current_font_gfx16_bitmap_, font_preview_colors_))
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
for (int i = 0; i < font_preview_colors_.size(); i++) {
*color_palette.mutable_color(i) = font_preview_colors_[i];
}
*font_gfx_bitmap_.mutable_palette() = color_palette;
for (const auto& each_message : list_of_texts_) {
std::cout << "Message #" << each_message.ID << " at address "
<< core::HexLong(each_message.Address) << std::endl;
std::cout << " " << each_message.RawString << std::endl;
// Each string has a [:XX] char encoded
// The corresponding character is found in CharEncoder unordered_map
std::string parsed_message = "";
for (const auto& byte : each_message.Data) {
// Find the char byte in the CharEncoder map
if (CharEncoder.contains(byte)) {
parsed_message.push_back(CharEncoder.at(byte));
} else {
// If the byte is not found in the CharEncoder map, it is a command
// or a dictionary entry
if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
// Dictionary entry
auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
parsed_message.append(dictionaryEntry.Contents);
} else {
// Command
TextElement textElement = FindMatchingCommand(byte);
if (!textElement.Empty()) {
// If the element is line 2, 3 or V we add a newline
if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
textElement.ID == kLine3)
parsed_message.append("\n");
parsed_message.append(textElement.GenericToken);
}
}
}
}
std::cout << " > " << parsed_message << std::endl;
parsed_messages_.push_back(parsed_message);
}
parsed_messages_ =
ParseMessageData(list_of_texts_, message_preview_.all_dictionaries_);
current_message_ = list_of_texts_[1];
message_text_box_.text = parsed_messages_[current_message_.ID];
DrawMessagePreview();
return absl::OkStatus();
}
absl::Status MessageEditor::Update() {
if (rom()->is_loaded() && !data_loaded_) {
RETURN_IF_ERROR(Initialize());
current_message_ = list_of_texts_[1];
data_loaded_ = true;
}
absl::Status MessageEditor::Load() { return absl::OkStatus(); }
if (BeginTable("##MessageEditor", 4, kDictTableFlags)) {
absl::Status MessageEditor::Update() {
if (BeginTable("##MessageEditor", 4, kMessageTableFlags)) {
TableSetupColumn("List");
TableSetupColumn("Contents");
TableSetupColumn("Font Atlas");
TableSetupColumn("Commands");
TableSetupColumn("Dictionary");
TableHeadersRow();
TableNextColumn();
@@ -150,437 +108,289 @@ absl::Status MessageEditor::Update() {
DrawCurrentMessage();
TableNextColumn();
DrawTextCommands();
DrawFontAtlas();
DrawExpandedMessageSettings();
TableNextColumn();
DrawTextCommands();
DrawSpecialCharacters();
DrawDictionary();
EndTable();
}
return absl::OkStatus();
}
void MessageEditor::DrawMessageList() {
gui::BeginNoPadding();
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
gui::EndNoPadding();
if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
TableSetupColumn("Data");
TableHeadersRow();
for (const auto& message : list_of_texts_) {
TableNextColumn();
if (Button(core::HexWord(message.ID).c_str())) {
PushID(message.ID);
if (Button(util::HexWord(message.ID).c_str())) {
current_message_ = message;
message_text_box_.text = parsed_messages_[message.ID];
DrawMessagePreview();
}
PopID();
TableNextColumn();
TextWrapped("%s", parsed_messages_[message.ID].c_str());
TableNextColumn();
TextWrapped(
"%s",
core::HexLong(list_of_texts_[message.ID].Address).c_str());
TextWrapped("%s",
util::HexLong(list_of_texts_[message.ID].Address).c_str());
}
for (const auto& expanded_message : expanded_messages_) {
TableNextColumn();
PushID(expanded_message.ID + 0x18D);
if (Button(util::HexWord(expanded_message.ID + 0x18D).c_str())) {
current_message_ = expanded_message;
message_text_box_.text =
parsed_messages_[expanded_message.ID + 0x18D];
DrawMessagePreview();
}
PopID();
TableNextColumn();
TextWrapped("%s",
parsed_messages_[expanded_message.ID + 0x18C].c_str());
TableNextColumn();
TextWrapped("%s", util::HexLong(expanded_message.Address).c_str());
}
EndTable();
}
EndChild();
}
EndChild();
}
void MessageEditor::DrawCurrentMessage() {
Button(absl::StrCat("Message ", current_message_.ID).c_str());
if (InputTextMultiline("##MessageEditor",
&parsed_messages_[current_message_.ID],
if (InputTextMultiline("##MessageEditor", &message_text_box_.text,
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
current_message_.Data = ParseMessageToData(message_text_box_.text);
std::string temp = message_text_box_.text;
// Strip newline characters.
temp.erase(std::remove(temp.begin(), temp.end(), '\n'), temp.end());
current_message_.Data = ParseMessageToData(temp);
DrawMessagePreview();
}
Separator();
gui::MemoryEditorPopup("Message Data", current_message_.Data);
Text("Font Graphics");
gui::BeginPadding(1);
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
font_gfx_canvas_.DrawBackground();
font_gfx_canvas_.DrawContextMenu();
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
font_gfx_canvas_.DrawGrid();
font_gfx_canvas_.DrawOverlay();
EndChild();
gui::EndPadding();
Separator();
ImGui::BeginChild("##MessagePreview", ImVec2(0, 0), true, 1);
Text("Message Preview");
if (Button("Refresh Bitmap")) {
Renderer::GetInstance().UpdateBitmap(&current_font_gfx16_bitmap_);
if (Button("View Palette")) {
ImGui::OpenPopup("Palette");
}
if (ImGui::BeginPopup("Palette")) {
gui::DisplayPalette(font_preview_colors_, true);
ImGui::EndPopup();
}
gui::BeginPadding(1);
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
BeginChild("CurrentGfxFont", ImVec2(348, 0), true,
ImGuiWindowFlags_NoScrollWithMouse);
current_font_gfx16_canvas_.DrawBackground();
gui::EndPadding();
current_font_gfx16_canvas_.DrawContextMenu();
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
// Handle mouse wheel scrolling
if (ImGui::IsWindowHovered()) {
float wheel = ImGui::GetIO().MouseWheel;
if (wheel > 0 && message_preview_.shown_lines > 0) {
message_preview_.shown_lines--;
} else if (wheel < 0 &&
message_preview_.shown_lines < message_preview_.text_line - 2) {
message_preview_.shown_lines++;
}
}
// Draw only the visible portion of the text
current_font_gfx16_canvas_.DrawBitmap(
current_font_gfx16_bitmap_, ImVec2(0, 0), // Destination position
ImVec2(340,
font_gfx_canvas_.canvas_size().y), // Destination size
ImVec2(0, message_preview_.shown_lines * 16), // Source position
ImVec2(170,
font_gfx_canvas_.canvas_size().y / 2) // Source size
);
current_font_gfx16_canvas_.DrawGrid();
current_font_gfx16_canvas_.DrawOverlay();
EndChild();
ImGui::EndChild();
}
void MessageEditor::DrawFontAtlas() {
gui::BeginCanvas(font_gfx_canvas_, ImVec2(256, 256));
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0, 2.0f);
font_gfx_canvas_.DrawTileSelector(16, 32);
gui::EndCanvas(font_gfx_canvas_);
}
void MessageEditor::DrawExpandedMessageSettings() {
ImGui::BeginChild("##ExpandedMessageSettings", ImVec2(0, 100), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::Text("Expanded Messages");
static std::string expanded_message_path = "";
if (ImGui::Button("Load Expanded Message")) {
expanded_message_path = core::FileDialogWrapper::ShowOpenFileDialog();
if (!expanded_message_path.empty()) {
if (!LoadExpandedMessages(expanded_message_path, parsed_messages_,
expanded_messages_,
message_preview_.all_dictionaries_)
.ok()) {
context_->popup_manager->Show("Error");
}
}
}
if (expanded_messages_.size() > 0) {
ImGui::Text("Expanded Path: %s", expanded_message_path.c_str());
ImGui::Text("Expanded Messages: %lu", expanded_messages_.size());
if (ImGui::Button("Add New Message")) {
MessageData new_message;
new_message.ID = expanded_messages_.back().ID + 1;
new_message.Address = expanded_messages_.back().Address +
expanded_messages_.back().Data.size();
expanded_messages_.push_back(new_message);
}
if (ImGui::Button("Save Expanded Messages")) {
PRINT_IF_ERROR(SaveExpandedMessages());
}
}
EndChild();
}
void MessageEditor::DrawTextCommands() {
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
ImGui::BeginChild("##TextCommands",
ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
static uint8_t command_parameter = 0;
gui::InputHexByte("Command Parameter", &command_parameter);
for (const auto& text_element : TextCommands) {
if (Button(text_element.GenericToken.c_str())) {
message_text_box_.text.append(
text_element.GetParamToken(command_parameter));
}
EndChild();
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
void MessageEditor::DrawSpecialCharacters() {
ImGui::BeginChild("##SpecialChars",
ImVec2(0, ImGui::GetContentRegionAvail().y / 2), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (const auto& text_element : SpecialChars) {
if (Button(text_element.GenericToken.c_str())) {
message_text_box_.text.append(text_element.GenericToken);
}
SameLine();
TextWrapped("%s", text_element.Description.c_str());
Separator();
}
EndChild();
}
void MessageEditor::DrawDictionary() {
if (ImGui::BeginChild("##DictionaryChild", ImVec2(0, 0), true,
if (ImGui::BeginChild("##DictionaryChild",
ImVec2(0, ImGui::GetContentRegionAvail().y), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (BeginTable("##Dictionary", 2, kDictTableFlags)) {
if (BeginTable("##Dictionary", 2, kMessageTableFlags)) {
TableSetupColumn("ID");
TableSetupColumn("Contents");
for (const auto& dictionary : all_dictionaries_) {
TableHeadersRow();
for (const auto& dictionary : message_preview_.all_dictionaries_) {
TableNextColumn();
Text("%s", core::HexWord(dictionary.ID).c_str());
Text("%s", util::HexWord(dictionary.ID).c_str());
TableNextColumn();
Text("%s", dictionary.Contents.c_str());
}
EndTable();
}
EndChild();
}
}
// TODO: Fix the command parsing.
void MessageEditor::ReadAllTextDataV2() {
// Read all text data from the ROM.
int pos = kTextData;
int message_id = 0;
std::vector<uint8_t> raw_message;
std::vector<uint8_t> parsed_message;
std::string current_raw_message;
std::string current_parsed_message;
uint8_t current_byte = 0;
while (current_byte != 0xFF) {
current_byte = rom()->data()[pos++];
if (current_byte == kMessageTerminator) {
auto message =
MessageData(message_id++, pos, current_raw_message, raw_message,
current_parsed_message, parsed_message);
list_of_texts_.push_back(message);
raw_message.clear();
parsed_message.clear();
current_raw_message.clear();
current_parsed_message.clear();
continue;
}
raw_message.push_back(current_byte);
// Check for command.
TextElement text_element = FindMatchingCommand(current_byte);
if (!text_element.Empty()) {
parsed_message.push_back(current_byte);
if (text_element.HasArgument) {
current_byte = rom()->data()[pos++];
raw_message.push_back(current_byte);
parsed_message.push_back(current_byte);
}
current_raw_message.append(
text_element.GetParameterizedToken(current_byte));
current_parsed_message.append(
text_element.GetParameterizedToken(current_byte));
if (text_element.Token == BANKToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) {
current_raw_message.append(text_element.GetParameterizedToken());
current_parsed_message.append(text_element.GetParameterizedToken());
parsed_message.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_raw_message.append("[");
current_raw_message.append(DICTIONARYTOKEN);
current_raw_message.append(":");
current_raw_message.append(core::HexWord(dictionary));
current_raw_message.append("]");
uint32_t address = core::Get24LocalFromPC(
rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC(
rom()->mutable_data(),
kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
parsed_message.push_back(rom()->data()[i]);
current_parsed_message.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_raw_message.append(str);
current_parsed_message.append(str);
parsed_message.push_back(current_byte);
}
}
}
void MessageEditor::ReadAllTextData() {
int pos = kTextData;
int message_id = 0;
uint8_t current_byte;
std::vector<uint8_t> temp_bytes_raw;
std::vector<uint8_t> temp_bytes_parsed;
std::string current_message_raw;
std::string current_message_parsed;
TextElement text_element;
while (true) {
current_byte = rom()->data()[pos++];
if (current_byte == kMessageTerminator) {
auto message =
MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
current_message_parsed, temp_bytes_parsed);
list_of_texts_.push_back(message);
temp_bytes_raw.clear();
temp_bytes_parsed.clear();
current_message_raw.clear();
current_message_parsed.clear();
continue;
} else if (current_byte == 0xFF) {
break;
}
temp_bytes_raw.push_back(current_byte);
// Check for command.
text_element = FindMatchingCommand(current_byte);
if (!text_element.Empty()) {
temp_bytes_parsed.push_back(current_byte);
if (text_element.HasArgument) {
current_byte = rom()->data()[pos++];
temp_bytes_raw.push_back(current_byte);
temp_bytes_parsed.push_back(current_byte);
}
current_message_raw.append(
text_element.GetParameterizedToken(current_byte));
current_message_parsed.append(
text_element.GetParameterizedToken(current_byte));
if (text_element.Token == BANKToken) {
pos = kTextData2;
}
continue;
}
// Check for special characters.
text_element = FindMatchingSpecial(current_byte);
if (!text_element.Empty()) {
current_message_raw.append(text_element.GetParameterizedToken());
current_message_parsed.append(text_element.GetParameterizedToken());
temp_bytes_parsed.push_back(current_byte);
continue;
}
// Check for dictionary.
int dictionary = FindDictionaryEntry(current_byte);
if (dictionary >= 0) {
current_message_raw.append("[");
current_message_raw.append(DICTIONARYTOKEN);
current_message_raw.append(":");
current_message_raw.append(core::HexWord(dictionary));
current_message_raw.append("]");
uint32_t address = core::Get24LocalFromPC(
rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
uint32_t address_end = core::Get24LocalFromPC(
rom()->mutable_data(), kPointersDictionaries + ((dictionary + 1) * 2));
for (uint32_t i = address; i < address_end; i++) {
temp_bytes_parsed.push_back(rom()->data()[i]);
current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
}
continue;
}
// Everything else.
if (CharEncoder.contains(current_byte)) {
std::string str = "";
str.push_back(CharEncoder.at(current_byte));
current_message_raw.append(str);
current_message_parsed.append(str);
temp_bytes_parsed.push_back(current_byte);
}
}
}
std::string ReplaceAllDictionaryWords(std::string str,
std::vector<DictionaryEntry> dictionary) {
std::string temp = str;
for (const auto& entry : dictionary) {
if (absl::StrContains(temp, entry.Contents)) {
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
}
}
return temp;
}
DictionaryEntry MessageEditor::GetDictionaryFromID(uint8_t value) {
if (value < 0 || value >= all_dictionaries_.size()) {
return DictionaryEntry();
}
return all_dictionaries_[value];
}
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex, int sizey) {
const int num_x_tiles = 16;
const int img_width = 512; // (imgwidth/2)
int draw_id = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) {
int mx = xl;
int my = yl;
// Formula information to get tile index position in the array.
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
int tx = ((draw_id / num_x_tiles) * img_width) +
((draw_id - ((draw_id / 16) * 16)) * 4);
uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = x + (y * 172) + (mx * 2) + (my * 172);
if ((pixel & 0x0F) != 0) {
current_font_gfx16_data_[index + 1] =
(uint8_t)((pixel & 0x0F) + (0 * 4));
}
if (((pixel >> 4) & 0x0F) != 0) {
current_font_gfx16_data_[index + 0] =
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
}
}
}
}
void MessageEditor::DrawStringToPreview(std::string str) {
for (const auto c : str) {
DrawCharacterToPreview(c);
}
}
void MessageEditor::DrawCharacterToPreview(char c) {
DrawCharacterToPreview(FindMatchingCharacter(c));
}
void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
for (const uint8_t& value : text) {
if (skip_next) {
skip_next = false;
continue;
}
if (value < 100) {
int srcy = value / 16;
int srcx = value - (value & (~0xF));
if (text_position_ >= 170) {
text_position_ = 0;
text_line_++;
}
DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
text_position_ += width_array[value];
} else if (value == kLine1) {
text_position_ = 0;
text_line_ = 0;
} else if (value == kScrollVertical) {
text_position_ = 0;
text_line_ += 1;
} else if (value == kLine2) {
text_position_ = 0;
text_line_ = 1;
} else if (value == kLine3) {
text_position_ = 0;
text_line_ = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) {
skip_next = true;
continue;
} else if (value == 0x6C) // BCD numbers.
{
DrawCharacterToPreview('0');
skip_next = true;
continue;
} else if (value == 0x6A) {
// Includes parentheses to be longer, since player names can be up to 6
// characters.
DrawStringToPreview("(NAME)");
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
DrawCharacterToPreview(dictionaryEntry.Data);
}
}
EndChild();
}
void MessageEditor::DrawMessagePreview() {
// From Parsing.
text_line_ = 0;
for (int i = 0; i < (172 * 4096); i++) {
current_font_gfx16_data_[i] = 0;
message_preview_.DrawMessagePreview(current_message_);
if (current_font_gfx16_bitmap_.is_active()) {
current_font_gfx16_bitmap_.mutable_data() =
message_preview_.current_preview_data_;
Renderer::Get().UpdateBitmap(&current_font_gfx16_bitmap_);
} else {
Renderer::Get().CreateAndRenderBitmap(
kCurrentMessageWidth, kCurrentMessageHeight, 172,
message_preview_.current_preview_data_, current_font_gfx16_bitmap_,
font_preview_colors_);
}
text_position_ = 0;
DrawCharacterToPreview(current_message_.Data);
shown_lines_ = 0;
}
absl::Status MessageEditor::Save() {
std::vector<uint8_t> backup = rom()->vector();
for (int i = 0; i < kWidthArraySize; i++) {
RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i,
message_preview_.width_array[i]));
}
int pos = kTextData;
bool in_second_bank = false;
for (const auto& message : list_of_texts_) {
for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->WriteByte(pos, value));
if (value == kBlockTerminator) {
// Make sure we didn't go over the space available in the first block.
// 0x7FFF available.
if ((!in_second_bank & pos) > kTextDataEnd) {
return absl::InternalError(DisplayTextOverflowError(pos, true));
}
// Switch to the second block.
pos = kTextData2 - 1;
in_second_bank = true;
}
pos++;
}
RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
}
// Verify that we didn't go over the space available for the second block.
// 0x14BF available.
if ((in_second_bank & pos) > kTextData2End) {
std::copy(backup.begin(), backup.end(), rom()->mutable_data());
return absl::InternalError(DisplayTextOverflowError(pos, false));
}
RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
return absl::OkStatus();
}
absl::Status MessageEditor::SaveExpandedMessages() {
for (const auto& expanded_message : expanded_messages_) {
std::copy(expanded_message.Data.begin(), expanded_message.Data.end(),
expanded_message_bin_.mutable_data() + expanded_message.Address);
}
RETURN_IF_ERROR(expanded_message_bin_.WriteByte(
expanded_messages_.back().Address + expanded_messages_.back().Data.size(),
0xFF));
RETURN_IF_ERROR(expanded_message_bin_.SaveToFile(
Rom::SaveSettings{.backup = true, .save_new = false, .z3_save = false}));
return absl::OkStatus();
}
absl::Status MessageEditor::Cut() {
@@ -622,64 +432,12 @@ absl::Status MessageEditor::Undo() {
return absl::OkStatus();
}
absl::Status MessageEditor::Save() {
std::vector<uint8_t> backup = rom()->vector();
for (int i = 0; i < 100; i++) {
RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, width_array[i]));
}
int pos = kTextData;
bool in_second_bank = false;
for (const auto& message : list_of_texts_) {
for (const auto value : message.Data) {
RETURN_IF_ERROR(rom()->Write(pos, value));
if (value == kBlockTerminator) {
// Make sure we didn't go over the space available in the first block.
// 0x7FFF available.
if ((!in_second_bank & pos) > kTextDataEnd) {
return absl::InternalError(DisplayTextOverflowError(pos, true));
}
// Switch to the second block.
pos = kTextData2 - 1;
in_second_bank = true;
}
pos++;
}
RETURN_IF_ERROR(
rom()->Write(pos++, kMessageTerminator)); // , true, "Terminator text"
}
// Verify that we didn't go over the space available for the second block.
// 0x14BF available.
if ((in_second_bank & pos) > kTextData2End) {
// rom()->data() = backup;
return absl::InternalError(DisplayTextOverflowError(pos, false));
}
RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
absl::Status MessageEditor::Redo() {
// Implementation of redo functionality
// This would require tracking a redo stack in the TextBox struct
return absl::OkStatus();
}
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
std::string bankSTR = bank ? "1st" : "2nd";
std::string posSTR =
bank ? absl::StrFormat("%X4", pos & 0xFFFF)
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
std::string message = absl::StrFormat(
"There is too much text data in the %s block to save.\n"
"Available: %X4 | Used: %s",
bankSTR, space, posSTR);
return message;
}
void MessageEditor::Delete() {
// Determine if any text is selected in the TextBox control.
if (message_text_box_.selection_length == 0) {
@@ -699,5 +457,33 @@ void MessageEditor::SelectAll() {
}
}
absl::Status MessageEditor::Find() {
if (ImGui::Begin("Find Text", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
static char find_text[256] = "";
ImGui::InputText("Search", find_text, IM_ARRAYSIZE(find_text));
if (ImGui::Button("Find Next")) {
search_text_ = find_text;
}
ImGui::SameLine();
if (ImGui::Button("Find All")) {
search_text_ = find_text;
}
ImGui::SameLine();
if (ImGui::Button("Replace")) {
// TODO: Implement replace functionality
}
ImGui::Checkbox("Case Sensitive", &case_sensitive_);
ImGui::SameLine();
ImGui::Checkbox("Match Whole Word", &match_whole_word_);
}
ImGui::End();
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -1,164 +1,89 @@
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
#include <array>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "app/editor/message/message_data.h"
#include "app/editor/editor.h"
#include "app/editor/message/message_data.h"
#include "app/editor/message/message_preview.h"
#include "app/gfx/bitmap.h"
#include "app/gui/canvas.h"
#include "app/gui/style.h"
#include "app/rom.h"
namespace yaze {
namespace editor {
constexpr int kGfxFont = 0x70000; // 2bpp format
constexpr int kTextData2 = 0x75F40;
constexpr int kTextData2End = 0x773FF;
constexpr int kCharactersWidth = 0x74ADF;
constexpr int kNumMessages = 396;
constexpr int kCurrentMessageWidth = 172;
constexpr int kCurrentMessageHeight = 4096;
constexpr int kFontGfxMessageSize = 128;
constexpr int kFontGfxMessageDepth = 8;
constexpr int kFontGfxMessageDepth = 64;
constexpr int kFontGfx16Size = 172 * 4096;
constexpr uint8_t kWidthArraySize = 100;
constexpr uint8_t kBlockTerminator = 0x80;
constexpr uint8_t kMessageBankChangeId = 0x80;
constexpr uint8_t kScrollVertical = 0x73;
constexpr uint8_t kLine1 = 0x74;
constexpr uint8_t kLine2 = 0x75;
constexpr uint8_t kLine3 = 0x76;
static TextElement DictionaryElement =
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
class MessageEditor : public Editor, public SharedRom {
class MessageEditor : public Editor {
public:
MessageEditor() { type_ = EditorType::kMessage; }
explicit MessageEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kMessage;
}
absl::Status Initialize();
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
void DrawMessageList();
void DrawCurrentMessage();
void DrawFontAtlas();
void DrawTextCommands();
void DrawSpecialCharacters();
void DrawExpandedMessageSettings();
void DrawDictionary();
void DrawMessagePreview();
void ReadAllTextDataV2();
[[deprecated]] void ReadAllTextData();
absl::Status Save() override;
absl::Status SaveExpandedMessages();
absl::Status Cut() override;
absl::Status Copy() override;
absl::Status Paste() override;
absl::Status Undo() override;
absl::Status Redo() override {
return absl::UnimplementedError("Redo not implemented");
}
absl::Status Find() override {
return absl::UnimplementedError("Find not implemented");
}
absl::Status Save();
absl::Status Redo() override;
absl::Status Find() override;
void Delete();
void SelectAll();
DictionaryEntry GetDictionaryFromID(uint8_t value);
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex = 1, int sizey = 1);
void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
void DrawStringToPreview(std::string str);
void DrawMessagePreview();
std::string DisplayTextOverflowError(int pos, bool bank);
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private:
bool skip_next = false;
bool data_loaded_ = false;
int text_line_ = 0;
int text_position_ = 0;
int shown_lines_ = 0;
uint8_t width_array[kWidthArraySize];
bool case_sensitive_ = false;
bool match_whole_word_ = false;
std::string search_text_ = "";
std::vector<uint8_t> font_gfx16_data_;
std::vector<uint8_t> current_font_gfx16_data_;
std::array<uint8_t, 0x4000> raw_font_gfx_data_;
std::vector<std::string> parsed_messages_;
std::vector<MessageData> list_of_texts_;
std::vector<DictionaryEntry> all_dictionaries_;
std::vector<MessageData> expanded_messages_;
MessageData current_message_;
MessagePreview message_preview_;
gfx::Bitmap font_gfx_bitmap_;
gfx::Bitmap current_font_gfx16_bitmap_;
gfx::SnesPalette font_preview_colors_;
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(256, 256)};
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
ImVec2(172, 4096)};
struct TextBox {
std::string text;
std::string buffer;
int cursor_pos = 0;
int selection_start = 0;
int selection_end = 0;
int selection_length = 0;
bool has_selection = false;
bool has_focus = false;
bool changed = false;
bool can_undo = false;
void Undo() {
text = buffer;
cursor_pos = selection_start;
has_selection = false;
}
void clearUndo() { can_undo = false; }
void Copy() { ImGui::SetClipboardText(text.c_str()); }
void Cut() {
Copy();
text.erase(selection_start, selection_end - selection_start);
cursor_pos = selection_start;
has_selection = false;
changed = true;
}
void Paste() {
text.erase(selection_start, selection_end - selection_start);
text.insert(selection_start, ImGui::GetClipboardText());
std::string str = ImGui::GetClipboardText();
cursor_pos = selection_start + str.size();
has_selection = false;
changed = true;
}
void clear() {
text.clear();
buffer.clear();
cursor_pos = 0;
selection_start = 0;
selection_end = 0;
selection_length = 0;
has_selection = false;
has_focus = false;
changed = false;
can_undo = false;
}
void SelectAll() {
selection_start = 0;
selection_end = text.size();
selection_length = text.size();
has_selection = true;
}
void Focus() { has_focus = true; }
};
TextBox message_text_box_;
ImVec2(172 * 2, 4096)};
gui::TextBox message_text_box_;
Rom* rom_;
Rom expanded_message_bin_;
};
} // namespace editor

View File

@@ -0,0 +1,117 @@
#include "app/editor/message/message_preview.h"
namespace yaze {
namespace editor {
void MessagePreview::DrawTileToPreview(int x, int y, int srcx, int srcy,
int pal, int sizex, int sizey) {
const int num_x_tiles = 16;
const int img_width = 512; // (imgwidth/2)
int draw_id = srcx + (srcy * 32);
for (int yl = 0; yl < sizey * 8; yl++) {
for (int xl = 0; xl < 4; xl++) {
int mx = xl;
int my = yl;
// Formula information to get tile index position in the array.
int tx = ((draw_id / num_x_tiles) * img_width) + ((draw_id & 0xF) << 2);
uint8_t pixel = font_gfx16_data_2_[tx + (yl * 64) + xl];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = x + (y * 172) + (mx * 2) + (my * 172);
if ((pixel & 0x0F) != 0) {
current_preview_data_[index + 1] = (uint8_t)((pixel & 0x0F) + (0 * 4));
}
if (((pixel >> 4) & 0x0F) != 0) {
current_preview_data_[index + 0] =
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
}
}
}
}
void MessagePreview::DrawStringToPreview(const std::string& str) {
for (const auto& c : str) {
DrawCharacterToPreview(c);
}
}
void MessagePreview::DrawCharacterToPreview(char c) {
std::vector<uint8_t> text;
text.push_back(FindMatchingCharacter(c));
DrawCharacterToPreview(text);
}
void MessagePreview::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
for (const uint8_t& value : text) {
if (skip_next) {
skip_next = false;
continue;
}
if (value < 100) {
int srcy = value >> 4;
int srcx = value & 0xF;
if (text_position >= 170) {
text_position = 0;
text_line++;
}
DrawTileToPreview(text_position, text_line * 16, srcx, srcy, 0, 1, 2);
text_position += width_array[value];
} else if (value == kLine1) {
text_position = 0;
text_line = 0;
} else if (value == kScrollVertical) {
text_position = 0;
text_line += 1;
} else if (value == kLine2) {
text_position = 0;
text_line = 1;
} else if (value == kLine3) {
text_position = 0;
text_line = 2;
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
value == 0x77 || value == 0x78 || value == 0x79 ||
value == 0x7A) {
skip_next = true;
continue;
} else if (value == 0x6C) // BCD numbers.
{
DrawCharacterToPreview('0');
skip_next = true;
continue;
} else if (value == 0x6A) {
// Includes parentheses to be longer, since player names can be up to 6
// characters.
const std::string name = "(NAME)";
DrawStringToPreview(name);
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
int pos = value - DICTOFF;
if (pos < 0 || pos >= all_dictionaries_.size()) {
// Invalid dictionary entry.
std::cerr << "Invalid dictionary entry: " << pos << std::endl;
continue;
}
auto dictionary_entry = FindRealDictionaryEntry(value, all_dictionaries_);
DrawCharacterToPreview(dictionary_entry.Data);
}
}
}
void MessagePreview::DrawMessagePreview(const MessageData& message) {
// From Parsing.
text_line = 0;
std::fill(current_preview_data_.begin(), current_preview_data_.end(), 0);
text_position = 0;
DrawCharacterToPreview(message.Data);
shown_lines = 0;
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,44 @@
#ifndef YAZEE_MESSAGE_PREVIEW_H_
#define YAZEE_MESSAGE_PREVIEW_H_
#include <string>
#include <vector>
#include "app/editor/message/message_data.h"
namespace yaze {
namespace editor {
constexpr int kCurrentMessageWidth = 172;
constexpr int kCurrentMessageHeight = 4096;
struct MessagePreview {
MessagePreview() {
current_preview_data_.resize(kCurrentMessageWidth * kCurrentMessageHeight);
std::fill(current_preview_data_.begin(), current_preview_data_.end(), 0);
}
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
int sizex = 1, int sizey = 1);
void DrawStringToPreview(const std::string& str);
void DrawCharacterToPreview(char c);
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
void DrawMessagePreview(const MessageData& message);
bool skip_next = false;
int text_line = 0;
int text_position = 0;
int shown_lines = 0;
std::array<uint8_t, kWidthArraySize> width_array = {0};
std::vector<uint8_t> font_gfx16_data_;
std::vector<uint8_t> font_gfx16_data_2_;
std::vector<uint8_t> current_preview_data_;
std::vector<DictionaryEntry> all_dictionaries_;
};
} // namespace editor
} // namespace yaze
#endif // YAZEE_MESSAGE_PREVIEW_H_

View File

@@ -1,16 +1,20 @@
#include "music_editor.h"
#include "imgui/imgui.h"
#include "absl/strings/str_format.h"
#include "app/editor/code/assembly_editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
void MusicEditor::Initialize() {}
absl::Status MusicEditor::Load() {
return absl::OkStatus();
}
absl::Status MusicEditor::Update() {
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
ImVec2(0, 0))) {
@@ -194,9 +198,21 @@ void MusicEditor::DrawToolset() {
is_playing = !is_playing;
}
BUTTON_COLUMN(ICON_MD_FAST_REWIND)
BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
BUTTON_COLUMN(ICON_MD_VOLUME_UP)
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_FAST_REWIND)) {
// Handle rewind button click
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_FAST_FORWARD)) {
// Handle fast forward button click
}
ImGui::TableNextColumn();
if (ImGui::Button(ICON_MD_VOLUME_UP)) {
// Handle volume up button click
}
if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
music_tracker_.LoadSongs(*rom());
}

View File

@@ -1,12 +1,8 @@
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
#include "absl/strings/str_format.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/music/tracker.h"
#include "imgui/imgui.h"
@@ -50,24 +46,39 @@ static constexpr absl::string_view kSongNotes[] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
const ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
const ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable;
/**
* @class MusicEditor
* @brief A class for editing music data in a Rom.
*/
class MusicEditor : public SharedRom, public Editor {
class MusicEditor : public Editor {
public:
MusicEditor() { type_ = EditorType::kMusic; }
explicit MusicEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kMusic;
}
void Initialize() override;
absl::Status Load() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
private:
Rom* rom_;
void DrawChannels();
void DrawPianoStaff();
void DrawPianoRoll();
@@ -77,10 +88,6 @@ class MusicEditor : public SharedRom, public Editor {
zelda3::music::Tracker music_tracker_;
AssemblyEditor assembly_editor_;
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
ImGuiTableFlags music_editor_flags_ = ImGuiTableFlags_SizingFixedFit |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_Reorderable;
};
} // namespace editor

View File

@@ -3,6 +3,7 @@
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "util/hex.h"
namespace yaze {
namespace editor {
@@ -91,7 +92,7 @@ void HandleEntityDragging(zelda3::GameEntity *entity, ImVec2 canvas_p0,
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
sizeof(zelda3::GameEntity));
Text("Moving %s ID: %s", entity_type.c_str(),
core::HexByte(entity->entity_id_).c_str());
util::HexByte(entity->entity_id_).c_str());
ImGui::EndDragDropSource();
}
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
@@ -125,51 +126,70 @@ bool DrawEntranceInserterPopup() {
return set_done;
}
// TODO: Implement deleting OverworldEntrance objects, currently only hides them
bool DrawOverworldEntrancePopup(
zelda3::OverworldEntrance &entrance) {
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance) {
static bool set_done = false;
if (set_done) {
set_done = false;
return true;
}
if (ImGui::BeginPopupModal("Entrance editor", NULL,
if (ImGui::BeginPopupModal("Entrance Editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Entrance ID: %d", entrance.entrance_id_);
ImGui::Separator();
gui::InputHexWord("Map ID", &entrance.map_id_);
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
kInputFieldSize + 20);
gui::InputHex("X", &entrance.x_);
gui::InputHex("Y", &entrance.y_);
if (Button(ICON_MD_DONE)) {
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
gui::InputHex("X Position", &entrance.x_);
gui::InputHex("Y Position", &entrance.y_);
ImGui::Checkbox("Is Hole", &entrance.is_hole_);
ImGui::Separator();
if (Button("Save")) {
set_done = true;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_DELETE)) {
ImGui::SameLine();
if (Button("Delete")) {
entrance.deleted = true;
set_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
return set_done;
}
// TODO: Implement deleting OverworldExit objects
void DrawExitInserterPopup() {
if (ImGui::BeginPopup("Exit Inserter")) {
static int exit_id = 0;
static int room_id = 0;
static int x_pos = 0;
static int y_pos = 0;
ImGui::Text("Insert New Exit");
ImGui::Separator();
gui::InputHex("Exit ID", &exit_id);
gui::InputHex("Room ID", &room_id);
gui::InputHex("X Position", &x_pos);
gui::InputHex("Y Position", &y_pos);
if (Button(ICON_MD_DONE)) {
if (Button("Create Exit")) {
// This would need to be connected to the overworld editor to actually create the exit
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
if (Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
@@ -317,8 +337,7 @@ void DrawItemInsertPopup() {
BeginChild("ScrollRegion", ImVec2(150, 150), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::kSecretItemNames[i].c_str(),
i == new_item_id)) {
if (Selectable(zelda3::kSecretItemNames[i].c_str(), i == new_item_id)) {
new_item_id = i;
}
}
@@ -351,8 +370,7 @@ bool DrawItemEditorPopup(zelda3::OverworldItem &item) {
ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::BeginGroup();
for (size_t i = 0; i < zelda3::kSecretItemNames.size(); i++) {
if (Selectable(zelda3::kSecretItemNames[i].c_str(),
item.id_ == i)) {
if (Selectable(zelda3::kSecretItemNames[i].c_str(), item.id_ == i)) {
item.id_ = i;
}
}
@@ -395,8 +413,8 @@ void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
if (ImGui::BeginTable("##sprites", 2,
ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
MyItemColumnID_ID);
ImGui::TableSetupColumn("Name", 0, 0.0f, MyItemColumnID_Name);
SpriteItemColumnID_ID);
ImGui::TableSetupColumn("Name", 0, 0.0f, SpriteItemColumnID_Name);
ImGui::TableHeadersRow();
// Handle sorting
@@ -426,24 +444,35 @@ void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
}
}
// TODO: Implement deleting OverworldSprite objects
void DrawSpriteInserterPopup() {
if (ImGui::BeginPopup("Sprite Inserter")) {
static int new_sprite_id = 0;
Text("Add Sprite");
BeginChild("ScrollRegion", ImVec2(250, 250), true,
static int x_pos = 0;
static int y_pos = 0;
ImGui::Text("Add New Sprite");
ImGui::Separator();
BeginChild("ScrollRegion", ImVec2(250, 200), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; });
EndChild();
ImGui::Separator();
ImGui::Text("Position:");
gui::InputHex("X Position", &x_pos);
gui::InputHex("Y Position", &y_pos);
if (Button(ICON_MD_DONE)) {
// Add the new item to the overworld
if (Button("Add Sprite")) {
// This would need to be connected to the overworld editor to actually create the sprite
new_sprite_id = 0;
x_pos = 0;
y_pos = 0;
ImGui::CloseCurrentPopup();
}
SameLine();
if (Button(ICON_MD_CANCEL)) {
if (Button("Cancel")) {
ImGui::CloseCurrentPopup();
}

View File

@@ -1,10 +1,12 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
#define YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
#include "imgui/imgui.h"
#include "app/zelda3/common.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/zelda3/overworld/overworld_entrance.h"
#include "app/zelda3/overworld/overworld_exit.h"
#include "app/zelda3/overworld/overworld_item.h"
#include "app/zelda3/sprite/sprite.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
@@ -31,12 +33,14 @@ void DrawItemInsertPopup();
bool DrawItemEditorPopup(zelda3::OverworldItem &item);
enum MyItemColumnID {
MyItemColumnID_ID,
MyItemColumnID_Name,
MyItemColumnID_Action,
MyItemColumnID_Quantity,
MyItemColumnID_Description
/**
* @brief Column IDs for the sprite table.
*
*/
enum SpriteItemColumnID {
SpriteItemColumnID_ID,
SpriteItemColumnID_Name,
SpriteItemColumnID_Description
};
struct SpriteItem {
@@ -59,10 +63,10 @@ struct SpriteItem {
&s_current_sort_specs->Specs[n];
int delta = 0;
switch (sort_spec->ColumnUserID) {
case MyItemColumnID_ID:
case SpriteItemColumnID_ID:
delta = (a.id - b.id);
break;
case MyItemColumnID_Name:
case SpriteItemColumnID_Name:
delta = strcmp(a.name + 2, b.name + 2);
break;
}

View File

@@ -0,0 +1,950 @@
#include "app/editor/overworld/map_properties.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/zelda3/overworld/overworld_map.h"
#include "app/editor/overworld/overworld_editor.h"
#include "app/editor/overworld/ui_constants.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
using ImGui::BeginTable;
// HOVER_HINT is defined in util/macro.h
using ImGui::Separator;
using ImGui::TableNextColumn;
using ImGui::Text;
// Using centralized UI constants
void MapPropertiesSystem::DrawSimplifiedMapSettings(int& current_world, int& current_map,
bool& current_map_lock, bool& show_map_properties_panel,
bool& show_custom_bg_color_editor, bool& show_overlay_editor,
bool& show_overlay_preview, int& game_state, int& current_mode) {
// Enhanced settings table with popup buttons for quick access and integrated toolset
if (BeginTable("SimplifiedMapSettings", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0), -1)) {
ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed, kTableColumnWorld);
ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, kTableColumnMap);
ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed, kTableColumnAreaSize);
ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed, kTableColumnLock);
ImGui::TableSetupColumn("Graphics", ImGuiTableColumnFlags_WidthFixed, kTableColumnGraphics);
ImGui::TableSetupColumn("Palettes", ImGuiTableColumnFlags_WidthFixed, kTableColumnPalettes);
ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed, kTableColumnProperties);
ImGui::TableSetupColumn("View", ImGuiTableColumnFlags_WidthFixed, kTableColumnView);
ImGui::TableSetupColumn("Quick", ImGuiTableColumnFlags_WidthFixed, kTableColumnQuick);
TableNextColumn();
ImGui::SetNextItemWidth(kComboWorldWidth);
if (ImGui::Combo("##world", &current_world, kWorldNames, 3)) {
// World changed, update current map if needed
if (current_map >= 0x40 && current_world == 0) {
current_map -= 0x40;
} else if (current_map < 0x40 && current_world == 1) {
current_map += 0x40;
} else if (current_map < 0x80 && current_world == 2) {
current_map += 0x80;
} else if (current_map >= 0x80 && current_world != 2) {
current_map -= 0x80;
}
}
TableNextColumn();
ImGui::Text("%d (0x%02X)", current_map, current_map);
TableNextColumn();
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF) {
int current_area_size = static_cast<int>(overworld_->overworld_map(current_map)->area_size());
ImGui::SetNextItemWidth(kComboAreaSizeWidth);
if (ImGui::Combo("##AreaSize", &current_area_size, kAreaSizeNames, 4)) {
overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(current_area_size));
RefreshOverworldMap();
}
} else {
ImGui::Text("N/A");
}
TableNextColumn();
if (ImGui::Button(current_map_lock ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN, ImVec2(40, 0))) {
current_map_lock = !current_map_lock;
}
HOVER_HINT(current_map_lock ? "Unlock Map" : "Lock Map");
TableNextColumn();
if (ImGui::Button("Graphics", ImVec2(kTableButtonGraphics, 0))) {
ImGui::OpenPopup("GraphicsPopup");
}
HOVER_HINT("Graphics Settings");
DrawGraphicsPopup(current_map, game_state);
TableNextColumn();
if (ImGui::Button("Palettes", ImVec2(kTableButtonPalettes, 0))) {
ImGui::OpenPopup("PalettesPopup");
}
HOVER_HINT("Palette Settings");
DrawPalettesPopup(current_map, game_state, show_custom_bg_color_editor);
TableNextColumn();
if (ImGui::Button("Properties", ImVec2(kTableButtonProperties, 0))) {
ImGui::OpenPopup("PropertiesPopup");
}
HOVER_HINT("Map Properties & Overlays");
DrawPropertiesPopup(current_map, show_map_properties_panel, show_overlay_preview, game_state);
TableNextColumn();
// View Controls
if (ImGui::Button("View", ImVec2(kTableButtonView, 0))) {
ImGui::OpenPopup("ViewPopup");
}
HOVER_HINT("View Controls");
DrawViewPopup();
TableNextColumn();
// Quick Access Tools
if (ImGui::Button("Quick", ImVec2(kTableButtonQuick, 0))) {
ImGui::OpenPopup("QuickPopup");
}
HOVER_HINT("Quick Access Tools");
DrawQuickAccessPopup();
ImGui::EndTable();
}
}
void MapPropertiesSystem::DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel) {
if (!overworld_->is_loaded()) {
Text("No overworld loaded");
return;
}
// Header with map info and lock status
ImGui::BeginGroup();
Text("Current Map: %d (0x%02X)", current_map, current_map);
ImGui::EndGroup();
Separator();
// Create tabs for different property categories
if (ImGui::BeginTabBar("MapPropertiesTabs", ImGuiTabBarFlags_FittingPolicyScroll)) {
// Basic Properties Tab
if (ImGui::BeginTabItem("Basic Properties")) {
DrawBasicPropertiesTab(current_map);
ImGui::EndTabItem();
}
// Sprite Properties Tab
if (ImGui::BeginTabItem("Sprite Properties")) {
DrawSpritePropertiesTab(current_map);
ImGui::EndTabItem();
}
// Custom Overworld Features Tab
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF && ImGui::BeginTabItem("Custom Features")) {
DrawCustomFeaturesTab(current_map);
ImGui::EndTabItem();
}
// Tile Graphics Tab
if (ImGui::BeginTabItem("Tile Graphics")) {
DrawTileGraphicsTab(current_map);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
void MapPropertiesSystem::DrawCustomBackgroundColorEditor(int current_map, bool& show_custom_bg_color_editor) {
if (!overworld_->is_loaded()) {
Text("No overworld loaded");
return;
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 2) {
Text("Custom background colors require ZSCustomOverworld v2+");
return;
}
Text("Custom Background Color Editor");
Separator();
// Enable/disable area-specific background color
static bool use_area_specific_bg_color = false;
if (ImGui::Checkbox("Use Area-Specific Background Color", &use_area_specific_bg_color)) {
// Update ROM data
(*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] = use_area_specific_bg_color ? 1 : 0;
}
if (use_area_specific_bg_color) {
// Get current color
uint16_t current_color = overworld_->overworld_map(current_map)->area_specific_bg_color();
gfx::SnesColor snes_color(current_color);
// Convert to ImVec4 for color picker
ImVec4 color_vec = gui::ConvertSnesColorToImVec4(snes_color);
if (ImGui::ColorPicker4("Background Color", (float*)&color_vec,
ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) {
// Convert back to SNES color and update
gfx::SnesColor new_snes_color = gui::ConvertImVec4ToSnesColor(color_vec);
overworld_->mutable_overworld_map(current_map)->set_area_specific_bg_color(new_snes_color.snes());
// Update ROM
int rom_address = zelda3::OverworldCustomAreaSpecificBGPalette + (current_map * 2);
(*rom_)[rom_address] = new_snes_color.snes() & 0xFF;
(*rom_)[rom_address + 1] = (new_snes_color.snes() >> 8) & 0xFF;
}
Text("SNES Color: 0x%04X", current_color);
}
}
void MapPropertiesSystem::DrawOverlayEditor(int current_map, bool& show_overlay_editor) {
if (!overworld_->is_loaded()) {
Text("No overworld loaded");
return;
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 1) {
Text("Subscreen overlays require ZSCustomOverworld v1+");
return;
}
Text("Overlay Editor");
Separator();
// Enable/disable subscreen overlay
static bool use_subscreen_overlay = false;
if (ImGui::Checkbox("Use Subscreen Overlay", &use_subscreen_overlay)) {
// Update ROM data
(*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = use_subscreen_overlay ? 1 : 0;
}
if (use_subscreen_overlay) {
uint16_t current_overlay = overworld_->overworld_map(current_map)->subscreen_overlay();
if (gui::InputHexWord("Overlay ID", &current_overlay, kInputFieldSize + 20)) {
overworld_->mutable_overworld_map(current_map)->set_subscreen_overlay(current_overlay);
// Update ROM
int rom_address = zelda3::OverworldCustomSubscreenOverlayArray + (current_map * 2);
(*rom_)[rom_address] = current_overlay & 0xFF;
(*rom_)[rom_address + 1] = (current_overlay >> 8) & 0xFF;
}
Text("Common overlay IDs:");
Text("0x0000 = None");
Text("0x0001 = Map overlay");
Text("0x0002 = Dungeon overlay");
}
}
void MapPropertiesSystem::SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock,
bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
bool& show_overlay_editor) {
// Clear any existing context menu items
canvas.ClearContextMenuItems();
// Add overworld-specific context menu items
gui::Canvas::ContextMenuItem lock_item;
lock_item.label = current_map_lock ? "Unlock Map" : "Lock to This Map";
lock_item.callback = [&current_map_lock]() {
current_map_lock = !current_map_lock;
};
canvas.AddContextMenuItem(lock_item);
// Map Properties
gui::Canvas::ContextMenuItem properties_item;
properties_item.label = "Map Properties";
properties_item.callback = [&show_map_properties_panel]() {
show_map_properties_panel = true;
};
canvas.AddContextMenuItem(properties_item);
// Custom overworld features (only show if v3+)
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 3 && asm_version != 0xFF) {
// Custom Background Color
gui::Canvas::ContextMenuItem bg_color_item;
bg_color_item.label = "Custom Background Color";
bg_color_item.callback = [&show_custom_bg_color_editor]() {
show_custom_bg_color_editor = true;
};
canvas.AddContextMenuItem(bg_color_item);
// Overlay Settings
gui::Canvas::ContextMenuItem overlay_item;
overlay_item.label = "Overlay Settings";
overlay_item.callback = [&show_overlay_editor]() {
show_overlay_editor = true;
};
canvas.AddContextMenuItem(overlay_item);
}
// Canvas controls
gui::Canvas::ContextMenuItem reset_pos_item;
reset_pos_item.label = "Reset Canvas Position";
reset_pos_item.callback = [&canvas]() {
canvas.set_scrolling(ImVec2(0, 0));
};
canvas.AddContextMenuItem(reset_pos_item);
gui::Canvas::ContextMenuItem zoom_fit_item;
zoom_fit_item.label = "Zoom to Fit";
zoom_fit_item.callback = [&canvas]() {
canvas.set_global_scale(1.0f);
canvas.set_scrolling(ImVec2(0, 0));
};
canvas.AddContextMenuItem(zoom_fit_item);
}
// Private method implementations
void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
if (ImGui::BeginPopup("GraphicsPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::Text("Graphics Settings");
ImGui::Separator();
if (gui::InputHexByteCustom("Area Graphics",
overworld_->mutable_overworld_map(current_map)->mutable_area_graphics(),
kHexByteInputWidth)) {
RefreshMapProperties();
RefreshOverworldMap();
}
if (gui::InputHexByteCustom(absl::StrFormat("Sprite GFX (%s)", kGameStateNames[game_state]).c_str(),
overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(game_state),
kHexByteInputWidth)) {
RefreshMapProperties();
RefreshOverworldMap();
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 3) {
if (gui::InputHexByte("Animated GFX",
overworld_->mutable_overworld_map(current_map)->mutable_animated_gfx(),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
ImGui::Separator();
ImGui::Text("Custom Tile Graphics (8 sheets):");
// Show the 8 custom graphics IDs in a more accessible way
for (int i = 0; i < 8; i++) {
std::string label = absl::StrFormat("Sheet %d", i);
if (gui::InputHexByte(label.c_str(),
overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i),
80.f)) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
ImGui::EndPopup();
}
}
void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state, bool& show_custom_bg_color_editor) {
if (ImGui::BeginPopup("PalettesPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::Text("Palette Settings");
ImGui::Separator();
if (gui::InputHexByteCustom("Area Palette",
overworld_->mutable_overworld_map(current_map)->mutable_area_palette(),
kHexByteInputWidth)) {
RefreshMapProperties();
auto status = RefreshMapPalette();
RefreshOverworldMap();
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 2) {
if (gui::InputHexByteCustom("Main Palette",
overworld_->mutable_overworld_map(current_map)->mutable_main_palette(),
kHexByteInputWidth)) {
RefreshMapProperties();
auto status = RefreshMapPalette();
RefreshOverworldMap();
}
}
if (gui::InputHexByteCustom(absl::StrFormat("Sprite Palette (%s)", kGameStateNames[game_state]).c_str(),
overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(game_state),
kHexByteInputWidth)) {
RefreshMapProperties();
RefreshOverworldMap();
}
ImGui::Separator();
if (ImGui::Button("Background Color")) {
show_custom_bg_color_editor = !show_custom_bg_color_editor;
}
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
ImGui::EndPopup();
}
}
void MapPropertiesSystem::DrawPropertiesPopup(int current_map, bool& show_map_properties_panel,
bool& show_overlay_preview, int& game_state) {
if (ImGui::BeginPopup("PropertiesPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::Text("Map Properties");
ImGui::Separator();
// Basic Map Properties Section
ImGui::Text("Basic Properties");
ImGui::Separator();
if (gui::InputHexWordCustom("Message ID",
overworld_->mutable_overworld_map(current_map)->mutable_message_id(),
kHexWordInputWidth)) {
RefreshMapProperties();
RefreshOverworldMap();
}
ImGui::SetNextItemWidth(kComboGameStateWidth);
if (ImGui::Combo("Game State", &game_state, kGameStateNames, 3)) {
RefreshMapProperties();
RefreshOverworldMap();
}
// Area Configuration Section
ImGui::Separator();
ImGui::Text("Area Configuration");
ImGui::Separator();
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF) {
int current_area_size = static_cast<int>(overworld_->overworld_map(current_map)->area_size());
ImGui::SetNextItemWidth(kComboAreaSizeWidth);
if (ImGui::Combo("Area Size", &current_area_size, kAreaSizeNames, 4)) {
overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(current_area_size));
RefreshOverworldMap();
}
} else {
// Vanilla ROM - show small/large map controls
auto* map = overworld_->mutable_overworld_map(current_map);
bool is_small = !map->is_large_map();
if (ImGui::Checkbox("Small Map", &is_small)) {
if (is_small) {
map->SetAsSmallMap();
} else {
// For vanilla, use default parent and quadrant values
map->SetAsLargeMap(0, 0);
}
RefreshOverworldMap();
}
}
// Visual Effects Section
ImGui::Separator();
ImGui::Text("Visual Effects");
ImGui::Separator();
DrawMosaicControls(current_map);
DrawOverlayControls(current_map, show_overlay_preview);
// Advanced Options Section
ImGui::Separator();
ImGui::Text("Advanced Options");
ImGui::Separator();
if (ImGui::Button("Full Properties Panel", ImVec2(kLargeButtonWidth + 50, 0))) {
show_map_properties_panel = true;
ImGui::CloseCurrentPopup();
}
HOVER_HINT("Open comprehensive properties editor");
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
ImGui::EndPopup();
}
}
void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) {
if (BeginTable("BasicProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
TableNextColumn(); ImGui::Text("Area Graphics");
TableNextColumn();
if (gui::InputHexByte("##AreaGfx",
overworld_->mutable_overworld_map(current_map)->mutable_area_graphics(),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Area Palette");
TableNextColumn();
if (gui::InputHexByte("##AreaPal",
overworld_->mutable_overworld_map(current_map)->mutable_area_palette(),
kInputFieldSize)) {
RefreshMapProperties();
auto status = RefreshMapPalette();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Message ID");
TableNextColumn();
if (gui::InputHexWord("##MsgId",
overworld_->mutable_overworld_map(current_map)->mutable_message_id(),
kInputFieldSize + 20)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Mosaic Effect");
TableNextColumn();
if (ImGui::Checkbox("##mosaic",
overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) {
RefreshMapProperties();
RefreshOverworldMap();
}
HOVER_HINT("Enable Mosaic effect for the current map");
ImGui::EndTable();
}
}
void MapPropertiesSystem::DrawSpritePropertiesTab(int current_map) {
if (BeginTable("SpriteProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
TableNextColumn(); ImGui::Text("Game State");
TableNextColumn();
static int game_state = 0;
ImGui::SetNextItemWidth(100.f);
if (ImGui::Combo("##GameState", &game_state, kGameStateNames, 3)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Sprite Graphics 1");
TableNextColumn();
if (gui::InputHexByte("##SprGfx1",
overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(1),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Sprite Graphics 2");
TableNextColumn();
if (gui::InputHexByte("##SprGfx2",
overworld_->mutable_overworld_map(current_map)->mutable_sprite_graphics(2),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Sprite Palette 1");
TableNextColumn();
if (gui::InputHexByte("##SprPal1",
overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(1),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Sprite Palette 2");
TableNextColumn();
if (gui::InputHexByte("##SprPal2",
overworld_->mutable_overworld_map(current_map)->mutable_sprite_palette(2),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
ImGui::EndTable();
}
}
void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) {
if (BeginTable("CustomFeatures", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
TableNextColumn(); ImGui::Text("Area Size");
TableNextColumn();
static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"};
int current_area_size = static_cast<int>(overworld_->overworld_map(current_map)->area_size());
ImGui::SetNextItemWidth(120.f);
if (ImGui::Combo("##AreaSize", &current_area_size, area_size_names, 4)) {
overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(current_area_size));
RefreshOverworldMap();
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 2) {
TableNextColumn(); ImGui::Text("Main Palette");
TableNextColumn();
if (gui::InputHexByte("##MainPal",
overworld_->mutable_overworld_map(current_map)->mutable_main_palette(),
kInputFieldSize)) {
RefreshMapProperties();
auto status = RefreshMapPalette();
RefreshOverworldMap();
}
}
if (asm_version >= 3) {
TableNextColumn(); ImGui::Text("Animated GFX");
TableNextColumn();
if (gui::InputHexByte("##AnimGfx",
overworld_->mutable_overworld_map(current_map)->mutable_animated_gfx(),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn(); ImGui::Text("Subscreen Overlay");
TableNextColumn();
if (gui::InputHexWord("##SubOverlay",
overworld_->mutable_overworld_map(current_map)->mutable_subscreen_overlay(),
kInputFieldSize + 20)) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
ImGui::EndTable();
}
}
void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) {
ImGui::Text("Custom Tile Graphics (8 sheets per map):");
Separator();
if (BeginTable("TileGraphics", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80);
ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80);
for (int i = 0; i < 4; i++) {
TableNextColumn();
ImGui::Text("Sheet %d", i);
TableNextColumn();
if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(),
overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
TableNextColumn();
ImGui::Text("Sheet %d", i + 4);
TableNextColumn();
if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i + 4).c_str(),
overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i + 4),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
ImGui::EndTable();
}
}
void MapPropertiesSystem::RefreshMapProperties() {
// Implementation would refresh map properties
}
void MapPropertiesSystem::RefreshOverworldMap() {
// Implementation would refresh the overworld map display
}
absl::Status MapPropertiesSystem::RefreshMapPalette() {
// Implementation would refresh the map palette
return absl::OkStatus();
}
void MapPropertiesSystem::DrawMosaicControls(int current_map) {
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 2) {
ImGui::Separator();
ImGui::Text("Mosaic Effects (per direction):");
auto* current_map_ptr = overworld_->mutable_overworld_map(current_map);
std::array<bool, 4> mosaic_expanded = current_map_ptr->mosaic_expanded();
const char* direction_names[] = {"North", "South", "East", "West"};
for (int i = 0; i < 4; i++) {
if (ImGui::Checkbox(direction_names[i], &mosaic_expanded[i])) {
current_map_ptr->set_mosaic_expanded(i, mosaic_expanded[i]);
RefreshMapProperties();
RefreshOverworldMap();
}
}
} else {
if (ImGui::Checkbox("Mosaic Effect",
overworld_->mutable_overworld_map(current_map)
->mutable_mosaic())) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
}
void MapPropertiesSystem::DrawOverlayControls(int current_map, bool& show_overlay_preview) {
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
// Determine if this is a special overworld map (0x80-0x9F)
bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
if (is_special_overworld_map) {
// Special overworld maps (0x80-0x9F) do not support subscreen overlays
ImGui::Text("Special overworld maps (0x80-0x9F) do not support");
ImGui::Text("subscreen overlays (visual effects).");
ImGui::Text("Map 0x%02X is a special overworld map", current_map);
} else {
// Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen overlays for all versions
// Subscreen Overlay Section
ImGui::Text("Subscreen Overlay (Visual Effects)");
ImGui::SameLine();
if (ImGui::Button("?")) {
ImGui::OpenPopup("SubscreenOverlayHelp");
}
if (ImGui::BeginPopup("SubscreenOverlayHelp")) {
ImGui::Text("Subscreen overlays are visual effects like fog, rain, canopy,");
ImGui::Text("and backgrounds that are displayed using tile16 graphics.");
ImGui::Text("They reference special area maps (0x80-0x9F) for their tile data.");
ImGui::EndPopup();
}
uint16_t current_overlay = overworld_->mutable_overworld_map(current_map)->subscreen_overlay();
if (gui::InputHexWord("Subscreen Overlay ID", &current_overlay, kInputFieldSize + 20)) {
overworld_->mutable_overworld_map(current_map)->set_subscreen_overlay(current_overlay);
RefreshMapProperties();
RefreshOverworldMap();
}
HOVER_HINT("Subscreen overlay ID - visual effects like fog, rain, backgrounds");
// Show subscreen overlay description
std::string overlay_desc = GetOverlayDescription(current_overlay);
ImGui::Text("Description: %s", overlay_desc.c_str());
// Preview checkbox
if (ImGui::Checkbox("Preview Subscreen Overlay on Map", &show_overlay_preview)) {
// Toggle subscreen overlay preview
}
HOVER_HINT("Show semi-transparent preview of subscreen overlay on the map");
ImGui::Separator();
// Interactive Overlay Section (for vanilla ROMs)
if (asm_version == 0xFF) {
ImGui::Text("Interactive Overlay (Holes/Changes)");
ImGui::SameLine();
if (ImGui::Button("?")) {
ImGui::OpenPopup("InteractiveOverlayHelp");
}
if (ImGui::BeginPopup("InteractiveOverlayHelp")) {
ImGui::Text("Interactive overlays reveal holes or change elements on top");
ImGui::Text("of the map. They use tile16 graphics and are present in");
ImGui::Text("vanilla ROMs. ZSCustomOverworld expands this functionality.");
ImGui::EndPopup();
}
auto *current_map_ptr = overworld_->overworld_map(current_map);
if (current_map_ptr->has_overlay()) {
ImGui::Text("Interactive Overlay ID: 0x%04X", current_map_ptr->overlay_id());
ImGui::Text("Overlay Data Size: %d bytes", static_cast<int>(current_map_ptr->overlay_data().size()));
} else {
ImGui::Text("No interactive overlay data for this map");
}
HOVER_HINT("Interactive overlay for revealing holes/changing elements (read-only in vanilla)");
}
// Show version info
if (asm_version == 0xFF) {
ImGui::Text("Vanilla ROM - subscreen overlays reference special area maps");
ImGui::Text("(0x80-0x9F) for visual effects like fog, rain, backgrounds.");
} else {
ImGui::Text("ZSCustomOverworld v%d", asm_version);
}
}
}
std::string MapPropertiesSystem::GetOverlayDescription(uint16_t overlay_id) {
if (overlay_id == 0x0093) {
return "Triforce Room Curtain";
} else if (overlay_id == 0x0094) {
return "Under the Bridge";
} else if (overlay_id == 0x0095) {
return "Sky Background (LW Death Mountain)";
} else if (overlay_id == 0x0096) {
return "Pyramid Background";
} else if (overlay_id == 0x0097) {
return "First Fog Overlay (Master Sword Area)";
} else if (overlay_id == 0x009C) {
return "Lava Background (DW Death Mountain)";
} else if (overlay_id == 0x009D) {
return "Second Fog Overlay (Lost Woods/Skull Woods)";
} else if (overlay_id == 0x009E) {
return "Tree Canopy (Forest)";
} else if (overlay_id == 0x009F) {
return "Rain Effect (Misery Mire)";
} else if (overlay_id == 0x00FF) {
return "No Overlay";
} else {
return "Custom overlay";
}
}
void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview) {
if (!show_overlay_preview || !maps_bmp_ || !canvas_) return;
// Get subscreen overlay information based on ROM version and map type
uint16_t overlay_id = 0x00FF;
bool has_subscreen_overlay = false;
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
if (is_special_overworld_map) {
// Special overworld maps (0x80-0x9F) do not support subscreen overlays
return;
}
// Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen overlays for all versions
overlay_id = overworld_->overworld_map(current_map)->subscreen_overlay();
has_subscreen_overlay = (overlay_id != 0x00FF);
if (!has_subscreen_overlay) return;
// Map subscreen overlay ID to special area map for bitmap
int overlay_map_index = -1;
if (overlay_id >= 0x80 && overlay_id < 0xA0) {
overlay_map_index = overlay_id;
}
if (overlay_map_index < 0 || overlay_map_index >= zelda3::kNumOverworldMaps) return;
// Get the subscreen overlay map's bitmap
const auto &overlay_bitmap = (*maps_bmp_)[overlay_map_index];
if (!overlay_bitmap.is_active()) return;
// Calculate position for subscreen overlay preview on the current map
int current_map_x = current_map % 8;
int current_map_y = current_map / 8;
if (current_world == 1) {
current_map_x = (current_map - 0x40) % 8;
current_map_y = (current_map - 0x40) / 8;
} else if (current_world == 2) {
current_map_x = (current_map - 0x80) % 8;
current_map_y = (current_map - 0x80) / 8;
}
int scale = static_cast<int>(canvas_->global_scale());
int map_x = current_map_x * kOverworldMapSize * scale;
int map_y = current_map_y * kOverworldMapSize * scale;
// Determine if this is a background or foreground subscreen overlay
bool is_background_overlay = (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C);
// Set alpha for semi-transparent preview
ImU32 overlay_color = is_background_overlay ?
IM_COL32(255, 255, 255, 128) : // Background subscreen overlays - lighter
IM_COL32(255, 255, 255, 180); // Foreground subscreen overlays - more opaque
// Draw the subscreen overlay bitmap with semi-transparency
canvas_->draw_list()->AddImage(
(ImTextureID)(intptr_t)overlay_bitmap.texture(),
ImVec2(map_x, map_y),
ImVec2(map_x + kOverworldMapSize * scale, map_y + kOverworldMapSize * scale),
ImVec2(0, 0),
ImVec2(1, 1),
overlay_color);
}
void MapPropertiesSystem::DrawViewPopup() {
if (ImGui::BeginPopup("ViewPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::Text("View Controls");
ImGui::Separator();
// Horizontal layout for view controls
if (ImGui::Button(ICON_MD_ZOOM_OUT, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the canvas zoom function
// For now, just show the option
}
HOVER_HINT("Zoom out on the canvas");
ImGui::SameLine();
if (ImGui::Button(ICON_MD_ZOOM_IN, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the canvas zoom function
// For now, just show the option
}
HOVER_HINT("Zoom in on the canvas");
ImGui::SameLine();
if (ImGui::Button(ICON_MD_OPEN_IN_FULL, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the fullscreen toggle
// For now, just show the option
}
HOVER_HINT("Toggle fullscreen canvas (F11)");
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
ImGui::EndPopup();
}
}
void MapPropertiesSystem::DrawQuickAccessPopup() {
if (ImGui::BeginPopup("QuickPopup")) {
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(kCompactItemSpacing, kCompactFramePadding));
ImGui::Text("Quick Access");
ImGui::Separator();
// Horizontal layout for quick access buttons
if (ImGui::Button(ICON_MD_GRID_VIEW, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the Tile16 editor toggle
// For now, just show the option
}
HOVER_HINT("Open Tile16 Editor (Ctrl+T)");
ImGui::SameLine();
if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the copy map function
// For now, just show the option
}
HOVER_HINT("Copy current map to clipboard");
ImGui::SameLine();
if (ImGui::Button(ICON_MD_LOCK, ImVec2(kIconButtonWidth, 0))) {
// This would need to be connected to the map lock toggle
// For now, just show the option
}
HOVER_HINT("Lock/unlock current map (Ctrl+L)");
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
ImGui::EndPopup();
}
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,84 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H
#define YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H
#include "app/zelda3/overworld/overworld.h"
#include "app/rom.h"
#include "app/gui/canvas.h"
// Forward declaration
namespace yaze {
namespace editor {
class OverworldEditor;
}
}
namespace yaze {
namespace editor {
class MapPropertiesSystem {
public:
explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom,
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp = nullptr,
gui::Canvas* canvas = nullptr)
: overworld_(overworld), rom_(rom), maps_bmp_(maps_bmp), canvas_(canvas) {}
// Main interface methods
void DrawSimplifiedMapSettings(int& current_world, int& current_map,
bool& current_map_lock, bool& show_map_properties_panel,
bool& show_custom_bg_color_editor, bool& show_overlay_editor,
bool& show_overlay_preview, int& game_state, int& current_mode);
void DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel);
void DrawCustomBackgroundColorEditor(int current_map, bool& show_custom_bg_color_editor);
void DrawOverlayEditor(int current_map, bool& show_overlay_editor);
// Overlay preview functionality
void DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview);
// Context menu integration
void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock,
bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
bool& show_overlay_editor);
private:
// Property category drawers
void DrawGraphicsPopup(int current_map, int game_state);
void DrawPalettesPopup(int current_map, int game_state, bool& show_custom_bg_color_editor);
void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel,
bool& show_overlay_preview, int& game_state);
// Overlay and mosaic functionality
void DrawMosaicControls(int current_map);
void DrawOverlayControls(int current_map, bool& show_overlay_preview);
std::string GetOverlayDescription(uint16_t overlay_id);
// Integrated toolset popup functions
void DrawToolsPopup(int& current_mode);
void DrawViewPopup();
void DrawQuickAccessPopup();
// Tab content drawers
void DrawBasicPropertiesTab(int current_map);
void DrawSpritePropertiesTab(int current_map);
void DrawCustomFeaturesTab(int current_map);
void DrawTileGraphicsTab(int current_map);
// Utility methods
void RefreshMapProperties();
void RefreshOverworldMap();
absl::Status RefreshMapPalette();
zelda3::Overworld* overworld_;
Rom* rom_;
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp_;
gui::Canvas* canvas_;
// Using centralized UI constants from ui_constants.h
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_MAP_PROPERTIES_H

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,11 @@
#include "app/editor/editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/tile16_editor.h"
#include "app/editor/overworld/tile16_editor.h"
#include "app/editor/overworld/map_properties.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/tilemap.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/gui/zeml.h"
@@ -18,13 +20,11 @@
namespace yaze {
namespace editor {
constexpr uint k4BPP = 4;
constexpr uint kByteSize = 3;
constexpr uint kMessageIdSize = 5;
constexpr uint kNumSheetsToLoad = 223;
constexpr uint kTile8DisplayHeight = 64;
constexpr uint kOverworldMapSize = 0x200;
constexpr float kInputFieldSize = 30.f;
constexpr unsigned int k4BPP = 4;
constexpr unsigned int kByteSize = 3;
constexpr unsigned int kMessageIdSize = 5;
constexpr unsigned int kNumSheetsToLoad = 223;
constexpr unsigned int kOverworldMapSize = 0x200;
constexpr ImVec2 kOverworldCanvasSize(kOverworldMapSize * 8,
kOverworldMapSize * 8);
constexpr ImVec2 kCurrentGfxCanvasSize(0x100 + 1, 0x10 * 0x40 + 1);
@@ -32,7 +32,8 @@ constexpr ImVec2 kBlocksetCanvasSize(0x100 + 1, 0x4000 + 1);
constexpr ImVec2 kGraphicsBinCanvasSize(0x100 + 1, kNumSheetsToLoad * 0x40 + 1);
constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp;
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
@@ -74,26 +75,45 @@ constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
*/
class OverworldEditor : public Editor, public gfx::GfxContext {
public:
OverworldEditor(Rom& rom) : rom_(rom) { type_ = EditorType::kOverworld; }
void Initialize();
explicit OverworldEditor(Rom* rom) : rom_(rom) {
type_ = EditorType::kOverworld;
gfx_group_editor_.set_rom(rom);
// MapPropertiesSystem will be initialized after maps_bmp_ and canvas are ready
}
void Initialize() override;
absl::Status Load() override;
absl::Status Update() final;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override {
return absl::UnimplementedError("Find Unused Tiles");
}
absl::Status Save();
absl::Status Copy() override;
absl::Status Paste() override;
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status Save() override;
absl::Status Clear() override;
/**
* @brief Apply ZSCustomOverworld ASM patch to upgrade ROM version
*/
absl::Status ApplyZSCustomOverworldASM(int target_version);
auto overworld() { return &overworld_; }
/**
* @brief Update ROM version markers and feature flags after ASM patching
*/
absl::Status UpdateROMVersionMarkers(int target_version);
int jump_to_tab() { return jump_to_tab_; }
int jump_to_tab_ = -1;
// ROM state methods (from Editor base class)
bool IsRomLoaded() const override { return rom_ && rom_->is_loaded(); }
std::string GetRomStatus() const override {
if (!rom_) return "No ROM loaded";
if (!rom_->is_loaded()) return "ROM failed to load";
return absl::StrFormat("ROM loaded: %s", rom_->title());
}
/**
* @brief Load the Bitmap objects for each OverworldMap.
*
@@ -104,24 +124,9 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
absl::Status LoadGraphics();
private:
/**
* @brief Draws the canvas, tile16 selector, and toolset in fullscreen
*/
void DrawFullscreenCanvas();
/**
* @brief Toolset for entrances, exits, items, sprites, and transports.
*/
void DrawToolset();
/**
* @brief Draws the overworld map settings. Graphics, palettes, etc.
*/
void DrawOverworldMapSettings();
/**
* @brief Draw the overworld settings for ZSCustomOverworld.
*/
void DrawCustomOverworldMapSettings();
void RefreshChildMap(int i);
@@ -163,10 +168,6 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
*/
absl::Status CheckForCurrentMap();
void CheckForMousePan();
/**
* @brief Allows the user to make changes to the overworld map.
*/
void DrawOverworldCanvas();
absl::Status DrawTile16Selector();
@@ -177,6 +178,16 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
absl::Status LoadSpriteGraphics();
void DrawOverworldProperties();
void DrawCustomBackgroundColorEditor();
void DrawOverlayEditor();
void DrawMapLockControls();
void DrawOverlayPreview();
void DrawOverlayPreviewOnMap();
void DrawOverworldContextMenu();
void DrawSimplifiedMapSettings();
void DrawMapPropertiesPanel();
void HandleMapInteraction();
void SetupOverworldCanvasContextMenu();
absl::Status UpdateUsageStats();
void DrawUsageGrid();
@@ -228,20 +239,26 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
bool map_blockset_loaded_ = false;
bool selected_tile_loaded_ = false;
bool show_tile16_editor_ = false;
bool show_gfx_group_editor_ = false;
bool show_properties_editor_ = false;
bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false;
bool is_dragging_entity_ = false;
bool current_map_lock_ = false;
bool show_custom_bg_color_editor_ = false;
bool show_overlay_editor_ = false;
bool use_area_specific_bg_color_ = false;
bool show_map_properties_panel_ = false;
bool show_overlay_preview_ = false;
std::vector<uint8_t> selected_tile_data_;
std::vector<std::vector<uint8_t>> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
// Map properties system for UI organization
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
std::vector<std::vector<uint8_t>> tile8_individual_data_;
std::vector<gfx::Bitmap> tile8_individual_;
gfx::Tilemap tile16_blockset_;
Rom& rom_;
Rom* rom_;
Tile16Editor tile16_editor_;
Tile16Editor tile16_editor_{rom_, &tile16_blockset_};
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_;
@@ -252,11 +269,11 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gfx::Bitmap current_gfx_bmp_;
gfx::Bitmap all_gfx_bmp;
gfx::BitmapTable maps_bmp_;
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps> maps_bmp_;
gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_;
std::vector<gfx::Bitmap> sprite_previews_;
zelda3::Overworld overworld_;
zelda3::Overworld overworld_{rom_};
zelda3::OverworldBlockset refresh_blockset_;
zelda3::Sprite current_sprite_;
@@ -264,10 +281,10 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
zelda3::OverworldEntrance current_entrance_;
zelda3::OverworldExit current_exit_;
zelda3::OverworldItem current_item_;
zelda3::OverworldEntranceTileTypes entrance_tiletypes_;
zelda3::OverworldEntranceTileTypes entrance_tiletypes_ = {};
zelda3::GameEntity* current_entity_;
zelda3::GameEntity* dragged_entity_;
zelda3::GameEntity* current_entity_ = nullptr;
zelda3::GameEntity* dragged_entity_ = nullptr;
gui::Canvas ow_map_canvas_{"OwMap", kOverworldCanvasSize,
gui::CanvasGridSize::k64x64};
@@ -279,7 +296,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::Table toolset_table_{"##ToolsetTable0", 22, kToolsetTableFlags};
gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags};
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,
ImVec2(0, 0)};

View File

@@ -0,0 +1,686 @@
#include "tile16_editor.h"
#include "absl/status/status.h"
#include "app/core/platform/file_dialog.h"
#include "app/core/window.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
#include "util/hex.h"
#include "util/log.h"
namespace yaze {
namespace editor {
using core::Renderer;
using namespace ImGui;
absl::Status Tile16Editor::Initialize(
const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp,
std::array<uint8_t, 0x200> &all_tiles_types) {
all_tiles_types_ = all_tiles_types;
current_gfx_bmp_.Create(current_gfx_bmp.width(), current_gfx_bmp.height(),
current_gfx_bmp.depth(), current_gfx_bmp.vector());
current_gfx_bmp_.SetPalette(current_gfx_bmp.palette());
core::Renderer::Get().RenderBitmap(&current_gfx_bmp_);
tile16_blockset_bmp_.Create(
tile16_blockset_bmp.width(), tile16_blockset_bmp.height(),
tile16_blockset_bmp.depth(), tile16_blockset_bmp.vector());
tile16_blockset_bmp_.SetPalette(tile16_blockset_bmp.palette());
core::Renderer::Get().RenderBitmap(&tile16_blockset_bmp_);
RETURN_IF_ERROR(LoadTile8());
map_blockset_loaded_ = true;
ImVector<std::string> tile16_names;
for (int i = 0; i < 0x200; ++i) {
std::string str = util::HexByte(all_tiles_types_[i]);
tile16_names.push_back(str);
}
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
*tile8_source_canvas_.custom_labels_enabled() = true;
gui::AddTableColumn(tile_edit_table_, "##tile16ID",
[&]() { Text("Tile16 ID: %02X", current_tile16_); });
gui::AddTableColumn(tile_edit_table_, "##tile8ID",
[&]() { Text("Tile8 ID: %02X", current_tile8_); });
gui::AddTableColumn(tile_edit_table_, "##tile16Flip", [&]() {
Checkbox("X Flip", &x_flip);
Checkbox("Y Flip", &y_flip);
Checkbox("Priority", &priority_tile);
});
return absl::OkStatus();
}
absl::Status Tile16Editor::Update() {
if (!map_blockset_loaded_) {
return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
}
if (BeginMenuBar()) {
if (BeginMenu("View")) {
Checkbox("Show Collision Types",
tile8_source_canvas_.custom_labels_enabled());
EndMenu();
}
if (BeginMenu("Edit")) {
if (MenuItem("Copy Current Tile16", "Ctrl+C")) {
RETURN_IF_ERROR(CopyTile16ToClipboard(current_tile16_));
}
if (MenuItem("Paste to Current Tile16", "Ctrl+V")) {
RETURN_IF_ERROR(PasteTile16FromClipboard());
}
EndMenu();
}
if (BeginMenu("Scratch Space")) {
for (int i = 0; i < 4; i++) {
std::string slot_name = "Slot " + std::to_string(i + 1);
if (scratch_space_used_[i]) {
if (MenuItem((slot_name + " (Load)").c_str())) {
RETURN_IF_ERROR(LoadTile16FromScratchSpace(i));
}
if (MenuItem((slot_name + " (Save)").c_str())) {
RETURN_IF_ERROR(SaveTile16ToScratchSpace(i));
}
if (MenuItem((slot_name + " (Clear)").c_str())) {
RETURN_IF_ERROR(ClearScratchSpace(i));
}
} else {
if (MenuItem((slot_name + " (Save)").c_str())) {
RETURN_IF_ERROR(SaveTile16ToScratchSpace(i));
}
}
if (i < 3) Separator();
}
EndMenu();
}
EndMenuBar();
}
// About popup
if (BeginPopupModal("About Tile16 Editor", NULL,
ImGuiWindowFlags_AlwaysAutoResize)) {
Text("Tile16 Editor for Link to the Past");
Text("This editor allows you to edit 16x16 tiles used in the game.");
Text("Features:");
BulletText("Edit Tile16 graphics by placing 8x8 tiles in the quadrants");
BulletText("Copy and paste Tile16 graphics");
BulletText("Save and load Tile16 graphics to/from scratch space");
BulletText("Preview Tile16 graphics at a larger size");
Separator();
if (Button("Close")) {
CloseCurrentPopup();
}
EndPopup();
}
if (BeginTabBar("Tile16 Editor Tabs")) {
DrawTile16Editor();
RETURN_IF_ERROR(UpdateTile16Transfer());
EndTabBar();
}
return absl::OkStatus();
}
void Tile16Editor::DrawTile16Editor() {
if (BeginTabItem("Tile16 Editing")) {
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x);
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x);
TableHeadersRow();
TableNextRow();
TableNextColumn();
status_ = UpdateBlockset();
TableNextColumn();
status_ = UpdateTile16Edit();
EndTable();
}
EndTabItem();
}
}
absl::Status Tile16Editor::UpdateBlockset() {
gui::BeginPadding(2);
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
blockset_canvas_.DrawBackground();
gui::EndPadding();
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawTileSelector(32);
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, 2.0f);
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
EndChild();
if (!blockset_canvas_.points().empty()) {
notify_tile16.edit() = blockset_canvas_.GetTileIdFromMousePos();
notify_tile16.commit();
if (notify_tile16.modified()) {
current_tile16_ = notify_tile16.get();
gfx::RenderTile(*tile16_blockset_, current_tile16_);
current_tile16_bmp_ = tile16_blockset_->tile_bitmaps[notify_tile16];
auto ow_main_pal_group = rom()->palette_group().overworld_main;
current_tile16_bmp_.SetPalette(ow_main_pal_group[current_palette_]);
Renderer::Get().UpdateBitmap(&current_tile16_bmp_);
}
}
return absl::OkStatus();
}
absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
constexpr int tile8_size = 8;
constexpr int tile16_size = 16;
// Bounds check for current_tile8_
if (current_tile8_ < 0 || current_tile8_ >= static_cast<int>(current_gfx_individual_.size())) {
return absl::OutOfRangeError(absl::StrFormat("Invalid tile8 index: %d", current_tile8_));
}
if (!current_gfx_individual_[current_tile8_].is_active()) {
return absl::FailedPreconditionError("Source tile8 bitmap not active");
}
if (!current_tile16_bmp_.is_active()) {
return absl::FailedPreconditionError("Target tile16 bitmap not active");
}
// Calculate the tile index for x and y based on the click_position
// Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
// Ensure we're within the bounds of the Tile16 (0-1 for both x and y)
tile_index_x = std::min(1, std::max(0, tile_index_x));
tile_index_y = std::min(1, std::max(0, tile_index_y));
// Calculate the pixel start position within the Tile16
// Each Tile8 is 8x8 pixels, so we multiply by 8 to get the pixel offset
int start_x = tile_index_x * tile8_size;
int start_y = tile_index_y * tile8_size;
// Get source tile data
const auto& source_tile = current_gfx_individual_[current_tile8_];
if (source_tile.size() < 64) { // 8x8 = 64 pixels
return absl::FailedPreconditionError("Source tile data too small");
}
// Draw the Tile8 to the correct position within the Tile16
for (int y = 0; y < tile8_size; ++y) {
for (int x = 0; x < tile8_size; ++x) {
// Calculate the pixel position in the Tile16 bitmap
int pixel_x = start_x + x;
int pixel_y = start_y + y;
int pixel_index = pixel_y * tile16_size + pixel_x;
// Bounds check for tile16 bitmap
if (pixel_index < 0 || pixel_index >= static_cast<int>(current_tile16_bmp_.size())) {
continue;
}
// Calculate the pixel position in the Tile8 bitmap
int gfx_pixel_index = y * tile8_size + x;
// Apply flipping if needed
if (x_flip) {
gfx_pixel_index = y * tile8_size + (tile8_size - 1 - x);
}
if (y_flip) {
gfx_pixel_index = (tile8_size - 1 - y) * tile8_size + x;
}
if (x_flip && y_flip) {
gfx_pixel_index =
(tile8_size - 1 - y) * tile8_size + (tile8_size - 1 - x);
}
// Bounds check for source tile
if (gfx_pixel_index >= 0 && gfx_pixel_index < static_cast<int>(source_tile.size())) {
// Write the pixel to the Tile16 bitmap
current_tile16_bmp_.WriteToPixel(pixel_index, source_tile.data()[gfx_pixel_index]);
}
}
}
current_tile16_bmp_.set_modified(true);
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTile16Edit() {
static const auto ow_main_pal_group = rom()->palette_group().overworld_main;
// Create a more organized layout with tabs
if (BeginTabBar("Tile16EditorTabs")) {
// Main editing tab
if (BeginTabItem("Edit")) {
// Top section: Tile8 selector and Tile16 editor side by side
if (BeginTable("##Tile16EditorLayout", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
// Left column: Tile8 selector
TableSetupColumn("Tile8 Selector", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x * 0.6f);
// Right column: Tile16 editor
TableSetupColumn("Tile16 Editor", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x * 0.4f);
TableHeadersRow();
TableNextRow();
// Tile8 selector column
TableNextColumn();
if (BeginChild("Tile8 Selector", ImVec2(0, 0x175), true)) {
tile8_source_canvas_.DrawBackground();
tile8_source_canvas_.DrawContextMenu();
if (tile8_source_canvas_.DrawTileSelector(32)) {
// Bounds check before accessing current_gfx_individual_
if (current_tile8_ >= 0 && current_tile8_ < static_cast<int>(current_gfx_individual_.size()) &&
current_gfx_individual_[current_tile8_].is_active()) {
current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
ow_main_pal_group[0], current_palette_);
Renderer::Get().UpdateBitmap(
&current_gfx_individual_[current_tile8_]);
}
}
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
tile8_source_canvas_.DrawGrid();
tile8_source_canvas_.DrawOverlay();
}
EndChild();
// Tile16 editor column
TableNextColumn();
if (BeginChild("Tile16 Editor", ImVec2(0, 0x175), true)) {
tile16_edit_canvas_.DrawBackground();
tile16_edit_canvas_.DrawContextMenu();
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
if (!tile8_source_canvas_.points().empty()) {
if (tile16_edit_canvas_.DrawTilePainter(
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
RETURN_IF_ERROR(DrawToCurrentTile16(
tile16_edit_canvas_.drawn_tile_position()));
Renderer::Get().UpdateBitmap(&current_tile16_bmp_);
}
}
tile16_edit_canvas_.DrawGrid();
tile16_edit_canvas_.DrawOverlay();
}
EndChild();
EndTable();
}
// Bottom section: Options and controls
Separator();
// Create a table for the options
if (BeginTable("##Tile16EditorOptions", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
// Left column: Tile properties
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x * 0.5f);
// Right column: Actions
TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthStretch,
GetContentRegionAvail().x * 0.5f);
TableHeadersRow();
TableNextRow();
// Properties column
TableNextColumn();
Text("Tile Properties:");
gui::DrawTable(tile_edit_table_);
// Palette selector
Text("Palette:");
gui::InputHexByte("Palette", &notify_palette.edit());
notify_palette.commit();
if (notify_palette.modified()) {
auto palette = palettesets_[current_palette_].main_;
auto value = notify_palette.get();
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
palette = palettesets_[current_palette_].aux1;
value -= 0x04;
} else if (notify_palette.get() > 0x06) {
palette = palettesets_[current_palette_].aux2;
value -= 0x06;
}
if (value > 0x00) {
current_gfx_bmp_.SetPaletteWithTransparent(palette, value);
Renderer::Get().UpdateBitmap(&current_gfx_bmp_);
current_tile16_bmp_.SetPaletteWithTransparent(palette, value);
Renderer::Get().UpdateBitmap(&current_tile16_bmp_);
}
}
// Actions column
TableNextColumn();
Text("Quick Actions:");
// Clipboard actions in a more compact layout
if (BeginTable("##ClipboardActions", 2, ImGuiTableFlags_SizingFixedFit)) {
TableNextColumn();
if (Button("Copy", ImVec2(60, 0))) {
RETURN_IF_ERROR(CopyTile16ToClipboard(current_tile16_));
}
TableNextColumn();
if (Button("Paste", ImVec2(60, 0))) {
RETURN_IF_ERROR(PasteTile16FromClipboard());
}
EndTable();
}
// Scratch space in a compact 2x2 grid
Text("Scratch Space:");
if (BeginTable("##ScratchSpace", 2, ImGuiTableFlags_SizingFixedFit)) {
for (int i = 0; i < 4; i++) {
TableNextColumn();
std::string slot_name = "Slot " + std::to_string(i + 1);
if (scratch_space_used_[i]) {
if (Button((slot_name + " (Load)").c_str(), ImVec2(80, 0))) {
RETURN_IF_ERROR(LoadTile16FromScratchSpace(i));
}
SameLine();
if (Button("Clear", ImVec2(40, 0))) {
RETURN_IF_ERROR(ClearScratchSpace(i));
}
} else {
if (Button((slot_name + " (Empty)").c_str(), ImVec2(120, 0))) {
RETURN_IF_ERROR(SaveTile16ToScratchSpace(i));
}
}
}
EndTable();
}
EndTable();
}
EndTabItem();
}
// Preview tab
if (BeginTabItem("Preview")) {
if (BeginChild("Tile16Preview", ImVec2(0, 0), true)) {
// Display the current Tile16 at a larger size
auto texture = current_tile16_bmp_.texture();
if (texture) {
// Scale the 16x16 tile to 256x256 for better visibility
ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(256, 256));
}
// Display information about the current Tile16
Text("Tile16 ID: %02X", current_tile16_);
Text("Current Palette: %02X", current_palette_);
Text("X Flip: %s", x_flip ? "Yes" : "No");
Text("Y Flip: %s", y_flip ? "Yes" : "No");
Text("Priority: %s", priority_tile ? "Yes" : "No");
}
EndChild();
EndTabItem();
}
EndTabBar();
}
// The user selected a tile8
if (!tile8_source_canvas_.points().empty()) {
uint16_t x = tile8_source_canvas_.points().front().x / 16;
uint16_t y = tile8_source_canvas_.points().front().y / 16;
current_tile8_ = x + (y * 8);
// Bounds check before accessing current_gfx_individual_
if (current_tile8_ >= 0 && current_tile8_ < static_cast<int>(current_gfx_individual_.size()) &&
current_gfx_individual_[current_tile8_].is_active()) {
current_gfx_individual_[current_tile8_].SetPaletteWithTransparent(
ow_main_pal_group[0], current_palette_);
Renderer::Get().UpdateBitmap(&current_gfx_individual_[current_tile8_]);
}
}
return absl::OkStatus();
}
absl::Status Tile16Editor::LoadTile8() {
if (!current_gfx_bmp_.is_active() || current_gfx_bmp_.data() == nullptr) {
return absl::FailedPreconditionError("Current graphics bitmap not initialized");
}
const auto& ow_main_pal_group = rom()->palette_group().overworld_main;
if (ow_main_pal_group.size() == 0) {
return absl::FailedPreconditionError("Overworld palette group not loaded");
}
current_gfx_individual_.clear();
current_gfx_individual_.reserve(1024);
// Process tiles sequentially to avoid race conditions
for (int index = 0; index < 1024; index++) {
std::array<uint8_t, 0x40> tile_data{};
// Calculate the position in the current gfx data
int num_columns = current_gfx_bmp_.width() / 8;
if (num_columns <= 0) {
continue; // Skip invalid tiles
}
// Copy the pixel data for the current tile into the vector
for (int ty = 0; ty < 8; ty++) {
for (int tx = 0; tx < 8; tx++) {
// Calculate the position in the tile data vector
int position = tx + (ty * 8);
// Calculate the position in the current gfx data
int x = (index % num_columns) * 8 + tx;
int y = (index / num_columns) * 8 + ty;
int gfx_position = x + (y * current_gfx_bmp_.width());
// Bounds check
if (gfx_position >= 0 && gfx_position < static_cast<int>(current_gfx_bmp_.size())) {
uint8_t value = current_gfx_bmp_.data()[gfx_position];
// Handle palette adjustment
if (value & 0x80) {
value -= 0x88;
}
tile_data[position] = value;
}
}
}
// Create the tile bitmap
current_gfx_individual_.emplace_back();
auto &tile_bitmap = current_gfx_individual_.back();
try {
tile_bitmap.Create(8, 8, 8, tile_data);
if (current_palette_ < ow_main_pal_group.size()) {
tile_bitmap.SetPaletteWithTransparent(ow_main_pal_group[0], current_palette_);
}
Renderer::Get().RenderBitmap(&tile_bitmap);
} catch (const std::exception& e) {
// Log error but continue with other tiles
util::logf("Error creating tile %d: %s", index, e.what());
continue;
}
}
return absl::OkStatus();
}
absl::Status Tile16Editor::SetCurrentTile(int id) {
if (id < 0 || id >= zelda3::kNumTile16Individual) {
return absl::OutOfRangeError(absl::StrFormat("Invalid tile16 id: %d", id));
}
if (!tile16_blockset_) {
return absl::FailedPreconditionError("Tile16 blockset not initialized");
}
current_tile16_ = id;
try {
gfx::RenderTile(*tile16_blockset_, current_tile16_);
current_tile16_bmp_ = tile16_blockset_->tile_bitmaps[current_tile16_];
const auto& ow_main_pal_group = rom()->palette_group().overworld_main;
if (ow_main_pal_group.size() > 0 && current_palette_ < ow_main_pal_group.size()) {
current_tile16_bmp_.SetPalette(ow_main_pal_group[current_palette_]);
}
Renderer::Get().UpdateBitmap(&current_tile16_bmp_);
} catch (const std::exception& e) {
return absl::InternalError(absl::StrFormat("Failed to set current tile: %s", e.what()));
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTile16Transfer() {
if (BeginTabItem("Tile16 Transfer")) {
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
ImVec2(0, 0))) {
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x / 2);
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
GetContentRegionAvail().x / 2);
TableHeadersRow();
TableNextRow();
TableNextColumn();
RETURN_IF_ERROR(UpdateBlockset());
TableNextColumn();
RETURN_IF_ERROR(UpdateTransferTileCanvas());
EndTable();
}
EndTabItem();
}
return absl::OkStatus();
}
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
// Create a button for loading another ROM
if (Button("Load ROM")) {
auto transfer_rom = std::make_unique<Rom>();
transfer_rom_ = transfer_rom.get();
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
transfer_status_ = transfer_rom_->LoadFromFile(file_name);
transfer_started_ = true;
}
// TODO: Implement tile16 transfer
if (transfer_started_ && !transfer_blockset_loaded_) {
ASSIGN_OR_RETURN(transfer_gfx_, LoadAllGraphicsData(*transfer_rom_));
// Load the Link to the Past overworld.
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
transfer_overworld_.set_current_map(0);
palette_ = transfer_overworld_.current_area_palette();
// Create the tile16 blockset image
Renderer::Get().CreateAndRenderBitmap(
0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
transfer_blockset_bmp_, palette_);
transfer_blockset_loaded_ = true;
}
// Create a canvas for holding the tiles which will be exported
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
3);
return absl::OkStatus();
}
absl::Status Tile16Editor::CopyTile16ToClipboard(int tile_id) {
if (tile_id < 0 || tile_id >= zelda3::kNumTile16Individual) {
return absl::InvalidArgumentError("Invalid tile ID");
}
// Create a copy of the tile16 bitmap
gfx::RenderTile(*tile16_blockset_, tile_id);
clipboard_tile16_.Create(16, 16, 8,
tile16_blockset_->tile_bitmaps[tile_id].vector());
clipboard_tile16_.SetPalette(
tile16_blockset_->tile_bitmaps[tile_id].palette());
core::Renderer::Get().RenderBitmap(&clipboard_tile16_);
clipboard_has_data_ = true;
return absl::OkStatus();
}
absl::Status Tile16Editor::PasteTile16FromClipboard() {
if (!clipboard_has_data_) {
return absl::FailedPreconditionError("Clipboard is empty");
}
// Copy the clipboard data to the current tile16
current_tile16_bmp_.Create(16, 16, 8, clipboard_tile16_.vector());
current_tile16_bmp_.SetPalette(clipboard_tile16_.palette());
core::Renderer::Get().RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
absl::Status Tile16Editor::SaveTile16ToScratchSpace(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch space slot");
}
// Create a copy of the current tile16 bitmap
scratch_space_[slot].Create(16, 16, 8, current_tile16_bmp_.vector());
scratch_space_[slot].SetPalette(current_tile16_bmp_.palette());
core::Renderer::Get().RenderBitmap(&scratch_space_[slot]);
scratch_space_used_[slot] = true;
return absl::OkStatus();
}
absl::Status Tile16Editor::LoadTile16FromScratchSpace(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch space slot");
}
if (!scratch_space_used_[slot]) {
return absl::FailedPreconditionError("Scratch space slot is empty");
}
// Copy the scratch space data to the current tile16
current_tile16_bmp_.Create(16, 16, 8, scratch_space_[slot].vector());
current_tile16_bmp_.SetPalette(scratch_space_[slot].palette());
core::Renderer::Get().RenderBitmap(&current_tile16_bmp_);
return absl::OkStatus();
}
absl::Status Tile16Editor::ClearScratchSpace(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch space slot");
}
scratch_space_used_[slot] = false;
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -1,16 +1,20 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_H
#include <array>
#include <vector>
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "imgui/imgui.h"
#include "util/notify.h"
namespace yaze {
namespace editor {
@@ -18,15 +22,15 @@ namespace editor {
/**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public gfx::GfxContext, public SharedRom {
class Tile16Editor : public gfx::GfxContext {
public:
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
const gfx::Bitmap& current_gfx_bmp,
const std::vector<gfx::Bitmap>& tile16_individual,
std::array<uint8_t, 0x200>& all_tiles_types);
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset)
: rom_(rom), tile16_blockset_(tile16_blockset) {}
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp,
const gfx::Bitmap &current_gfx_bmp,
std::array<uint8_t, 0x200> &all_tiles_types);
absl::Status Update();
absl::Status DrawMenu();
void DrawTile16Editor();
absl::Status UpdateTile16Transfer();
@@ -36,37 +40,52 @@ class Tile16Editor : public gfx::GfxContext, public SharedRom {
absl::Status UpdateTile16Edit();
absl::Status DrawTileEditControls();
absl::Status UpdateTransferTileCanvas();
absl::Status LoadTile8();
absl::Status SetCurrentTile(int id);
// New methods for clipboard and scratch space
absl::Status CopyTile16ToClipboard(int tile_id);
absl::Status PasteTile16FromClipboard();
absl::Status SaveTile16ToScratchSpace(int slot);
absl::Status LoadTile16FromScratchSpace(int slot);
absl::Status ClearScratchSpace(int slot);
void set_rom(Rom *rom) { rom_ = rom; }
Rom *rom() const { return rom_; }
private:
Rom *rom_ = nullptr;
bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
bool transfer_blockset_loaded_ = false;
bool x_flip = false;
bool y_flip = false;
bool priority_tile = false;
int tile_size;
int current_tile16_ = 0;
int current_tile8_ = 0;
uint8_t current_palette_ = 0;
core::NotifyValue<uint32_t> notify_tile16;
core::NotifyValue<uint8_t> notify_palette;
// Clipboard for Tile16 graphics
gfx::Bitmap clipboard_tile16_;
bool clipboard_has_data_ = false;
// Various options for the Tile16 Editor
bool x_flip;
bool y_flip;
bool priority_tile;
int tile_size;
// Scratch space for Tile16 graphics (4 slots)
std::array<gfx::Bitmap, 4> scratch_space_;
std::array<bool, 4> scratch_space_used_ = {false, false, false, false};
util::NotifyValue<uint32_t> notify_tile16;
util::NotifyValue<uint8_t> notify_palette;
std::array<uint8_t, 0x200> all_tiles_types_;
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32};
gui::CanvasGridSize::k32x32,};
gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile
@@ -84,20 +103,23 @@ class Tile16Editor : public gfx::GfxContext, public SharedRom {
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
std::vector<gfx::Bitmap> tile16_individual_;
gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders};
gfx::Tilemap *tile16_blockset_ = nullptr;
std::vector<gfx::Bitmap> current_gfx_individual_;
PaletteEditor palette_editor_;
gfx::SnesPalette palette_;
zelda3::Overworld transfer_overworld_;
absl::Status status_;
Rom transfer_rom_;
Rom *transfer_rom_ = nullptr;
zelda3::Overworld transfer_overworld_{transfer_rom_};
std::array<gfx::Bitmap, kNumGfxSheets> transfer_gfx_;
absl::Status transfer_status_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H

View File

@@ -0,0 +1,73 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_UI_CONSTANTS_H
#define YAZE_APP_EDITOR_OVERWORLD_UI_CONSTANTS_H
namespace yaze {
namespace editor {
// Game State Labels
inline constexpr const char* kGameStateNames[] = {
"Rain & Rescue Zelda",
"Pendants",
"Crystals"
};
// World Labels
inline constexpr const char* kWorldNames[] = {
"Light World",
"Dark World",
"Special World"
};
// Area Size Names
inline constexpr const char* kAreaSizeNames[] = {
"Small (1x1)",
"Large (2x2)",
"Wide (2x1)",
"Tall (1x2)"
};
// UI Styling Constants
inline constexpr float kInputFieldSize = 30.f;
inline constexpr float kHexByteInputWidth = 50.f;
inline constexpr float kHexWordInputWidth = 70.f;
inline constexpr float kCompactButtonWidth = 60.f;
inline constexpr float kIconButtonWidth = 30.f;
inline constexpr float kSmallButtonWidth = 80.f;
inline constexpr float kMediumButtonWidth = 90.f;
inline constexpr float kLargeButtonWidth = 100.f;
// Table Column Width Constants
inline constexpr float kTableColumnWorld = 120.f;
inline constexpr float kTableColumnMap = 80.f;
inline constexpr float kTableColumnAreaSize = 120.f;
inline constexpr float kTableColumnLock = 50.f;
inline constexpr float kTableColumnGraphics = 80.f;
inline constexpr float kTableColumnPalettes = 80.f;
inline constexpr float kTableColumnProperties = 100.f;
inline constexpr float kTableColumnTools = 80.f;
inline constexpr float kTableColumnView = 80.f;
inline constexpr float kTableColumnQuick = 80.f;
// Combo Box Width Constants
inline constexpr float kComboWorldWidth = 115.f;
inline constexpr float kComboAreaSizeWidth = 115.f;
inline constexpr float kComboGameStateWidth = 100.f;
// Button Width Constants for Table
inline constexpr float kTableButtonGraphics = 75.f;
inline constexpr float kTableButtonPalettes = 75.f;
inline constexpr float kTableButtonProperties = 95.f;
inline constexpr float kTableButtonTools = 75.f;
inline constexpr float kTableButtonView = 75.f;
inline constexpr float kTableButtonQuick = 75.f;
// Spacing and Padding
inline constexpr float kCompactItemSpacing = 4.f;
inline constexpr float kCompactFramePadding = 2.f;
// Map Size Constants - using the one from overworld_editor.h
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_UI_CONSTANTS_H

View File

@@ -2,9 +2,11 @@
#include "app/core/platform/file_dialog.h"
#include "app/editor/sprite/zsprite.h"
#include "app/gfx/arena.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/zelda3/sprite/sprite.h"
#include "util/hex.h"
namespace yaze {
namespace editor {
@@ -20,6 +22,10 @@ using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
void SpriteEditor::Initialize() {}
absl::Status SpriteEditor::Load() { return absl::OkStatus(); }
absl::Status SpriteEditor::Update() {
if (rom()->is_loaded() && !sheets_loaded_) {
// Load the values for current_sheets_ array
@@ -175,12 +181,13 @@ void SpriteEditor::DrawCurrentSheets() {
graphics_sheet_canvas_.DrawTileSelector(32);
for (int i = 0; i < 8; i++) {
graphics_sheet_canvas_.DrawBitmap(
rom()->gfx_sheets().at(current_sheets_[i]), 1, (i * 0x40) + 1, 2);
gfx::Arena::Get().gfx_sheets().at(current_sheets_[i]), 1,
(i * 0x40) + 1, 2);
}
graphics_sheet_canvas_.DrawGrid();
graphics_sheet_canvas_.DrawOverlay();
ImGui::EndChild();
}
ImGui::EndChild();
}
void SpriteEditor::DrawSpritesList() {
@@ -190,7 +197,7 @@ void SpriteEditor::DrawSpritesList() {
int i = 0;
for (const auto each_sprite_name : zelda3::kSpriteDefaultNames) {
rom()->resource_label()->SelectableLabelWithNameEdit(
current_sprite_id_ == i, "Sprite Names", core::HexByte(i),
current_sprite_id_ == i, "Sprite Names", util::HexByte(i),
zelda3::kSpriteDefaultNames[i].data());
if (ImGui::IsItemClicked()) {
current_sprite_id_ = i;

View File

@@ -1,9 +1,12 @@
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "app/editor/sprite/zsprite.h"
#include "app/editor/editor.h"
#include "app/editor/sprite/zsprite.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
@@ -30,23 +33,28 @@ constexpr ImGuiTableFlags kSpriteTableFlags =
* This class provides functionality for updating the sprite editor, drawing the
* editor table, drawing the sprite canvas, and drawing the current sheets.
*/
class SpriteEditor : public SharedRom, public Editor {
class SpriteEditor : public Editor {
public:
SpriteEditor() { type_ = EditorType::kSprite; }
explicit SpriteEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kSprite;
}
/**
* @brief Updates the sprite editor.
*
* @return An absl::Status indicating the success or failure of the update.
*/
void Initialize() override;
absl::Status Load() override;
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
absl::Status Save() override { return absl::UnimplementedError("Save"); }
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
private:
void DrawVanillaSpriteEditor();
@@ -65,9 +73,7 @@ class SpriteEditor : public SharedRom, public Editor {
* @brief Draws the current sheets.
*/
void DrawCurrentSheets();
void DrawCustomSprites();
void DrawCustomSpritesMetadata();
/**
@@ -107,9 +113,11 @@ class SpriteEditor : public SharedRom, public Editor {
std::vector<zsprite::ZSprite> custom_sprites_; /**< Sprites. */
absl::Status status_; /**< Status. */
Rom* rom_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H

View File

@@ -1,16 +1,13 @@
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <string>
#include <vector>
#include "app/core/constants.h"
#include "absl/status/status.h"
#include "app/gfx/snes_tile.h"
#include "imgui/imgui.h"
#include "util/macro.h"
namespace yaze {
namespace editor {
@@ -109,8 +106,8 @@ struct ZSprite {
offset += sizeof(int);
for (int j = 0; j < tCount; j++) {
ushort tid = *reinterpret_cast<ushort*>(&buffer[offset]);
offset += sizeof(ushort);
uint16_t tid = *reinterpret_cast<uint16_t*>(&buffer[offset]);
offset += sizeof(uint16_t);
uint8_t tpal = *reinterpret_cast<uint8_t*>(&buffer[offset]);
offset += sizeof(uint8_t);
bool tmx = *reinterpret_cast<bool*>(&buffer[offset]);
@@ -249,7 +246,7 @@ struct ZSprite {
for (int j = 0; j < editor.Frames[i].Tiles.size(); j++) {
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].id),
sizeof(ushort));
sizeof(uint16_t));
fs.write(
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].palette),
sizeof(uint8_t));

View File

@@ -2,73 +2,12 @@
#include <fstream>
#include "app/gui/input.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key) {
switch (key) {
case 'A':
return ImGuiKey_A;
case 'B':
return ImGuiKey_B;
case 'C':
return ImGuiKey_C;
case 'D':
return ImGuiKey_D;
case 'E':
return ImGuiKey_E;
case 'F':
return ImGuiKey_F;
case 'G':
return ImGuiKey_G;
case 'H':
return ImGuiKey_H;
case 'I':
return ImGuiKey_I;
case 'J':
return ImGuiKey_J;
case 'K':
return ImGuiKey_K;
case 'L':
return ImGuiKey_L;
case 'M':
return ImGuiKey_M;
case 'N':
return ImGuiKey_N;
case 'O':
return ImGuiKey_O;
case 'P':
return ImGuiKey_P;
case 'Q':
return ImGuiKey_Q;
case 'R':
return ImGuiKey_R;
case 'S':
return ImGuiKey_S;
case 'T':
return ImGuiKey_T;
case 'U':
return ImGuiKey_U;
case 'V':
return ImGuiKey_V;
case 'W':
return ImGuiKey_W;
case 'X':
return ImGuiKey_X;
case 'Y':
return ImGuiKey_Y;
case 'Z':
return ImGuiKey_Z;
case '/':
return ImGuiKey_Slash;
case '-':
return ImGuiKey_Minus;
default:
return ImGuiKey_COUNT;
}
}
// When the player presses Space, a popup will appear fixed to the bottom of the
// ImGui window with a list of the available key commands which can be used.
@@ -100,13 +39,13 @@ void CommandManager::ShowWhichKey() {
if (ImGui::BeginTable("CommandsTable", commands_.size(),
ImGuiTableFlags_SizingStretchProp)) {
for (const auto &[shortcut, info] : commands_) {
ImGui::TableNextColumn();
ImGui::TextColored(colors[colorIndex], "%c: %s",
info.command_info.mnemonic,
info.command_info.name.c_str());
colorIndex = (colorIndex + 1) % numColors;
}
for (const auto &[shortcut, group] : commands_) {
ImGui::TableNextColumn();
ImGui::TextColored(colors[colorIndex], "%c: %s",
group.main_command.mnemonic,
group.main_command.name.c_str());
colorIndex = (colorIndex + 1) % numColors;
}
ImGui::EndTable();
}
ImGui::EndPopup();
@@ -116,9 +55,9 @@ void CommandManager::ShowWhichKey() {
void CommandManager::SaveKeybindings(const std::string &filepath) {
std::ofstream out(filepath);
if (out.is_open()) {
for (const auto &[shortcut, info] : commands_) {
out << shortcut << " " << info.command_info.mnemonic << " "
<< info.command_info.name << " " << info.command_info.desc << "\n";
for (const auto &[shortcut, group] : commands_) {
out << shortcut << " " << group.main_command.mnemonic << " "
<< group.main_command.name << " " << group.main_command.desc << "\n";
}
out.close();
}
@@ -131,7 +70,7 @@ void CommandManager::LoadKeybindings(const std::string &filepath) {
std::string shortcut, name, desc;
char mnemonic;
while (in >> shortcut >> mnemonic >> name >> desc) {
commands_[shortcut].command_info = {nullptr, mnemonic, name, desc};
commands_[shortcut].main_command = {nullptr, mnemonic, name, desc};
}
in.close();
}

View File

@@ -2,6 +2,7 @@
#define YAZE_APP_EDITOR_SYSTEM_COMMAND_MANAGER_H
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
@@ -10,8 +11,6 @@
namespace yaze {
namespace editor {
ImGuiKey MapKeyToImGuiKey(char key);
class CommandManager {
public:
CommandManager() = default;
@@ -33,38 +32,36 @@ class CommandManager {
CommandInfo() = default;
};
// New command info which supports subsections of commands
struct CommandInfoOrPrefix {
CommandInfo command_info;
std::unordered_map<std::string, CommandInfoOrPrefix> subcommands;
CommandInfoOrPrefix(CommandInfo command_info)
: command_info(std::move(command_info)) {}
CommandInfoOrPrefix() = default;
// Simplified command structure without recursive types
struct CommandGroup {
CommandInfo main_command;
std::unordered_map<std::string, CommandInfo> subcommands;
CommandGroup() = default;
CommandGroup(CommandInfo main) : main_command(std::move(main)) {}
};
void RegisterPrefix(const std::string &group_name, const char prefix,
const std::string &name, const std::string &desc) {
commands_[group_name].command_info = {nullptr, prefix, name, desc};
commands_[group_name].main_command = {nullptr, prefix, name, desc};
}
void RegisterSubcommand(const std::string &group_name,
const std::string &shortcut, const char mnemonic,
const std::string &name, const std::string &desc,
Command command) {
commands_[group_name].subcommands[shortcut].command_info = {
command, mnemonic, name, desc};
commands_[group_name].subcommands[shortcut] = {command, mnemonic, name, desc};
}
void RegisterCommand(const std::string &shortcut, Command command,
char mnemonic, const std::string &name,
const std::string &desc) {
commands_[shortcut].command_info = {std::move(command), mnemonic, name,
desc};
commands_[shortcut].main_command = {std::move(command), mnemonic, name, desc};
}
void ExecuteCommand(const std::string &shortcut) {
if (commands_.find(shortcut) != commands_.end()) {
commands_[shortcut].command_info.command();
commands_[shortcut].main_command.command();
}
}
@@ -74,7 +71,7 @@ class CommandManager {
void LoadKeybindings(const std::string &filepath);
private:
std::unordered_map<std::string, CommandInfoOrPrefix> commands_;
std::unordered_map<std::string, CommandGroup> commands_;
};
} // namespace editor

View File

@@ -1,75 +0,0 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H
#include <cstddef>
#include "app/zelda3/overworld/overworld_map.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
class ConstantManager {
public:
void ShowConstantManager() {
ImGui::Begin("Constant Manager");
// Tab bar for the different types of constants
// Overworld, dungeon, graphics, expanded
ImGui::TextWrapped(
"This is the constant manager. It allows you to view and edit "
"constants in the ROM. You should only edit these if you know what "
"you're doing.");
if (ImGui::BeginTabBar("Constant Manager Tabs")) {
if (ImGui::BeginTabItem("Overworld")) {
ImGui::Text("Overworld constants");
ImGui::Separator();
ImGui::Text("OverworldCustomASMHasBeenApplied: %d",
zelda3::OverworldCustomASMHasBeenApplied);
ImGui::Text("OverworldCustomAreaSpecificBGPalette: %d",
zelda3::OverworldCustomAreaSpecificBGPalette);
ImGui::Text("OverworldCustomAreaSpecificBGEnabled: %d",
zelda3::OverworldCustomAreaSpecificBGEnabled);
ImGui::Text("OverworldCustomMainPaletteArray: %d",
zelda3::OverworldCustomMainPaletteArray);
ImGui::Text("OverworldCustomMainPaletteEnabled: %d",
zelda3::OverworldCustomMainPaletteEnabled);
ImGui::Text("OverworldCustomMosaicArray: %d",
zelda3::OverworldCustomMosaicArray);
ImGui::Text("OverworldCustomMosaicEnabled: %d",
zelda3::OverworldCustomMosaicEnabled);
ImGui::Text("OverworldCustomAnimatedGFXArray: %d",
zelda3::OverworldCustomAnimatedGFXArray);
ImGui::Text("OverworldCustomAnimatedGFXEnabled: %d",
zelda3::OverworldCustomAnimatedGFXEnabled);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Dungeon")) {
ImGui::Text("Dungeon constants");
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Graphics")) {
ImGui::Text("Graphics constants");
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Expanded")) {
ImGui::Text("Expanded constants");
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
}
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_CONSTANT_MANAGER_H

View File

@@ -71,13 +71,5 @@ void ExtensionManager::ShutdownExtensions() {
// }
}
void ExtensionManager::ExecuteExtensionUI(yaze_editor_context* context) {
for (auto& extension : extensions_) {
if (extension->extend_ui) {
extension->extend_ui(context);
}
}
}
} // namespace editor
} // namespace yaze

View File

@@ -15,7 +15,6 @@ class ExtensionManager {
void RegisterExtension(yaze_extension* extension);
void InitializeExtensions(yaze_editor_context* context);
void ShutdownExtensions();
void ExecuteExtensionUI(yaze_editor_context* context);
private:
std::vector<yaze_extension*> extensions_;

View File

@@ -1,63 +0,0 @@
#ifndef YAZE_APP_EDITOR_UTILS_FLAGS_H
#define YAZE_APP_EDITOR_UTILS_FLAGS_H
#include "core/common.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
using core::ExperimentFlags;
using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::EndMenu;
using ImGui::MenuItem;
using ImGui::Separator;
struct FlagsMenu {
void Draw() {
if (BeginMenu("Overworld Flags")) {
Checkbox("Enable Overworld Sprites",
&ExperimentFlags::get().overworld.kDrawOverworldSprites);
Separator();
Checkbox("Save Overworld Maps",
&ExperimentFlags::get().overworld.kSaveOverworldMaps);
Checkbox("Save Overworld Entrances",
&ExperimentFlags::get().overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&ExperimentFlags::get().overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&ExperimentFlags::get().overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&ExperimentFlags::get().overworld.kSaveOverworldProperties);
Checkbox("Load Custom Overworld",
&ExperimentFlags::get().overworld.kLoadCustomOverworld);
ImGui::EndMenu();
}
if (BeginMenu("Dungeon Flags")) {
Checkbox("Draw Dungeon Room Graphics",
&ExperimentFlags::get().kDrawDungeonRoomGraphics);
Separator();
Checkbox("Save Dungeon Maps", &ExperimentFlags::get().kSaveDungeonMaps);
ImGui::EndMenu();
}
Checkbox("Use built-in file dialog",
&ExperimentFlags::get().kNewFileDialogWrapper);
Checkbox("Enable Console Logging", &ExperimentFlags::get().kLogToConsole);
Checkbox("Enable Texture Streaming",
&ExperimentFlags::get().kLoadTexturesAsStreaming);
Checkbox("Log Instructions to Debugger",
&ExperimentFlags::get().kLogInstructions);
Checkbox("Save All Palettes", &ExperimentFlags::get().kSaveAllPalettes);
Checkbox("Save Gfx Groups", &ExperimentFlags::get().kSaveGfxGroups);
Checkbox("Save Graphics Sheets",
&ExperimentFlags::get().kSaveGraphicsSheet);
}
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_UTILS_FLAGS_H_

View File

@@ -1,18 +1,495 @@
#include "popup_manager.h"
#include "imgui/imgui.h"
#include "absl/strings/str_format.h"
#include "app/editor/editor_manager.h"
#include "app/gui/style.h"
#include "app/gui/icons.h"
#include "util/hex.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
namespace yaze {
namespace editor {
PopupManager::PopupManager() {
using namespace ImGui;
PopupManager::PopupManager(EditorManager* editor_manager)
: editor_manager_(editor_manager), status_(absl::OkStatus()) {}
void PopupManager::Initialize() {
// Register all popups
popups_["About"] = {"About", false, [this]() { DrawAboutPopup(); }};
popups_["ROM Information"] = {"ROM Information", false, [this]() { DrawRomInfoPopup(); }};
popups_["Save As.."] = {"Save As..", false, [this]() { DrawSaveAsPopup(); }};
popups_["New Project"] = {"New Project", false, [this]() { DrawNewProjectPopup(); }};
popups_["Supported Features"] = {"Supported Features", false, [this]() { DrawSupportedFeaturesPopup(); }};
popups_["Open a ROM"] = {"Open a ROM", false, [this]() { DrawOpenRomHelpPopup(); }};
popups_["Manage Project"] = {"Manage Project", false, [this]() { DrawManageProjectPopup(); }};
// v0.3 Help Documentation popups
popups_["Getting Started"] = {"Getting Started", false, [this]() { DrawGettingStartedPopup(); }};
popups_["Asar Integration"] = {"Asar Integration", false, [this]() { DrawAsarIntegrationPopup(); }};
popups_["Build Instructions"] = {"Build Instructions", false, [this]() { DrawBuildInstructionsPopup(); }};
popups_["CLI Usage"] = {"CLI Usage", false, [this]() { DrawCLIUsagePopup(); }};
popups_["Troubleshooting"] = {"Troubleshooting", false, [this]() { DrawTroubleshootingPopup(); }};
popups_["Contributing"] = {"Contributing", false, [this]() { DrawContributingPopup(); }};
popups_["Whats New v03"] = {"What's New in v0.3", false, [this]() { DrawWhatsNewPopup(); }};
// Workspace-related popups
popups_["Workspace Help"] = {"Workspace Help", false, [this]() { DrawWorkspaceHelpPopup(); }};
popups_["Session Limit Warning"] = {"Session Limit Warning", false, [this]() { DrawSessionLimitWarningPopup(); }};
popups_["Layout Reset Confirm"] = {"Reset Layout Confirmation", false, [this]() { DrawLayoutResetConfirmPopup(); }};
}
PopupManager::~PopupManager() {
void PopupManager::DrawPopups() {
// Draw status popup if needed
DrawStatusPopup();
// Draw all registered popups
for (auto& [name, params] : popups_) {
if (params.is_visible) {
OpenPopup(name.c_str());
if (BeginPopupModal(name.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
params.draw_function();
EndPopup();
}
}
}
}
void PopupManager::Show(const char* name) {
auto it = popups_.find(name);
if (it != popups_.end()) {
it->second.is_visible = true;
}
}
void PopupManager::Hide(const char* name) {
auto it = popups_.find(name);
if (it != popups_.end()) {
it->second.is_visible = false;
CloseCurrentPopup();
}
}
} // namespace editor
bool PopupManager::IsVisible(const char* name) const {
auto it = popups_.find(name);
if (it != popups_.end()) {
return it->second.is_visible;
}
return false;
}
} // namespace yaze
void PopupManager::SetStatus(const absl::Status& status) {
if (!status.ok()) {
show_status_ = true;
prev_status_ = status;
status_ = status;
}
}
bool PopupManager::BeginCentered(const char* name) {
ImGuiIO const& io = GetIO();
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGuiWindowFlags flags =
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
return Begin(name, nullptr, flags);
}
void PopupManager::DrawStatusPopup() {
if (show_status_ && BeginCentered("StatusWindow")) {
Text("%s", ICON_MD_ERROR);
Text("%s", prev_status_.ToString().c_str());
Spacing();
NextColumn();
Columns(1);
Separator();
NewLine();
SameLine(128);
if (Button("OK", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Space)) {
show_status_ = false;
status_ = absl::OkStatus();
}
SameLine();
if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
SetClipboardText(prev_status_.ToString().c_str());
}
End();
}
}
void PopupManager::DrawAboutPopup() {
Text("Yet Another Zelda3 Editor - v%s", editor_manager_->version().c_str());
Text("Written by: scawful");
Spacing();
Text("Special Thanks: Zarby89, JaredBrian");
Separator();
if (Button("Close", gui::kDefaultModalSize)) {
Hide("About");
}
}
void PopupManager::DrawRomInfoPopup() {
auto* current_rom = editor_manager_->GetCurrentRom();
if (!current_rom) return;
Text("Title: %s", current_rom->title().c_str());
Text("ROM Size: %s", util::HexLongLong(current_rom->size()).c_str());
if (Button("Close", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Escape)) {
Hide("ROM Information");
}
}
void PopupManager::DrawSaveAsPopup() {
static std::string save_as_filename = "";
InputText("Filename", &save_as_filename);
if (Button("Save", gui::kDefaultModalSize)) {
// Call the save function from editor manager
// This will need to be implemented in the editor manager
Hide("Save As..");
}
SameLine();
if (Button("Cancel", gui::kDefaultModalSize)) {
Hide("Save As..");
}
}
void PopupManager::DrawNewProjectPopup() {
static std::string save_as_filename = "";
InputText("Project Name", &save_as_filename);
// These would need to be implemented in the editor manager
if (Button("Destination Filepath", gui::kDefaultModalSize)) {
// Call file dialog
}
SameLine();
Text("%s", "filepath"); // This would be from the editor manager
if (Button("ROM File", gui::kDefaultModalSize)) {
// Call file dialog
}
SameLine();
Text("%s", "rom_filename"); // This would be from the editor manager
if (Button("Labels File", gui::kDefaultModalSize)) {
// Call file dialog
}
SameLine();
Text("%s", "labels_filename"); // This would be from the editor manager
if (Button("Code Folder", gui::kDefaultModalSize)) {
// Call file dialog
}
SameLine();
Text("%s", "code_folder"); // This would be from the editor manager
Separator();
if (Button("Create", gui::kDefaultModalSize)) {
// Create project
Hide("New Project");
}
SameLine();
if (Button("Cancel", gui::kDefaultModalSize)) {
Hide("New Project");
}
}
void PopupManager::DrawSupportedFeaturesPopup() {
if (CollapsingHeader(absl::StrFormat("%s Overworld Editor", ICON_MD_LAYERS).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
BulletText("LW/DW/SW Tilemap Editing");
BulletText("LW/DW/SW Map Properties");
BulletText("Create/Delete/Update Entrances");
BulletText("Create/Delete/Update Exits");
BulletText("Create/Delete/Update Sprites");
BulletText("Create/Delete/Update Items");
BulletText("Multi-session map editing support");
}
if (CollapsingHeader(absl::StrFormat("%s Dungeon Editor", ICON_MD_CASTLE).c_str())) {
BulletText("View Room Header Properties");
BulletText("View Entrance Properties");
BulletText("Enhanced room navigation");
}
if (CollapsingHeader(absl::StrFormat("%s Graphics & Themes", ICON_MD_PALETTE).c_str())) {
BulletText("View Decompressed Graphics Sheets");
BulletText("View/Update Graphics Groups");
BulletText("5+ Built-in themes (Classic, Cyberpunk, Sunset, Forest, Midnight)");
BulletText("Custom theme creation and editing");
BulletText("Theme import/export functionality");
BulletText("Animated background grid effects");
}
if (CollapsingHeader(absl::StrFormat("%s Palettes", ICON_MD_COLOR_LENS).c_str())) {
BulletText("View Palette Groups");
BulletText("Enhanced palette editing tools");
BulletText("Color conversion utilities");
}
if (CollapsingHeader(absl::StrFormat("%s Project Management", ICON_MD_FOLDER).c_str())) {
BulletText("Multi-session workspace support");
BulletText("Enhanced project creation and management");
BulletText("ZScream project format compatibility");
BulletText("Workspace settings and feature flags");
}
if (CollapsingHeader(absl::StrFormat("%s Development Tools", ICON_MD_BUILD).c_str())) {
BulletText("Asar 65816 assembler integration");
BulletText("Enhanced CLI tools with TUI interface");
BulletText("Memory editor with advanced features");
BulletText("Hex editor with search and navigation");
BulletText("Assembly validation and symbol extraction");
}
if (CollapsingHeader(absl::StrFormat("%s Save Capabilities", ICON_MD_SAVE).c_str())) {
BulletText("All Overworld editing features");
BulletText("Hex Editor changes");
BulletText("Theme configurations");
BulletText("Project settings and workspace layouts");
BulletText("Custom assembly patches");
}
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Supported Features");
}
}
void PopupManager::DrawOpenRomHelpPopup() {
Text("File -> Open");
Text("Select a ROM file to open");
Text("Supported ROMs (headered or unheadered):");
Text("The Legend of Zelda: A Link to the Past");
Text("US Version 1.0");
Text("JP Version 1.0");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Open a ROM");
}
}
void PopupManager::DrawManageProjectPopup() {
Text("Project Menu");
Text("Create a new project or open an existing one.");
Text("Save the project to save the current state of the project.");
TextWrapped(
"To save a project, you need to first open a ROM and initialize your "
"code path and labels file. Label resource manager can be found in "
"the View menu. Code path is set in the Code editor after opening a "
"folder.");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Manage Project");
}
}
void PopupManager::DrawGettingStartedPopup() {
TextWrapped("Welcome to YAZE v0.3!");
TextWrapped("This software allows you to modify 'The Legend of Zelda: A Link to the Past' (US or JP) ROMs.");
Spacing();
TextWrapped("General Tips:");
BulletText("Experiment flags determine whether certain features are enabled");
BulletText("Backup files are enabled by default for safety");
BulletText("Use File > Options to configure settings");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Getting Started");
}
}
void PopupManager::DrawAsarIntegrationPopup() {
TextWrapped("Asar 65816 Assembly Integration");
TextWrapped("YAZE v0.3 includes full Asar assembler support for ROM patching.");
Spacing();
TextWrapped("Features:");
BulletText("Cross-platform ROM patching with assembly code");
BulletText("Symbol extraction with addresses and opcodes");
BulletText("Assembly validation with error reporting");
BulletText("Memory-safe operations with automatic ROM size management");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Asar Integration");
}
}
void PopupManager::DrawBuildInstructionsPopup() {
TextWrapped("Build Instructions");
TextWrapped("YAZE uses modern CMake for cross-platform builds.");
Spacing();
TextWrapped("Quick Start:");
BulletText("cmake -B build");
BulletText("cmake --build build --target yaze");
Spacing();
TextWrapped("Development:");
BulletText("cmake --preset dev");
BulletText("cmake --build --preset dev");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Build Instructions");
}
}
void PopupManager::DrawCLIUsagePopup() {
TextWrapped("Command Line Interface (z3ed)");
TextWrapped("Enhanced CLI tool with Asar integration.");
Spacing();
TextWrapped("Commands:");
BulletText("z3ed asar patch.asm --rom=file.sfc");
BulletText("z3ed extract symbols.asm");
BulletText("z3ed validate assembly.asm");
BulletText("z3ed patch file.bps --rom=file.sfc");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("CLI Usage");
}
}
void PopupManager::DrawTroubleshootingPopup() {
TextWrapped("Troubleshooting");
TextWrapped("Common issues and solutions:");
Spacing();
BulletText("ROM won't load: Check file format (SFC/SMC supported)");
BulletText("Graphics issues: Try disabling experimental features");
BulletText("Performance: Enable hardware acceleration in display settings");
BulletText("Crashes: Check ROM file integrity and available memory");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Troubleshooting");
}
}
void PopupManager::DrawContributingPopup() {
TextWrapped("Contributing to YAZE");
TextWrapped("YAZE is open source and welcomes contributions!");
Spacing();
TextWrapped("How to contribute:");
BulletText("Fork the repository on GitHub");
BulletText("Create feature branches for new work");
BulletText("Follow C++ coding standards");
BulletText("Include tests for new features");
BulletText("Submit pull requests for review");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Contributing");
}
}
void PopupManager::DrawWhatsNewPopup() {
TextWrapped("What's New in YAZE v0.3");
Spacing();
if (CollapsingHeader(absl::StrFormat("%s User Interface & Theming", ICON_MD_PALETTE).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
BulletText("Complete theme management system with 5+ built-in themes");
BulletText("Custom theme editor with save-to-file functionality");
BulletText("Animated background grid with breathing effects (optional)");
BulletText("Enhanced welcome screen with themed elements");
BulletText("Multi-session workspace support with docking");
BulletText("Improved editor organization and navigation");
}
if (CollapsingHeader(absl::StrFormat("%s Development & Build System", ICON_MD_BUILD).c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
BulletText("Asar 65816 assembler integration for ROM patching");
BulletText("Enhanced CLI tools with TUI (Terminal User Interface)");
BulletText("Modernized CMake build system with presets");
BulletText("Cross-platform CI/CD pipeline (Windows, macOS, Linux)");
BulletText("Comprehensive testing framework with 46+ core tests");
BulletText("Professional packaging for all platforms (DMG, MSI, DEB)");
}
if (CollapsingHeader(absl::StrFormat("%s Core Improvements", ICON_MD_SETTINGS).c_str())) {
BulletText("Enhanced project management with YazeProject structure");
BulletText("Improved ROM loading and validation");
BulletText("Better error handling and status reporting");
BulletText("Memory safety improvements with sanitizers");
BulletText("Enhanced file dialog integration");
BulletText("Improved logging and debugging capabilities");
}
if (CollapsingHeader(absl::StrFormat("%s Editor Features", ICON_MD_EDIT).c_str())) {
BulletText("Enhanced overworld editing capabilities");
BulletText("Improved graphics sheet viewing and editing");
BulletText("Better palette management and editing");
BulletText("Enhanced memory and hex editing tools");
BulletText("Improved sprite and item management");
BulletText("Better entrance and exit editing");
}
Spacing();
if (Button(absl::StrFormat("%s View Theme Editor", ICON_MD_PALETTE).c_str(), ImVec2(-1, 30))) {
// Close this popup and show theme settings
Hide("Whats New v03");
// Could trigger theme editor opening here
}
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Whats New v03");
}
}
void PopupManager::DrawWorkspaceHelpPopup() {
TextWrapped("Workspace Management");
TextWrapped("YAZE supports multiple ROM sessions and flexible workspace layouts.");
Spacing();
TextWrapped("Session Management:");
BulletText("Ctrl+Shift+N: Create new session");
BulletText("Ctrl+Shift+W: Close current session");
BulletText("Ctrl+Tab: Quick session switcher");
BulletText("Each session maintains its own ROM and editor state");
Spacing();
TextWrapped("Layout Management:");
BulletText("Drag window tabs to dock/undock");
BulletText("Ctrl+Shift+S: Save current layout");
BulletText("Ctrl+Shift+O: Load saved layout");
BulletText("F11: Maximize current window");
Spacing();
TextWrapped("Preset Layouts:");
BulletText("Developer: Code, memory, testing tools");
BulletText("Designer: Graphics, palettes, sprites");
BulletText("Modder: All gameplay editing tools");
if (Button("Close", gui::kDefaultModalSize)) {
Hide("Workspace Help");
}
}
void PopupManager::DrawSessionLimitWarningPopup() {
TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Warning", ICON_MD_WARNING);
TextWrapped("You have reached the recommended session limit.");
TextWrapped("Having too many sessions open may impact performance.");
Spacing();
TextWrapped("Consider closing unused sessions or saving your work.");
if (Button("Understood", gui::kDefaultModalSize)) {
Hide("Session Limit Warning");
}
SameLine();
if (Button("Open Session Manager", gui::kDefaultModalSize)) {
Hide("Session Limit Warning");
// This would trigger the session manager to open
}
}
void PopupManager::DrawLayoutResetConfirmPopup() {
TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "%s Confirm Reset", ICON_MD_WARNING);
TextWrapped("This will reset your current workspace layout to default.");
TextWrapped("Any custom window arrangements will be lost.");
Spacing();
TextWrapped("Do you want to continue?");
if (Button("Reset Layout", gui::kDefaultModalSize)) {
Hide("Layout Reset Confirm");
// This would trigger the actual reset
}
SameLine();
if (Button("Cancel", gui::kDefaultModalSize)) {
Hide("Layout Reset Confirm");
}
}
} // namespace editor
} // namespace yaze

View File

@@ -1,20 +1,100 @@
#ifndef YAZE_APP_EDITOR_POPUP_MANAGER_H
#define YAZE_APP_EDITOR_POPUP_MANAGER_H
#include <functional>
#include <string>
#include <unordered_map>
#include "absl/status/status.h"
namespace yaze {
namespace editor {
// Forward declaration
class EditorManager;
struct PopupParams {
std::string name;
bool is_visible = false;
std::function<void()> draw_function;
};
// ImGui popup manager.
class PopupManager {
public:
PopupManager();
~PopupManager();
PopupManager(EditorManager* editor_manager);
// Initialize popups
void Initialize();
// Draw all popups
void DrawPopups();
// Show a specific popup
void Show(const char* name);
// Hide a specific popup
void Hide(const char* name);
// Check if a popup is visible
bool IsVisible(const char* name) const;
// Set the status for status popup
void SetStatus(const absl::Status& status);
// Get the current status
absl::Status GetStatus() const { return status_; }
private:
// Helper function to begin a centered popup
bool BeginCentered(const char* name);
// Draw the about popup
void DrawAboutPopup();
// Draw the ROM info popup
void DrawRomInfoPopup();
// Draw the status popup
void DrawStatusPopup();
// Draw the save as popup
void DrawSaveAsPopup();
// Draw the new project popup
void DrawNewProjectPopup();
// Draw the supported features popup
void DrawSupportedFeaturesPopup();
// Draw the open ROM help popup
void DrawOpenRomHelpPopup();
// Draw the manage project popup
void DrawManageProjectPopup();
// v0.3 Help Documentation popups
void DrawGettingStartedPopup();
void DrawAsarIntegrationPopup();
void DrawBuildInstructionsPopup();
void DrawCLIUsagePopup();
void DrawTroubleshootingPopup();
void DrawContributingPopup();
void DrawWhatsNewPopup();
// Workspace-related popups
void DrawWorkspaceHelpPopup();
void DrawSessionLimitWarningPopup();
void DrawLayoutResetConfirmPopup();
EditorManager* editor_manager_;
std::unordered_map<std::string, PopupParams> popups_;
absl::Status status_;
bool show_status_ = false;
absl::Status prev_status_;
};
} // namespace editor
} // namespace editor
} // namespace yaze
} // namespace yaze
#endif // YAZE_APP_EDITOR_POPUP_MANAGER_H
#endif // YAZE_APP_EDITOR_POPUP_MANAGER_H

View File

@@ -1,28 +0,0 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H
#include <cstddef>
namespace yaze {
namespace editor {
// System resource manager.
class ResourceManager {
public:
ResourceManager() : count_(0) {}
~ResourceManager() = default;
void Load(const char* path);
void Unload(const char* path);
void UnloadAll();
size_t Count() const;
private:
size_t count_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_RESOURCE_MANAGER_H

View File

@@ -2,31 +2,28 @@
#include "app/editor/system/settings_editor.h"
#include "absl/status/status.h"
#include "app/editor/system/flags.h"
#include "app/core/features.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
using ImGui::BeginChild;
using ImGui::BeginMenu;
using ImGui::BeginTabBar;
using ImGui::BeginTabItem;
using ImGui::BeginTable;
using ImGui::Checkbox;
using ImGui::EndChild;
using ImGui::EndMenu;
using ImGui::EndTabBar;
using ImGui::EndTabItem;
using ImGui::EndTable;
using ImGui::TableHeader;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetBgColor;
using ImGui::TableSetColumnIndex;
using ImGui::TableSetupColumn;
using ImGui::Text;
void SettingsEditor::Initialize() {}
absl::Status SettingsEditor::Load() { return absl::OkStatus(); }
absl::Status SettingsEditor::Update() {
if (BeginTabBar("Settings", ImGuiTabBarFlags_None)) {
@@ -34,7 +31,12 @@ absl::Status SettingsEditor::Update() {
DrawGeneralSettings();
EndTabItem();
}
if (BeginTabItem("Font Manager")) {
gui::DrawFontManager();
EndTabItem();
}
if (BeginTabItem("Keyboard Shortcuts")) {
DrawKeyboardShortcuts();
EndTabItem();
}
EndTabBar();
@@ -44,37 +46,44 @@ absl::Status SettingsEditor::Update() {
}
void SettingsEditor::DrawGeneralSettings() {
if (BeginTable("##SettingsTable", 2,
static core::FlagsMenu flags;
if (BeginTable("##SettingsTable", 4,
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
TableSetupColumn("Experiment Flags", ImGuiTableColumnFlags_WidthFixed,
250.0f);
TableSetupColumn("General Setting", ImGuiTableColumnFlags_WidthStretch,
TableSetupColumn("System Flags", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Overworld Flags", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Dungeon Flags", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn("Resource Flags", ImGuiTableColumnFlags_WidthStretch,
0.0f);
TableHeadersRow();
TableNextColumn();
if (BeginChild("##GeneralSettingsStyleWrapper", ImVec2(0, 0),
ImGuiChildFlags_FrameStyle)) {
static FlagsMenu flags;
flags.Draw();
EndChild();
}
flags.DrawSystemFlags();
TableNextColumn();
if (BeginChild("##GeneralSettingsWrapper", ImVec2(0, 0),
ImGuiChildFlags_FrameStyle)) {
Text("TODO: Add some settings here");
EndChild();
}
flags.DrawOverworldFlags();
TableNextColumn();
flags.DrawDungeonFlags();
TableNextColumn();
flags.DrawResourceFlags();
EndTable();
}
}
absl::Status SettingsEditor::DrawKeyboardShortcuts() {
return absl::OkStatus();
void SettingsEditor::DrawKeyboardShortcuts() {
for (const auto& [name, shortcut] :
context_->shortcut_manager.GetShortcuts()) {
ImGui::PushID(name.c_str());
ImGui::Text("%s", name.c_str());
ImGui::SameLine(ImGui::GetWindowWidth() - 100);
ImGui::Text("%s", PrintShortcut(shortcut.keys).c_str());
ImGui::PopID();
}
}
} // namespace editor

View File

@@ -1,10 +1,10 @@
#ifndef YAZE_APP_EDITOR_SETTINGS_EDITOR_H
#define YAZE_APP_EDITOR_SETTINGS_EDITOR_H
#include "imgui/imgui.h"
#include "absl/status/status.h"
#include "app/editor/editor.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
@@ -207,21 +207,33 @@ static void ShowExampleAppPropertyEditor(bool* p_open) {
class SettingsEditor : public Editor {
public:
SettingsEditor() : Editor() { type_ = EditorType::kSettings; }
explicit SettingsEditor(Rom* rom = nullptr) : rom_(rom) {
type_ = EditorType::kSettings;
}
void Initialize() override;
absl::Status Load() override;
absl::Status Save() override { return absl::UnimplementedError("Save"); }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
// Set the ROM pointer
void set_rom(Rom* rom) { rom_ = rom; }
// Get the ROM pointer
Rom* rom() const { return rom_; }
bool IsRomLoaded() const override { return true; } // Allow access without ROM for global settings
private:
Rom* rom_;
void DrawGeneralSettings();
absl::Status DrawKeyboardShortcuts();
void DrawKeyboardShortcuts();
};
} // namespace editor

View File

@@ -0,0 +1,168 @@
#include "shortcut_manager.h"
#include <algorithm>
#include <string>
#include "app/gui/input.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
namespace {
constexpr std::pair<ImGuiKey, const char*> kKeyNames[] = {
{ImGuiKey_Tab, "Tab"},
{ImGuiKey_LeftArrow, "Left"},
{ImGuiKey_RightArrow, "Right"},
{ImGuiKey_UpArrow, "Up"},
{ImGuiKey_DownArrow, "Down"},
{ImGuiKey_PageUp, "PageUp"},
{ImGuiKey_PageDown, "PageDown"},
{ImGuiKey_Home, "Home"},
{ImGuiKey_End, "End"},
{ImGuiKey_Insert, "Insert"},
{ImGuiKey_Delete, "Delete"},
{ImGuiKey_Backspace, "Backspace"},
{ImGuiKey_Space, "Space"},
{ImGuiKey_Enter, "Enter"},
{ImGuiKey_Escape, "Escape"},
{ImGuiMod_Ctrl, "Ctrl"},
{ImGuiMod_Shift, "Shift"},
{ImGuiMod_Alt, "Alt"},
{ImGuiMod_Super, "Super"},
{ImGuiKey_A, "A"},
{ImGuiKey_B, "B"},
{ImGuiKey_C, "C"},
{ImGuiKey_D, "D"},
{ImGuiKey_E, "E"},
{ImGuiKey_F, "F"},
{ImGuiKey_G, "G"},
{ImGuiKey_H, "H"},
{ImGuiKey_I, "I"},
{ImGuiKey_J, "J"},
{ImGuiKey_K, "K"},
{ImGuiKey_L, "L"},
{ImGuiKey_M, "M"},
{ImGuiKey_N, "N"},
{ImGuiKey_O, "O"},
{ImGuiKey_P, "P"},
{ImGuiKey_Q, "Q"},
{ImGuiKey_R, "R"},
{ImGuiKey_S, "S"},
{ImGuiKey_T, "T"},
{ImGuiKey_U, "U"},
{ImGuiKey_V, "V"},
{ImGuiKey_W, "W"},
{ImGuiKey_X, "X"},
{ImGuiKey_Y, "Y"},
{ImGuiKey_Z, "Z"},
{ImGuiKey_F1, "F1"},
{ImGuiKey_F2, "F2"},
{ImGuiKey_F3, "F3"},
{ImGuiKey_F4, "F4"},
{ImGuiKey_F5, "F5"},
{ImGuiKey_F6, "F6"},
{ImGuiKey_F7, "F7"},
{ImGuiKey_F8, "F8"},
{ImGuiKey_F9, "F9"},
{ImGuiKey_F10, "F10"},
{ImGuiKey_F11, "F11"},
{ImGuiKey_F12, "F12"},
{ImGuiKey_F13, "F13"},
{ImGuiKey_F14, "F14"},
{ImGuiKey_F15, "F15"},
};
constexpr const char* GetKeyName(ImGuiKey key) {
for (const auto& pair : kKeyNames) {
if (pair.first == key) {
return pair.second;
}
}
return "";
}
} // namespace
std::string PrintShortcut(const std::vector<ImGuiKey>& keys) {
std::string shortcut;
for (size_t i = keys.size(); i > 0; --i) {
shortcut += GetKeyName(keys[i - 1]);
if (i > 1) {
shortcut += "+";
}
}
return shortcut;
}
const static std::string kCtrlKey = "Ctrl";
const static std::string kAltKey = "Alt";
const static std::string kShiftKey = "Shift";
const static std::string kSuperKey = "Super";
std::vector<ImGuiKey> ParseShortcut(const std::string& shortcut) {
std::vector<ImGuiKey> shortcuts;
// Search for special keys and the + symbol to combine with the
// MapKeyToImGuiKey function
size_t start = 0;
size_t end = shortcut.find(kCtrlKey);
if (end != std::string::npos) {
shortcuts.push_back(ImGuiMod_Ctrl);
start = end + kCtrlKey.size();
}
end = shortcut.find(kAltKey, start);
if (end != std::string::npos) {
shortcuts.push_back(ImGuiMod_Alt);
start = end + kAltKey.size();
}
end = shortcut.find(kShiftKey, start);
if (end != std::string::npos) {
shortcuts.push_back(ImGuiMod_Shift);
start = end + kShiftKey.size();
}
end = shortcut.find(kSuperKey, start);
if (end != std::string::npos) {
shortcuts.push_back(ImGuiMod_Super);
start = end + kSuperKey.size();
}
// Parse the rest of the keys
while (start < shortcut.size()) {
shortcuts.push_back(gui::MapKeyToImGuiKey(shortcut[start]));
start++;
}
return shortcuts;
}
void ExecuteShortcuts(const ShortcutManager& shortcut_manager) {
// Check for keyboard shortcuts using the shortcut manager
for (const auto& shortcut : shortcut_manager.GetShortcuts()) {
bool keys_pressed = true;
// Check for all the keys in the shortcut
for (const auto& key : shortcut.second.keys) {
if (key == ImGuiMod_Ctrl) {
keys_pressed &= ImGui::GetIO().KeyCtrl;
} else if (key == ImGuiMod_Alt) {
keys_pressed &= ImGui::GetIO().KeyAlt;
} else if (key == ImGuiMod_Shift) {
keys_pressed &= ImGui::GetIO().KeyShift;
} else if (key == ImGuiMod_Super) {
keys_pressed &= ImGui::GetIO().KeySuper;
} else {
keys_pressed &= ImGui::IsKeyDown(key);
}
if (!keys_pressed) {
break;
}
}
if (keys_pressed) {
shortcut.second.callback();
}
}
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,69 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_SHORTCUT_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_SHORTCUT_MANAGER_H
#include <functional>
#include <string>
#include <unordered_map>
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
struct Shortcut {
std::string name;
std::vector<ImGuiKey> keys;
std::function<void()> callback;
};
std::vector<ImGuiKey> ParseShortcut(const std::string &shortcut);
std::string PrintShortcut(const std::vector<ImGuiKey> &keys);
class ShortcutManager {
public:
void RegisterShortcut(const std::string &name,
const std::vector<ImGuiKey> &keys) {
shortcuts_[name] = {name, keys};
}
void RegisterShortcut(const std::string &name,
const std::vector<ImGuiKey> &keys,
std::function<void()> callback) {
shortcuts_[name] = {name, keys, callback};
}
void RegisterShortcut(const std::string &name, ImGuiKey key,
std::function<void()> callback) {
shortcuts_[name] = {name, {key}, callback};
}
void ExecuteShortcut(const std::string &name) const {
shortcuts_.at(name).callback();
}
// Access the shortcut and print the readable name of the shortcut for menus
const Shortcut &GetShortcut(const std::string &name) const {
return shortcuts_.at(name);
}
// Get shortcut callback function
std::function<void()> GetCallback(const std::string &name) const {
return shortcuts_.at(name).callback;
}
const std::string GetKeys(const std::string &name) const {
return PrintShortcut(shortcuts_.at(name).keys);
}
auto GetShortcuts() const { return shortcuts_; }
private:
std::unordered_map<std::string, Shortcut> shortcuts_;
};
void ExecuteShortcuts(const ShortcutManager &shortcut_manager);
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_SHORTCUT_MANAGER_H

View File

@@ -0,0 +1,76 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_TOAST_MANAGER_H
#define YAZE_APP_EDITOR_SYSTEM_TOAST_MANAGER_H
#include <deque>
#include <string>
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
enum class ToastType { kInfo, kSuccess, kWarning, kError };
struct Toast {
std::string message;
ToastType type = ToastType::kInfo;
float ttl_seconds = 3.0f;
};
class ToastManager {
public:
void Show(const std::string &message, ToastType type = ToastType::kInfo,
float ttl_seconds = 3.0f) {
toasts_.push_back({message, type, ttl_seconds});
}
void Draw() {
if (toasts_.empty()) return;
ImGuiIO &io = ImGui::GetIO();
ImVec2 pos(io.DisplaySize.x - 10.f, 40.f);
// Iterate copy so we can mutate ttl while drawing ordered from newest.
for (auto it = toasts_.begin(); it != toasts_.end();) {
Toast &t = *it;
ImVec4 bg;
switch (t.type) {
case ToastType::kInfo: bg = ImVec4(0.10f, 0.10f, 0.10f, 0.85f); break;
case ToastType::kSuccess: bg = ImVec4(0.10f, 0.30f, 0.10f, 0.85f); break;
case ToastType::kWarning: bg = ImVec4(0.30f, 0.25f, 0.05f, 0.90f); break;
case ToastType::kError: bg = ImVec4(0.40f, 0.10f, 0.10f, 0.90f); break;
}
ImGui::SetNextWindowBgAlpha(bg.w);
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.f, 0.f));
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoNav;
ImGui::PushStyleColor(ImGuiCol_WindowBg, bg);
if (ImGui::Begin("##toast", nullptr, flags)) {
ImGui::TextUnformatted(t.message.c_str());
}
ImGui::End();
ImGui::PopStyleColor(1);
// Decrease TTL
t.ttl_seconds -= io.DeltaTime;
if (t.ttl_seconds <= 0.f) {
it = toasts_.erase(it);
} else {
// Next toast stacks below
pos.y += ImGui::GetItemRectSize().y + 6.f;
++it;
}
}
}
private:
std::deque<Toast> toasts_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_SYSTEM_TOAST_MANAGER_H