14#include "imgui/imgui.h"
22int AllocatedRowsForWorld(
int world) {
23 const int clamped_world = std::clamp(world, 0, 2);
24 const int world_start = clamped_world * 0x40;
25 const int maps_available =
27 return (maps_available + 7) / 8;
31 return map_x >= 0 && map_x < 8 && map_y >= 0 &&
32 map_y < AllocatedRowsForWorld(world);
36 return (std::clamp(world, 0, 2) * 0x40) + map_x + (map_y * 8);
43 : deps_(deps), callbacks_(callbacks) {}
57 ImVec2 mouse_position =
58 ImVec2(scaled_position.x / scale, scaled_position.y / scale);
59 if (mouse_position.x < 0.0f || mouse_position.y < 0.0f) {
69 const int target_map =
82 "Error: tile16_blockset is not properly initialized (active: %s, "
97 const int superY = local_map / 8;
98 const int superX = local_map % 8;
99 int mouse_x_i =
static_cast<int>(mouse_position.x);
100 int mouse_y_i =
static_cast<int>(mouse_position.y);
106 auto& selected_world =
113 int index_x = superX * 32 + tile16_x;
114 int index_y = superY * 32 + tile16_y;
115 if (index_x < 0 || index_y < 0 ||
116 index_x >=
static_cast<int>(selected_world.size()) ||
117 index_y >=
static_cast<int>(selected_world[index_x].size())) {
122 int old_tile_id = selected_world[index_x][index_y];
127 index_x, index_y, old_tile_id);
138 const ImVec2& click_position,
const std::vector<uint8_t>& tile_data) {
143 "ERROR: RenderUpdatedMapBitmap - Invalid current_map %d "
144 "(maps_bmp size=%zu)",
156 ImVec2 start_position;
157 start_position.x =
static_cast<float>(tile_index_x *
kTile16Size);
158 start_position.y =
static_cast<float>(tile_index_y *
kTile16Size);
164 if (!current_bitmap.
is_active() || current_bitmap.
size() == 0) {
166 "TilePaintingManager",
167 "ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no "
168 "data (active=%s, size=%zu)",
170 current_bitmap.
size());
180 if (pixel_index < 0 ||
181 pixel_index >=
static_cast<int>(current_bitmap.
size())) {
183 "TilePaintingManager",
184 "ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds "
186 pixel_index, current_bitmap.
size());
192 if (tile_data_index < 0 ||
193 tile_data_index >=
static_cast<int>(tile_data.size())) {
195 "TilePaintingManager",
196 "ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds "
197 "(tile_data size=%zu)",
198 tile_data_index, tile_data.size());
202 current_bitmap.
WriteToPixel(pixel_index, tile_data[tile_data_index]);
217 LOG_DEBUG(
"TilePaintingManager",
"CheckForOverworldEdits: Frame %d",
218 ImGui::GetFrameCount());
236 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
243 if (scaled_position.x < 0.0f || scaled_position.y < 0.0f) {
253 const int target_map =
257 std::vector<int> pattern_ids;
267 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
269 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
271 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
273 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
275 pattern_w = std::max(1, end_x - start_x + 1);
276 pattern_h = std::max(1, end_y - start_y + 1);
277 pattern_ids.reserve(pattern_w * pattern_h);
281 for (
int y = start_y; y <= end_y; ++y) {
282 for (
int x = start_x; x <= end_x; ++x) {
298 for (
int y = 0; y < 32; ++y) {
299 for (
int x = 0; x < 32; ++x) {
300 const int pattern_x = x % pattern_w;
301 const int pattern_y = y % pattern_h;
302 const int new_tile_id =
303 pattern_ids[pattern_y * pattern_w + pattern_x];
305 const int global_x = map_x * 32 + x;
306 const int global_y = map_y * 32 + y;
307 if (global_x < 0 || global_y < 0 ||
308 global_x >=
static_cast<int>(world_tiles.size()) ||
309 global_y >=
static_cast<int>(world_tiles[global_x].size())) {
313 const int old_tile_id = world_tiles[global_x][global_y];
314 if (old_tile_id == new_tile_id) {
319 global_x, global_y, old_tile_id);
320 world_tiles[global_x][global_y] = new_tile_id;
335 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
336 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
338 "CheckForOverworldEdits: About to apply rectangle selection");
340 auto& selected_world =
357 std::swap(start_x, end_x);
359 std::swap(start_y, end_y);
361 constexpr int local_map_size = 512;
363 constexpr int tiles_per_local_map = local_map_size /
kTile16Size;
366 "CheckForOverworldEdits: About to fill rectangle with "
375 for (
int y = start_y;
379 for (
int x = start_x;
384 int local_map_x = x / local_map_size;
385 int local_map_y = y / local_map_size;
392 int index_x = local_map_x * tiles_per_local_map + tile16_x;
393 int index_y = local_map_y * tiles_per_local_map + tile16_y;
398 int rect_width = ((end_x - start_x) /
kTile16Size) + 1;
399 int rect_height = ((end_y - start_y) /
kTile16Size) + 1;
402 int start_local_map_x = start_x / local_map_size;
403 int start_local_map_y = start_y / local_map_size;
404 int end_local_map_x = end_x / local_map_size;
405 int end_local_map_y = end_y / local_map_size;
407 bool in_same_local_map = (start_local_map_x == end_local_map_x) &&
408 (start_local_map_y == end_local_map_y);
410 if (in_same_local_map && index_x >= 0 &&
411 (index_x + rect_width - 1) < 0x200 && index_y >= 0 &&
412 (index_y + rect_height - 1) < 0x200) {
414 int old_tile_id = selected_world[index_x][index_y];
415 if (old_tile_id != tile16_id) {
418 index_y, old_tile_id);
421 selected_world[index_x][index_y] = tile16_id;
425 ImVec2 tile_position(x, y);
428 if (!tile_data.empty()) {
431 "TilePaintingManager",
432 "CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
437 "ERROR: Failed to get tile data for tile16_id=%d",
507 if (scaled_position.x < 0.0f || scaled_position.y < 0.0f) {
519 const int local_tile_x =
522 const int local_tile_y =
525 if (local_tile_x < 0 || local_tile_x >= 32 || local_tile_y < 0 ||
526 local_tile_y >= 32) {
530 const int world_tile_x = map_x * 32 + local_tile_x;
531 const int world_tile_y = map_y * 32 + local_tile_y;
532 if (world_tile_x < 0 || world_tile_x >= 256 || world_tile_y < 0 ||
533 world_tile_y >= 256) {
543 ? map_tiles->dark_world
544 : map_tiles->special_world;
545 if (world_tile_x >=
static_cast<int>(world_tiles.size()) ||
546 world_tile_y >=
static_cast<int>(world_tiles[world_tile_x].size())) {
549 const int tile_id = world_tiles[world_tile_x][world_tile_y];
558 auto set_tile_status =
560 if (!set_tile_status.ok()) {
561 util::logf(
"Failed to sync Tile16 editor after eyedropper: %s",
562 set_tile_status.message().data());
void set_dirty(bool dirty)
absl::Status SetCurrentTile(int id)
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
TilePaintingCallbacks callbacks_
void DrawOverworldEdits()
Handle the actual drawing of a single tile (called by CheckForOverworldEdits when DrawTilemapPainter ...
void CheckForOverworldEdits()
Main entry point: check for tile edits (paint, fill, stamp).
void RenderUpdatedMapBitmap(const ImVec2 &click_position, const std::vector< uint8_t > &tile_data)
Update bitmap pixels after a single tile paint.
bool PickTile16FromHoveredCanvas()
Eyedropper: pick the tile16 under the hovered canvas position.
void ActivateFillTool()
Toggle FILL_TILE mode on/off.
void ToggleBrushTool()
Toggle between DRAW_TILE and MOUSE modes.
TilePaintingManager(const TilePaintingDependencies &deps, const TilePaintingCallbacks &callbacks)
TilePaintingDependencies deps_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
const std::vector< uint8_t > & vector() const
void set_modified(bool modified)
auto selected_tile_pos() const
auto global_scale() const
auto select_rect_active() const
void SetUsageMode(CanvasUsage usage)
auto selected_tiles() const
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
auto hover_mouse_pos() const
auto drawn_tile_position() const
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
void set_selected_tile_pos(ImVec2 pos)
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
bool IsMouseHovering() const
auto selected_points() const
void set_current_world(int world)
int GetTileFromPosition(ImVec2 position) const
void set_current_map(int i)
uint16_t GetTile(int x, int y) const
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
bool IsValidMapGridPosition(int world, int map_x, int map_y)
int MapIndexForGridPosition(int world, int map_x, int map_y)
Editors are the view controllers for the application.
constexpr unsigned int kOverworldMapSize
constexpr int kTile16Size
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
constexpr int kNumOverworldMaps
Callbacks for undo integration and map refresh.
std::function< void(int tile_id)> request_tile16_selection
When set, eyedropper routes here (guarded RequestTileSwitch path).
std::function< void(int map_index)> refresh_overworld_map_on_demand
std::function< void()> scroll_blockset_to_current_tile
std::function< void()> finalize_paint_operation
std::function< void()> refresh_overworld_map
std::function< void(int map_id, int world, int x, int y, int old_tile_id)> create_undo_point
Shared state for the tile painting system.
Tile16Editor * tile16_editor
gui::Canvas * ow_map_canvas
zelda3::Overworld * overworld
std::vector< int > * selected_tile16_ids
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
gfx::Tilemap * tile16_blockset
EditingMode * current_mode
Bitmap atlas
Master bitmap containing all tiles.