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
if (!current_rom_) {
// Only auto-show welcome screen if it hasn't been manually closed
if (!welcome_screen_manually_closed_) {
// Only show welcome screen for truly empty state, not when ROM is loaded but current_rom_ is null
if (sessions_.empty() && !welcome_screen_manually_closed_) {
show_welcome_screen_ = true;
}
return absl::OkStatus();
}
// Check if any editors are active across ALL sessions
bool any_editor_active = false;
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;
}
// ROM is loaded and valid - don't auto-show welcome screen
// Welcome screen should only be shown manually at this point
// Iterate through ALL sessions to support multi-session docking
for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
@@ -819,6 +804,21 @@ absl::Status EditorManager::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
current_rom_ = prev_rom;
current_editor_set_ = prev_editor_set;
@@ -1285,6 +1285,13 @@ void EditorManager::DrawMenuBar() {
if (show_palette_editor_ && current_editor_set_) {
Begin("Palette Editor", &show_palette_editor_);
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();
}
@@ -1514,8 +1521,8 @@ absl::Status EditorManager::LoadRom() {
manager.Save();
RETURN_IF_ERROR(LoadAssets());
// Reset welcome screen state when ROM is loaded
welcome_screen_manually_closed_ = false;
// Hide welcome screen when ROM is successfully loaded - don't reset manual close state
show_welcome_screen_ = false;
return absl::OkStatus();
}

View File

@@ -39,7 +39,6 @@ namespace yaze::editor {
using core::Renderer;
using namespace ImGui;
constexpr int kTile16Size = 0x10;
constexpr float kInputFieldSize = 30.f;
void OverworldEditor::Initialize() {
@@ -161,6 +160,7 @@ void OverworldEditor::Initialize() {
}
HOVER_HINT("Copy Map to Clipboard");
});
}
absl::Status OverworldEditor::Load() {
@@ -956,6 +956,9 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
const int large_map_size = 1024;
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
int map_x = (mouse_position.x - canvas_zero_point.x) / 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_);
}
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();
}
if (IsItemHovered()) status_ = CheckForCurrentMap();
@@ -1117,31 +1121,38 @@ absl::Status OverworldEditor::DrawTile16Selector() {
ImGui::BeginGroup();
gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
blockset_canvas_.DrawBackground();
gui::EndNoPadding();
{
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
map_blockset_loaded_, /*scale=*/2);
gui::EndPadding(); // Fixed: was EndNoPadding()
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
map_blockset_loaded_, /*scale=*/2);
if (blockset_canvas_.DrawTileSelector(32.0f)) {
// Open the tile16 editor to the tile
auto tile_pos = blockset_canvas_.points().front();
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;
// Improved tile interaction detection - use proper canvas interaction
bool tile_selected = false;
if (blockset_canvas_.DrawTileSelector(32.0f)) {
tile_selected = true;
} else if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && blockset_canvas_.IsMouseHovering()) {
// 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));
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();
ImGui::EndGroup();
return absl::OkStatus();
@@ -1223,6 +1234,10 @@ absl::Status OverworldEditor::DrawTileSelector() {
status_ = DrawAreaGraphics();
EndTabItem();
}
if (BeginTabItem("Scratch Space")) {
status_ = DrawScratchSpace();
EndTabItem();
}
EndTabBar();
}
return absl::OkStatus();
@@ -2816,4 +2831,426 @@ absl::Status OverworldEditor::UpdateROMVersionMarkers(int target_version) {
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 HandleMapInteraction();
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();
void DrawUsageGrid();
@@ -253,6 +263,23 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
// Map properties system for UI organization
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_;
@@ -295,6 +322,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), gui::CanvasGridSize::k32x32};
gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags};
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
#include <array>
#include <chrono>
#include <functional>
#include <vector>
#include "absl/status/status.h"
@@ -19,29 +21,43 @@
namespace yaze {
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
*/
class Tile16Editor : public gfx::GfxContext {
public:
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset)
Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
: rom_(rom), tile16_blockset_(tile16_blockset) {}
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp,
const gfx::Bitmap &current_gfx_bmp,
std::array<uint8_t, 0x200> &all_tiles_types);
absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
const gfx::Bitmap& current_gfx_bmp,
std::array<uint8_t, 0x200>& all_tiles_types);
absl::Status Update();
void DrawTile16Editor();
absl::Status UpdateTile16Transfer();
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 UpdateTile16Edit();
absl::Status UpdateTransferTileCanvas();
absl::Status LoadTile8();
absl::Status SetCurrentTile(int id);
@@ -53,14 +69,61 @@ class Tile16Editor : public gfx::GfxContext {
absl::Status LoadTile16FromScratchSpace(int slot);
absl::Status ClearScratchSpace(int slot);
void set_rom(Rom *rom) { rom_ = rom; }
Rom *rom() const { return rom_; }
// Advanced editing features
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:
Rom *rom_ = nullptr;
Rom* rom_ = nullptr;
bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
bool transfer_blockset_loaded_ = false;
bool x_flip = false;
bool y_flip = false;
bool priority_tile = false;
@@ -78,19 +141,67 @@ class Tile16Editor : public gfx::GfxContext {
std::array<gfx::Bitmap, 4> scratch_space_;
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<uint8_t> notify_palette;
std::array<uint8_t, 0x200> all_tiles_types_;
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32,};
gui::Canvas blockset_canvas_{
"blocksetCanvas", ImVec2(kTilesheetEditorWidth, kTilesheetEditorHeight),
gui::CanvasGridSize::k32x32};
gfx::Bitmap tile16_blockset_bmp_;
// Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
// Canvas for editing the selected tile - smaller size for table fit
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas",
ImVec2(128, 128), // Reduced from kTile16CanvasSize to fit tables
gui::CanvasGridSize::k64x64, 2.0f}; // Reduced scale to fit tables
gfx::Bitmap current_tile16_bmp_;
// 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};
gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
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_;
PaletteEditor palette_editor_;
gfx::SnesPalette palette_;
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