Refactor tile selection and painting logic in OverworldEditor and Tile16Editor
- Updated tile selection handling in Tile16Editor to prevent conflicts with dragging, ensuring a smoother user experience. - Improved click detection for tile selection and painting, enhancing accuracy and responsiveness. - Added logging for tile interactions to facilitate debugging and user feedback. - Implemented critical fixes to ensure consistent behavior during tile editing operations.
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/core/features.h"
|
||||
#include "app/core/performance_monitor.h"
|
||||
#include "app/core/window.h"
|
||||
@@ -20,6 +19,7 @@
|
||||
#include "app/editor/overworld/tile16_editor.h"
|
||||
#include "app/gfx/arena.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/tilemap.h"
|
||||
#include "app/gui/canvas.h"
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "util/log.h"
|
||||
#include "util/macro.h"
|
||||
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
using core::Renderer;
|
||||
@@ -142,19 +143,19 @@ absl::Status OverworldEditor::Load() {
|
||||
RETURN_IF_ERROR(
|
||||
tile16_editor_.Initialize(tile16_blockset_bmp_, current_gfx_bmp_,
|
||||
*overworld_.mutable_all_tiles_types()));
|
||||
|
||||
|
||||
// Set up callback for when tile16 changes are committed
|
||||
tile16_editor_.set_on_changes_committed([this]() -> absl::Status {
|
||||
// Regenerate the overworld editor's tile16 blockset
|
||||
RETURN_IF_ERROR(RefreshTile16Blockset());
|
||||
|
||||
|
||||
// Force refresh of the current overworld map to show changes
|
||||
RefreshOverworldMap();
|
||||
|
||||
|
||||
util::logf("Overworld editor refreshed after Tile16 changes");
|
||||
return absl::OkStatus();
|
||||
});
|
||||
|
||||
|
||||
ASSIGN_OR_RETURN(entrance_tiletypes_, zelda3::LoadEntranceTileTypes(rom_));
|
||||
all_gfx_loaded_ = true;
|
||||
return absl::OkStatus();
|
||||
@@ -162,10 +163,10 @@ absl::Status OverworldEditor::Load() {
|
||||
|
||||
absl::Status OverworldEditor::Update() {
|
||||
status_ = absl::OkStatus();
|
||||
|
||||
|
||||
// Process deferred textures for smooth loading
|
||||
ProcessDeferredTextures();
|
||||
|
||||
|
||||
if (overworld_canvas_fullscreen_) {
|
||||
DrawFullscreenCanvas();
|
||||
return status_;
|
||||
@@ -722,21 +723,22 @@ void OverworldEditor::DrawOverworldMaps() {
|
||||
int yy = 0;
|
||||
for (int i = 0; i < 0x40; i++) {
|
||||
int world_index = i + (current_world_ * 0x40);
|
||||
|
||||
|
||||
// Bounds checking to prevent crashes
|
||||
if (world_index < 0 || world_index >= static_cast<int>(maps_bmp_.size())) {
|
||||
continue; // Skip invalid map index
|
||||
continue; // Skip invalid map index
|
||||
}
|
||||
|
||||
|
||||
int scale = static_cast<int>(ow_map_canvas_.global_scale());
|
||||
int map_x = (xx * kOverworldMapSize * scale);
|
||||
int map_y = (yy * kOverworldMapSize * scale);
|
||||
|
||||
|
||||
// Check if the map has a texture, if not, ensure it gets loaded
|
||||
if (!maps_bmp_[world_index].texture() && maps_bmp_[world_index].is_active()) {
|
||||
if (!maps_bmp_[world_index].texture() &&
|
||||
maps_bmp_[world_index].is_active()) {
|
||||
EnsureMapTexture(world_index);
|
||||
}
|
||||
|
||||
|
||||
// Only draw if the map has a texture or is the currently selected map
|
||||
if (maps_bmp_[world_index].texture() || world_index == current_map_) {
|
||||
ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y,
|
||||
@@ -745,21 +747,24 @@ void OverworldEditor::DrawOverworldMaps() {
|
||||
// Draw a placeholder for maps that haven't loaded yet
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 canvas_pos = ow_map_canvas_.zero_point();
|
||||
ImVec2 placeholder_pos = ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y);
|
||||
ImVec2 placeholder_size = ImVec2(kOverworldMapSize * scale, kOverworldMapSize * scale);
|
||||
|
||||
ImVec2 placeholder_pos =
|
||||
ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y);
|
||||
ImVec2 placeholder_size =
|
||||
ImVec2(kOverworldMapSize * scale, kOverworldMapSize * scale);
|
||||
|
||||
// Draw a subtle loading indicator
|
||||
draw_list->AddRectFilled(placeholder_pos,
|
||||
ImVec2(placeholder_pos.x + placeholder_size.x,
|
||||
placeholder_pos.y + placeholder_size.y),
|
||||
IM_COL32(32, 32, 32, 128)); // Dark gray with transparency
|
||||
|
||||
draw_list->AddRectFilled(
|
||||
placeholder_pos,
|
||||
ImVec2(placeholder_pos.x + placeholder_size.x,
|
||||
placeholder_pos.y + placeholder_size.y),
|
||||
IM_COL32(32, 32, 32, 128)); // Dark gray with transparency
|
||||
|
||||
// Draw loading text
|
||||
ImVec2 text_pos = ImVec2(placeholder_pos.x + placeholder_size.x / 2 - 20,
|
||||
placeholder_pos.y + placeholder_size.y / 2);
|
||||
placeholder_pos.y + placeholder_size.y / 2);
|
||||
draw_list->AddText(text_pos, IM_COL32(128, 128, 128, 255), "Loading...");
|
||||
}
|
||||
|
||||
|
||||
xx++;
|
||||
if (xx >= 8) {
|
||||
yy++;
|
||||
@@ -783,23 +788,29 @@ void OverworldEditor::DrawOverworldEdits() {
|
||||
|
||||
// Bounds checking to prevent crashes
|
||||
if (current_map_ < 0 || current_map_ >= static_cast<int>(maps_bmp_.size())) {
|
||||
return; // Invalid map index, skip drawing
|
||||
return; // Invalid map index, skip drawing
|
||||
}
|
||||
|
||||
// Validate tile16_blockset_ before calling GetTilemapData
|
||||
if (!tile16_blockset_.atlas.is_active() || tile16_blockset_.atlas.vector().empty()) {
|
||||
util::logf("Error: tile16_blockset_ is not properly initialized (active: %s, size: %zu)",
|
||||
tile16_blockset_.atlas.is_active() ? "true" : "false",
|
||||
tile16_blockset_.atlas.vector().size());
|
||||
return; // Skip drawing if blockset is invalid
|
||||
if (!tile16_blockset_.atlas.is_active() ||
|
||||
tile16_blockset_.atlas.vector().empty()) {
|
||||
util::logf(
|
||||
"Error: tile16_blockset_ is not properly initialized (active: %s, "
|
||||
"size: %zu)",
|
||||
tile16_blockset_.atlas.is_active() ? "true" : "false",
|
||||
tile16_blockset_.atlas.vector().size());
|
||||
return; // Skip drawing if blockset is invalid
|
||||
}
|
||||
|
||||
|
||||
// Validate current_tile16_ before proceeding
|
||||
if (current_tile16_ < 0 || current_tile16_ >= 512) {
|
||||
util::logf("ERROR: DrawOverworldEdits - Invalid current_tile16_=%d (should be 0-511)", current_tile16_);
|
||||
util::logf(
|
||||
"ERROR: DrawOverworldEdits - Invalid current_tile16_=%d (should be "
|
||||
"0-511)",
|
||||
current_tile16_);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Render the updated map bitmap.
|
||||
auto tile_data = gfx::GetTilemapData(tile16_blockset_, current_tile16_);
|
||||
RenderUpdatedMapBitmap(mouse_position, tile_data);
|
||||
@@ -830,9 +841,11 @@ void OverworldEditor::RenderUpdatedMapBitmap(
|
||||
|
||||
// Bounds checking to prevent crashes
|
||||
if (current_map_ < 0 || current_map_ >= static_cast<int>(maps_bmp_.size())) {
|
||||
util::logf("ERROR: RenderUpdatedMapBitmap - Invalid current_map_ %d (maps_bmp_.size()=%zu)",
|
||||
current_map_, maps_bmp_.size());
|
||||
return; // Invalid map index, skip rendering
|
||||
util::logf(
|
||||
"ERROR: RenderUpdatedMapBitmap - Invalid current_map_ %d "
|
||||
"(maps_bmp_.size()=%zu)",
|
||||
current_map_, maps_bmp_.size());
|
||||
return; // Invalid map index, skip rendering
|
||||
}
|
||||
|
||||
// Calculate the tile index for x and y based on the click_position
|
||||
@@ -851,8 +864,11 @@ void OverworldEditor::RenderUpdatedMapBitmap(
|
||||
|
||||
// Validate bitmap state before writing
|
||||
if (!current_bitmap.is_active() || current_bitmap.size() == 0) {
|
||||
util::logf("ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no data (active=%s, size=%zu)",
|
||||
current_map_, current_bitmap.is_active() ? "true" : "false", current_bitmap.size());
|
||||
util::logf(
|
||||
"ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no "
|
||||
"data (active=%s, size=%zu)",
|
||||
current_map_, current_bitmap.is_active() ? "true" : "false",
|
||||
current_bitmap.size());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -862,17 +878,23 @@ void OverworldEditor::RenderUpdatedMapBitmap(
|
||||
(start_position.y + y) * kOverworldMapSize + (start_position.x + x);
|
||||
|
||||
// Bounds check for pixel index
|
||||
if (pixel_index < 0 || pixel_index >= static_cast<int>(current_bitmap.size())) {
|
||||
util::logf("ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds (bitmap size=%zu)",
|
||||
pixel_index, current_bitmap.size());
|
||||
if (pixel_index < 0 ||
|
||||
pixel_index >= static_cast<int>(current_bitmap.size())) {
|
||||
util::logf(
|
||||
"ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds "
|
||||
"(bitmap size=%zu)",
|
||||
pixel_index, current_bitmap.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bounds check for tile data
|
||||
int tile_data_index = y * kTile16Size + x;
|
||||
if (tile_data_index < 0 || tile_data_index >= static_cast<int>(tile_data.size())) {
|
||||
util::logf("ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds (tile_data size=%zu)",
|
||||
tile_data_index, tile_data.size());
|
||||
if (tile_data_index < 0 ||
|
||||
tile_data_index >= static_cast<int>(tile_data.size())) {
|
||||
util::logf(
|
||||
"ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds "
|
||||
"(tile_data size=%zu)",
|
||||
tile_data_index, tile_data.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -881,7 +903,7 @@ void OverworldEditor::RenderUpdatedMapBitmap(
|
||||
}
|
||||
|
||||
current_bitmap.set_modified(true);
|
||||
|
||||
|
||||
// Immediately update the texture to reflect changes
|
||||
core::Renderer::Get().UpdateBitmap(¤t_bitmap);
|
||||
}
|
||||
@@ -901,7 +923,7 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
util::logf("CheckForOverworldEdits: About to apply rectangle selection");
|
||||
|
||||
|
||||
auto& selected_world =
|
||||
(current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
|
||||
: (current_world_ == 1)
|
||||
@@ -926,7 +948,10 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
// Number of tiles per local map (since each tile is 16x16)
|
||||
constexpr int tiles_per_local_map = local_map_size / kTile16Size;
|
||||
|
||||
util::logf("CheckForOverworldEdits: About to fill rectangle with current_tile16_=%d", current_tile16_);
|
||||
util::logf(
|
||||
"CheckForOverworldEdits: About to fill rectangle with "
|
||||
"current_tile16_=%d",
|
||||
current_tile16_);
|
||||
|
||||
// Apply the current selected tile to each position in the rectangle
|
||||
for (int y = start_y; y <= end_y; y += kTile16Size) {
|
||||
@@ -942,24 +967,31 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
// Calculate the index within the overall map structure
|
||||
int index_x = local_map_x * tiles_per_local_map + tile16_x;
|
||||
int index_y = local_map_y * tiles_per_local_map + tile16_y;
|
||||
|
||||
|
||||
// Bounds check for the selected world array
|
||||
if (index_x >= 0 && index_x < 0x200 && index_y >= 0 && index_y < 0x200) {
|
||||
if (index_x >= 0 && index_x < 0x200 && index_y >= 0 &&
|
||||
index_y < 0x200) {
|
||||
// CRITICAL FIX: Set the tile to the currently selected tile16, not read from canvas
|
||||
selected_world[index_x][index_y] = current_tile16_;
|
||||
|
||||
|
||||
// CRITICAL FIX: Also update the bitmap directly like single tile drawing
|
||||
ImVec2 tile_position(x, y);
|
||||
auto tile_data = gfx::GetTilemapData(tile16_blockset_, current_tile16_);
|
||||
auto tile_data =
|
||||
gfx::GetTilemapData(tile16_blockset_, current_tile16_);
|
||||
if (!tile_data.empty()) {
|
||||
RenderUpdatedMapBitmap(tile_position, tile_data);
|
||||
util::logf("CheckForOverworldEdits: Updated bitmap at position (%d,%d) with tile16_id=%d",
|
||||
x, y, current_tile16_);
|
||||
util::logf(
|
||||
"CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
|
||||
"with tile16_id=%d",
|
||||
x, y, current_tile16_);
|
||||
} else {
|
||||
util::logf("ERROR: Failed to get tile data for tile16_id=%d", current_tile16_);
|
||||
util::logf("ERROR: Failed to get tile data for tile16_id=%d",
|
||||
current_tile16_);
|
||||
}
|
||||
} else {
|
||||
util::logf("ERROR: Rectangle selection position [%d,%d] out of bounds", index_x, index_y);
|
||||
util::logf(
|
||||
"ERROR: Rectangle selection position [%d,%d] out of bounds",
|
||||
index_x, index_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -967,7 +999,8 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
// Clear the rectangle selection after applying
|
||||
ow_map_canvas_.mutable_selected_tiles()->clear();
|
||||
ow_map_canvas_.mutable_points()->clear();
|
||||
util::logf("CheckForOverworldEdits: Rectangle selection applied and cleared");
|
||||
util::logf(
|
||||
"CheckForOverworldEdits: Rectangle selection applied and cleared");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1125,7 +1158,7 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
||||
if (!current_map_lock_) {
|
||||
current_map_ = hovered_map;
|
||||
current_parent_ = overworld_.overworld_map(current_map_)->parent();
|
||||
|
||||
|
||||
// Ensure the current map is built (on-demand loading)
|
||||
RETURN_IF_ERROR(overworld_.EnsureMapBuilt(current_map_));
|
||||
}
|
||||
@@ -1165,7 +1198,7 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
||||
|
||||
// Ensure current map has texture created for rendering
|
||||
EnsureMapTexture(current_map_);
|
||||
|
||||
|
||||
if (maps_bmp_[current_map_].modified()) {
|
||||
RefreshOverworldMap();
|
||||
RETURN_IF_ERROR(RefreshTile16Blockset());
|
||||
@@ -1280,47 +1313,35 @@ absl::Status OverworldEditor::DrawTile16Selector() {
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
|
||||
map_blockset_loaded_, /*scale=*/2);
|
||||
bool tile_selected = false;
|
||||
|
||||
// Fixed tile interaction detection - handle single clicks properly
|
||||
bool tile_selected = false;
|
||||
|
||||
// First, call DrawTileSelector to handle the visual feedback
|
||||
blockset_canvas_.DrawTileSelector(32.0f);
|
||||
|
||||
// Handle both single click (select) and double click (edit) properly
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && blockset_canvas_.IsMouseHovering()) {
|
||||
// Call DrawTileSelector after event detection for visual feedback
|
||||
if (blockset_canvas_.DrawTileSelector(32.0f)) {
|
||||
tile_selected = true;
|
||||
show_tile16_editor_ = true;
|
||||
}
|
||||
|
||||
// Check for double click to open tile16 editor
|
||||
bool open_tile16_editor = false;
|
||||
if (blockset_canvas_.IsMouseHovering() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
|
||||
// Then check for single click (if not double-click)
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
blockset_canvas_.IsMouseHovering()) {
|
||||
tile_selected = true;
|
||||
open_tile16_editor = true;
|
||||
}
|
||||
|
||||
if (tile_selected) {
|
||||
// Get mouse position relative to canvas
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImVec2 canvas_pos = blockset_canvas_.zero_point();
|
||||
ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
|
||||
|
||||
ImVec2 mouse_pos =
|
||||
ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
|
||||
|
||||
// Calculate grid position (32x32 tiles in blockset)
|
||||
int grid_x = static_cast<int>(mouse_pos.x / 32);
|
||||
int grid_y = static_cast<int>(mouse_pos.y / 32);
|
||||
int id = grid_x + grid_y * 8; // 8 tiles per row in blockset
|
||||
int id = grid_x + grid_y * 8; // 8 tiles per row in blockset
|
||||
|
||||
if (id != current_tile16_ && id >= 0 && id < 512) {
|
||||
current_tile16_ = id;
|
||||
// CRITICAL FIX: Always sync tile16 editor with overworld editor selection
|
||||
RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id));
|
||||
|
||||
if (open_tile16_editor) {
|
||||
show_tile16_editor_ = true;
|
||||
util::logf("Opened Tile16 editor for tile %d (double-click)", id);
|
||||
} else {
|
||||
util::logf("Selected Tile16: %d (single-click)", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1674,7 +1695,7 @@ absl::Status OverworldEditor::Save() {
|
||||
|
||||
absl::Status OverworldEditor::LoadGraphics() {
|
||||
core::ScopedTimer timer("LoadGraphics");
|
||||
|
||||
|
||||
util::logf("Loading overworld.");
|
||||
// Load the Link to the Past overworld.
|
||||
{
|
||||
@@ -1684,22 +1705,22 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
palette_ = overworld_.current_area_palette();
|
||||
|
||||
util::logf("Loading overworld graphics (optimized).");
|
||||
|
||||
|
||||
// Phase 1: Create bitmaps without textures for faster loading
|
||||
// This avoids blocking the main thread with GPU texture creation
|
||||
{
|
||||
core::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
|
||||
Renderer::Get().CreateBitmapWithoutTexture(0x80, kOverworldMapSize, 0x40,
|
||||
overworld_.current_graphics(),
|
||||
current_gfx_bmp_, palette_);
|
||||
overworld_.current_graphics(),
|
||||
current_gfx_bmp_, palette_);
|
||||
}
|
||||
|
||||
util::logf("Loading overworld tileset (deferred textures).");
|
||||
{
|
||||
core::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
|
||||
Renderer::Get().CreateBitmapWithoutTexture(0x80, 0x2000, 0x08,
|
||||
overworld_.tile16_blockset_data(),
|
||||
tile16_blockset_bmp_, palette_);
|
||||
Renderer::Get().CreateBitmapWithoutTexture(
|
||||
0x80, 0x2000, 0x08, overworld_.tile16_blockset_data(),
|
||||
tile16_blockset_bmp_, palette_);
|
||||
}
|
||||
map_blockset_loaded_ = true;
|
||||
|
||||
@@ -1718,28 +1739,34 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
// Non-essential maps will be created on-demand when accessed
|
||||
constexpr int kEssentialMapsPerWorld = 8;
|
||||
constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
|
||||
constexpr int kDarkWorldEssential = zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
|
||||
constexpr int kSpecialWorldEssential = zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
|
||||
|
||||
util::logf("Creating bitmaps for essential maps only (first %d maps per world)", kEssentialMapsPerWorld);
|
||||
|
||||
constexpr int kDarkWorldEssential =
|
||||
zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
|
||||
constexpr int kSpecialWorldEssential =
|
||||
zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
|
||||
|
||||
util::logf(
|
||||
"Creating bitmaps for essential maps only (first %d maps per world)",
|
||||
kEssentialMapsPerWorld);
|
||||
|
||||
std::vector<gfx::Bitmap*> maps_to_texture;
|
||||
maps_to_texture.reserve(kEssentialMapsPerWorld * 3); // 8 maps per world * 3 worlds
|
||||
|
||||
maps_to_texture.reserve(kEssentialMapsPerWorld *
|
||||
3); // 8 maps per world * 3 worlds
|
||||
|
||||
{
|
||||
core::ScopedTimer maps_timer("CreateEssentialOverworldMaps");
|
||||
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
|
||||
bool is_essential = false;
|
||||
|
||||
|
||||
// Check if this is an essential map
|
||||
if (i < kLightWorldEssential) {
|
||||
is_essential = true;
|
||||
} else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) {
|
||||
is_essential = true;
|
||||
} else if (i >= zelda3::kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
|
||||
} else if (i >= zelda3::kSpecialWorldMapIdStart &&
|
||||
i < kSpecialWorldEssential) {
|
||||
is_essential = true;
|
||||
}
|
||||
|
||||
|
||||
if (is_essential) {
|
||||
overworld_.set_current_map(i);
|
||||
auto palette = overworld_.current_area_palette();
|
||||
@@ -1750,7 +1777,8 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
maps_bmp_[i].SetPalette(palette);
|
||||
maps_to_texture.push_back(&maps_bmp_[i]);
|
||||
} catch (const std::bad_alloc& e) {
|
||||
std::cout << "Error allocating map " << i << ": " << e.what() << std::endl;
|
||||
std::cout << "Error allocating map " << i << ": " << e.what()
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1760,16 +1788,17 @@ absl::Status OverworldEditor::LoadGraphics() {
|
||||
|
||||
// Phase 3: Create textures only for currently visible maps
|
||||
// Only create textures for the first few maps initially
|
||||
const int initial_texture_count = std::min(4, static_cast<int>(maps_to_texture.size()));
|
||||
const int initial_texture_count =
|
||||
std::min(4, static_cast<int>(maps_to_texture.size()));
|
||||
{
|
||||
core::ScopedTimer initial_textures_timer("CreateInitialTextures");
|
||||
for (int i = 0; i < initial_texture_count; ++i) {
|
||||
Renderer::Get().RenderBitmap(maps_to_texture[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Store remaining maps for lazy texture creation
|
||||
deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count,
|
||||
deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count,
|
||||
maps_to_texture.end());
|
||||
|
||||
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
|
||||
@@ -1805,15 +1834,15 @@ absl::Status OverworldEditor::LoadSpriteGraphics() {
|
||||
|
||||
void OverworldEditor::ProcessDeferredTextures() {
|
||||
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
|
||||
|
||||
|
||||
if (deferred_map_textures_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Priority-based loading: process more textures for visible maps
|
||||
const int textures_per_frame = 8; // Increased from 2 to 8 for faster loading
|
||||
const int textures_per_frame = 8; // Increased from 2 to 8 for faster loading
|
||||
int processed = 0;
|
||||
|
||||
|
||||
// First pass: prioritize textures for the current world
|
||||
auto it = deferred_map_textures_.begin();
|
||||
while (it != deferred_map_textures_.end() && processed < textures_per_frame) {
|
||||
@@ -1826,18 +1855,19 @@ void OverworldEditor::ProcessDeferredTextures() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool is_current_world = false;
|
||||
if (map_index >= 0) {
|
||||
int map_world = map_index / 0x40; // 64 maps per world
|
||||
int map_world = map_index / 0x40; // 64 maps per world
|
||||
is_current_world = (map_world == current_world_);
|
||||
}
|
||||
|
||||
|
||||
// Prioritize current world maps, but also process others if we have capacity
|
||||
if (is_current_world || processed < textures_per_frame / 2) {
|
||||
Renderer::Get().RenderBitmap(*it);
|
||||
processed++;
|
||||
it = deferred_map_textures_.erase(it); // Remove immediately after processing
|
||||
it = deferred_map_textures_.erase(
|
||||
it); // Remove immediately after processing
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@@ -1845,11 +1875,12 @@ void OverworldEditor::ProcessDeferredTextures() {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Second pass: process remaining textures if we still have capacity
|
||||
if (processed < textures_per_frame) {
|
||||
it = deferred_map_textures_.begin();
|
||||
while (it != deferred_map_textures_.end() && processed < textures_per_frame) {
|
||||
while (it != deferred_map_textures_.end() &&
|
||||
processed < textures_per_frame) {
|
||||
if (*it && !(*it)->texture()) {
|
||||
Renderer::Get().RenderBitmap(*it);
|
||||
processed++;
|
||||
@@ -1859,10 +1890,11 @@ void OverworldEditor::ProcessDeferredTextures() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Third pass: process deferred map refreshes for visible maps
|
||||
if (processed < textures_per_frame) {
|
||||
for (int i = 0; i < zelda3::kNumOverworldMaps && processed < textures_per_frame; ++i) {
|
||||
for (int i = 0;
|
||||
i < zelda3::kNumOverworldMaps && processed < textures_per_frame; ++i) {
|
||||
if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) {
|
||||
// Check if this map is visible
|
||||
bool is_visible = (i == current_map_) || (i / 0x40 == current_world_);
|
||||
@@ -1879,16 +1911,16 @@ void OverworldEditor::EnsureMapTexture(int map_index) {
|
||||
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Ensure the map is built first (on-demand loading)
|
||||
auto status = overworld_.EnsureMapBuilt(map_index);
|
||||
if (!status.ok()) {
|
||||
util::logf("Failed to build map %d: %s", map_index, status.message());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto& bitmap = maps_bmp_[map_index];
|
||||
|
||||
|
||||
// If bitmap doesn't exist yet (non-essential map), create it now
|
||||
if (!bitmap.is_active()) {
|
||||
overworld_.set_current_map(map_index);
|
||||
@@ -1902,13 +1934,14 @@ void OverworldEditor::EnsureMapTexture(int map_index) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!bitmap.texture() && bitmap.is_active()) {
|
||||
Renderer::Get().RenderBitmap(&bitmap);
|
||||
|
||||
|
||||
// Remove from deferred list if it was there
|
||||
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
|
||||
auto it = std::find(deferred_map_textures_.begin(), deferred_map_textures_.end(), &bitmap);
|
||||
auto it = std::find(deferred_map_textures_.begin(),
|
||||
deferred_map_textures_.end(), &bitmap);
|
||||
if (it != deferred_map_textures_.end()) {
|
||||
deferred_map_textures_.erase(it);
|
||||
}
|
||||
@@ -1946,18 +1979,18 @@ void OverworldEditor::RefreshOverworldMapOnDemand(int map_index) {
|
||||
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check if the map is actually visible or being edited
|
||||
bool is_current_map = (map_index == current_map_);
|
||||
bool is_current_world = (map_index / 0x40 == current_world_);
|
||||
|
||||
|
||||
// For non-current maps in non-current worlds, defer the refresh
|
||||
if (!is_current_map && !is_current_world) {
|
||||
// Mark for deferred refresh - will be processed when the map becomes visible
|
||||
maps_bmp_[map_index].set_modified(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// For visible maps, do immediate refresh
|
||||
RefreshChildMapOnDemand(map_index);
|
||||
}
|
||||
@@ -1967,45 +2000,50 @@ void OverworldEditor::RefreshOverworldMapOnDemand(int map_index) {
|
||||
*/
|
||||
void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
|
||||
auto* map = overworld_.mutable_overworld_map(map_index);
|
||||
|
||||
|
||||
// Check what actually needs to be refreshed
|
||||
bool needs_graphics_rebuild = maps_bmp_[map_index].modified();
|
||||
bool needs_palette_rebuild = false; // Could be tracked more granularly
|
||||
|
||||
bool needs_palette_rebuild = false; // Could be tracked more granularly
|
||||
|
||||
if (needs_graphics_rebuild) {
|
||||
// Only rebuild what's actually changed
|
||||
map->LoadAreaGraphics();
|
||||
|
||||
|
||||
// Rebuild tileset only if graphics changed
|
||||
auto status = map->BuildTileset();
|
||||
if (!status.ok()) {
|
||||
util::logf("Failed to build tileset for map %d: %s", map_index, status.message().data());
|
||||
util::logf("Failed to build tileset for map %d: %s", map_index,
|
||||
status.message().data());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Rebuild tiles16 graphics
|
||||
status = map->BuildTiles16Gfx(*overworld_.mutable_tiles16(), overworld_.tiles16().size());
|
||||
status = map->BuildTiles16Gfx(*overworld_.mutable_tiles16(),
|
||||
overworld_.tiles16().size());
|
||||
if (!status.ok()) {
|
||||
util::logf("Failed to build tiles16 graphics for map %d: %s", map_index, status.message().data());
|
||||
util::logf("Failed to build tiles16 graphics for map %d: %s", map_index,
|
||||
status.message().data());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Rebuild bitmap
|
||||
status = map->BuildBitmap(overworld_.GetMapTiles(current_world_));
|
||||
if (!status.ok()) {
|
||||
util::logf("Failed to build bitmap for map %d: %s", map_index, status.message().data());
|
||||
util::logf("Failed to build bitmap for map %d: %s", map_index,
|
||||
status.message().data());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Update bitmap data
|
||||
maps_bmp_[map_index].set_data(map->bitmap_data());
|
||||
maps_bmp_[map_index].set_modified(false);
|
||||
|
||||
|
||||
// Validate surface synchronization to help debug crashes
|
||||
if (!maps_bmp_[map_index].ValidateDataSurfaceSync()) {
|
||||
util::logf("Warning: Surface synchronization issue detected for map %d", map_index);
|
||||
util::logf("Warning: Surface synchronization issue detected for map %d",
|
||||
map_index);
|
||||
}
|
||||
|
||||
|
||||
// Update texture on main thread
|
||||
if (maps_bmp_[map_index].texture()) {
|
||||
Renderer::Get().UpdateBitmap(&maps_bmp_[map_index]);
|
||||
@@ -2014,7 +2052,7 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
|
||||
EnsureMapTexture(map_index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle multi-area maps (large, wide, tall) with safe coordination
|
||||
RefreshMultiAreaMapsSafely(map_index, map);
|
||||
}
|
||||
@@ -2026,29 +2064,32 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
|
||||
* by using a non-recursive approach with explicit map list processing.
|
||||
* It respects the ZScream area size logic and prevents infinite recursion.
|
||||
*/
|
||||
void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap* map) {
|
||||
void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index,
|
||||
zelda3::OverworldMap* map) {
|
||||
using zelda3::AreaSizeEnum;
|
||||
|
||||
|
||||
// Skip if this is already a processed sibling to avoid double-processing
|
||||
static std::set<int> currently_processing;
|
||||
if (currently_processing.count(map_index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
auto area_size = map->area_size();
|
||||
if (area_size == AreaSizeEnum::SmallArea) {
|
||||
return; // No siblings to coordinate
|
||||
return; // No siblings to coordinate
|
||||
}
|
||||
|
||||
util::logf("RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
|
||||
(area_size == AreaSizeEnum::LargeArea) ? "large" :
|
||||
(area_size == AreaSizeEnum::WideArea) ? "wide" : "tall",
|
||||
map_index, map->parent());
|
||||
|
||||
|
||||
util::logf(
|
||||
"RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
|
||||
(area_size == AreaSizeEnum::LargeArea) ? "large"
|
||||
: (area_size == AreaSizeEnum::WideArea) ? "wide"
|
||||
: "tall",
|
||||
map_index, map->parent());
|
||||
|
||||
// Determine all maps that are part of this multi-area structure
|
||||
std::vector<int> sibling_maps;
|
||||
int parent_id = map->parent();
|
||||
|
||||
|
||||
// Use the same logic as ZScream for area coordination
|
||||
switch (area_size) {
|
||||
case AreaSizeEnum::LargeArea: {
|
||||
@@ -2056,76 +2097,82 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
|
||||
// Parent is top-left (quadrant 0), siblings are:
|
||||
// +1 (top-right, quadrant 1), +8 (bottom-left, quadrant 2), +9 (bottom-right, quadrant 3)
|
||||
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||
util::logf("RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
|
||||
parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
|
||||
util::logf(
|
||||
"RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
|
||||
parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case AreaSizeEnum::WideArea: {
|
||||
// Wide Area: 2x1 grid (2 maps total, horizontally adjacent)
|
||||
// Parent is left, sibling is +1 (right)
|
||||
sibling_maps = {parent_id, parent_id + 1};
|
||||
util::logf("RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
|
||||
util::logf("RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
|
||||
parent_id, parent_id + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case AreaSizeEnum::TallArea: {
|
||||
// Tall Area: 1x2 grid (2 maps total, vertically adjacent)
|
||||
// Parent is top, sibling is +8 (bottom)
|
||||
sibling_maps = {parent_id, parent_id + 8};
|
||||
util::logf("RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
|
||||
util::logf("RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
|
||||
parent_id, parent_id + 8);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
util::logf("RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
|
||||
util::logf("RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
|
||||
static_cast<int>(area_size), map_index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Mark all siblings as being processed to prevent recursion
|
||||
for (int sibling : sibling_maps) {
|
||||
currently_processing.insert(sibling);
|
||||
}
|
||||
|
||||
|
||||
// Only refresh siblings that are visible/current and need updating
|
||||
for (int sibling : sibling_maps) {
|
||||
if (sibling == map_index) {
|
||||
continue; // Skip self (already processed above)
|
||||
continue; // Skip self (already processed above)
|
||||
}
|
||||
|
||||
|
||||
// Bounds check
|
||||
if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Only refresh if it's visible or current
|
||||
bool is_current_map = (sibling == current_map_);
|
||||
bool is_current_world = (sibling / 0x40 == current_world_);
|
||||
bool needs_refresh = maps_bmp_[sibling].modified();
|
||||
|
||||
|
||||
if ((is_current_map || is_current_world) && needs_refresh) {
|
||||
util::logf("RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d (parent: %d)",
|
||||
(area_size == AreaSizeEnum::LargeArea) ? "large" :
|
||||
(area_size == AreaSizeEnum::WideArea) ? "wide" : "tall",
|
||||
sibling, parent_id);
|
||||
|
||||
util::logf(
|
||||
"RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d "
|
||||
"(parent: %d)",
|
||||
(area_size == AreaSizeEnum::LargeArea) ? "large"
|
||||
: (area_size == AreaSizeEnum::WideArea) ? "wide"
|
||||
: "tall",
|
||||
sibling, parent_id);
|
||||
|
||||
// Direct refresh without calling RefreshChildMapOnDemand to avoid recursion
|
||||
auto* sibling_map = overworld_.mutable_overworld_map(sibling);
|
||||
if (sibling_map && maps_bmp_[sibling].modified()) {
|
||||
sibling_map->LoadAreaGraphics();
|
||||
|
||||
|
||||
auto status = sibling_map->BuildTileset();
|
||||
if (status.ok()) {
|
||||
status = sibling_map->BuildTiles16Gfx(*overworld_.mutable_tiles16(), overworld_.tiles16().size());
|
||||
status = sibling_map->BuildTiles16Gfx(*overworld_.mutable_tiles16(),
|
||||
overworld_.tiles16().size());
|
||||
if (status.ok()) {
|
||||
status = sibling_map->BuildBitmap(overworld_.GetMapTiles(current_world_));
|
||||
status = sibling_map->BuildBitmap(
|
||||
overworld_.GetMapTiles(current_world_));
|
||||
if (status.ok()) {
|
||||
maps_bmp_[sibling].set_data(sibling_map->bitmap_data());
|
||||
maps_bmp_[sibling].set_modified(false);
|
||||
|
||||
|
||||
// Update texture if it exists
|
||||
if (maps_bmp_[sibling].texture()) {
|
||||
core::Renderer::Get().UpdateBitmap(&maps_bmp_[sibling]);
|
||||
@@ -2135,10 +2182,12 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!status.ok()) {
|
||||
util::logf("RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: %s",
|
||||
sibling, status.message().data());
|
||||
util::logf(
|
||||
"RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: "
|
||||
"%s",
|
||||
sibling, status.message().data());
|
||||
}
|
||||
}
|
||||
} else if (!is_current_map && !is_current_world) {
|
||||
@@ -2146,7 +2195,7 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
|
||||
maps_bmp_[sibling].set_modified(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Clear processing set after completion
|
||||
for (int sibling : sibling_maps) {
|
||||
currently_processing.erase(sibling);
|
||||
@@ -3603,7 +3652,7 @@ void OverworldEditor::DrawScratchSpacePattern() {
|
||||
void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y,
|
||||
int tile_id, int slot) {
|
||||
gfx::ScopedTimer timer("overworld_update_scratch_tile");
|
||||
|
||||
|
||||
// Use current slot if not specified
|
||||
if (slot == -1)
|
||||
slot = current_scratch_slot_;
|
||||
@@ -3649,13 +3698,14 @@ void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y,
|
||||
|
||||
scratch_slot.scratch_bitmap.set_modified(true);
|
||||
// Use batch operations for texture updates
|
||||
scratch_slot.scratch_bitmap.QueueTextureUpdate(core::Renderer::Get().renderer());
|
||||
scratch_slot.scratch_bitmap.QueueTextureUpdate(
|
||||
core::Renderer::Get().renderer());
|
||||
scratch_slot.in_use = true;
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) {
|
||||
gfx::ScopedTimer timer("overworld_save_selection_to_scratch");
|
||||
|
||||
|
||||
if (slot < 0 || slot >= 4) {
|
||||
return absl::InvalidArgumentError("Invalid scratch slot");
|
||||
}
|
||||
@@ -3732,7 +3782,7 @@ absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) {
|
||||
|
||||
// Process all queued texture updates at once
|
||||
gfx::Arena::Get().ProcessBatchTextureUpdates();
|
||||
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -707,19 +707,30 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||
ImVec2(tile8_source_canvas_.width(), tile8_source_canvas_.height()),
|
||||
true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||
|
||||
// Enable dragging for scrolling behavior
|
||||
tile8_source_canvas_.set_draggable(true);
|
||||
// CRITICAL FIX: Don't use draggable mode as it conflicts with tile selection
|
||||
tile8_source_canvas_.set_draggable(false);
|
||||
tile8_source_canvas_.DrawBackground();
|
||||
tile8_source_canvas_.DrawContextMenu();
|
||||
|
||||
// Tile8 selection with improved feedback
|
||||
bool tile8_selected = false;
|
||||
tile8_source_canvas_.DrawTileSelector(32.0F);
|
||||
|
||||
// Check for clicks properly
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
tile8_selected = true;
|
||||
}
|
||||
|
||||
if (tile8_source_canvas_.WasClicked() ||
|
||||
tile8_source_canvas_.WasDoubleClicked()) {
|
||||
auto tile_pos = tile8_source_canvas_.GetLastClickPosition();
|
||||
int tile_x = static_cast<int>(tile_pos.x / 32);
|
||||
int tile_y = static_cast<int>(tile_pos.y / 32);
|
||||
if (tile8_selected) {
|
||||
// Get mouse position relative to canvas more accurately
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImVec2 canvas_pos = tile8_source_canvas_.zero_point();
|
||||
ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
|
||||
|
||||
// Account for the 4x scale when calculating tile position
|
||||
int tile_x = static_cast<int>(mouse_pos.x / (8 * 4)); // 8 pixel tile * 4x scale = 32 pixels per tile
|
||||
int tile_y = static_cast<int>(mouse_pos.y / (8 * 4));
|
||||
|
||||
// Calculate tiles per row based on bitmap width (should be 16 for 128px wide bitmap)
|
||||
int tiles_per_row = current_gfx_bmp_.width() / 8;
|
||||
int new_tile8 = tile_x + (tile_y * tiles_per_row);
|
||||
@@ -791,22 +802,28 @@ absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||
tile_to_paint = &flipped_tile;
|
||||
}
|
||||
|
||||
// Paint with 8x8 tile size at 4x scale (32 display pixels per 8x8 tile)
|
||||
// Always use the flipped tile for consistency between preview and actual drawing
|
||||
if (tile16_edit_canvas_.DrawTilePainter(*tile_to_paint, 8, 4.0F)) {
|
||||
ImVec2 click_pos = tile16_edit_canvas_.drawn_tile_position();
|
||||
// CRITICAL FIX: Handle tile painting with simple click instead of click+drag
|
||||
// Draw the preview first
|
||||
tile16_edit_canvas_.DrawTilePainter(*tile_to_paint, 8, 4.0F);
|
||||
|
||||
// Check for simple click to paint tile8 to tile16
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
// Get mouse position relative to tile16 canvas
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImVec2 canvas_pos = tile16_edit_canvas_.zero_point();
|
||||
ImVec2 mouse_pos = ImVec2(io.MousePos.x - canvas_pos.x, io.MousePos.y - canvas_pos.y);
|
||||
|
||||
// Convert canvas coordinates to tile16 coordinates (0-15 range)
|
||||
// The canvas is 64x64 display pixels showing a 16x16 tile at 4x scale
|
||||
int tile_x = static_cast<int>(click_pos.x / 4.0F);
|
||||
int tile_y = static_cast<int>(click_pos.y / 4.0F);
|
||||
int tile_x = static_cast<int>(mouse_pos.x / 4.0F);
|
||||
int tile_y = static_cast<int>(mouse_pos.y / 4.0F);
|
||||
|
||||
// Clamp to valid range
|
||||
tile_x = std::max(0, std::min(15, tile_x));
|
||||
tile_y = std::max(0, std::min(15, tile_y));
|
||||
|
||||
util::logf("Canvas click: (%.2f, %.2f) -> Tile16: (%d, %d)",
|
||||
click_pos.x, click_pos.y, tile_x, tile_y);
|
||||
util::logf("Tile16 canvas click: (%.2f, %.2f) -> Tile16: (%d, %d)",
|
||||
mouse_pos.x, mouse_pos.y, tile_x, tile_y);
|
||||
|
||||
// Pass the flipped tile if we created one, otherwise pass nullptr to use original with flips
|
||||
const gfx::Bitmap* tile_to_draw =
|
||||
|
||||
Reference in New Issue
Block a user