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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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(¤t_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
|
||||
@@ -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
@@ -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 ¤t_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
|
||||
|
||||
Reference in New Issue
Block a user