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:
scawful
2025-09-29 10:45:25 -04:00
parent 1432e2c3f9
commit 2c4ccc9d0a
2 changed files with 262 additions and 195 deletions

View File

@@ -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;
@@ -733,7 +734,8 @@ void OverworldEditor::DrawOverworldMaps() {
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);
}
@@ -745,11 +747,14 @@ 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,
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
@@ -787,8 +792,11 @@ void OverworldEditor::DrawOverworldEdits() {
}
// 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)",
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
@@ -796,7 +804,10 @@ void OverworldEditor::DrawOverworldEdits() {
// 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;
}
@@ -830,7 +841,9 @@ 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)",
util::logf(
"ERROR: RenderUpdatedMapBitmap - Invalid current_map_ %d "
"(maps_bmp_.size()=%zu)",
current_map_, maps_bmp_.size());
return; // Invalid map index, skip rendering
}
@@ -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,16 +878,22 @@ 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)",
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)",
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;
}
@@ -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) {
@@ -944,22 +969,29 @@ void OverworldEditor::CheckForOverworldEdits() {
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",
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");
}
}
}
@@ -1280,30 +1313,26 @@ absl::Status OverworldEditor::DrawTile16Selector() {
blockset_canvas_.DrawContextMenu();
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
map_blockset_loaded_, /*scale=*/2);
// 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);
@@ -1312,15 +1341,7 @@ absl::Status OverworldEditor::DrawTile16Selector() {
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);
}
}
}
@@ -1697,8 +1718,8 @@ absl::Status OverworldEditor::LoadGraphics() {
util::logf("Loading overworld tileset (deferred textures).");
{
core::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
Renderer::Get().CreateBitmapWithoutTexture(0x80, 0x2000, 0x08,
overworld_.tile16_blockset_data(),
Renderer::Get().CreateBitmapWithoutTexture(
0x80, 0x2000, 0x08, overworld_.tile16_blockset_data(),
tile16_blockset_bmp_, palette_);
}
map_blockset_loaded_ = true;
@@ -1718,13 +1739,18 @@ 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;
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);
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");
@@ -1736,7 +1762,8 @@ absl::Status OverworldEditor::LoadGraphics() {
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;
}
@@ -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,7 +1788,8 @@ 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) {
@@ -1837,7 +1866,8 @@ void OverworldEditor::ProcessDeferredTextures() {
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;
}
@@ -1849,7 +1879,8 @@ void OverworldEditor::ProcessDeferredTextures() {
// 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++;
@@ -1862,7 +1893,8 @@ 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_);
@@ -1908,7 +1940,8 @@ void OverworldEditor::EnsureMapTexture(int map_index) {
// 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);
}
@@ -1979,21 +2012,25 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
// 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;
}
@@ -2003,7 +2040,8 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
// 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
@@ -2026,7 +2064,8 @@ 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
@@ -2040,9 +2079,11 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
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",
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
@@ -2056,7 +2097,8 @@ 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",
util::logf(
"RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
break;
}
@@ -2107,9 +2149,12 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
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",
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
@@ -2119,9 +2164,11 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
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);
@@ -2137,7 +2184,9 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index, zelda3::Overworl
}
if (!status.ok()) {
util::logf("RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: %s",
util::logf(
"RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: "
"%s",
sibling, status.message().data());
}
}
@@ -3649,7 +3698,8 @@ 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;
}

View File

@@ -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);
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);
// Check for clicks properly
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
tile8_selected = true;
}
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 =