Refactor EditorManager and OverworldEditor for enhanced functionality and error handling

- Updated EditorManager to improve welcome screen logic, ensuring it only displays in truly empty states and not when a ROM is loaded but current_rom_ is null.
- Enhanced error handling in EditorManager by routing editor errors to the toast manager for better user feedback.
- Improved OverworldEditor with enhanced tile interaction detection and added scratch space functionality for better layout management.
- Introduced a new ScratchSpaceSlot structure in OverworldEditor to manage scratch space for tile16 layouts, allowing for more flexible editing and selection.
- Added utility functions in canvas_utils for grid alignment and effective scaling, improving overall canvas interaction.
- Implemented an enhanced palette editor with ROM integration, providing users with tools for color analysis and palette management.
This commit is contained in:
scawful
2025-09-27 15:24:58 -04:00
parent 261032b1bd
commit a9ead0a45c
17 changed files with 4434 additions and 821 deletions

View File

@@ -763,30 +763,15 @@ absl::Status EditorManager::Update() {
// Check if current ROM is valid // Check if current ROM is valid
if (!current_rom_) { if (!current_rom_) {
// Only auto-show welcome screen if it hasn't been manually closed // Only show welcome screen for truly empty state, not when ROM is loaded but current_rom_ is null
if (!welcome_screen_manually_closed_) { if (sessions_.empty() && !welcome_screen_manually_closed_) {
show_welcome_screen_ = true; show_welcome_screen_ = true;
} }
return absl::OkStatus(); return absl::OkStatus();
} }
// Check if any editors are active across ALL sessions // ROM is loaded and valid - don't auto-show welcome screen
bool any_editor_active = false; // Welcome screen should only be shown manually at this point
for (const auto& session : sessions_) {
if (!session.rom.is_loaded()) continue;
for (auto editor : session.editors.active_editors_) {
if (*editor->active()) {
any_editor_active = true;
break;
}
}
if (any_editor_active) break;
}
// Only auto-show welcome screen if no editors are active AND it hasn't been manually closed
if (!any_editor_active && !welcome_screen_manually_closed_) {
show_welcome_screen_ = true;
}
// Iterate through ALL sessions to support multi-session docking // Iterate through ALL sessions to support multi-session docking
for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) { for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
@@ -819,6 +804,21 @@ absl::Status EditorManager::Update() {
status_ = editor->Update(); status_ = editor->Update();
// Route editor errors to toast manager
if (!status_.ok()) {
std::string editor_name = "Editor"; // Get actual editor name if available
if (editor == &session.editors.overworld_editor_) editor_name = "Overworld Editor";
else if (editor == &session.editors.dungeon_editor_) editor_name = "Dungeon Editor";
else if (editor == &session.editors.sprite_editor_) editor_name = "Sprite Editor";
else if (editor == &session.editors.graphics_editor_) editor_name = "Graphics Editor";
else if (editor == &session.editors.music_editor_) editor_name = "Music Editor";
else if (editor == &session.editors.palette_editor_) editor_name = "Palette Editor";
else if (editor == &session.editors.screen_editor_) editor_name = "Screen Editor";
toast_manager_.Show(absl::StrFormat("%s Error: %s", editor_name, status_.message()),
editor::ToastType::kError, 8.0f);
}
// Restore context // Restore context
current_rom_ = prev_rom; current_rom_ = prev_rom;
current_editor_set_ = prev_editor_set; current_editor_set_ = prev_editor_set;
@@ -1285,6 +1285,13 @@ void EditorManager::DrawMenuBar() {
if (show_palette_editor_ && current_editor_set_) { if (show_palette_editor_ && current_editor_set_) {
Begin("Palette Editor", &show_palette_editor_); Begin("Palette Editor", &show_palette_editor_);
status_ = current_editor_set_->palette_editor_.Update(); status_ = current_editor_set_->palette_editor_.Update();
// Route palette editor errors to toast manager
if (!status_.ok()) {
toast_manager_.Show(absl::StrFormat("Palette Editor Error: %s", status_.message()),
editor::ToastType::kError, 8.0f);
}
End(); End();
} }
@@ -1514,8 +1521,8 @@ absl::Status EditorManager::LoadRom() {
manager.Save(); manager.Save();
RETURN_IF_ERROR(LoadAssets()); RETURN_IF_ERROR(LoadAssets());
// Reset welcome screen state when ROM is loaded // Hide welcome screen when ROM is successfully loaded - don't reset manual close state
welcome_screen_manually_closed_ = false; show_welcome_screen_ = false;
return absl::OkStatus(); return absl::OkStatus();
} }

View File

@@ -39,7 +39,6 @@ namespace yaze::editor {
using core::Renderer; using core::Renderer;
using namespace ImGui; using namespace ImGui;
constexpr int kTile16Size = 0x10;
constexpr float kInputFieldSize = 30.f; constexpr float kInputFieldSize = 30.f;
void OverworldEditor::Initialize() { void OverworldEditor::Initialize() {
@@ -161,6 +160,7 @@ void OverworldEditor::Initialize() {
} }
HOVER_HINT("Copy Map to Clipboard"); HOVER_HINT("Copy Map to Clipboard");
}); });
} }
absl::Status OverworldEditor::Load() { absl::Status OverworldEditor::Load() {
@@ -956,6 +956,9 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
const int large_map_size = 1024; const int large_map_size = 1024;
const auto canvas_zero_point = ow_map_canvas_.zero_point(); const auto canvas_zero_point = ow_map_canvas_.zero_point();
// Check if the mouse is over the canvas
// Calculate which small map the mouse is currently over // Calculate which small map the mouse is currently over
int map_x = (mouse_position.x - canvas_zero_point.x) / kOverworldMapSize; int map_x = (mouse_position.x - canvas_zero_point.x) / kOverworldMapSize;
int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize; int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize;
@@ -1094,7 +1097,8 @@ void OverworldEditor::DrawOverworldCanvas() {
current_map_, current_world_, show_overlay_preview_); current_map_, current_world_, show_overlay_preview_);
} }
if (current_mode == EditingMode::DRAW_TILE) { if (current_mode == EditingMode::DRAW_TILE && ow_map_canvas_.IsMouseHovering()) {
// Only allow overworld edits when mouse is actually over the main overworld canvas
CheckForOverworldEdits(); CheckForOverworldEdits();
} }
if (IsItemHovered()) status_ = CheckForCurrentMap(); if (IsItemHovered()) status_ = CheckForCurrentMap();
@@ -1117,31 +1121,38 @@ absl::Status OverworldEditor::DrawTile16Selector() {
ImGui::BeginGroup(); ImGui::BeginGroup();
gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion"); gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
blockset_canvas_.DrawBackground(); blockset_canvas_.DrawBackground();
gui::EndNoPadding(); gui::EndPadding(); // Fixed: was EndNoPadding()
{
blockset_canvas_.DrawContextMenu(); blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2, blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
map_blockset_loaded_, /*scale=*/2); map_blockset_loaded_, /*scale=*/2);
if (blockset_canvas_.DrawTileSelector(32.0f)) { // Improved tile interaction detection - use proper canvas interaction
// Open the tile16 editor to the tile bool tile_selected = false;
auto tile_pos = blockset_canvas_.points().front(); if (blockset_canvas_.DrawTileSelector(32.0f)) {
int grid_x = static_cast<int>(tile_pos.x / 32); tile_selected = true;
int grid_y = static_cast<int>(tile_pos.y / 32); } else if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && blockset_canvas_.IsMouseHovering()) {
int id = grid_x + grid_y * 8; // Secondary detection for direct clicks
tile_selected = true;
}
if (tile_selected && blockset_canvas_.HasValidSelection()) {
auto tile_pos = blockset_canvas_.GetLastClickPosition();
int grid_x = static_cast<int>(tile_pos.x / 32);
int grid_y = static_cast<int>(tile_pos.y / 32);
int id = grid_x + grid_y * 8;
if (id != current_tile16_ && id >= 0 && id < 512) {
current_tile16_ = id;
RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id)); RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id));
show_tile16_editor_ = true; show_tile16_editor_ = true;
util::logf("Selected Tile16: %d (grid: %d,%d)", id, grid_x, grid_y);
} }
if (ImGui::IsItemClicked() && !blockset_canvas_.points().empty()) {
int x = blockset_canvas_.points().front().x / 32;
int y = blockset_canvas_.points().front().y / 32;
current_tile16_ = x + (y * 8);
}
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
} }
blockset_canvas_.DrawGrid();
blockset_canvas_.DrawOverlay();
EndChild(); EndChild();
ImGui::EndGroup(); ImGui::EndGroup();
return absl::OkStatus(); return absl::OkStatus();
@@ -1223,6 +1234,10 @@ absl::Status OverworldEditor::DrawTileSelector() {
status_ = DrawAreaGraphics(); status_ = DrawAreaGraphics();
EndTabItem(); EndTabItem();
} }
if (BeginTabItem("Scratch Space")) {
status_ = DrawScratchSpace();
EndTabItem();
}
EndTabBar(); EndTabBar();
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -2816,4 +2831,426 @@ absl::Status OverworldEditor::UpdateROMVersionMarkers(int target_version) {
return absl::OkStatus(); return absl::OkStatus();
} }
} // namespace yaze::editor // Scratch space canvas methods
absl::Status OverworldEditor::DrawScratchSpace() {
// Slot selector
Text("Scratch Space Slot:");
for (int i = 0; i < 4; i++) {
if (i > 0) SameLine();
bool is_current = (current_scratch_slot_ == i);
if (is_current) PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f));
if (Button(std::to_string(i + 1).c_str(), ImVec2(25, 25))) {
current_scratch_slot_ = i;
}
if (is_current) PopStyleColor();
}
SameLine();
if (Button("Save Selection")) {
RETURN_IF_ERROR(SaveCurrentSelectionToScratch(current_scratch_slot_));
}
SameLine();
if (Button("Load")) {
RETURN_IF_ERROR(LoadScratchToSelection(current_scratch_slot_));
}
SameLine();
if (Button("Clear")) {
RETURN_IF_ERROR(ClearScratchSpace(current_scratch_slot_));
}
Text("Slot %d: %s (%dx%d)", current_scratch_slot_ + 1,
scratch_spaces_[current_scratch_slot_].name.c_str(),
scratch_spaces_[current_scratch_slot_].width,
scratch_spaces_[current_scratch_slot_].height);
Text("Select tiles from Tile16 tab or make selections in overworld, then draw here!");
// Initialize scratch bitmap with proper size based on scratch space dimensions
auto& current_slot = scratch_spaces_[current_scratch_slot_];
if (!current_slot.scratch_bitmap.is_active()) {
// Create bitmap based on scratch space dimensions (each tile is 16x16)
int bitmap_width = current_slot.width * 16;
int bitmap_height = current_slot.height * 16;
std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
current_slot.scratch_bitmap.Create(bitmap_width, bitmap_height, 8, empty_data);
if (all_gfx_loaded_) {
palette_ = overworld_.current_area_palette();
current_slot.scratch_bitmap.SetPalette(palette_);
core::Renderer::Get().RenderBitmap(&current_slot.scratch_bitmap);
}
}
// Draw the scratch space canvas with dynamic sizing
gui::BeginPadding(3);
ImGui::BeginGroup();
// Set proper content size for scrolling based on scratch space dimensions
ImVec2 scratch_content_size(current_slot.width * 16 + 4, current_slot.height * 16 + 4);
gui::BeginChildWithScrollbar("##ScratchSpaceScrollRegion", scratch_content_size);
scratch_canvas_.DrawBackground();
gui::EndPadding();
// Disable context menu for scratch space to allow right-click selection
scratch_canvas_.SetContextMenuEnabled(false);
// Draw the scratch bitmap with proper scaling
if (current_slot.scratch_bitmap.is_active()) {
scratch_canvas_.DrawBitmap(current_slot.scratch_bitmap, 2, 2, 1.0f);
}
// Handle independent scratch space selection system
DrawScratchSpaceSelection();
// Handle drawing in scratch space - independent of main overworld
// Use the currently selected tile from blockset for painting
if (!blockset_canvas_.points().empty() && map_blockset_loaded_) {
// DrawTilemapPainter provides hover preview and painting functionality
if (scratch_canvas_.DrawTilemapPainter(tile16_blockset_, current_tile16_)) {
DrawScratchSpaceEdits();
}
}
// Handle rectangle selection patterns from main overworld as preview/stamp tool
if (ow_map_canvas_.select_rect_active() && !ow_map_canvas_.selected_tiles().empty()) {
// Create tile IDs from the selection
std::vector<int> selected_tile_ids;
overworld_.set_current_world(current_world_);
overworld_.set_current_map(current_map_);
for (const auto& pos : ow_map_canvas_.selected_tiles()) {
selected_tile_ids.push_back(overworld_.GetTileFromPosition(pos));
}
// Show the pattern as a preview that can be stamped
std::vector<int> tile_ids_copy = selected_tile_ids; // Make a copy for DrawBitmapGroup
scratch_canvas_.DrawBitmapGroup(tile_ids_copy, tile16_blockset_, 16, 1.0f);
// Allow stamping the pattern with left click
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && scratch_canvas_.IsMouseHovering()) {
DrawScratchSpacePattern();
}
}
scratch_canvas_.DrawGrid();
scratch_canvas_.DrawOverlay();
EndChild();
ImGui::EndGroup();
return absl::OkStatus();
}
void OverworldEditor::DrawScratchSpaceEdits() {
// Handle painting like the main overworld - continuous drawing
auto mouse_position = scratch_canvas_.drawn_tile_position();
// Calculate tile position accounting for padding and 1:1 scale (16px per tile)
int tile_x = static_cast<int>(mouse_position.x - 2) / 16; // 16px per tile at 1x scale
int tile_y = static_cast<int>(mouse_position.y - 2) / 16;
// Bounds check for 20x30 scratch space (320x480 pixels / 16 = 20x30 tiles)
if (tile_x >= 0 && tile_x < 20 && tile_y >= 0 && tile_y < 30) {
// Update the scratch space with the currently selected tile
if (tile_x < 32 && tile_y < 32) { // Bounds check for our tile_data array
scratch_spaces_[current_scratch_slot_].tile_data[tile_x][tile_y] = current_tile16_;
}
// Update the bitmap immediately for visual feedback
UpdateScratchBitmapTile(tile_x, tile_y, current_tile16_);
// Mark this scratch space as in use
if (!scratch_spaces_[current_scratch_slot_].in_use) {
scratch_spaces_[current_scratch_slot_].in_use = true;
scratch_spaces_[current_scratch_slot_].name = absl::StrFormat("Layout %d", current_scratch_slot_ + 1);
}
}
}
void OverworldEditor::DrawScratchSpacePattern() {
// Handle drawing patterns from overworld selections
auto mouse_position = scratch_canvas_.drawn_tile_position();
int start_tile_x = static_cast<int>(mouse_position.x - 2) / 16;
int start_tile_y = static_cast<int>(mouse_position.y - 2) / 16;
// Get the selected tiles from overworld
overworld_.set_current_world(current_world_);
overworld_.set_current_map(current_map_);
// Calculate pattern dimensions
const auto& selected_tiles = ow_map_canvas_.selected_tiles();
if (selected_tiles.empty()) return;
// Determine pattern size (assume rectangular selection)
int pattern_width = 1;
int pattern_height = static_cast<int>(selected_tiles.size());
// For rectangular selections, try to determine width/height
if (selected_tiles.size() > 1) {
// Simple heuristic - assume square or common rectangular patterns
pattern_width = static_cast<int>(std::sqrt(selected_tiles.size()));
pattern_height = static_cast<int>(selected_tiles.size()) / pattern_width;
}
// Draw the pattern to scratch space (bounds updated for 20x30)
int idx = 0;
for (int py = 0; py < pattern_height && (start_tile_y + py) < 30; ++py) {
for (int px = 0; px < pattern_width && (start_tile_x + px) < 20; ++px) {
if (idx < static_cast<int>(selected_tiles.size())) {
int tile_id = overworld_.GetTileFromPosition(selected_tiles[idx]);
int scratch_x = start_tile_x + px;
int scratch_y = start_tile_y + py;
// Bounds check for tile_data array
if (scratch_x < 32 && scratch_y < 32) {
scratch_spaces_[current_scratch_slot_].tile_data[scratch_x][scratch_y] = tile_id;
}
UpdateScratchBitmapTile(scratch_x, scratch_y, tile_id);
idx++;
}
}
}
}
void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot) {
// Use current slot if not specified
if (slot == -1) slot = current_scratch_slot_;
// Get the tile data from the tile16 blockset
auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
if (tile_data.empty()) return;
auto& scratch_slot = scratch_spaces_[slot];
// Update the scratch bitmap with the new tile (dimensions based on scratch space size)
int scratch_bitmap_width = scratch_slot.width * 16;
for (int y = 0; y < 16; ++y) {
for (int x = 0; x < 16; ++x) {
int src_index = y * 16 + x;
int dst_x = tile_x * 16 + x;
int dst_y = tile_y * 16 + y;
int dst_index = dst_y * scratch_bitmap_width + dst_x;
if (src_index < static_cast<int>(tile_data.size()) &&
dst_index < static_cast<int>(scratch_slot.scratch_bitmap.size()) &&
dst_x < scratch_bitmap_width) {
scratch_slot.scratch_bitmap.WriteToPixel(dst_index, tile_data[src_index]);
}
}
}
scratch_slot.scratch_bitmap.set_modified(true);
core::Renderer::Get().UpdateBitmap(&scratch_slot.scratch_bitmap);
scratch_slot.in_use = true;
}
absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch slot");
}
if (ow_map_canvas_.select_rect_active() && !ow_map_canvas_.selected_tiles().empty()) {
// Calculate actual selection dimensions from overworld rectangle
const auto& selected_points = ow_map_canvas_.selected_points();
if (selected_points.size() >= 2) {
const auto start = selected_points[0];
const auto end = selected_points[1];
// Calculate width and height in tiles
int selection_width = std::abs(static_cast<int>((end.x - start.x) / 16)) + 1;
int selection_height = std::abs(static_cast<int>((end.y - start.y) / 16)) + 1;
// Update scratch space dimensions to match selection
scratch_spaces_[slot].width = std::max(1, std::min(selection_width, 32));
scratch_spaces_[slot].height = std::max(1, std::min(selection_height, 32));
scratch_spaces_[slot].in_use = true;
scratch_spaces_[slot].name = absl::StrFormat("Selection %dx%d",
scratch_spaces_[slot].width,
scratch_spaces_[slot].height);
// Recreate bitmap with new dimensions
int bitmap_width = scratch_spaces_[slot].width * 16;
int bitmap_height = scratch_spaces_[slot].height * 16;
std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
scratch_spaces_[slot].scratch_bitmap.Create(bitmap_width, bitmap_height, 8, empty_data);
if (all_gfx_loaded_) {
palette_ = overworld_.current_area_palette();
scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_);
core::Renderer::Get().RenderBitmap(&scratch_spaces_[slot].scratch_bitmap);
}
// Save selected tiles to scratch data with proper layout
overworld_.set_current_world(current_world_);
overworld_.set_current_map(current_map_);
int idx = 0;
for (int y = 0; y < scratch_spaces_[slot].height && idx < static_cast<int>(ow_map_canvas_.selected_tiles().size()); ++y) {
for (int x = 0; x < scratch_spaces_[slot].width && idx < static_cast<int>(ow_map_canvas_.selected_tiles().size()); ++x) {
if (idx < static_cast<int>(ow_map_canvas_.selected_tiles().size())) {
int tile_id = overworld_.GetTileFromPosition(ow_map_canvas_.selected_tiles()[idx]);
if (x < 32 && y < 32) {
scratch_spaces_[slot].tile_data[x][y] = tile_id;
}
// Update the bitmap immediately
UpdateScratchBitmapTile(x, y, tile_id, slot);
idx++;
}
}
}
}
} else {
// Default single-tile scratch space
scratch_spaces_[slot].width = 16; // Default size
scratch_spaces_[slot].height = 16;
scratch_spaces_[slot].name = absl::StrFormat("Map %d Area", current_map_);
scratch_spaces_[slot].in_use = true;
}
return absl::OkStatus();
}
absl::Status OverworldEditor::LoadScratchToSelection(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch slot");
}
if (!scratch_spaces_[slot].in_use) {
return absl::FailedPreconditionError("Scratch slot is empty");
}
// Placeholder - could restore tiles to current map position
util::logf("Loading scratch slot %d: %s", slot, scratch_spaces_[slot].name.c_str());
return absl::OkStatus();
}
absl::Status OverworldEditor::ClearScratchSpace(int slot) {
if (slot < 0 || slot >= 4) {
return absl::InvalidArgumentError("Invalid scratch slot");
}
scratch_spaces_[slot].in_use = false;
scratch_spaces_[slot].name = "Empty";
// Clear the bitmap
if (scratch_spaces_[slot].scratch_bitmap.is_active()) {
auto& data = scratch_spaces_[slot].scratch_bitmap.mutable_data();
std::fill(data.begin(), data.end(), 0);
scratch_spaces_[slot].scratch_bitmap.set_modified(true);
core::Renderer::Get().UpdateBitmap(&scratch_spaces_[slot].scratch_bitmap);
}
return absl::OkStatus();
}
void OverworldEditor::DrawScratchSpaceSelection() {
const ImGuiIO &io = ImGui::GetIO();
const ImVec2 origin(scratch_canvas_.zero_point().x + scratch_canvas_.scrolling().x,
scratch_canvas_.zero_point().y + scratch_canvas_.scrolling().y);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
static ImVec2 drag_start_pos;
static bool dragging = false;
const float tile_size = 16.0f; // 16px per tile at 1x scale
auto& current_slot = scratch_spaces_[current_scratch_slot_];
// Handle right click for scratch space selection (completely independent)
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && scratch_canvas_.IsMouseHovering()) {
// Clear any existing selection
current_slot.selected_tiles.clear();
current_slot.selected_points.clear();
current_slot.select_rect_active = false;
// Start new selection
ImVec2 aligned_pos;
aligned_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size; // Account for padding
aligned_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
drag_start_pos = aligned_pos;
}
// Handle drag for rectangle selection
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && scratch_canvas_.IsMouseHovering()) {
ImVec2 drag_end_pos;
drag_end_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size;
drag_end_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
// Draw selection rectangle
auto start = ImVec2(scratch_canvas_.zero_point().x + drag_start_pos.x + 2.0f,
scratch_canvas_.zero_point().y + drag_start_pos.y + 2.0f);
auto end = ImVec2(scratch_canvas_.zero_point().x + drag_end_pos.x + tile_size + 2.0f,
scratch_canvas_.zero_point().y + drag_end_pos.y + tile_size + 2.0f);
ImGui::GetWindowDrawList()->AddRect(start, end, IM_COL32(255, 255, 255, 255));
dragging = true;
}
// Handle release to finalize selection
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
dragging = false;
ImVec2 drag_end_pos;
drag_end_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size;
drag_end_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
// Ensure proper ordering
ImVec2 selection_start = drag_start_pos;
ImVec2 selection_end = drag_end_pos;
if (selection_start.x > selection_end.x) std::swap(selection_start.x, selection_end.x);
if (selection_start.y > selection_end.y) std::swap(selection_start.y, selection_end.y);
// Convert to tile coordinates
int start_tile_x = static_cast<int>(selection_start.x) / static_cast<int>(tile_size);
int start_tile_y = static_cast<int>(selection_start.y) / static_cast<int>(tile_size);
int end_tile_x = static_cast<int>(selection_end.x) / static_cast<int>(tile_size);
int end_tile_y = static_cast<int>(selection_end.y) / static_cast<int>(tile_size);
// Store selection data in scratch space
current_slot.selected_tiles.clear();
current_slot.selected_points.clear();
current_slot.selected_points.push_back(selection_start);
current_slot.selected_points.push_back(selection_end);
// Collect tile positions
for (int y = start_tile_y; y <= end_tile_y; ++y) {
for (int x = start_tile_x; x <= end_tile_x; ++x) {
if (x >= 0 && x < 20 && y >= 0 && y < 30) { // Bounds check for 20x30 scratch space
current_slot.selected_tiles.push_back(ImVec2(x, y));
}
}
}
current_slot.select_rect_active = !current_slot.selected_tiles.empty();
// Copy selected tiles to shared clipboard for use in main overworld
if (current_slot.select_rect_active && context_) {
std::vector<int> scratch_tile_ids;
for (const auto& tile_pos : current_slot.selected_tiles) {
int x = static_cast<int>(tile_pos.x);
int y = static_cast<int>(tile_pos.y);
if (x < 32 && y < 32) { // Bounds check for tile_data array
int tile_id = current_slot.tile_data[x][y];
scratch_tile_ids.push_back(tile_id);
}
}
int width = end_tile_x - start_tile_x + 1;
int height = end_tile_y - start_tile_y + 1;
context_->shared_clipboard.overworld_tile16_ids = std::move(scratch_tile_ids);
context_->shared_clipboard.overworld_width = width;
context_->shared_clipboard.overworld_height = height;
context_->shared_clipboard.has_overworld_tile16 = true;
}
}
// Draw scratch space selection overlay (independent)
if (current_slot.select_rect_active && current_slot.selected_points.size() >= 2) {
ImVec2 start = current_slot.selected_points[0];
ImVec2 end = current_slot.selected_points[1];
// Draw selection rectangle on scratch canvas
auto rect_start = ImVec2(origin.x + start.x + 2.0f, origin.y + start.y + 2.0f);
auto rect_end = ImVec2(origin.x + end.x + tile_size + 2.0f, origin.y + end.y + tile_size + 2.0f);
ImGui::GetWindowDrawList()->AddRect(rect_start, rect_end, IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
}
}
} // namespace yaze::editor

View File

@@ -188,6 +188,16 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
void DrawMapPropertiesPanel(); void DrawMapPropertiesPanel();
void HandleMapInteraction(); void HandleMapInteraction();
void SetupOverworldCanvasContextMenu(); void SetupOverworldCanvasContextMenu();
// Scratch space canvas methods
absl::Status DrawScratchSpace();
absl::Status SaveCurrentSelectionToScratch(int slot);
absl::Status LoadScratchToSelection(int slot);
absl::Status ClearScratchSpace(int slot);
void DrawScratchSpaceEdits();
void DrawScratchSpacePattern();
void DrawScratchSpaceSelection();
void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot = -1);
absl::Status UpdateUsageStats(); absl::Status UpdateUsageStats();
void DrawUsageGrid(); void DrawUsageGrid();
@@ -253,6 +263,23 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
// Map properties system for UI organization // Map properties system for UI organization
std::unique_ptr<MapPropertiesSystem> map_properties_system_; std::unique_ptr<MapPropertiesSystem> map_properties_system_;
// Scratch space for large layouts
// Scratch space canvas for tile16 drawing (like a mini overworld)
struct ScratchSpaceSlot {
gfx::Bitmap scratch_bitmap;
std::array<std::array<int, 32>, 32> tile_data; // 32x32 grid of tile16 IDs
bool in_use = false;
std::string name = "Empty";
int width = 16; // Default 16x16 tiles
int height = 16;
// Independent selection system for scratch space
std::vector<ImVec2> selected_tiles;
std::vector<ImVec2> selected_points;
bool select_rect_active = false;
};
std::array<ScratchSpaceSlot, 4> scratch_spaces_;
int current_scratch_slot_ = 0;
gfx::Tilemap tile16_blockset_; gfx::Tilemap tile16_blockset_;
@@ -295,6 +322,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize, gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
gui::CanvasGridSize::k16x16}; gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_; gui::Canvas properties_canvas_;
gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), gui::CanvasGridSize::k32x32};
gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags}; gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags};
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags, gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
#define YAZE_APP_EDITOR_TILE16EDITOR_H #define YAZE_APP_EDITOR_TILE16EDITOR_H
#include <array> #include <array>
#include <chrono>
#include <functional>
#include <vector> #include <vector>
#include "absl/status/status.h" #include "absl/status/status.h"
@@ -19,29 +21,43 @@
namespace yaze { namespace yaze {
namespace editor { namespace editor {
// Constants for tile editing
constexpr int kTile16Size = 16;
constexpr int kTile8Size = 8;
constexpr int kTilesheetEditorWidth = 0x100;
constexpr int kTilesheetEditorHeight = 0x4000;
constexpr int kTile16CanvasSize = 0x20;
constexpr int kTile8CanvasHeight = 0x175;
constexpr int kNumScratchSlots = 4;
constexpr int kNumPalettes = 8;
constexpr int kTile8PixelCount = 64;
constexpr int kTile16PixelCount = 256;
/** /**
* @brief Popup window to edit Tile16 data * @brief Popup window to edit Tile16 data
*/ */
class Tile16Editor : public gfx::GfxContext { class Tile16Editor : public gfx::GfxContext {
public: public:
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset) Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
: rom_(rom), tile16_blockset_(tile16_blockset) {} : rom_(rom), tile16_blockset_(tile16_blockset) {}
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
const gfx::Bitmap &current_gfx_bmp, const gfx::Bitmap& current_gfx_bmp,
std::array<uint8_t, 0x200> &all_tiles_types); std::array<uint8_t, 0x200>& all_tiles_types);
absl::Status Update(); absl::Status Update();
void DrawTile16Editor(); void DrawTile16Editor();
absl::Status UpdateTile16Transfer();
absl::Status UpdateBlockset(); absl::Status UpdateBlockset();
// Scratch space for tile16 layouts
void DrawScratchSpace();
absl::Status SaveLayoutToScratch(int slot);
absl::Status LoadLayoutFromScratch(int slot);
absl::Status DrawToCurrentTile16(ImVec2 pos); absl::Status DrawToCurrentTile16(ImVec2 pos);
absl::Status UpdateTile16Edit(); absl::Status UpdateTile16Edit();
absl::Status UpdateTransferTileCanvas();
absl::Status LoadTile8(); absl::Status LoadTile8();
absl::Status SetCurrentTile(int id); absl::Status SetCurrentTile(int id);
@@ -53,14 +69,61 @@ class Tile16Editor : public gfx::GfxContext {
absl::Status LoadTile16FromScratchSpace(int slot); absl::Status LoadTile16FromScratchSpace(int slot);
absl::Status ClearScratchSpace(int slot); absl::Status ClearScratchSpace(int slot);
void set_rom(Rom *rom) { rom_ = rom; } // Advanced editing features
Rom *rom() const { return rom_; } absl::Status FlipTile16Horizontal();
absl::Status FlipTile16Vertical();
absl::Status RotateTile16();
absl::Status FillTile16WithTile8(int tile8_id);
absl::Status AutoTileTile16();
absl::Status ClearTile16();
// Palette management
absl::Status CyclePalette(bool forward = true);
absl::Status ApplyPaletteToAll(uint8_t palette_id);
absl::Status PreviewPaletteChange(uint8_t palette_id);
// Batch operations
absl::Status ApplyToSelection(const std::function<void(int)>& operation);
absl::Status BatchEdit(const std::vector<int>& tile_ids,
const std::function<void(int)>& operation);
// History and undo system
absl::Status Undo();
absl::Status Redo();
void SaveUndoState();
// Live preview system
void EnableLivePreview(bool enable) { live_preview_enabled_ = enable; }
absl::Status UpdateLivePreview();
// Validation and integrity checks
absl::Status ValidateTile16Data();
bool IsTile16Valid(int tile_id) const;
// Integration with overworld system
absl::Status SaveTile16ToROM();
absl::Status UpdateOverworldTilemap();
absl::Status CommitChangesToBlockset();
// Helper methods for palette management
absl::Status UpdateTile8Palette(int tile8_id);
absl::Status RefreshAllPalettes();
void DrawPaletteSettings();
// ROM data access and modification
absl::Status UpdateROMTile16Data();
absl::Status RefreshTile16Blockset();
gfx::Tile16* GetCurrentTile16Data();
// Manual tile8 input controls
void DrawManualTile8Inputs();
void set_rom(Rom* rom) { rom_ = rom; }
Rom* rom() const { return rom_; }
private: private:
Rom *rom_ = nullptr; Rom* rom_ = nullptr;
bool map_blockset_loaded_ = false; bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
bool transfer_blockset_loaded_ = false;
bool x_flip = false; bool x_flip = false;
bool y_flip = false; bool y_flip = false;
bool priority_tile = false; bool priority_tile = false;
@@ -78,19 +141,67 @@ class Tile16Editor : public gfx::GfxContext {
std::array<gfx::Bitmap, 4> scratch_space_; std::array<gfx::Bitmap, 4> scratch_space_;
std::array<bool, 4> scratch_space_used_ = {false, false, false, false}; std::array<bool, 4> scratch_space_used_ = {false, false, false, false};
// Layout scratch space for tile16 arrangements (4 slots of 8x8 grids)
struct LayoutScratch {
std::array<std::array<int, 8>, 8> tile_layout; // 8x8 grid of tile16 IDs
bool in_use = false;
std::string name = "Empty";
};
std::array<LayoutScratch, 4> layout_scratch_;
// Undo/Redo system
struct UndoState {
int tile_id;
gfx::Bitmap tile_bitmap;
gfx::Tile16 tile_data;
uint8_t palette;
bool x_flip, y_flip, priority;
};
std::vector<UndoState> undo_stack_;
std::vector<UndoState> redo_stack_;
static constexpr size_t kMaxUndoStates_ = 50;
// Live preview system
bool live_preview_enabled_ = true;
gfx::Bitmap preview_tile16_;
bool preview_dirty_ = false;
// Selection system
std::vector<int> selected_tiles_;
int selection_start_tile_ = -1;
bool multi_select_mode_ = false;
// Advanced editing state
bool auto_tile_mode_ = false;
bool grid_snap_enabled_ = true;
bool show_tile_info_ = true;
bool show_palette_preview_ = true;
// Palette management settings
bool show_palette_settings_ = false;
int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc.
uint8_t palette_normalization_mask_ = 0x0F; // Default 4-bit mask
bool auto_normalize_pixels_ = true;
// Performance tracking
std::chrono::steady_clock::time_point last_edit_time_;
bool batch_mode_ = false;
util::NotifyValue<uint32_t> notify_tile16; util::NotifyValue<uint32_t> notify_tile16;
util::NotifyValue<uint8_t> notify_palette; util::NotifyValue<uint8_t> notify_palette;
std::array<uint8_t, 0x200> all_tiles_types_; std::array<uint8_t, 0x200> all_tiles_types_;
// Tile16 blockset for selecting the tile to edit // Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000), gui::Canvas blockset_canvas_{
gui::CanvasGridSize::k32x32,}; "blocksetCanvas", ImVec2(kTilesheetEditorWidth, kTilesheetEditorHeight),
gui::CanvasGridSize::k32x32};
gfx::Bitmap tile16_blockset_bmp_; gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile // Canvas for editing the selected tile - smaller size for table fit
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40), gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas",
gui::CanvasGridSize::k64x64}; ImVec2(128, 128), // Reduced from kTile16CanvasSize to fit tables
gui::CanvasGridSize::k64x64, 2.0f}; // Reduced scale to fit tables
gfx::Bitmap current_tile16_bmp_; gfx::Bitmap current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_ // Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
@@ -100,23 +211,15 @@ class Tile16Editor : public gfx::GfxContext {
gui::CanvasGridSize::k32x32}; gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_; gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders}; gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders};
gfx::Tilemap *tile16_blockset_ = nullptr; gfx::Tilemap* tile16_blockset_ = nullptr;
std::vector<gfx::Bitmap> current_gfx_individual_; std::vector<gfx::Bitmap> current_gfx_individual_;
PaletteEditor palette_editor_; PaletteEditor palette_editor_;
gfx::SnesPalette palette_; gfx::SnesPalette palette_;
absl::Status status_; absl::Status status_;
Rom *transfer_rom_ = nullptr;
zelda3::Overworld transfer_overworld_{transfer_rom_};
std::array<gfx::Bitmap, kNumGfxSheets> transfer_gfx_;
absl::Status transfer_status_;
}; };
} // namespace editor } // namespace editor

View File

@@ -7,6 +7,8 @@
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/gui/color.h" #include "app/gui/color.h"
#include "app/gui/style.h" #include "app/gui/style.h"
#include "app/gui/canvas_utils.h"
#include "util/log.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
#include "imgui_memory_editor.h" #include "imgui_memory_editor.h"
@@ -45,10 +47,135 @@ ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
} }
} // namespace } // namespace
// Canvas class implementation begins here
void Canvas::InitializeDefaults() {
// Initialize configuration with sensible defaults
config_.enable_grid = true;
config_.enable_hex_labels = false;
config_.enable_custom_labels = false;
config_.enable_context_menu = true;
config_.is_draggable = false;
config_.grid_step = 32.0f;
config_.global_scale = 1.0f;
config_.canvas_size = ImVec2(0, 0);
config_.custom_canvas_size = false;
// Initialize selection state
selection_.Clear();
// Initialize palette editor
palette_editor_ = std::make_unique<EnhancedPaletteEditor>();
// Initialize legacy compatibility variables to match config
enable_grid_ = config_.enable_grid;
enable_hex_tile_labels_ = config_.enable_hex_labels;
enable_custom_labels_ = config_.enable_custom_labels;
enable_context_menu_ = config_.enable_context_menu;
draggable_ = config_.is_draggable;
custom_step_ = config_.grid_step;
global_scale_ = config_.global_scale;
custom_canvas_size_ = config_.custom_canvas_size;
select_rect_active_ = selection_.select_rect_active;
selected_tile_pos_ = selection_.selected_tile_pos;
}
void Canvas::Cleanup() {
palette_editor_.reset();
selection_.Clear();
}
void Canvas::InitializePaletteEditor(Rom* rom) {
rom_ = rom;
if (palette_editor_) {
palette_editor_->Initialize(rom);
}
}
void Canvas::ShowPaletteEditor() {
if (palette_editor_ && bitmap_) {
auto mutable_palette = bitmap_->mutable_palette();
palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor");
}
}
void Canvas::ShowColorAnalysis() {
if (palette_editor_ && bitmap_) {
palette_editor_->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
}
}
bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
if (palette_editor_ && bitmap_) {
return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index);
}
return false;
}
// Size reporting methods for table integration
ImVec2 Canvas::GetMinimumSize() const {
return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale);
}
ImVec2 Canvas::GetPreferredSize() const {
return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale);
}
void Canvas::ReserveTableSpace(const std::string& label) {
ImVec2 size = config_.auto_resize ? GetPreferredSize() : config_.canvas_size;
CanvasUtils::ReserveCanvasSpace(size, label);
}
bool Canvas::BeginTableCanvas(const std::string& label) {
if (config_.auto_resize) {
ImVec2 preferred_size = GetPreferredSize();
CanvasUtils::SetNextCanvasSize(preferred_size, true);
}
// Begin child window that properly reports size to tables
std::string child_id = canvas_id_ + "_TableChild";
ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
bool result = ImGui::BeginChild(child_id.c_str(), child_size,
true, // Always show border for table integration
ImGuiWindowFlags_AlwaysVerticalScrollbar);
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
return result;
}
void Canvas::EndTableCanvas() {
ImGui::EndChild();
}
// Improved interaction detection methods
bool Canvas::HasValidSelection() const {
return !points_.empty() && points_.size() >= 2;
}
bool Canvas::WasClicked(ImGuiMouseButton button) const {
return ImGui::IsItemClicked(button) && HasValidSelection();
}
bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection();
}
ImVec2 Canvas::GetLastClickPosition() const {
if (HasValidSelection()) {
return points_[0]; // Return the first point of the selection
}
return ImVec2(-1, -1); // Invalid position
}
void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, const std::function<void()> &event,
int tile_size, float scale) { int tile_size, float scale) {
global_scale_ = scale; config_.global_scale = scale;
global_scale_ = scale; // Legacy compatibility
DrawBackground(); DrawBackground();
DrawContextMenu(); DrawContextMenu();
DrawBitmap(bitmap, 2, scale); DrawBitmap(bitmap, 2, scale);
@@ -60,7 +187,8 @@ void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
} }
void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) { void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
enable_custom_labels_ = true; config_.enable_custom_labels = true;
enable_custom_labels_ = true; // Legacy compatibility
DrawBackground(bg_size); DrawBackground(bg_size);
DrawInfoGrid(grid_size, 8, label_id); DrawInfoGrid(grid_size, 8, label_id);
DrawOverlay(); DrawOverlay();
@@ -69,21 +197,27 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
void Canvas::DrawBackground(ImVec2 canvas_size) { void Canvas::DrawBackground(ImVec2 canvas_size) {
draw_list_ = GetWindowDrawList(); draw_list_ = GetWindowDrawList();
canvas_p0_ = GetCursorScreenPos(); canvas_p0_ = GetCursorScreenPos();
if (!custom_canvas_size_) canvas_sz_ = GetContentRegionAvail();
if (canvas_size.x != 0) canvas_sz_ = canvas_size; // Calculate canvas size using utility function
canvas_p1_ = ImVec2(canvas_p0_.x + (canvas_sz_.x * global_scale_), ImVec2 content_region = GetContentRegionAvail();
canvas_p0_.y + (canvas_sz_.y * global_scale_)); canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size);
if (canvas_size.x != 0) {
canvas_sz_ = canvas_size;
config_.canvas_size = canvas_size;
}
// Calculate scaled canvas bounds
ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale);
canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y);
// Draw border and background color // Draw border and background color
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor); draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
ImGui::InvisibleButton( ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags);
canvas_id_.c_str(),
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_),
kMouseFlags);
if (draggable_ && IsItemHovered()) { if (config_.is_draggable && IsItemHovered()) {
const ImGuiIO &io = GetIO(); const ImGuiIO &io = GetIO();
const bool is_active = IsItemActive(); // Held const bool is_active = IsItemActive(); // Held
const ImVec2 origin(canvas_p0_.x + scrolling_.x, const ImVec2 origin(canvas_p0_.x + scrolling_.x,
@@ -140,14 +274,20 @@ void Canvas::DrawContextMenu() {
if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) { if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) {
SetZoomToFit(*bitmap_); SetZoomToFit(*bitmap_);
} }
if (MenuItem("Advanced Properties", nullptr, false)) {
ImGui::OpenPopup("Advanced Canvas Properties");
}
ImGui::Separator(); ImGui::Separator();
MenuItem("Show Grid", nullptr, &enable_grid_); MenuItem("Show Grid", nullptr, &enable_grid_);
Selectable("Show Position Labels", &enable_hex_tile_labels_); Selectable("Show Position Labels", &enable_hex_tile_labels_);
if (MenuItem("Bitmap Properties", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Bitmap Properties");
}
if (MenuItem("Edit Palette", nullptr, false) && bitmap_) { if (MenuItem("Edit Palette", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Palette Editor"); ShowPaletteEditor();
}
if (MenuItem("Color Analysis", nullptr, false) && bitmap_) {
ShowColorAnalysis();
}
if (MenuItem("Scaling Controls", nullptr, false)) {
ImGui::OpenPopup("Scaling Controls");
} }
if (BeginMenu("Canvas Properties")) { if (BeginMenu("Canvas Properties")) {
Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y); Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
@@ -178,38 +318,78 @@ void Canvas::DrawContextMenu() {
EndMenu(); EndMenu();
} }
if (BeginMenu("Change Palette")) { if (BeginMenu("ROM Palette Selection") && rom_) {
Text("Work in progress"); Text("Select ROM Palette Group:");
// TODO: Get ROM data for change palette
// gui::TextWithSeparators("ROM Palette"); // Enhanced ROM palette group selection
// ImGui::SetNextItemWidth(100.f); if (palette_editor_) {
// ImGui::Combo("Palette Group", (int *)&edit_palette_group_name_index_, // Use our enhanced palette editor's ROM selection
// gfx::kPaletteGroupAddressesKeys, if (MenuItem("Open Enhanced Palette Manager")) {
// IM_ARRAYSIZE(gfx::kPaletteGroupAddressesKeys)); palette_editor_->ShowROMPaletteManager();
// ImGui::SetNextItemWidth(100.f); }
// gui::InputHexWord("Palette Group Index", &edit_palette_index_);
ImGui::Separator();
// auto palette_group = rom()->mutable_palette_group()->get_group(
// gfx::kPaletteGroupAddressesKeys[edit_palette_group_name_index_]); // Quick palette group selection
// auto palette = palette_group->mutable_palette(edit_palette_index_); const char* palette_groups[] = {
"Overworld Main", "Overworld Aux", "Overworld Animated",
// if (ImGui::BeginChild("Palette", ImVec2(0, 300), true)) { "Dungeon Main", "Global Sprites", "Armor", "Swords"
// gui::SelectablePalettePipeline(edit_palette_sub_index_, };
// refresh_graphics_, *palette);
if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_,
// if (refresh_graphics_) { palette_groups, IM_ARRAYSIZE(palette_groups))) {
// bitmap_->SetPaletteWithTransparent(*palette, // Group selection changed
// edit_palette_sub_index_); }
// Renderer::Get().UpdateBitmap(bitmap_);
// refresh_graphics_ = false; ImGui::SetNextItemWidth(100.f);
// } if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) {
// ImGui::EndChild(); // Palette index changed
// } }
// Apply button with enhanced functionality
if (ImGui::Button("Apply to Canvas") && bitmap_) {
if (palette_editor_->ApplyROMPalette(bitmap_,
edit_palette_group_name_index_,
edit_palette_index_)) {
util::logf("Applied ROM palette group %d, index %d via context menu",
edit_palette_group_name_index_, edit_palette_index_);
}
}
// Direct palette editing with SelectablePalettePipeline
if (ImGui::TreeNode("Interactive Palette Editor")) {
if (rom_ && bitmap_) {
ImGui::Text("Interactive ROM Palette Editing");
ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]);
// Get the enhanced palette editor's ROM palette if available
if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) {
auto editable_palette = const_cast<gfx::SnesPalette&>(*rom_palette);
if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) {
// Use the existing SelectablePalettePipeline for interactive editing
gui::SelectablePalettePipeline(edit_palette_sub_index_,
refresh_graphics_, editable_palette);
if (refresh_graphics_) {
bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_);
Renderer::Get().UpdateBitmap(bitmap_);
refresh_graphics_ = false;
util::logf("Applied interactive palette changes to canvas");
}
ImGui::EndChild();
}
} else {
ImGui::Text("Load ROM palettes first using Enhanced Palette Manager");
}
}
ImGui::TreePop();
}
}
EndMenu(); EndMenu();
} }
if (BeginMenu("View Palette")) { if (BeginMenu("View Palette")) {
DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, (void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8);
8);
EndMenu(); EndMenu();
} }
EndMenu(); EndMenu();
@@ -235,11 +415,9 @@ void Canvas::DrawContextMenu() {
ImGui::EndPopup(); ImGui::EndPopup();
} }
// Draw enhanced property dialogs // Draw enhanced property dialogs
if (bitmap_) { ShowAdvancedCanvasProperties();
ShowBitmapProperties(*bitmap_); ShowScalingControls();
ShowPaletteEditor(*bitmap_->mutable_palette());
}
} }
void Canvas::DrawContextMenuItem(const ContextMenuItem& item) { void Canvas::DrawContextMenuItem(const ContextMenuItem& item) {
@@ -275,66 +453,7 @@ void Canvas::ClearContextMenuItems() {
context_menu_items_.clear(); context_menu_items_.clear();
} }
void Canvas::ShowBitmapProperties(const gfx::Bitmap& bitmap) { // Old ShowPaletteEditor method removed - now handled by EnhancedPaletteEditor
if (ImGui::BeginPopupModal("Bitmap Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Bitmap Information");
ImGui::Separator();
ImGui::Text("Size: %d x %d", bitmap.width(), bitmap.height());
ImGui::Text("Depth: %d bits", bitmap.depth());
ImGui::Text("Data Size: %zu bytes", bitmap.size());
ImGui::Text("Active: %s", bitmap.is_active() ? "Yes" : "No");
ImGui::Text("Modified: %s", bitmap.modified() ? "Yes" : "No");
if (bitmap.surface()) {
ImGui::Separator();
ImGui::Text("SDL Surface");
ImGui::Text("Pitch: %d", bitmap.surface()->pitch);
ImGui::Text("Bits Per Pixel: %d", bitmap.surface()->format->BitsPerPixel);
ImGui::Text("Bytes Per Pixel: %d", bitmap.surface()->format->BytesPerPixel);
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void Canvas::ShowPaletteEditor(gfx::SnesPalette& palette) {
if (ImGui::BeginPopupModal("Palette Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Palette Editor");
ImGui::Separator();
// Display palette colors in a grid
int cols = 8;
for (int i = 0; i < palette.size(); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
if (ImGui::ColorButton("##color", display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
// Color selected - could open detailed editor
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d: 0x%04X\nR:%d G:%d B:%d",
i, color.snes(),
(int)(display_color.x * 255),
(int)(display_color.y * 255),
(int)(display_color.z * 255));
}
ImGui::PopID();
}
ImGui::Separator();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) { void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
if (!bitmap.is_active()) return; if (!bitmap.is_active()) return;
@@ -342,17 +461,20 @@ void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
ImVec2 available = ImGui::GetContentRegionAvail(); ImVec2 available = ImGui::GetContentRegionAvail();
float scale_x = available.x / bitmap.width(); float scale_x = available.x / bitmap.width();
float scale_y = available.y / bitmap.height(); float scale_y = available.y / bitmap.height();
global_scale_ = std::min(scale_x, scale_y); config_.global_scale = std::min(scale_x, scale_y);
// Ensure minimum readable scale // Ensure minimum readable scale
if (global_scale_ < 0.25f) global_scale_ = 0.25f; if (config_.global_scale < 0.25f) config_.global_scale = 0.25f;
global_scale_ = config_.global_scale; // Legacy compatibility
// Center the view // Center the view
scrolling_ = ImVec2(0, 0); scrolling_ = ImVec2(0, 0);
} }
void Canvas::ResetView() { void Canvas::ResetView() {
global_scale_ = 1.0f; config_.global_scale = 1.0f;
global_scale_ = 1.0f; // Legacy compatibility
scrolling_ = ImVec2(0, 0); scrolling_ = ImVec2(0, 0);
} }
@@ -656,6 +778,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) {
return; return;
} }
bitmap_ = &bitmap; bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(), draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x, canvas_p0_.y), ImVec2(canvas_p0_.x, canvas_p0_.y),
ImVec2(canvas_p0_.x + (bitmap.width() * scale), ImVec2(canvas_p0_.x + (bitmap.width() * scale),
@@ -669,14 +795,24 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale,
return; return;
} }
bitmap_ = &bitmap; bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
// Calculate the actual rendered size including scale and offsets
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
draw_list_->AddImage( draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(), (ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x, ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
canvas_p0_.y + y_offset + scrolling_.y), canvas_p0_.y + y_offset + scrolling_.y),
ImVec2( ImVec2(
canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale), canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x,
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)), canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y),
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
// Note: Content size for child windows should be set before BeginChild, not here
} }
void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
@@ -685,6 +821,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
return; return;
} }
bitmap_ = &bitmap; bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage( draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(), (ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y), ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
@@ -710,28 +850,15 @@ void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) {
} }
void Canvas::DrawOutline(int x, int y, int w, int h) { void Canvas::DrawOutline(int x, int y, int w, int h) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x, CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200));
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, kOutlineRect, 0, 0, 1.5f);
} }
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x, CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
} }
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
ImVec2 origin(canvas_p0_.x + scrolling_.x + x, CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
canvas_p0_.y + scrolling_.y + y);
ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
canvas_p0_.y + scrolling_.y + y + h);
draw_list_->AddRect(origin, size, color);
} }
void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap, void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
@@ -840,50 +967,15 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
} }
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
// Apply global scale to position and size CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale);
float scaled_x = x * global_scale_;
float scaled_y = y * global_scale_;
float scaled_w = w * global_scale_;
float scaled_h = h * global_scale_;
ImVec2 origin(canvas_p0_.x + scrolling_.x + scaled_x,
canvas_p0_.y + scrolling_.y + scaled_y);
ImVec2 size(canvas_p0_.x + scrolling_.x + scaled_x + scaled_w,
canvas_p0_.y + scrolling_.y + scaled_y + scaled_h);
draw_list_->AddRectFilled(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list_->AddRect(outline_origin, outline_size, kBlackColor);
} }
void Canvas::DrawText(std::string text, int x, int y) { void Canvas::DrawText(std::string text, int x, int y) {
// Apply global scale to text position CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale);
float scaled_x = x * global_scale_;
float scaled_y = y * global_scale_;
draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + scaled_x + 1,
canvas_p0_.y + scrolling_.y + scaled_y + 1),
kBlackColor, text.data());
draw_list_->AddText(
ImVec2(canvas_p0_.x + scrolling_.x + scaled_x, canvas_p0_.y + scrolling_.y + scaled_y),
kWhiteColor, text.data());
} }
void Canvas::DrawGridLines(float grid_step) { void Canvas::DrawGridLines(float grid_step) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50); CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale);
const float grid_thickness = 0.5f;
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
ImVec2(canvas_p0_.x + x, canvas_p1_.y), grid_color,
grid_thickness);
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step)
draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
ImVec2(canvas_p1_.x, canvas_p0_.y + y), grid_color,
grid_thickness);
} }
void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) { void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
@@ -923,91 +1015,50 @@ void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
} }
void Canvas::DrawCustomHighlight(float grid_step) { void Canvas::DrawCustomHighlight(float grid_step) {
if (highlight_tile_id != -1) { CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step);
int tile_x = highlight_tile_id % 8;
int tile_y = highlight_tile_id / 8;
ImVec2 tile_pos(canvas_p0_.x + scrolling_.x + tile_x * grid_step,
canvas_p0_.y + scrolling_.y + tile_y * grid_step);
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
draw_list_->AddRectFilled(tile_pos, tile_pos_end,
IM_COL32(255, 0, 255, 255));
}
} }
void Canvas::DrawGrid(float grid_step, int tile_id_offset) { void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
// Draw grid + all lines in the canvas if (config_.grid_step != 0.f) grid_step = config_.grid_step;
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
if (enable_grid_) { // Create render context for utilities
if (custom_step_ != 0.f) grid_step = custom_step_; CanvasUtils::CanvasRenderContext ctx = {
grid_step *= global_scale_; // Apply global scale to grid step .draw_list = draw_list_,
.canvas_p0 = canvas_p0_,
DrawGridLines(grid_step); .canvas_p1 = canvas_p1_,
DrawCustomHighlight(grid_step); .scrolling = scrolling_,
.global_scale = config_.global_scale,
if (enable_hex_tile_labels_) { .enable_grid = config_.enable_grid,
// Draw the hex ID of the tile in the center of the tile square .enable_hex_labels = config_.enable_hex_labels,
for (float x = fmodf(scrolling_.x, grid_step); .grid_step = grid_step
x < canvas_sz_.x * global_scale_; x += grid_step) { };
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) { // Use high-level utility function
int tile_x = (x - scrolling_.x) / grid_step; CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * 16); // Draw custom labels if enabled
std::string hex_id = absl::StrFormat("%02X", tile_id); if (config_.enable_custom_labels) {
draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 4, draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
canvas_p0_.y + y + (grid_step / 2) - 4), CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset);
kWhiteColor, hex_id.data()); draw_list_->PopClipRect();
}
}
}
if (!enable_custom_labels_) {
return;
}
// Draw the contents of labels on the grid
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step) {
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) {
int tile_x = (x - scrolling_.x) / grid_step;
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels_[current_labels_].size()) {
break;
}
std::string label = labels_[current_labels_][tile_id];
draw_list_->AddText(
ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
kWhiteColor, label.data());
}
}
} }
} }
void Canvas::DrawOverlay() { void Canvas::DrawOverlay() {
const ImVec2 origin(canvas_p0_.x + scrolling_.x, // Create render context for utilities
canvas_p0_.y + scrolling_.y); // Lock scrolled origin CanvasUtils::CanvasRenderContext ctx = {
for (int n = 0; n < points_.Size; n += 2) { .draw_list = draw_list_,
draw_list_->AddRect( .canvas_p0 = canvas_p0_,
ImVec2(origin.x + points_[n].x, origin.y + points_[n].y), .canvas_p1 = canvas_p1_,
ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y), .scrolling = scrolling_,
kWhiteColor, 1.0f); .global_scale = config_.global_scale,
} .enable_grid = config_.enable_grid,
.enable_hex_labels = config_.enable_hex_labels,
if (!selected_points_.empty()) { .grid_step = config_.grid_step
for (int n = 0; n < selected_points_.size(); n += 2) { };
draw_list_->AddRect(ImVec2(origin.x + selected_points_[n].x,
origin.y + selected_points_[n].y), // Use high-level utility function
ImVec2(origin.x + selected_points_[n + 1].x + 0x10, CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
origin.y + selected_points_[n + 1].y + 0x10),
kWhiteColor, 1.0f);
}
}
draw_list_->PopClipRect();
} }
void Canvas::DrawLayeredElements() { void Canvas::DrawLayeredElements() {
@@ -1053,7 +1104,20 @@ void Canvas::DrawLayeredElements() {
void BeginCanvas(Canvas &canvas, ImVec2 child_size) { void BeginCanvas(Canvas &canvas, ImVec2 child_size) {
gui::BeginPadding(1); gui::BeginPadding(1);
ImGui::BeginChild(canvas.canvas_id().c_str(), child_size, true);
// Use improved canvas sizing for table integration
ImVec2 effective_size = child_size;
if (child_size.x == 0 && child_size.y == 0) {
// Auto-size based on canvas configuration
if (canvas.IsAutoResize()) {
effective_size = canvas.GetPreferredSize();
} else {
effective_size = canvas.GetCurrentSize();
}
}
ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
canvas.DrawBackground(); canvas.DrawBackground();
gui::EndPadding(); gui::EndPadding();
canvas.DrawContextMenu(); canvas.DrawContextMenu();
@@ -1122,4 +1186,208 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
} }
} }
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
const std::string& label, bool auto_resize) {
// Configure canvas for table integration
canvas.SetAutoResize(auto_resize);
if (auto_resize && bitmap.is_active()) {
// Auto-calculate size based on bitmap content
ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height());
ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale());
canvas.SetCanvasSize(preferred_size);
}
// Begin table-aware canvas
if (canvas.BeginTableCanvas(label)) {
// Draw the canvas content
canvas.DrawBackground();
canvas.DrawContextMenu();
if (bitmap.is_active()) {
canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale());
}
canvas.DrawGrid();
canvas.DrawOverlay();
}
canvas.EndTableCanvas();
}
void Canvas::ShowAdvancedCanvasProperties() {
if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Advanced Canvas Configuration");
ImGui::Separator();
// Canvas properties (read-only info)
ImGui::Text("Canvas Properties");
ImGui::Text("ID: %s", canvas_id_.c_str());
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y);
ImGui::Text("Global Scale: %.3f", config_.global_scale);
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (config_.content_size.x > 0 && config_.content_size.y > 0) {
ImVec2 min_size = GetMinimumSize();
ImVec2 preferred_size = GetPreferredSize();
ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y);
}
// Editable properties using new config system
ImGui::Separator();
ImGui::Text("View Settings");
if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) {
enable_grid_ = config_.enable_grid; // Legacy sync
}
if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) {
enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) {
enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) {
enable_context_menu_ = config_.enable_context_menu; // Legacy sync
}
if (ImGui::Checkbox("Draggable", &config_.is_draggable)) {
draggable_ = config_.is_draggable; // Legacy sync
}
if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) {
// Auto resize setting changed
}
// Grid controls
ImGui::Separator();
ImGui::Text("Grid Configuration");
if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Scale controls
ImGui::Separator();
ImGui::Text("Scale Configuration");
if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Scrolling controls
ImGui::Separator();
ImGui::Text("Scrolling Configuration");
ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y);
if (ImGui::Button("Reset Scroll")) {
scrolling_ = ImVec2(0, 0);
}
ImGui::SameLine();
if (ImGui::Button("Center View")) {
if (bitmap_) {
scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f,
-(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f);
}
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor
void Canvas::ShowScalingControls() {
if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Canvas Scaling and Display Controls");
ImGui::Separator();
// Global scale with new config system
ImGui::Text("Global Scale: %.3f", config_.global_scale);
if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Preset scale buttons
ImGui::Text("Preset Scales:");
if (ImGui::Button("0.25x")) {
config_.global_scale = 0.25f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("0.5x")) {
config_.global_scale = 0.5f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("1x")) {
config_.global_scale = 1.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("2x")) {
config_.global_scale = 2.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("4x")) {
config_.global_scale = 4.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("8x")) {
config_.global_scale = 8.0f;
global_scale_ = config_.global_scale;
}
// Grid configuration
ImGui::Separator();
ImGui::Text("Grid Configuration");
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Grid size presets
ImGui::Text("Grid Presets:");
if (ImGui::Button("8x8")) {
config_.grid_step = 8.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("16x16")) {
config_.grid_step = 16.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("32x32")) {
config_.grid_step = 32.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("64x64")) {
config_.grid_step = 64.0f;
custom_step_ = config_.grid_step;
}
// Canvas size info
ImGui::Separator();
ImGui::Text("Canvas Information");
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Scaled Size: %.0f x %.0f",
config_.canvas_size.x * config_.global_scale,
config_.canvas_size.y * config_.global_scale);
if (bitmap_) {
ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height());
ImGui::Text("Effective Scale: %.3f x %.3f",
(config_.canvas_size.x * config_.global_scale) / bitmap_->width(),
(config_.canvas_size.y * config_.global_scale) / bitmap_->height());
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ROM palette management methods removed - now handled by EnhancedPaletteEditor
} // namespace yaze::gui } // namespace yaze::gui

View File

@@ -6,9 +6,13 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <functional>
#include <memory>
#include "app/gfx/bitmap.h" #include "app/gfx/bitmap.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/gui/canvas_utils.h"
#include "app/gui/enhanced_palette_editor.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
namespace yaze { namespace yaze {
@@ -28,55 +32,68 @@ enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 };
/** /**
* @class Canvas * @class Canvas
* @brief Represents a canvas for drawing and manipulating graphics. * @brief Modern, robust canvas for drawing and manipulating graphics.
* *
* The Canvas class provides various functions for updating and drawing graphics * Following ImGui design patterns, this Canvas class provides:
* on a canvas. It supports features such as bitmap drawing, context menu * - Modular configuration through CanvasConfig
* handling, tile painting, custom grid, and more. * - Separate selection state management
* - Enhanced palette management integration
* - Performance-optimized rendering
* - Comprehensive context menu system
*/ */
class Canvas { class Canvas {
public: public:
Canvas() = default; Canvas() = default;
explicit Canvas(const std::string &id) : canvas_id_(id) {
context_id_ = id + "Context"; explicit Canvas(const std::string& id)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
} }
explicit Canvas(const std::string &id, ImVec2 canvas_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) { explicit Canvas(const std::string& id, ImVec2 canvas_size)
context_id_ = id + "Context"; : canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
} }
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size) explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) { : canvas_id_(id), context_id_(id + "Context") {
context_id_ = id + "Context"; InitializeDefaults();
SetCanvasGridSize(grid_size); config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
SetGridSize(grid_size);
} }
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size, float global_scale) explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
: custom_canvas_size_(true), : canvas_id_(id), context_id_(id + "Context") {
global_scale_(global_scale), InitializeDefaults();
canvas_sz_(canvas_size), config_.canvas_size = canvas_size;
canvas_id_(id) { config_.custom_canvas_size = true;
context_id_ = id + "Context"; config_.global_scale = global_scale;
SetCanvasGridSize(grid_size); SetGridSize(grid_size);
} }
void SetCanvasGridSize(CanvasGridSize grid_size) { void SetGridSize(CanvasGridSize grid_size) {
switch (grid_size) { switch (grid_size) {
case CanvasGridSize::k8x8: case CanvasGridSize::k8x8:
custom_step_ = 8.0f; config_.grid_step = 8.0f;
break; break;
case CanvasGridSize::k16x16: case CanvasGridSize::k16x16:
custom_step_ = 16.0f; config_.grid_step = 16.0f;
break; break;
case CanvasGridSize::k32x32: case CanvasGridSize::k32x32:
custom_step_ = 32.0f; config_.grid_step = 32.0f;
break; break;
case CanvasGridSize::k64x64: case CanvasGridSize::k64x64:
custom_step_ = 64.0f; config_.grid_step = 64.0f;
break; break;
} }
} }
// Legacy compatibility
void SetCanvasGridSize(CanvasGridSize grid_size) { SetGridSize(grid_size); }
void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, int tile_size, const std::function<void()> &event, int tile_size,
float scale = 1.0f); float scale = 1.0f);
@@ -106,11 +123,45 @@ class Canvas {
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; } void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
// Enhanced view and edit operations // Enhanced view and edit operations
void ShowBitmapProperties(const gfx::Bitmap& bitmap); void ShowAdvancedCanvasProperties();
void ShowPaletteEditor(gfx::SnesPalette& palette); void ShowScalingControls();
void SetZoomToFit(const gfx::Bitmap& bitmap); void SetZoomToFit(const gfx::Bitmap& bitmap);
void ResetView(); void ResetView();
// Modular component access
CanvasConfig& GetConfig() { return config_; }
const CanvasConfig& GetConfig() const { return config_; }
CanvasSelection& GetSelection() { return selection_; }
const CanvasSelection& GetSelection() const { return selection_; }
// Enhanced palette management
void InitializePaletteEditor(Rom* rom);
void ShowPaletteEditor();
void ShowColorAnalysis();
bool ApplyROMPalette(int group_index, int palette_index);
// Initialization and cleanup
void InitializeDefaults();
void Cleanup();
// Size reporting for ImGui table integration
ImVec2 GetMinimumSize() const;
ImVec2 GetPreferredSize() const;
ImVec2 GetCurrentSize() const { return config_.canvas_size; }
void SetAutoResize(bool auto_resize) { config_.auto_resize = auto_resize; }
bool IsAutoResize() const { return config_.auto_resize; }
// Table integration helpers
void ReserveTableSpace(const std::string& label = "");
bool BeginTableCanvas(const std::string& label = "");
void EndTableCanvas();
// Improved interaction detection
bool HasValidSelection() const;
bool WasClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
bool WasDoubleClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
ImVec2 GetLastClickPosition() const;
private: private:
void DrawContextMenuItem(const ContextMenuItem& item); void DrawContextMenuItem(const ContextMenuItem& item);
@@ -173,19 +224,34 @@ class Canvas {
void set_global_scale(float scale) { global_scale_ = scale; } void set_global_scale(float scale) { global_scale_ = scale; }
void set_draggable(bool draggable) { draggable_ = draggable; } void set_draggable(bool draggable) { draggable_ = draggable; }
// Public accessors for commonly used private members // Modern accessors using modular structure
auto select_rect_active() const { return select_rect_active_; } bool IsSelectRectActive() const { return selection_.select_rect_active; }
auto selected_tiles() const { return selected_tiles_; } const std::vector<ImVec2>& GetSelectedTiles() const { return selection_.selected_tiles; }
auto selected_tile_pos() const { return selected_tile_pos_; } ImVec2 GetSelectedTilePos() const { return selection_.selected_tile_pos; }
void set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; } void SetSelectedTilePos(ImVec2 pos) { selection_.selected_tile_pos = pos; }
// Public methods for commonly used private methods // Configuration accessors
void SetCanvasSize(ImVec2 canvas_size) { canvas_sz_ = canvas_size; custom_canvas_size_ = true; } void SetCanvasSize(ImVec2 canvas_size) {
auto global_scale() const { return global_scale_; } config_.canvas_size = canvas_size;
auto custom_labels_enabled() { return &enable_custom_labels_; } config_.custom_canvas_size = true;
auto custom_step() const { return custom_step_; } }
auto width() const { return canvas_sz_.x; } float GetGlobalScale() const { return config_.global_scale; }
auto height() const { return canvas_sz_.y; } void SetGlobalScale(float scale) { config_.global_scale = scale; }
bool* GetCustomLabelsEnabled() { return &config_.enable_custom_labels; }
float GetGridStep() const { return config_.grid_step; }
float GetCanvasWidth() const { return config_.canvas_size.x; }
float GetCanvasHeight() const { return config_.canvas_size.y; }
// Legacy compatibility accessors
auto select_rect_active() const { return selection_.select_rect_active; }
auto selected_tiles() const { return selection_.selected_tiles; }
auto selected_tile_pos() const { return selection_.selected_tile_pos; }
void set_selected_tile_pos(ImVec2 pos) { selection_.selected_tile_pos = pos; }
auto global_scale() const { return config_.global_scale; }
auto custom_labels_enabled() { return &config_.enable_custom_labels; }
auto custom_step() const { return config_.grid_step; }
auto width() const { return config_.canvas_size.x; }
auto height() const { return config_.canvas_size.y; }
// Public accessors for methods that need to be accessed externally // Public accessors for methods that need to be accessed externally
auto canvas_id() const { return canvas_id_; } auto canvas_id() const { return canvas_id_; }
@@ -231,50 +297,60 @@ class Canvas {
Rom *rom() const { return rom_; } Rom *rom() const { return rom_; }
private: private:
bool draggable_ = false; // Modular configuration and state
CanvasConfig config_;
CanvasSelection selection_;
std::unique_ptr<EnhancedPaletteEditor> palette_editor_;
// Core canvas state
bool is_hovered_ = false; bool is_hovered_ = false;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool select_rect_active_ = false;
bool refresh_graphics_ = false; bool refresh_graphics_ = false;
// Context menu system // Context menu system
std::vector<ContextMenuItem> context_menu_items_; std::vector<ContextMenuItem> context_menu_items_;
bool context_menu_enabled_ = true; bool context_menu_enabled_ = true;
float custom_step_ = 0.0f; // Legacy members (to be gradually replaced)
float global_scale_ = 1.0f;
int current_labels_ = 0; int current_labels_ = 0;
int highlight_tile_id = -1; int highlight_tile_id = -1;
uint16_t edit_palette_index_ = 0; uint16_t edit_palette_index_ = 0;
uint64_t edit_palette_group_name_index_ = 0; uint64_t edit_palette_group_name_index_ = 0;
uint64_t edit_palette_sub_index_ = 0; uint64_t edit_palette_sub_index_ = 0;
// Core canvas state
Bitmap *bitmap_ = nullptr; Bitmap *bitmap_ = nullptr;
Rom *rom_ = nullptr; Rom *rom_ = nullptr;
ImDrawList *draw_list_ = nullptr; ImDrawList *draw_list_ = nullptr;
// Canvas geometry and interaction state
ImVec2 scrolling_; ImVec2 scrolling_;
ImVec2 canvas_sz_; ImVec2 canvas_sz_;
ImVec2 canvas_p0_; ImVec2 canvas_p0_;
ImVec2 canvas_p1_; ImVec2 canvas_p1_;
ImVec2 drawn_tile_pos_; ImVec2 drawn_tile_pos_;
ImVec2 mouse_pos_in_canvas_; ImVec2 mouse_pos_in_canvas_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
// Drawing and labeling
ImVector<ImVec2> points_; ImVector<ImVec2> points_;
ImVector<ImVec2> selected_points_;
ImVector<ImVector<std::string>> labels_; ImVector<ImVector<std::string>> labels_;
// Identification
std::string canvas_id_ = "Canvas"; std::string canvas_id_ = "Canvas";
std::string context_id_ = "CanvasContext"; std::string context_id_ = "CanvasContext";
// Legacy compatibility (gradually being replaced by selection_)
std::vector<ImVec2> selected_tiles_; std::vector<ImVec2> selected_tiles_;
ImVector<ImVec2> selected_points_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
bool select_rect_active_ = false;
float custom_step_ = 32.0f;
float global_scale_ = 1.0f;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool draggable_ = false;
}; };
void BeginCanvas(Canvas &canvas, ImVec2 child_size = ImVec2(0, 0)); void BeginCanvas(Canvas &canvas, ImVec2 child_size = ImVec2(0, 0));
@@ -288,6 +364,10 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
int height, int tile_size, bool is_loaded, int height, int tile_size, bool is_loaded,
bool scrollbar, int canvas_id); bool scrollbar, int canvas_id);
// Table-optimized canvas pipeline with automatic sizing
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
const std::string& label = "", bool auto_resize = true);
} // namespace gui } // namespace gui
} // namespace yaze } // namespace yaze

366
src/app/gui/canvas_utils.cc Normal file
View File

@@ -0,0 +1,366 @@
#include "canvas_utils.h"
#include <cmath>
#include "app/core/window.h"
#include "app/gfx/snes_palette.h"
#include "util/log.h"
namespace yaze {
namespace gui {
namespace CanvasUtils {
using core::Renderer;
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
std::floor(pos.y / grid_step) * grid_step);
}
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale) {
if (content_size.x <= 0 || content_size.y <= 0) return global_scale;
float scale_x = (canvas_size.x * global_scale) / content_size.x;
float scale_y = (canvas_size.y * global_scale) / content_size.y;
return std::min(scale_x, scale_y);
}
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row) {
float scaled_tile_size = tile_size * scale;
int tile_x = static_cast<int>(mouse_pos.x / scaled_tile_size);
int tile_y = static_cast<int>(mouse_pos.y / scaled_tile_size);
return tile_x + (tile_y * tiles_per_row);
}
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
if (!rom || palette_manager.palettes_loaded) {
return palette_manager.palettes_loaded;
}
try {
const auto& palette_groups = rom->palette_group();
palette_manager.rom_palette_groups.clear();
palette_manager.palette_group_names.clear();
// Overworld palettes
if (palette_groups.overworld_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.overworld_main[0]);
palette_manager.palette_group_names.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.overworld_aux[0]);
palette_manager.palette_group_names.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.overworld_animated[0]);
palette_manager.palette_group_names.push_back("Overworld Animated");
}
// Dungeon palettes
if (palette_groups.dungeon_main.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.dungeon_main[0]);
palette_manager.palette_group_names.push_back("Dungeon Main");
}
// Sprite palettes
if (palette_groups.global_sprites.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.global_sprites[0]);
palette_manager.palette_group_names.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.armors[0]);
palette_manager.palette_group_names.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
palette_manager.rom_palette_groups.push_back(palette_groups.swords[0]);
palette_manager.palette_group_names.push_back("Swords");
}
palette_manager.palettes_loaded = true;
util::logf("Canvas: Loaded %zu ROM palette groups", palette_manager.rom_palette_groups.size());
return true;
} catch (const std::exception& e) {
util::logf("Canvas: Failed to load ROM palette groups: %s", e.what());
return false;
}
}
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index) {
if (!bitmap || group_index < 0 ||
group_index >= static_cast<int>(palette_manager.rom_palette_groups.size())) {
return false;
}
try {
const auto& selected_palette = palette_manager.rom_palette_groups[group_index];
// Apply the palette based on the index
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
util::logf("Canvas: Applied palette group %d, index %d to bitmap", group_index, palette_index);
return true;
} catch (const std::exception& e) {
util::logf("Canvas: Failed to apply palette: %s", e.what());
return false;
}
}
// Drawing utility functions
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color, float global_scale) {
// Apply global scale to position and size
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
float scaled_w = w * global_scale;
float scaled_h = h * global_scale;
ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w,
canvas_p0.y + scrolling.y + scaled_y + scaled_h);
uint32_t color_u32 = IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
draw_list->AddRectFilled(origin, size, color_u32);
// Add a black outline
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
ImVec2 outline_size(size.x + 1, size.y + 1);
draw_list->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
}
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale) {
// Apply global scale to text position
float scaled_x = x * global_scale;
float scaled_y = y * global_scale;
ImVec2 text_pos(canvas_p0.x + scrolling.x + scaled_x,
canvas_p0.y + scrolling.y + scaled_y);
// Draw text with black shadow for better visibility
draw_list->AddText(ImVec2(text_pos.x + 1, text_pos.y + 1),
IM_COL32(0, 0, 0, 255), text.c_str());
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), text.c_str());
}
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, uint32_t color) {
ImVec2 origin(canvas_p0.x + scrolling.x + x,
canvas_p0.y + scrolling.y + y);
ImVec2 size(canvas_p0.x + scrolling.x + x + w,
canvas_p0.y + scrolling.y + y + h);
draw_list->AddRect(origin, size, color, 0, 0, 1.5f);
}
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color) {
uint32_t color_u32 = IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
DrawCanvasOutline(draw_list, canvas_p0, scrolling, x, y, w, h, color_u32);
}
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1,
ImVec2 scrolling, float grid_step, float global_scale) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
const float grid_thickness = 0.5f;
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < (canvas_p1.x - canvas_p0.x); x += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y),
ImVec2(canvas_p0.x + x, canvas_p1.y),
grid_color, grid_thickness);
}
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < (canvas_p1.y - canvas_p0.y); y += scaled_grid_step) {
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y),
ImVec2(canvas_p1.x, canvas_p0.y + y),
grid_color, grid_thickness);
}
}
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int highlight_tile_id, float grid_step) {
if (highlight_tile_id == -1) return;
int tile_x = highlight_tile_id % 8;
int tile_y = highlight_tile_id / 8;
ImVec2 tile_pos(canvas_p0.x + scrolling.x + tile_x * grid_step,
canvas_p0.y + scrolling.y + tile_y * grid_step);
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
draw_list->AddRectFilled(tile_pos, tile_pos_end, IM_COL32(255, 0, 255, 255));
}
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
ImVec2 canvas_sz, float grid_step, float global_scale) {
float scaled_grid_step = grid_step * global_scale;
for (float x = fmodf(scrolling.x, scaled_grid_step);
x < canvas_sz.x * global_scale; x += scaled_grid_step) {
for (float y = fmodf(scrolling.y, scaled_grid_step);
y < canvas_sz.y * global_scale; y += scaled_grid_step) {
int tile_x = (x - scrolling.x) / scaled_grid_step;
int tile_y = (y - scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * 16);
char hex_id[8];
snprintf(hex_id, sizeof(hex_id), "%02X", tile_id);
draw_list->AddText(ImVec2(canvas_p0.x + x + (scaled_grid_step / 2) - 4,
canvas_p0.y + y + (scaled_grid_step / 2) - 4),
IM_COL32(255, 255, 255, 255), hex_id);
}
}
}
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom) {
return use_custom ? custom_size : content_region;
}
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale) {
return ImVec2(canvas_size.x * global_scale, canvas_size.y * global_scale);
}
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1) {
return point.x >= canvas_p0.x && point.x <= canvas_p1.x &&
point.y >= canvas_p0.y && point.y <= canvas_p1.y;
}
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding) {
// Calculate minimum size needed to display content with padding
ImVec2 min_size = ImVec2(content_size.x * global_scale + padding * 2,
content_size.y * global_scale + padding * 2);
// Ensure minimum practical size
min_size.x = std::max(min_size.x, 64.0f);
min_size.y = std::max(min_size.y, 64.0f);
return min_size;
}
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale) {
// Calculate preferred size with minimum scale constraint
float effective_scale = std::max(global_scale, min_scale);
ImVec2 preferred_size = ImVec2(content_size.x * effective_scale + 8.0f,
content_size.y * effective_scale + 8.0f);
// Cap to reasonable maximum sizes for table integration
preferred_size.x = std::min(preferred_size.x, 800.0f);
preferred_size.y = std::min(preferred_size.y, 600.0f);
return preferred_size;
}
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label) {
// Reserve space in ImGui layout so tables know the size
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
ImGui::Dummy(canvas_size);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() - canvas_size.x); // Move back to start
}
void SetNextCanvasSize(ImVec2 size, bool auto_resize) {
if (auto_resize) {
// Use auto-sizing child window for table integration
ImGui::SetNextWindowContentSize(size);
} else {
// Fixed size
ImGui::SetNextWindowSize(size);
}
}
// High-level composite operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id) {
if (!ctx.enable_grid) return;
ctx.draw_list->PushClipRect(ctx.canvas_p0, ctx.canvas_p1, true);
// Draw grid lines
DrawCanvasGridLines(ctx.draw_list, ctx.canvas_p0, ctx.canvas_p1,
ctx.scrolling, ctx.grid_step, ctx.global_scale);
// Draw highlight if specified
if (highlight_tile_id != -1) {
DrawCustomHighlight(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
highlight_tile_id, ctx.grid_step * ctx.global_scale);
}
// Draw hex labels if enabled
if (ctx.enable_hex_labels) {
DrawHexTileLabels(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
ImVec2(ctx.canvas_p1.x - ctx.canvas_p0.x, ctx.canvas_p1.y - ctx.canvas_p0.y),
ctx.grid_step, ctx.global_scale);
}
ctx.draw_list->PopClipRect();
}
void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points) {
const ImVec2 origin(ctx.canvas_p0.x + ctx.scrolling.x, ctx.canvas_p0.y + ctx.scrolling.y);
// Draw hover points
for (int n = 0; n < points.Size; n += 2) {
ctx.draw_list->AddRect(
ImVec2(origin.x + points[n].x, origin.y + points[n].y),
ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y),
IM_COL32(255, 255, 255, 255), 1.0f);
}
// Draw selection rectangles
if (!selected_points.empty()) {
for (int n = 0; n < selected_points.size(); n += 2) {
ctx.draw_list->AddRect(ImVec2(origin.x + selected_points[n].x,
origin.y + selected_points[n].y),
ImVec2(origin.x + selected_points[n + 1].x + 0x10,
origin.y + selected_points[n + 1].y + 0x10),
IM_COL32(255, 255, 255, 255), 1.0f);
}
}
}
void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset) {
if (current_labels >= labels.size()) return;
float scaled_grid_step = ctx.grid_step * ctx.global_scale;
for (float x = fmodf(ctx.scrolling.x, scaled_grid_step);
x < (ctx.canvas_p1.x - ctx.canvas_p0.x); x += scaled_grid_step) {
for (float y = fmodf(ctx.scrolling.y, scaled_grid_step);
y < (ctx.canvas_p1.y - ctx.canvas_p0.y); y += scaled_grid_step) {
int tile_x = (x - ctx.scrolling.x) / scaled_grid_step;
int tile_y = (y - ctx.scrolling.y) / scaled_grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels[current_labels].size()) {
break;
}
const std::string& label = labels[current_labels][tile_id];
ctx.draw_list->AddText(
ImVec2(ctx.canvas_p0.x + x + (scaled_grid_step / 2) - tile_id_offset,
ctx.canvas_p0.y + y + (scaled_grid_step / 2) - tile_id_offset),
IM_COL32(255, 255, 255, 255), label.c_str());
}
}
}
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze

147
src/app/gui/canvas_utils.h Normal file
View File

@@ -0,0 +1,147 @@
#ifndef YAZE_APP_GUI_CANVAS_UTILS_H
#define YAZE_APP_GUI_CANVAS_UTILS_H
#include <string>
#include <vector>
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Configuration for canvas display and interaction
*/
struct CanvasConfig {
bool enable_grid = true;
bool enable_hex_labels = false;
bool enable_custom_labels = false;
bool enable_context_menu = true;
bool is_draggable = false;
bool auto_resize = false;
float grid_step = 32.0f;
float global_scale = 1.0f;
ImVec2 canvas_size = ImVec2(0, 0);
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
bool custom_canvas_size = false;
};
/**
* @brief Selection state for canvas interactions
*/
struct CanvasSelection {
std::vector<ImVec2> selected_tiles;
std::vector<ImVec2> selected_points;
ImVec2 selected_tile_pos = ImVec2(-1, -1);
bool select_rect_active = false;
void Clear() {
selected_tiles.clear();
selected_points.clear();
selected_tile_pos = ImVec2(-1, -1);
select_rect_active = false;
}
};
/**
* @brief Palette management state for canvas
*/
struct CanvasPaletteManager {
std::vector<gfx::SnesPalette> rom_palette_groups;
std::vector<std::string> palette_group_names;
gfx::SnesPalette original_palette;
bool palettes_loaded = false;
int current_group_index = 0;
int current_palette_index = 0;
void Clear() {
rom_palette_groups.clear();
palette_group_names.clear();
original_palette.clear();
palettes_loaded = false;
current_group_index = 0;
current_palette_index = 0;
}
};
/**
* @brief Context menu item configuration
*/
struct CanvasContextMenuItem {
std::string label;
std::string shortcut;
std::function<void()> callback;
std::function<bool()> enabled_condition = []() { return true; };
std::vector<CanvasContextMenuItem> subitems;
};
/**
* @brief Utility functions for canvas operations
*/
namespace CanvasUtils {
// Core utility functions
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale);
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row);
// Palette management utilities
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index);
// Drawing utility functions (moved from Canvas class)
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color, float global_scale);
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
const std::string& text, int x, int y, float global_scale);
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, uint32_t color = IM_COL32(255, 255, 255, 200));
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int x, int y, int w, int h, ImVec4 color);
// Grid utility functions
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1,
ImVec2 scrolling, float grid_step, float global_scale);
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
int highlight_tile_id, float grid_step);
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
ImVec2 canvas_sz, float grid_step, float global_scale);
// Layout and interaction utilities
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom);
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale);
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1);
// Size reporting for ImGui table integration
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding = 4.0f);
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale = 1.0f);
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label = "");
void SetNextCanvasSize(ImVec2 size, bool auto_resize = false);
// High-level canvas operations
struct CanvasRenderContext {
ImDrawList* draw_list;
ImVec2 canvas_p0;
ImVec2 canvas_p1;
ImVec2 scrolling;
float global_scale;
bool enable_grid;
bool enable_hex_labels;
float grid_step;
};
// Composite drawing operations
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id = -1);
void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points);
void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector<ImVector<std::string>>& labels,
int current_labels, int tile_id_offset);
} // namespace CanvasUtils
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_UTILS_H

View File

@@ -0,0 +1,466 @@
#include "enhanced_palette_editor.h"
#include <algorithm>
#include <map>
#include "app/core/window.h"
#include "app/gui/color.h"
#include "util/log.h"
namespace yaze {
namespace gui {
using core::Renderer;
void EnhancedPaletteEditor::Initialize(Rom* rom) {
rom_ = rom;
rom_palettes_loaded_ = false;
if (rom_) {
LoadROMPalettes();
}
}
void EnhancedPaletteEditor::ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title) {
if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Enhanced Palette Editor");
ImGui::Separator();
// Palette grid editor
DrawPaletteGrid(palette);
ImGui::Separator();
// Analysis and tools
if (ImGui::CollapsingHeader("Palette Analysis")) {
DrawPaletteAnalysis(palette);
}
if (ImGui::CollapsingHeader("ROM Palette Manager") && rom_) {
DrawROMPaletteSelector();
if (ImGui::Button("Apply ROM Palette") && !rom_palette_groups_.empty()) {
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
palette = rom_palette_groups_[current_group_index_];
}
}
}
ImGui::Separator();
// Action buttons
if (ImGui::Button("Save Backup")) {
SavePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Restore Backup")) {
RestorePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void EnhancedPaletteEditor::ShowROMPaletteManager() {
if (!show_rom_manager_) return;
if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
if (!rom_) {
ImGui::Text("No ROM loaded");
ImGui::End();
return;
}
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
DrawROMPaletteSelector();
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Separator();
ImGui::Text("Preview of %s:", palette_group_names_[current_group_index_].c_str());
const auto& preview_palette = rom_palette_groups_[current_group_index_];
DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
DrawPaletteAnalysis(preview_palette);
}
}
ImGui::End();
}
void EnhancedPaletteEditor::ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title) {
if (!show_color_analysis_) return;
if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
ImGui::Text("Bitmap Color Analysis");
ImGui::Separator();
if (!bitmap.is_active()) {
ImGui::Text("Bitmap is not active");
ImGui::End();
return;
}
// Analyze pixel distribution
std::map<uint8_t, int> pixel_counts;
const auto& data = bitmap.vector();
for (uint8_t pixel : data) {
uint8_t palette_index = pixel & 0x0F; // 4-bit palette index
pixel_counts[palette_index]++;
}
ImGui::Text("Bitmap Size: %d x %d (%zu pixels)",
bitmap.width(), bitmap.height(), data.size());
ImGui::Separator();
ImGui::Text("Pixel Distribution:");
// Show distribution as bars
int total_pixels = static_cast<int>(data.size());
for (const auto& [index, count] : pixel_counts) {
float percentage = (static_cast<float>(count) / total_pixels) * 100.0f;
ImGui::Text("Index %d: %d pixels (%.1f%%)", index, count, percentage);
// Progress bar visualization
ImGui::SameLine();
ImGui::ProgressBar(percentage / 100.0f, ImVec2(100, 0));
// Color swatch if palette is available
if (index < static_cast<int>(bitmap.palette().size())) {
ImGui::SameLine();
auto color = bitmap.palette()[index];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##color" + std::to_string(index)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("SNES Color: 0x%04X\nRGB: (%d, %d, %d)",
color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
}
}
}
ImGui::End();
}
bool EnhancedPaletteEditor::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index) {
if (!bitmap || !rom_palettes_loaded_ ||
group_index < 0 || group_index >= static_cast<int>(rom_palette_groups_.size())) {
return false;
}
try {
const auto& selected_palette = rom_palette_groups_[group_index];
// Save current palette as backup
SavePaletteBackup(bitmap->palette());
// Apply new palette
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
current_group_index_ = group_index;
current_palette_index_ = palette_index;
util::logf("Applied ROM palette: %s (index %d)",
palette_group_names_[group_index].c_str(), palette_index);
return true;
} catch (const std::exception& e) {
util::logf("Failed to apply ROM palette: %s", e.what());
return false;
}
}
const gfx::SnesPalette* EnhancedPaletteEditor::GetSelectedROMPalette() const {
if (!rom_palettes_loaded_ || current_group_index_ < 0 ||
current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
return nullptr;
}
return &rom_palette_groups_[current_group_index_];
}
void EnhancedPaletteEditor::SavePaletteBackup(const gfx::SnesPalette& palette) {
backup_palette_ = palette;
}
bool EnhancedPaletteEditor::RestorePaletteBackup(gfx::SnesPalette& palette) {
if (backup_palette_.size() == 0) {
return false;
}
palette = backup_palette_;
return true;
}
void EnhancedPaletteEditor::DrawPaletteGrid(gfx::SnesPalette& palette, int cols) {
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
// Color button with editing capability
if (ImGui::ColorButton("##color", display_color,
ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
editing_color_index_ = i;
temp_color_ = display_color;
}
// Context menu for individual colors
if (ImGui::BeginPopupContextItem()) {
ImGui::Text("Color %d (0x%04X)", i, color.snes());
ImGui::Separator();
if (ImGui::MenuItem("Edit Color")) {
editing_color_index_ = i;
temp_color_ = display_color;
}
if (ImGui::MenuItem("Copy Color")) {
// Could implement color clipboard here
}
if (ImGui::MenuItem("Reset to Black")) {
palette[i] = gfx::SnesColor(0);
}
ImGui::EndPopup();
}
// Tooltip with detailed info
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)\nClick to edit",
i, color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
ImGui::PopID();
}
// Color editor popup
if (editing_color_index_ >= 0) {
ImGui::OpenPopup("Edit Color");
if (ImGui::BeginPopupModal("Edit Color", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Editing Color %d", editing_color_index_);
if (ImGui::ColorEdit4("Color", &temp_color_.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
// Update the palette color in real-time
auto new_snes_color = gfx::SnesColor(temp_color_);
palette[editing_color_index_] = new_snes_color;
}
if (ImGui::Button("Apply")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
}
void EnhancedPaletteEditor::DrawROMPaletteSelector() {
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
if (rom_palette_groups_.empty()) {
ImGui::Text("No ROM palettes available");
return;
}
// Group selector
ImGui::Text("Palette Group:");
if (ImGui::Combo("##PaletteGroup", &current_group_index_,
[](void* data, int idx, const char** out_text) -> bool {
auto* names = static_cast<std::vector<std::string>*>(data);
if (idx < 0 || idx >= static_cast<int>(names->size())) return false;
*out_text = (*names)[idx].c_str();
return true;
}, &palette_group_names_, static_cast<int>(palette_group_names_.size()))) {
// Group changed - could trigger preview update
}
// Palette index selector
ImGui::Text("Palette Index:");
ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
// Quick palette preview
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Text("Preview:");
const auto& preview_palette = rom_palette_groups_[current_group_index_];
// Show just first 8 colors in a row
for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size()); i++) {
if (i > 0) ImGui::SameLine();
auto color = preview_palette[i];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
}
}
}
void EnhancedPaletteEditor::DrawColorEditControls(gfx::SnesColor& color, int color_index) {
ImVec4 rgba = color.rgb();
ImGui::PushID(color_index);
if (ImGui::ColorEdit4("##color_edit", &rgba.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
color = gfx::SnesColor(rgba);
}
// SNES-specific controls
ImGui::Text("SNES Color: 0x%04X", color.snes());
// Individual RGB component sliders (0-31 for SNES)
int r = (color.snes() & 0x1F);
int g = (color.snes() >> 5) & 0x1F;
int b = (color.snes() >> 10) & 0x1F;
if (ImGui::SliderInt("Red", &r, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Green", &g, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Blue", &b, 0, 31)) {
uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
color = gfx::SnesColor(new_color);
}
ImGui::PopID();
}
void EnhancedPaletteEditor::DrawPaletteAnalysis(const gfx::SnesPalette& palette) {
ImGui::Text("Palette Information:");
ImGui::Text("Size: %zu colors", palette.size());
// Color distribution analysis
std::map<uint16_t, int> color_frequency;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
color_frequency[palette[i].snes()]++;
}
ImGui::Text("Unique Colors: %zu", color_frequency.size());
if (color_frequency.size() < palette.size()) {
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Warning: Duplicate colors detected!");
if (ImGui::TreeNode("Duplicate Colors")) {
for (const auto& [snes_color, count] : color_frequency) {
if (count > 1) {
ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(16, 16));
ImGui::SameLine();
ImGui::Text("0x%04X appears %d times", snes_color, count);
}
}
ImGui::TreePop();
}
}
// Brightness analysis
float total_brightness = 0.0f;
float min_brightness = 1.0f;
float max_brightness = 0.0f;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
ImVec4 color = palette[i].rgb();
float brightness = (color.x + color.y + color.z) / 3.0f;
total_brightness += brightness;
min_brightness = std::min(min_brightness, brightness);
max_brightness = std::max(max_brightness, brightness);
}
float avg_brightness = total_brightness / palette.size();
ImGui::Separator();
ImGui::Text("Brightness Analysis:");
ImGui::Text("Average: %.2f", avg_brightness);
ImGui::Text("Range: %.2f - %.2f", min_brightness, max_brightness);
// Show brightness as progress bar
ImGui::Text("Brightness Distribution:");
ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
}
void EnhancedPaletteEditor::LoadROMPalettes() {
if (!rom_ || rom_palettes_loaded_) return;
try {
const auto& palette_groups = rom_->palette_group();
rom_palette_groups_.clear();
palette_group_names_.clear();
// Load all available palette groups
if (palette_groups.overworld_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
palette_group_names_.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
palette_group_names_.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
palette_group_names_.push_back("Overworld Animated");
}
if (palette_groups.dungeon_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
palette_group_names_.push_back("Dungeon Main");
}
if (palette_groups.global_sprites.size() > 0) {
rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
palette_group_names_.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
rom_palette_groups_.push_back(palette_groups.armors[0]);
palette_group_names_.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
rom_palette_groups_.push_back(palette_groups.swords[0]);
palette_group_names_.push_back("Swords");
}
rom_palettes_loaded_ = true;
util::logf("Enhanced Palette Editor: Loaded %zu ROM palette groups", rom_palette_groups_.size());
} catch (const std::exception& e) {
util::logf("Enhanced Palette Editor: Failed to load ROM palettes: %s", e.what());
}
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,92 @@
#ifndef YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#define YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#include <vector>
#include <map>
#include "app/gfx/snes_palette.h"
#include "app/gfx/bitmap.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Enhanced palette editor with ROM integration and analysis tools
*/
class EnhancedPaletteEditor {
public:
EnhancedPaletteEditor() = default;
/**
* @brief Initialize the palette editor with ROM data
*/
void Initialize(Rom* rom);
/**
* @brief Show the main palette editor window
*/
void ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title = "Palette Editor");
/**
* @brief Show the ROM palette manager window
*/
void ShowROMPaletteManager();
/**
* @brief Show color analysis window for a bitmap
*/
void ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title = "Color Analysis");
/**
* @brief Apply a ROM palette group to a bitmap
*/
bool ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index);
/**
* @brief Get the currently selected ROM palette
*/
const gfx::SnesPalette* GetSelectedROMPalette() const;
/**
* @brief Save current palette as backup
*/
void SavePaletteBackup(const gfx::SnesPalette& palette);
/**
* @brief Restore palette from backup
*/
bool RestorePaletteBackup(gfx::SnesPalette& palette);
// Accessors
bool IsROMLoaded() const { return rom_ != nullptr; }
int GetCurrentGroupIndex() const { return current_group_index_; }
int GetCurrentPaletteIndex() const { return current_palette_index_; }
private:
void DrawPaletteGrid(gfx::SnesPalette& palette, int cols = 8);
void DrawROMPaletteSelector();
void DrawColorEditControls(gfx::SnesColor& color, int color_index);
void DrawPaletteAnalysis(const gfx::SnesPalette& palette);
void LoadROMPalettes();
Rom* rom_ = nullptr;
std::vector<gfx::SnesPalette> rom_palette_groups_;
std::vector<std::string> palette_group_names_;
gfx::SnesPalette backup_palette_;
int current_group_index_ = 0;
int current_palette_index_ = 0;
bool rom_palettes_loaded_ = false;
bool show_color_analysis_ = false;
bool show_rom_manager_ = false;
// Color editing state
int editing_color_index_ = -1;
ImVec4 temp_color_ = ImVec4(0, 0, 0, 1);
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H

View File

@@ -3,6 +3,8 @@ set(
app/gui/modules/asset_browser.cc app/gui/modules/asset_browser.cc
app/gui/modules/text_editor.cc app/gui/modules/text_editor.cc
app/gui/canvas.cc app/gui/canvas.cc
app/gui/canvas_utils.cc
app/gui/enhanced_palette_editor.cc
app/gui/input.cc app/gui/input.cc
app/gui/style.cc app/gui/style.cc
app/gui/color.cc app/gui/color.cc

View File

@@ -282,7 +282,27 @@ void BeginNoPadding() {
void EndNoPadding() { ImGui::PopStyleVar(2); } void EndNoPadding() { ImGui::PopStyleVar(2); }
void BeginChildWithScrollbar(const char *str_id) { void BeginChildWithScrollbar(const char *str_id) {
ImGui::BeginChild(str_id, ImGui::GetContentRegionAvail(), true, // Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size) {
// Set content size before beginning child to enable proper scrolling
if (content_size.x > 0 && content_size.y > 0) {
ImGui::SetNextWindowContentSize(content_size);
}
// Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar); ImGuiWindowFlags_AlwaysVerticalScrollbar);
} }

View File

@@ -34,6 +34,7 @@ void BeginNoPadding();
void EndNoPadding(); void EndNoPadding();
void BeginChildWithScrollbar(const char *str_id); void BeginChildWithScrollbar(const char *str_id);
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size);
void BeginChildBothScrollbars(int id); void BeginChildBothScrollbars(int id);

View File

@@ -2,9 +2,9 @@
#include <algorithm> #include <algorithm>
#include <future> #include <future>
#include <set>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <set>
#include "absl/status/status.h" #include "absl/status/status.h"
#include "app/core/features.h" #include "app/core/features.h"
@@ -19,7 +19,7 @@
namespace yaze { namespace yaze {
namespace zelda3 { namespace zelda3 {
absl::Status Overworld::Load(Rom *rom) { absl::Status Overworld::Load(Rom* rom) {
if (rom->size() == 0) { if (rom->size() == 0) {
return absl::InvalidArgumentError("ROM file not loaded"); return absl::InvalidArgumentError("ROM file not loaded");
} }
@@ -103,7 +103,7 @@ void Overworld::FetchLargeMaps() {
} }
absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32( absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
int index, int quadrant, int dimension, const uint32_t *map32address) { int index, int quadrant, int dimension, const uint32_t* map32address) {
ASSIGN_OR_RETURN( ASSIGN_OR_RETURN(
auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index))); auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
ASSIGN_OR_RETURN(auto arg2, ASSIGN_OR_RETURN(auto arg2,
@@ -120,11 +120,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
rom()->version_constants().kMap32TileTR, rom()->version_constants().kMap32TileTR,
rom()->version_constants().kMap32TileBL, rom()->version_constants().kMap32TileBL,
rom()->version_constants().kMap32TileBR}; rom()->version_constants().kMap32TileBR};
// Use ASM version to determine expanded tile32 support, with flag as override // Use ASM version to determine expanded tile32 support, with flag as override
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_custom_overworld = (asm_version != 0xFF) || bool use_custom_overworld =
core::FeatureFlags::get().overworld.kLoadCustomOverworld; (asm_version != 0xFF) ||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
if (rom()->data()[kMap32ExpandedFlagPos] != 0x04 && use_custom_overworld) { if (rom()->data()[kMap32ExpandedFlagPos] != 0x04 && use_custom_overworld) {
map32address[0] = rom()->version_constants().kMap32TileTL; map32address[0] = rom()->version_constants().kMap32TileTL;
map32address[1] = kMap32TileTRExpanded; map32address[1] = kMap32TileTRExpanded;
@@ -173,11 +174,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
absl::Status Overworld::AssembleMap16Tiles() { absl::Status Overworld::AssembleMap16Tiles() {
int tpos = kMap16Tiles; int tpos = kMap16Tiles;
int num_tile16 = kNumTile16Individual; int num_tile16 = kNumTile16Individual;
// Use ASM version to determine expanded tile16 support, with flag as override // Use ASM version to determine expanded tile16 support, with flag as override
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_custom_overworld = (asm_version != 0xFF) || bool use_custom_overworld =
core::FeatureFlags::get().overworld.kLoadCustomOverworld; (asm_version != 0xFF) ||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
if (rom()->data()[kMap16ExpandedFlagPos] != 0x0F && use_custom_overworld) { if (rom()->data()[kMap16ExpandedFlagPos] != 0x0F && use_custom_overworld) {
tpos = kMap16TilesExpanded; tpos = kMap16TilesExpanded;
num_tile16 = NumberOfMap16Ex; num_tile16 = NumberOfMap16Ex;
@@ -203,7 +205,7 @@ absl::Status Overworld::AssembleMap16Tiles() {
} }
void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos, void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
OverworldBlockset &world) { OverworldBlockset& world) {
int position_x1 = (x * 2) + (sx * 32); int position_x1 = (x * 2) + (sx * 32);
int position_y1 = (y * 2) + (sy * 32); int position_y1 = (y * 2) + (sy * 32);
int position_x2 = (x * 2) + 1 + (sx * 32); int position_x2 = (x * 2) + 1 + (sx * 32);
@@ -214,9 +216,9 @@ void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_; world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
} }
void Overworld::OrganizeMapTiles(std::vector<uint8_t> &bytes, void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
std::vector<uint8_t> &bytes2, int i, int sx, std::vector<uint8_t>& bytes2, int i, int sx,
int sy, int &ttpos) { int sy, int& ttpos) {
for (int y = 0; y < 16; y++) { for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) { for (int x = 0; x < 16; x++) {
auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]); auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
@@ -258,11 +260,15 @@ void Overworld::DecompressAllMapTiles() {
int ttpos = 0; int ttpos = 0;
if (p1 >= highest) highest = p1; if (p1 >= highest)
if (p2 >= highest) highest = p2; highest = p1;
if (p2 >= highest)
highest = p2;
if (p1 <= lowest && p1 > kBaseHighest) lowest = p1; if (p1 <= lowest && p1 > kBaseHighest)
if (p2 <= lowest && p2 > kBaseHighest) lowest = p2; lowest = p1;
if (p2 <= lowest && p2 > kBaseHighest)
lowest = p2;
int size1, size2; int size1, size2;
auto bytes = gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1); auto bytes = gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1);
@@ -302,7 +308,7 @@ absl::Status Overworld::LoadOverworldMaps() {
} }
// Wait for all tasks to complete and check their results // Wait for all tasks to complete and check their results
for (auto &future : futures) { for (auto& future : futures) {
future.wait(); future.wait();
RETURN_IF_ERROR(future.get()); RETURN_IF_ERROR(future.get());
} }
@@ -321,12 +327,14 @@ absl::Status Overworld::LoadEntrances() {
int ow_entrance_pos_ptr = kOverworldEntrancePos; int ow_entrance_pos_ptr = kOverworldEntrancePos;
int ow_entrance_id_ptr = kOverworldEntranceEntranceId; int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
int num_entrances = 129; int num_entrances = 129;
// Use ASM version to determine expanded entrance support, with flag as override // Use ASM version to determine expanded entrance support, with flag as override
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_custom_overworld = (asm_version != 0xFF) || bool use_custom_overworld =
core::FeatureFlags::get().overworld.kLoadCustomOverworld; (asm_version != 0xFF) ||
if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 && use_custom_overworld) { core::FeatureFlags::get().overworld.kLoadCustomOverworld;
if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 &&
use_custom_overworld) {
ow_entrance_map_ptr = kOverworldEntranceMapExpanded; ow_entrance_map_ptr = kOverworldEntranceMapExpanded;
ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
@@ -439,24 +447,29 @@ absl::Status Overworld::LoadItems() {
// Version 0x03 of the OW ASM added item support for the SW. // Version 0x03 of the OW ASM added item support for the SW.
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
// Determine max number of overworld maps based on ASM version // Determine max number of overworld maps based on ASM version
int max_ow = (asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80; int max_ow =
(asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80;
ASSIGN_OR_RETURN(uint32_t pointer_snes,
ASSIGN_OR_RETURN(uint32_t pointer_snes,
rom()->ReadLong(zelda3::overworldItemsAddress)); rom()->ReadLong(zelda3::overworldItemsAddress));
uint32_t item_pointer_address = SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9 uint32_t item_pointer_address =
SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
for (int i = 0; i < max_ow; i++) { for (int i = 0; i < max_ow; i++) {
ASSIGN_OR_RETURN(uint8_t bank_byte, rom()->ReadByte(zelda3::overworldItemsAddressBank)); ASSIGN_OR_RETURN(uint8_t bank_byte,
rom()->ReadByte(zelda3::overworldItemsAddressBank));
int bank = bank_byte & 0x7F; int bank = bank_byte & 0x7F;
ASSIGN_OR_RETURN(uint8_t addr_low, rom()->ReadByte(item_pointer_address + (i * 2))); ASSIGN_OR_RETURN(uint8_t addr_low,
ASSIGN_OR_RETURN(uint8_t addr_high, rom()->ReadByte(item_pointer_address + (i * 2) + 1)); rom()->ReadByte(item_pointer_address + (i * 2)));
ASSIGN_OR_RETURN(uint8_t addr_high,
uint32_t addr = (bank << 16) + // 1B rom()->ReadByte(item_pointer_address + (i * 2) + 1));
(addr_high << 8) + // F9
addr_low; // 3C uint32_t addr = (bank << 16) + // 1B
(addr_high << 8) + // F9
addr_low; // 3C
addr = SnesToPc(addr); addr = SnesToPc(addr);
// Check if this is a large map and skip if not the parent // Check if this is a large map and skip if not the parent
@@ -499,12 +512,13 @@ absl::Status Overworld::LoadItems() {
absl::Status Overworld::LoadSprites() { absl::Status Overworld::LoadSprites() {
std::vector<std::future<absl::Status>> futures; std::vector<std::future<absl::Status>> futures;
// Use ASM version to determine sprite table locations, with flag as override // Use ASM version to determine sprite table locations, with flag as override
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_custom_overworld = (asm_version != 0xFF) || bool use_custom_overworld =
core::FeatureFlags::get().overworld.kLoadCustomOverworld; (asm_version != 0xFF) ||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
if (use_custom_overworld && asm_version >= 3 && asm_version != 0xFF) { if (use_custom_overworld && asm_version >= 3 && asm_version != 0xFF) {
// v3: Use expanded sprite tables // v3: Use expanded sprite tables
futures.emplace_back(std::async(std::launch::async, [this]() { futures.emplace_back(std::async(std::launch::async, [this]() {
@@ -529,7 +543,7 @@ absl::Status Overworld::LoadSprites() {
})); }));
} }
for (auto &future : futures) { for (auto& future : futures) {
future.wait(); future.wait();
RETURN_IF_ERROR(future.get()); RETURN_IF_ERROR(future.get());
} }
@@ -540,7 +554,8 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
int num_maps_per_gamestate, int num_maps_per_gamestate,
int game_state) { int game_state) {
for (int i = 0; i < num_maps_per_gamestate; i++) { for (int i = 0; i < num_maps_per_gamestate; i++) {
if (map_parent_[i] != i) continue; if (map_parent_[i] != i)
continue;
int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2); int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2);
ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr)); ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr));
@@ -549,7 +564,8 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address)); ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address));
ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1)); ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1));
ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2)); ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2));
if (b1 == 0xFF) break; if (b1 == 0xFF)
break;
int editor_map_index = i; int editor_map_index = i;
if (game_state != 0) { if (game_state != 0) {
@@ -575,12 +591,18 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
return absl::OkStatus(); return absl::OkStatus();
} }
absl::Status Overworld::Save(Rom *rom) { absl::Status Overworld::Save(Rom* rom) {
rom_ = rom; rom_ = rom;
if (expanded_tile16_) RETURN_IF_ERROR(SaveMap16Expanded()) if (expanded_tile16_) {
RETURN_IF_ERROR(SaveMap16Tiles()) RETURN_IF_ERROR(SaveMap16Expanded())
if (expanded_tile32_) RETURN_IF_ERROR(SaveMap32Expanded()) } else {
RETURN_IF_ERROR(SaveMap32Tiles()) RETURN_IF_ERROR(SaveMap16Tiles())
}
if (expanded_tile32_) {
RETURN_IF_ERROR(SaveMap32Expanded())
} else {
RETURN_IF_ERROR(SaveMap32Tiles())
}
RETURN_IF_ERROR(SaveOverworldMaps()) RETURN_IF_ERROR(SaveOverworldMaps())
RETURN_IF_ERROR(SaveEntrances()) RETURN_IF_ERROR(SaveEntrances())
RETURN_IF_ERROR(SaveExits()) RETURN_IF_ERROR(SaveExits())
@@ -635,8 +657,8 @@ absl::Status Overworld::SaveOverworldMaps() {
pos = kOverworldMapDataOverflow; // 0x0F8780; pos = kOverworldMapDataOverflow; // 0x0F8780;
} }
const auto compare_array = [](const std::vector<uint8_t> &array1, const auto compare_array = [](const std::vector<uint8_t>& array1,
const std::vector<uint8_t> &array2) -> bool { const std::vector<uint8_t>& array2) -> bool {
if (array1.size() != array2.size()) { if (array1.size() != array2.size()) {
return false; return false;
} }
@@ -750,7 +772,7 @@ absl::Status Overworld::SaveLargeMaps() {
// 0x200 otherwise pos * 0x200 // 0x200 otherwise pos * 0x200
if (overworld_maps_[i].is_large_map()) { if (overworld_maps_[i].is_large_map()) {
const uint8_t large_map_offsets[] = {0, 1, 8, 9}; const uint8_t large_map_offsets[] = {0, 1, 8, 9};
for (const auto &offset : large_map_offsets) { for (const auto& offset : large_map_offsets) {
// Check 1 // Check 1
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
// Check 2 // Check 2
@@ -1048,7 +1070,7 @@ absl::Status Overworld::SaveLargeMaps() {
} }
namespace { namespace {
std::vector<uint64_t> GetAllTile16(OverworldMapTiles &map_tiles_) { std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
int sx = 0; int sx = 0;
@@ -1199,6 +1221,127 @@ absl::Status Overworld::SaveMap32Expanded() {
rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4))); rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4)));
RETURN_IF_ERROR( RETURN_IF_ERROR(
rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5))); rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5)));
constexpr int kTilesPer32x32Tile = 6;
int unique_tile_index = 0;
int num_unique_tiles = tiles32_unique_.size();
for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
if (unique_tile_index >= limit) {
return absl::AbortedError("Too many unique tile32 definitions.");
}
// Top Left.
auto top_left = rom()->version_constants().kMap32TileTL;
RETURN_IF_ERROR(rom()->WriteByte(
top_left + i,
(uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_left + (i + 1),
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_left + (i + 2),
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_left + (i + 3),
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_left + (i + 4),
(uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
0x0F))));
RETURN_IF_ERROR(rom()->WriteByte(
top_left + (i + 5),
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
0xF0) +
((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
0x0F))));
// Top Right.
auto top_right = topRight;
RETURN_IF_ERROR(rom()->WriteByte(
top_right + i,
(uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_right + (i + 1),
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_right + (i + 2),
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_right + (i + 3),
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
top_right + (i + 4),
(uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
0x0F))));
RETURN_IF_ERROR(rom()->WriteByte(
top_right + (i + 5),
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
0xF0) |
((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
0x0F))));
// Bottom Left.
auto bottom_left = bottomLeft;
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + i,
(uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + (i + 1),
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + (i + 2),
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + (i + 3),
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + (i + 4),
(uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
0x0F))));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_left + (i + 5),
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
0xF0) |
((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
0x0F))));
// Bottom Right.
auto bottom_right = bottomRight;
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + i,
(uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + (i + 1),
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + (i + 2),
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + (i + 3),
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + (i + 4),
(uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
0x0F))));
RETURN_IF_ERROR(rom()->WriteByte(
bottom_right + (i + 5),
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
0xF0) |
((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
0x0F))));
unique_tile_index += 4;
}
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -1406,6 +1549,23 @@ absl::Status Overworld::SaveMap16Expanded() {
SnesToPc(0x02FD39), SnesToPc(0x02FD39),
static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16))); static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
int tpos = kMap16TilesExpanded;
for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
{
RETURN_IF_ERROR(
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
tpos += 2;
RETURN_IF_ERROR(
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
tpos += 2;
RETURN_IF_ERROR(
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
tpos += 2;
RETURN_IF_ERROR(
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
tpos += 2;
}
return absl::OkStatus(); return absl::OkStatus();
} }
@@ -1532,7 +1692,7 @@ absl::Status Overworld::SaveItems() {
for (int i = 0; i < kNumOverworldMapItemPointers; i++) { for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
room_items[i] = std::vector<OverworldItem>(); room_items[i] = std::vector<OverworldItem>();
for (const OverworldItem &item : all_items_) { for (const OverworldItem& item : all_items_) {
if (item.room_map_id_ == i) { if (item.room_map_id_ == i) {
room_items[i].emplace_back(item); room_items[i].emplace_back(item);
if (item.id_ == 0x86) { if (item.id_ == 0x86) {
@@ -1570,7 +1730,7 @@ absl::Status Overworld::SaveItems() {
for (int i = 0; i < kNumOverworldMapItemPointers; i++) { for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
if (item_pointers_reuse[i] == -1) { if (item_pointers_reuse[i] == -1) {
item_pointers[i] = data_pos; item_pointers[i] = data_pos;
for (const OverworldItem &item : room_items[i]) { for (const OverworldItem& item : room_items[i]) {
short map_pos = short map_pos =
static_cast<short>(((item.game_y_ << 6) + item.game_x_) << 1); static_cast<short>(((item.game_y_ << 6) + item.game_x_) << 1);
@@ -1657,7 +1817,7 @@ absl::Status Overworld::SaveMapProperties() {
absl::Status Overworld::SaveMusic() { absl::Status Overworld::SaveMusic() {
util::logf("Saving Music Data"); util::logf("Saving Music Data");
// Save music data for Light World maps // Save music data for Light World maps
for (int i = 0; i < kDarkWorldMapIdStart; i++) { for (int i = 0; i < kDarkWorldMapIdStart; i++) {
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicBeginning + i, RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicBeginning + i,
@@ -1672,8 +1832,9 @@ absl::Status Overworld::SaveMusic() {
// Save music data for Dark World maps // Save music data for Dark World maps
for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) { for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart), RETURN_IF_ERROR(
overworld_maps_[i].area_music(0))); rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart),
overworld_maps_[i].area_music(0)));
} }
return absl::OkStatus(); return absl::OkStatus();
@@ -1681,7 +1842,7 @@ absl::Status Overworld::SaveMusic() {
absl::Status Overworld::SaveAreaSizes() { absl::Status Overworld::SaveAreaSizes() {
util::logf("Saving V3 Area Sizes"); util::logf("Saving V3 Area Sizes");
// Check if this is a v3 ROM // Check if this is a v3 ROM
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 3 || asm_version == 0xFF) { if (asm_version < 3 || asm_version == 0xFF) {
@@ -1690,14 +1851,16 @@ absl::Status Overworld::SaveAreaSizes() {
// Save area sizes to the expanded table // Save area sizes to the expanded table
for (int i = 0; i < kNumOverworldMaps; i++) { for (int i = 0; i < kNumOverworldMaps; i++) {
uint8_t area_size_byte = static_cast<uint8_t>(overworld_maps_[i].area_size()); uint8_t area_size_byte =
static_cast<uint8_t>(overworld_maps_[i].area_size());
RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte)); RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
} }
// Save message IDs to expanded table // Save message IDs to expanded table
for (int i = 0; i < kNumOverworldMaps; i++) { for (int i = 0; i < kNumOverworldMaps; i++) {
uint16_t message_id = overworld_maps_[i].message_id(); uint16_t message_id = overworld_maps_[i].message_id();
RETURN_IF_ERROR(rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id)); RETURN_IF_ERROR(
rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
} }
return absl::OkStatus(); return absl::OkStatus();

View File

@@ -41,6 +41,7 @@ add_executable(
dungeon_component_unit_test.cc dungeon_component_unit_test.cc
integration/asar_integration_test.cc integration/asar_integration_test.cc
integration/asar_rom_test.cc integration/asar_rom_test.cc
editor/tile16_editor_test.cc
zelda3/object_parser_test.cc zelda3/object_parser_test.cc
zelda3/object_parser_structs_test.cc zelda3/object_parser_structs_test.cc
zelda3/test_dungeon_objects.cc zelda3/test_dungeon_objects.cc

View File

@@ -0,0 +1,301 @@
#include "app/editor/overworld/tile16_editor.h"
#include <iostream>
#include <memory>
#include <vector>
#include <gtest/gtest.h>
#include "app/rom.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/tilemap.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/core/window.h"
namespace yaze {
namespace editor {
namespace test {
class Tile16EditorIntegrationTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
// Initialize SDL and rendering system once for all tests
InitializeTestEnvironment();
}
static void TearDownTestSuite() {
// Clean up SDL
if (window_initialized_) {
auto shutdown_result = core::ShutdownWindow(test_window_);
(void)shutdown_result; // Suppress unused variable warning
window_initialized_ = false;
}
}
void SetUp() override {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!window_initialized_) {
GTEST_SKIP() << "Failed to initialize graphics system";
}
// Load the test ROM
rom_ = std::make_unique<Rom>();
auto load_result = rom_->LoadFromFile(YAZE_TEST_ROM_PATH);
ASSERT_TRUE(load_result.ok()) << "Failed to load test ROM: " << load_result.message();
// Load overworld data
overworld_ = std::make_unique<zelda3::Overworld>(rom_.get());
auto overworld_load_result = overworld_->Load(rom_.get());
ASSERT_TRUE(overworld_load_result.ok()) << "Failed to load overworld: " << overworld_load_result.message();
// Create tile16 blockset
auto tile16_data = overworld_->tile16_blockset_data();
auto palette = overworld_->current_area_palette();
tile16_blockset_ = std::make_unique<gfx::Tilemap>(
gfx::CreateTilemap(tile16_data, 0x80, 0x2000, 16,
zelda3::kNumTile16Individual, palette));
// Create graphics bitmap
current_gfx_bmp_ = std::make_unique<gfx::Bitmap>();
core::Renderer::Get().CreateAndRenderBitmap(0x80, 512, 0x40,
overworld_->current_graphics(),
*current_gfx_bmp_, palette);
// Create tile16 blockset bitmap
tile16_blockset_bmp_ = std::make_unique<gfx::Bitmap>();
core::Renderer::Get().CreateAndRenderBitmap(0x80, 0x2000, 0x08,
tile16_data,
*tile16_blockset_bmp_, palette);
// Initialize the tile16 editor
editor_ = std::make_unique<Tile16Editor>(rom_.get(), tile16_blockset_.get());
auto init_result = editor_->Initialize(*tile16_blockset_bmp_, *current_gfx_bmp_,
*overworld_->mutable_all_tiles_types());
ASSERT_TRUE(init_result.ok()) << "Failed to initialize editor: " << init_result.message();
rom_loaded_ = true;
#else
// Fallback for non-ROM tests
rom_ = std::make_unique<Rom>();
tilemap_ = std::make_unique<gfx::Tilemap>();
editor_ = std::make_unique<Tile16Editor>(rom_.get(), tilemap_.get());
rom_loaded_ = false;
#endif
}
protected:
static void InitializeTestEnvironment() {
auto window_result = core::CreateWindow(test_window_, SDL_WINDOW_HIDDEN);
if (window_result.ok()) {
window_initialized_ = true;
} else {
window_initialized_ = false;
// Log the error but don't fail test setup
std::cerr << "Failed to initialize test window: " << window_result.message() << std::endl;
}
}
static bool window_initialized_;
static core::Window test_window_;
bool rom_loaded_ = false;
std::unique_ptr<Rom> rom_;
std::unique_ptr<gfx::Tilemap> tilemap_;
std::unique_ptr<gfx::Tilemap> tile16_blockset_;
std::unique_ptr<gfx::Bitmap> current_gfx_bmp_;
std::unique_ptr<gfx::Bitmap> tile16_blockset_bmp_;
std::unique_ptr<zelda3::Overworld> overworld_;
std::unique_ptr<Tile16Editor> editor_;
};
// Static member definitions
bool Tile16EditorIntegrationTest::window_initialized_ = false;
core::Window Tile16EditorIntegrationTest::test_window_;
// Basic validation tests (no ROM required)
TEST_F(Tile16EditorIntegrationTest, BasicValidation) {
// Test with invalid tile ID
EXPECT_FALSE(editor_->IsTile16Valid(-1));
EXPECT_FALSE(editor_->IsTile16Valid(9999));
// Test scratch space operations with invalid slots
auto save_invalid = editor_->SaveTile16ToScratchSpace(-1);
EXPECT_FALSE(save_invalid.ok());
EXPECT_EQ(save_invalid.code(), absl::StatusCode::kInvalidArgument);
auto load_invalid = editor_->LoadTile16FromScratchSpace(5);
EXPECT_FALSE(load_invalid.ok());
EXPECT_EQ(load_invalid.code(), absl::StatusCode::kInvalidArgument);
// Test valid scratch space clearing
auto clear_valid = editor_->ClearScratchSpace(0);
EXPECT_TRUE(clear_valid.ok());
}
// ROM-dependent tests
TEST_F(Tile16EditorIntegrationTest, ValidateTile16DataWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Test validation with properly loaded ROM
auto status = editor_->ValidateTile16Data();
EXPECT_TRUE(status.ok()) << "Validation failed: " << status.message();
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, SetCurrentTileWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Test setting a valid tile
auto valid_tile_result = editor_->SetCurrentTile(0);
EXPECT_TRUE(valid_tile_result.ok()) << "Failed to set tile 0: " << valid_tile_result.message();
auto valid_tile_result2 = editor_->SetCurrentTile(100);
EXPECT_TRUE(valid_tile_result2.ok()) << "Failed to set tile 100: " << valid_tile_result2.message();
// Test invalid ranges still fail
auto invalid_low = editor_->SetCurrentTile(-1);
EXPECT_FALSE(invalid_low.ok());
EXPECT_EQ(invalid_low.code(), absl::StatusCode::kOutOfRange);
auto invalid_high = editor_->SetCurrentTile(10000);
EXPECT_FALSE(invalid_high.ok());
EXPECT_EQ(invalid_high.code(), absl::StatusCode::kOutOfRange);
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, FlipOperationsWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Set a valid tile first
auto set_result = editor_->SetCurrentTile(1);
ASSERT_TRUE(set_result.ok()) << "Failed to set initial tile: " << set_result.message();
// Test flip operations
auto flip_h_result = editor_->FlipTile16Horizontal();
EXPECT_TRUE(flip_h_result.ok()) << "Horizontal flip failed: " << flip_h_result.message();
auto flip_v_result = editor_->FlipTile16Vertical();
EXPECT_TRUE(flip_v_result.ok()) << "Vertical flip failed: " << flip_v_result.message();
auto rotate_result = editor_->RotateTile16();
EXPECT_TRUE(rotate_result.ok()) << "Rotation failed: " << rotate_result.message();
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, UndoRedoWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Set a tile and perform an operation to create undo state
auto set_result = editor_->SetCurrentTile(1);
ASSERT_TRUE(set_result.ok());
auto clear_result = editor_->ClearTile16();
ASSERT_TRUE(clear_result.ok()) << "Clear operation failed: " << clear_result.message();
// Test undo
auto undo_result = editor_->Undo();
EXPECT_TRUE(undo_result.ok()) << "Undo failed: " << undo_result.message();
// Test redo
auto redo_result = editor_->Redo();
EXPECT_TRUE(redo_result.ok()) << "Redo failed: " << redo_result.message();
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, PaletteOperationsWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Test palette cycling
auto cycle_forward = editor_->CyclePalette(true);
EXPECT_TRUE(cycle_forward.ok()) << "Palette cycle forward failed: " << cycle_forward.message();
auto cycle_backward = editor_->CyclePalette(false);
EXPECT_TRUE(cycle_backward.ok()) << "Palette cycle backward failed: " << cycle_backward.message();
// Test valid palette preview
auto valid_palette = editor_->PreviewPaletteChange(3);
EXPECT_TRUE(valid_palette.ok()) << "Palette preview failed: " << valid_palette.message();
// Test invalid palette
auto invalid_palette = editor_->PreviewPaletteChange(10);
EXPECT_FALSE(invalid_palette.ok());
EXPECT_EQ(invalid_palette.code(), absl::StatusCode::kInvalidArgument);
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, CopyPasteOperationsWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Set a tile first
auto set_result = editor_->SetCurrentTile(10);
ASSERT_TRUE(set_result.ok());
// Test copy operation
auto copy_result = editor_->CopyTile16ToClipboard(10);
EXPECT_TRUE(copy_result.ok()) << "Copy failed: " << copy_result.message();
// Test paste operation
auto paste_result = editor_->PasteTile16FromClipboard();
EXPECT_TRUE(paste_result.ok()) << "Paste failed: " << paste_result.message();
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
TEST_F(Tile16EditorIntegrationTest, ScratchSpaceWithROM) {
#ifdef YAZE_ENABLE_ROM_TESTS
if (!rom_loaded_) {
GTEST_SKIP() << "ROM not loaded, skipping integration test";
}
// Set a tile first
auto set_result = editor_->SetCurrentTile(15);
ASSERT_TRUE(set_result.ok());
// Test scratch space save
auto save_result = editor_->SaveTile16ToScratchSpace(0);
EXPECT_TRUE(save_result.ok()) << "Scratch save failed: " << save_result.message();
// Test scratch space load
auto load_result = editor_->LoadTile16FromScratchSpace(0);
EXPECT_TRUE(load_result.ok()) << "Scratch load failed: " << load_result.message();
// Test scratch space clear
auto clear_result = editor_->ClearScratchSpace(0);
EXPECT_TRUE(clear_result.ok()) << "Scratch clear failed: " << clear_result.message();
#else
GTEST_SKIP() << "ROM tests disabled";
#endif
}
} // namespace test
} // namespace editor
} // namespace yaze