backend-infra-engineer: Release v0.3.0 snapshot
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
706
src/app/editor/dungeon/dungeon_canvas_viewer.cc
Normal file
706
src/app/editor/dungeon/dungeon_canvas_viewer.cc
Normal 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
|
||||
105
src/app/editor/dungeon/dungeon_canvas_viewer.h
Normal file
105
src/app/editor/dungeon/dungeon_canvas_viewer.h
Normal 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
@@ -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
|
||||
|
||||
308
src/app/editor/dungeon/dungeon_object_interaction.cc
Normal file
308
src/app/editor/dungeon/dungeon_object_interaction.cc
Normal 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
|
||||
94
src/app/editor/dungeon/dungeon_object_interaction.h
Normal file
94
src/app/editor/dungeon/dungeon_object_interaction.h
Normal 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
|
||||
1095
src/app/editor/dungeon/dungeon_object_selector.cc
Normal file
1095
src/app/editor/dungeon/dungeon_object_selector.cc
Normal file
File diff suppressed because it is too large
Load Diff
123
src/app/editor/dungeon/dungeon_object_selector.h
Normal file
123
src/app/editor/dungeon/dungeon_object_selector.h
Normal 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
|
||||
208
src/app/editor/dungeon/dungeon_renderer.cc
Normal file
208
src/app/editor/dungeon/dungeon_renderer.cc
Normal 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
|
||||
75
src/app/editor/dungeon/dungeon_renderer.h
Normal file
75
src/app/editor/dungeon/dungeon_renderer.h
Normal 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
|
||||
127
src/app/editor/dungeon/dungeon_room_loader.cc
Normal file
127
src/app/editor/dungeon/dungeon_room_loader.cc
Normal 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
|
||||
56
src/app/editor/dungeon/dungeon_room_loader.h
Normal file
56
src/app/editor/dungeon/dungeon_room_loader.h
Normal 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
|
||||
146
src/app/editor/dungeon/dungeon_room_selector.cc
Normal file
146
src/app/editor/dungeon/dungeon_room_selector.cc
Normal 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", ¤t_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", ¤t_entrance.entrance_id_);
|
||||
gui::InputHexWord("Room ID", ¤t_entrance.room_);
|
||||
SameLine();
|
||||
|
||||
gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true);
|
||||
gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true);
|
||||
SameLine();
|
||||
|
||||
gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("Floor", ¤t_entrance.floor_);
|
||||
ImGui::Separator();
|
||||
|
||||
gui::InputHexWord("Player X ", ¤t_entrance.x_position_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Player Y ", ¤t_entrance.y_position_);
|
||||
|
||||
gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_);
|
||||
|
||||
gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_);
|
||||
SameLine();
|
||||
gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_);
|
||||
|
||||
gui::InputHexWord("Exit", ¤t_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", ¤t_entrance.camera_boundary_qn_, 50.f,
|
||||
true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qe_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qs_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_qw_, 50.f, true);
|
||||
|
||||
gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
|
||||
true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_fe_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_entrance.camera_boundary_fs_, 50.f, true);
|
||||
SameLine();
|
||||
gui::InputHexByte("", ¤t_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
|
||||
64
src/app/editor/dungeon/dungeon_room_selector.h
Normal file
64
src/app/editor/dungeon/dungeon_room_selector.h
Normal 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
|
||||
151
src/app/editor/dungeon/dungeon_toolset.cc
Normal file
151
src/app/editor/dungeon/dungeon_toolset.cc
Normal 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
|
||||
69
src/app/editor/dungeon/dungeon_toolset.h
Normal file
69
src/app/editor/dungeon/dungeon_toolset.h
Normal 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
|
||||
87
src/app/editor/dungeon/dungeon_usage_tracker.cc
Normal file
87
src/app/editor/dungeon/dungeon_usage_tracker.cc
Normal 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
|
||||
57
src/app/editor/dungeon/dungeon_usage_tracker.h
Normal file
57
src/app/editor/dungeon/dungeon_usage_tracker.h
Normal 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
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "editor.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
43
src/app/editor/editor_safeguards.h
Normal file
43
src/app/editor/editor_safeguards.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_, ¤t_bitmap,
|
||||
current_color_);
|
||||
Renderer::GetInstance().UpdateBitmap(¤t_bitmap);
|
||||
Renderer::Get().UpdateBitmap(¤t_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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", ¤t_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", ¤t_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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¤t_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 ¤t_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", ¤t_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", ¤t_tile16_info[0]);
|
||||
ImGui::SameLine();
|
||||
gui::InputTileInfo("TR", ¤t_tile16_info[1]);
|
||||
gui::InputTileInfo("BL", ¤t_tile16_info[2]);
|
||||
ImGui::SameLine();
|
||||
gui::InputTileInfo("BR", ¤t_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", ¤t_tile16_info.tiles[0]);
|
||||
ImGui::SameLine();
|
||||
gui::InputTileInfo("TR", ¤t_tile16_info.tiles[1]);
|
||||
gui::InputTileInfo("BL", ¤t_tile16_info.tiles[2]);
|
||||
ImGui::SameLine();
|
||||
gui::InputTileInfo("BR", ¤t_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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¤t_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(¤t_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(
|
||||
¤t_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(
|
||||
¤t_gfx_individual_[current_tile8_]);
|
||||
}
|
||||
|
||||
if (BeginChild("Tile16 Editor Options",
|
||||
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
|
||||
tile16_edit_canvas_.DrawBackground();
|
||||
tile16_edit_canvas_.DrawContextMenu(¤t_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(¤t_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", ¬ify_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(¤t_gfx_bmp_);
|
||||
|
||||
RETURN_IF_ERROR(
|
||||
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
|
||||
Renderer::GetInstance().UpdateBitmap(¤t_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(¤t_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(¤t_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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(¤t_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(¤t_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
|
||||
|
||||
@@ -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
|
||||
|
||||
117
src/app/editor/message/message_preview.cc
Normal file
117
src/app/editor/message/message_preview.cc
Normal 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
|
||||
44
src/app/editor/message/message_preview.h
Normal file
44
src/app/editor/message/message_preview.h
Normal 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_
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
950
src/app/editor/overworld/map_properties.cc
Normal file
950
src/app/editor/overworld/map_properties.cc
Normal 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", ¤t_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", ¤t_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", ¤t_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 = [¤t_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", ¤t_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", ¤t_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", ¤t_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
|
||||
84
src/app/editor/overworld/map_properties.h
Normal file
84
src/app/editor/overworld/map_properties.h
Normal 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
@@ -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)};
|
||||
|
||||
|
||||
686
src/app/editor/overworld/tile16_editor.cc
Normal file
686
src/app/editor/overworld/tile16_editor.cc
Normal 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 ¤t_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(¤t_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(¤t_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(
|
||||
¤t_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(¤t_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", ¬ify_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(¤t_gfx_bmp_);
|
||||
|
||||
current_tile16_bmp_.SetPaletteWithTransparent(palette, value);
|
||||
Renderer::Get().UpdateBitmap(¤t_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(¤t_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(¤t_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(¤t_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(¤t_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
|
||||
@@ -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 ¤t_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
|
||||
73
src/app/editor/overworld/ui_constants.h
Normal file
73
src/app/editor/overworld/ui_constants.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
168
src/app/editor/system/shortcut_manager.cc
Normal file
168
src/app/editor/system/shortcut_manager.cc
Normal 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
|
||||
69
src/app/editor/system/shortcut_manager.h
Normal file
69
src/app/editor/system/shortcut_manager.h
Normal 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
|
||||
76
src/app/editor/system/toast_manager.h
Normal file
76
src/app/editor/system/toast_manager.h
Normal 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user