yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile_painting_manager.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <string>
6#include <vector>
7
8#include "absl/strings/str_format.h"
13#include "util/log.h"
14
15namespace yaze::editor {
16namespace {
17
19
21 const int world_start = std::clamp(world, 0, 2) * 0x40;
22 return (std::clamp(zelda3::kNumOverworldMaps - world_start, 0, 0x40) + 7) / 8;
23}
24
25int MapIndexForGridPosition(int world, int map_x, int map_y) {
26 return world * 0x40 + map_x + map_y * 8;
27}
28
29} // namespace
30
32 const TilePaintingCallbacks& callbacks)
33 : deps_(deps), callbacks_(callbacks) {}
34
35std::vector<std::vector<uint16_t>>& TilePaintingManager::WorldTiles() const {
37}
38
40 const auto& canvas = *deps_.ow_map_canvas;
41 const float scale = canvas.global_scale() > 0 ? canvas.global_scale() : 1.0f;
42 const ImVec2 origin(canvas.zero_point().x + canvas.scrolling().x,
43 canvas.zero_point().y + canvas.scrolling().y);
44 const ImVec2 mouse = ImGui::GetIO().MousePos;
45 const float x = (mouse.x - origin.x) / scale / kTile16Size;
46 const float y = (mouse.y - origin.y) / scale / kTile16Size;
47 if (!ImGui::IsMousePosValid(&mouse) || !std::isfinite(x) ||
48 !std::isfinite(y)) {
49 return {-256, -256}; // Outside even the largest valid brush footprint.
50 }
51 // Bound before converting to int; focus loss or extreme scrolling can supply
52 // coordinates outside the integer range. Neither bound reaches a valid tile.
53 return {static_cast<int>(std::floor(std::clamp(x, -256.0f, 512.0f))),
54 static_cast<int>(std::floor(std::clamp(y, -256.0f, 512.0f)))};
55}
56
59 ? std::clamp(callbacks_.allocated_rows(world), 0, 8)
60 : LegacyAllocatedRowsForWorld(world);
61}
62
64 int map_y) const {
65 return world >= 0 && world <= 2 && map_x >= 0 && map_x < 8 && map_y >= 0 &&
66 map_y < AllocatedRowsForWorld(world);
67}
68
74
75absl::StatusOr<uint16_t> TilePaintingManager::ReadTile(
76 TilePosition position) const {
77 if (position.x < 0 || position.y < 0 ||
78 !IsValidMapGridPosition(*deps_.current_world, position.x / kTilesPerMap,
79 position.y / kTilesPerMap)) {
80 return absl::OutOfRangeError("Tile is outside the displayed world");
81 }
82 if (IsProjectTile(position)) {
84 return absl::FailedPreconditionError("Project tile reader unavailable");
86 position.y);
87 }
88 if (MapIndexForGridPosition(*deps_.current_world, position.x / kTilesPerMap,
89 position.y / kTilesPerMap) >=
91 return absl::OutOfRangeError("Tile is outside the ROM map model");
92 }
93 const auto& tiles = WorldTiles();
94 if (position.x >= static_cast<int>(tiles.size()) ||
95 position.y >= static_cast<int>(tiles[position.x].size())) {
96 return absl::OutOfRangeError("Tile is outside the loaded world");
97 }
98 return tiles[position.x][position.y];
99}
100
102 return ReadTile(position).ok();
103}
104
107 return &brush_;
108 }
110 : nullptr;
111}
112
120
131
142
148
151 auto& canvas = *deps_.ow_map_canvas;
152 const auto anchor = HoveredTile();
153 if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
155 }
156 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
157 paint_gesture_owned_ = canvas.IsMouseHovering() && ImGui::IsItemActive();
161 } else if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
162 paint_gesture_owned_ = false;
163 }
168 (!canvas.IsMouseHovering() || !IsValidTile(anchor))) ||
169 (canvas.IsMouseHovering() && IsValidTile(anchor) &&
170 gesture_project_ != IsProjectTile(anchor)))) {
171 // A held gesture cannot acquire another storage backend or interpolate a
172 // project stroke across an unobserved gap outside its canvas/document.
174 paint_gesture_owned_ = false;
175 }
176 const bool can_paint =
177 paint_gesture_owned_ && canvas.IsMouseHovering() && ImGui::IsItemActive();
178 const auto* selected = selection_brush();
179 const TileBrush single{1, 1, {*deps_.current_tile16}};
180
182 if (can_paint && ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
183 IsValidTile(anchor)) {
184 const TilePosition screen{anchor.x / kTilesPerMap * kTilesPerMap,
185 anchor.y / kTilesPerMap * kTilesPerMap};
186 (void)PaintPattern(selected ? *selected : single, screen, kTilesPerMap,
187 kTilesPerMap);
188 }
189 return;
190 }
192 return;
193 }
194 if (selected) {
195 if (can_paint && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
196 ImGui::IsMouseDragging(ImGuiMouseButton_Left))) {
197 (void)PaintPattern(*selected, anchor, selected->width, selected->height,
198 false);
199 }
200 } else if (single.valid()) {
201 // The hovered tile shows the selected Tile16 as the map under the cursor
202 // draws it (its own graphics and palette), not as the current map does.
203 auto* points = canvas.mutable_points();
204 points->clear();
205 if (canvas.IsMouseHovering() && IsValidTile(anchor)) {
206 const float scale =
207 canvas.global_scale() > 0 ? canvas.global_scale() : 1.0f;
208 const float size = kTile16Size * scale;
209 points->push_back(ImVec2(anchor.x * size, anchor.y * size));
210 points->push_back(ImVec2((anchor.x + 1) * size, (anchor.y + 1) * size));
211 DrawBrushPreview(single, anchor, /*mark_selection=*/false, 255);
212 }
213 if (can_paint && (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
214 ImGui::IsMouseDragging(ImGuiMouseButton_Left))) {
215 (void)PaintPattern(single, anchor, 1, 1, false);
216 }
217 }
218}
219
220absl::Status TilePaintingManager::PasteBrush(const TileBrush& brush) {
221 if (!brush.valid()) {
222 return absl::InvalidArgumentError("Clipboard tile pattern is invalid");
223 }
225 return absl::FailedPreconditionError("Hover the overworld canvas to paste");
226 }
228 return PaintPattern(brush, HoveredTile(), brush.width, brush.height);
229}
230
232 TilePosition anchor, int width,
233 int height, bool finalize) {
234 if (!brush.valid()) {
235 return absl::InvalidArgumentError("Tile pattern is invalid");
236 }
237 if (IsProjectTile(anchor)) {
239 return absl::FailedPreconditionError("Project tile writer unavailable");
240 auto status = callbacks_.paint_project_pattern(brush, anchor.x, anchor.y,
241 width, height, finalize);
242 if (!status.ok()) {
244 paint_gesture_owned_ = false;
245 return status;
246 }
247 project_paint_pending_ = !finalize;
250 MapIndexForGridPosition(*deps_.current_world, anchor.x / kTilesPerMap,
251 anchor.y / kTilesPerMap));
252 }
253 return absl::OkStatus();
254 }
255 ChangedMaps changed_maps{};
256 for (int y = 0; y < height; ++y) {
257 for (int x = 0; x < width; ++x) {
258 PaintTile({anchor.x + x, anchor.y + y},
259 brush.at(x % brush.width, y % brush.height), changed_maps);
260 }
261 }
262 if (std::none_of(changed_maps.begin(), changed_maps.end(),
263 [](bool changed) { return changed; })) {
264 return absl::OkStatus();
265 }
266 deps_.rom->set_dirty(true);
267 if (finalize) {
269 single_paint_pending_ = false;
270 } else {
272 }
273 if (IsValidTile(anchor)) {
274 const int anchor_map = MapIndexForGridPosition(
275 *deps_.current_world, anchor.x / kTilesPerMap, anchor.y / kTilesPerMap);
276 // A direct write changes the selected map without refreshing its Tile16
277 // palette and ignores the pin. The editor callback does both.
280 } else {
281 *deps_.current_map = anchor_map;
282 }
283 }
284 RefreshChangedMaps(changed_maps);
285 return absl::OkStatus();
286}
287
289 int map_id, const std::vector<GuardedTileCell>& cells) {
290 if (map_id < 0 || map_id >= zelda3::kNumOverworldMaps) {
291 return absl::OutOfRangeError("Stencil map is outside the ROM map model");
292 }
293 if (map_id / 0x40 != *deps_.current_world) {
294 return absl::FailedPreconditionError(
295 "Stencil map is not in the displayed world");
296 }
297 if (cells.empty()) {
298 return absl::InvalidArgumentError("Stencil has no cells");
299 }
300 const int origin_x = (map_id % 0x40) % 8 * kTilesPerMap;
301 const int origin_y = (map_id % 0x40) / 8 * kTilesPerMap;
302 std::vector<bool> seen(kTilesPerMap * kTilesPerMap, false);
303 int mismatches = 0;
304 std::string first_mismatch;
305 for (const auto& cell : cells) {
306 if (cell.x < 0 || cell.x >= kTilesPerMap || cell.y < 0 ||
307 cell.y >= kTilesPerMap) {
308 return absl::InvalidArgumentError(absl::StrFormat(
309 "Stencil cell (%d,%d) is outside the map", cell.x, cell.y));
310 }
311 if (cell.proposed >= zelda3::kNumTile16Individual) {
312 return absl::InvalidArgumentError(
313 absl::StrFormat("Stencil cell (%d,%d) proposes invalid Tile16 0x%X",
314 cell.x, cell.y, cell.proposed));
315 }
316 const int index = cell.y * kTilesPerMap + cell.x;
317 if (seen[index]) {
318 return absl::InvalidArgumentError(
319 absl::StrFormat("Stencil repeats cell (%d,%d)", cell.x, cell.y));
320 }
321 seen[index] = true;
322 const TilePosition position{origin_x + cell.x, origin_y + cell.y};
323 if (IsProjectTile(position)) {
324 return absl::FailedPreconditionError(
325 "Stencil cells must be ROM map tiles, not project terrain");
326 }
327 const auto current = ReadTile(position);
328 if (!current.ok()) {
329 return current.status();
330 }
331 if (*current != cell.expected && mismatches++ == 0) {
332 first_mismatch = absl::StrFormat("(%d,%d) expected 0x%03X, found 0x%03X",
333 cell.x, cell.y, cell.expected, *current);
334 }
335 }
336 if (mismatches > 0) {
337 return absl::FailedPreconditionError(
338 absl::StrFormat("Stencil guard failed at %d cell(s); first %s",
339 mismatches, first_mismatch));
340 }
341
343 ChangedMaps changed_maps{};
344 int changed = 0;
345 for (const auto& cell : cells) {
346 if (PaintTile({origin_x + cell.x, origin_y + cell.y}, cell.proposed,
347 changed_maps)) {
348 ++changed;
349 }
350 }
351 if (changed == 0) {
352 return 0;
353 }
354 deps_.rom->set_dirty(true);
356 RefreshChangedMaps(changed_maps);
357 return changed;
358}
359
361 for (int map = 0; map < zelda3::kNumOverworldMaps; ++map) {
362 if (changed_maps[map]) {
363 if ((*deps_.maps_bmp)[map].is_active()) {
366 }
368 }
369 }
370}
371
373 ChangedMaps& changed_maps) {
374 if (IsProjectTile(position) || !IsValidTile(position)) {
375 return false;
376 }
377 auto& old_id = WorldTiles()[position.x][position.y];
378 if (old_id == tile_id) {
379 return false;
380 }
381 const int map =
382 MapIndexForGridPosition(*deps_.current_world, position.x / kTilesPerMap,
383 position.y / kTilesPerMap);
385 position.y, old_id);
386 old_id = tile_id;
387 changed_maps[map] = true;
388 (*deps_.maps_bmp)[map].set_modified(true);
389 // Draw with the destination map's graphics: a stroke can cross into an
390 // area whose sheets differ from the current map's blockset.
391 const auto pixels = Tile16PixelsForMap(map, tile_id);
392 if (!pixels.empty()) {
393 RenderMapTile(map, position, pixels);
394 }
395 return true;
396}
397
399 int map_id, int tile_id) const {
400 const auto* map =
401 deps_.overworld ? deps_.overworld->overworld_map(map_id) : nullptr;
402 if (map && map->is_built() && !map->current_tile16_blockset().empty()) {
403 auto pixels =
404 Tile16PixelsFromBlockset(map->current_tile16_blockset(), tile_id);
405 if (!pixels.empty()) {
406 return pixels;
407 }
408 }
410 return gfx::GetTilemapData(*deps_.tile16_blockset, tile_id);
411 }
412 return {};
413}
414
416 const std::vector<uint8_t>& tile_data) {
417 if (map_id < 0 || map_id >= static_cast<int>(deps_.maps_bmp->size())) {
418 return;
419 }
420 auto& bitmap = (*deps_.maps_bmp)[map_id];
421 if (!bitmap.is_active() || tile_data.size() < kTile16Size * kTile16Size) {
422 return;
423 }
424 const int pixel_x = position.x % kTilesPerMap * kTile16Size;
425 const int pixel_y = position.y % kTilesPerMap * kTile16Size;
426 for (int y = 0; y < kTile16Size; ++y) {
427 for (int x = 0; x < kTile16Size; ++x) {
428 const int offset = (pixel_y + y) * kOverworldMapSize + pixel_x + x;
429 if (offset >= 0 && offset < static_cast<int>(bitmap.size())) {
430 bitmap.WriteToPixel(offset, tile_data[y * kTile16Size + x]);
431 }
432 }
433 }
434 bitmap.set_modified(true);
435}
436
438 const ImVec2& position, const std::vector<uint8_t>& tile_data) {
440 {static_cast<int>(std::floor(position.x / kTile16Size)),
441 static_cast<int>(std::floor(position.y / kTile16Size))},
442 tile_data);
443 if (*deps_.current_map >= 0 &&
445 (*deps_.maps_bmp)[*deps_.current_map].is_active()) {
449 }
450}
451
453 auto& canvas = *deps_.ow_map_canvas;
454 const auto points = canvas.selected_points();
455 if (points.size() != 2) {
456 return;
457 }
458 const int left = std::max(
459 0, static_cast<int>(
460 std::floor(std::min(points[0].x, points[1].x) / kTile16Size)));
461 const int top = std::max(
462 0, static_cast<int>(
463 std::floor(std::min(points[0].y, points[1].y) / kTile16Size)));
464 const int right =
465 std::min(8 * kTilesPerMap - 1,
466 static_cast<int>(std::floor(std::max(points[0].x, points[1].x) /
467 kTile16Size)));
468 const int bottom =
469 std::min(AllocatedRowsForWorld(*deps_.current_world) * kTilesPerMap - 1,
470 static_cast<int>(std::floor(std::max(points[0].y, points[1].y) /
471 kTile16Size)));
472 TileBrush captured{right - left + 1, bottom - top + 1, {}};
473 if (captured.width <= 0 || captured.height <= 0) {
474 canvas.ClearSelection();
475 return;
476 }
477 for (int y = top; y <= bottom; ++y) {
478 for (int x = left; x <= right; ++x) {
479 const auto tile = ReadTile({x, y});
480 if (!tile.ok()) {
481 canvas.ClearSelection();
482 return;
483 }
484 captured.tile_ids.push_back(*tile);
485 }
486 }
487 brush_ = std::move(captured);
489}
490
492 auto& canvas = *deps_.ow_map_canvas;
493 const bool was_active = canvas.select_rect_active();
494 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
495 // Shift+right-click opens the map context menu instead of sampling.
496 selection_gesture_owned_ = canvas.IsMouseHovering() &&
497 ImGui::IsItemActive() &&
498 !ImGui::GetIO().KeyShift;
499 }
500 const float scale = canvas.global_scale() > 0 ? canvas.global_scale() : 1.0f;
501 canvas.DrawSelectRect(*deps_.current_map, kTile16Size, scale);
502 if (canvas.selected_tile_pos().x != -1) {
503 const auto source = HoveredTile();
504 const auto tile = ReadTile(source);
505 if (selection_gesture_owned_ && tile.ok()) {
506 // Same path as the I key: keeps staged Tile16 edits guarded.
507 const int tile_id = *tile;
510 } else {
511 *deps_.current_tile16 = tile_id;
512 }
514 }
515 canvas.set_selected_tile_pos(ImVec2(-1, -1));
516 }
517 if (!was_active && canvas.select_rect_active()) {
519 if (selection_gesture_owned_ && canvas.IsMouseHovering()) {
521 } else {
522 canvas.ClearSelection();
523 }
524 }
525 if (!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
527 }
528 if (!canvas.select_rect_active() && !external_brush_active_) {
529 brush_ = {};
530 deps_.selected_tile16_ids->clear();
531 }
532 if (selection_brush() && canvas.IsMouseHovering()) {
533 DrawBrushPreview(brush_, HoveredTile(), /*mark_selection=*/true, 180);
534 }
535}
536
538 TilePosition anchor,
539 bool mark_selection, int alpha) {
540 auto& canvas = *deps_.ow_map_canvas;
541 if (mark_selection) {
542 auto* points = canvas.mutable_selected_points();
543 points->clear();
544 points->push_back(ImVec2(anchor.x * kTile16Size, anchor.y * kTile16Size));
545 points->push_back(ImVec2((anchor.x + brush.width - 1) * kTile16Size,
546 (anchor.y + brush.height - 1) * kTile16Size));
547 }
548
549 // Build each destination piece once. The selected/pinned map's atlas is
550 // only a fallback for synthetic or unavailable map data.
551 std::array<MapBrushPreview, zelda3::kNumOverworldMaps> pieces;
552 std::array<bool, zelda3::kNumOverworldMaps> has_piece{};
553 const int world = *deps_.current_world;
554 const int first_x = std::max(0, anchor.x) / kTilesPerMap;
555 const int first_y = std::max(0, anchor.y) / kTilesPerMap;
556 const int last_x = std::min(255, anchor.x + brush.width - 1) / kTilesPerMap;
557 const int last_y = std::min(255, anchor.y + brush.height - 1) / kTilesPerMap;
558 for (int my = first_y; my <= last_y; ++my) {
559 for (int mx = first_x; mx <= last_x; ++mx) {
560 if (!IsValidMapGridPosition(world, mx, my))
561 continue;
562 const int map_id = MapIndexForGridPosition(world, mx, my);
563 if (IsProjectTile({mx * kTilesPerMap, my * kTilesPerMap}) ||
565 continue;
566 const auto* map = deps_.overworld->overworld_map(map_id);
567 if (!map)
568 continue;
569 if (!map->is_built() && !deps_.overworld->EnsureMapBuilt(map_id).ok())
570 continue;
571 const auto& source = map->current_tile16_blockset();
572 if (source.empty())
573 continue;
574 auto& piece = pieces[map_id];
575 piece = BuildMapBrushPreview(brush, anchor.x, anchor.y, mx, my, source);
576 if (piece.pixels.empty())
577 continue;
578 auto& preview = map_brush_previews_[map_id];
579 SyncMapBrushPreview(piece, map->current_palette(), preview);
580 has_piece[map_id] = true;
581 }
582 }
583 const float scale = canvas.global_scale() > 0 ? canvas.global_scale() : 1.0f;
584 const float size = kTile16Size * scale;
585 const ImVec2 origin(canvas.zero_point().x + canvas.scrolling().x,
586 canvas.zero_point().y + canvas.scrolling().y);
587 auto* draw = canvas.draw_list();
588 draw->PushClipRect(
589 canvas.zero_point(),
590 ImVec2(canvas.zero_point().x + canvas.canvas_size().x * scale,
591 canvas.zero_point().y + canvas.canvas_size().y * scale),
592 true);
593 for (int y = 0; y < brush.height; ++y) {
594 for (int x = 0; x < brush.width; ++x) {
595 const int id = brush.at(x, y);
596 const TilePosition position{anchor.x + x, anchor.y + y};
597 if (!IsValidTile(position))
598 continue;
599 const int map_id = MapIndexForGridPosition(
600 world, position.x / kTilesPerMap, position.y / kTilesPerMap);
601 const bool project_tile = IsProjectTile(position);
602 const bool use_piece = !project_tile && has_piece[map_id];
603 const gfx::Bitmap* source_atlas =
604 project_tile ? (callbacks_.project_tile_atlas
606 : nullptr)
607 : (use_piece ? &map_brush_previews_[map_id]
609 if (!source_atlas)
610 continue;
611 const auto& atlas = *source_atlas;
612 if (!atlas.is_active() || !atlas.texture() || atlas.width() < 16 ||
613 atlas.height() < 16)
614 continue;
615 const int columns = atlas.width() / 16;
616 const int source_x = use_piece ? (position.x - pieces[map_id].tile_x) * 16
617 : id % columns * 16;
618 const int source_y = use_piece ? (position.y - pieces[map_id].tile_y) * 16
619 : id / columns * 16;
620 if (source_y + 16 > atlas.height())
621 continue;
622 const ImVec2 start(origin.x + position.x * size,
623 origin.y + position.y * size);
624 const ImVec2 uv0(static_cast<float>(source_x) / atlas.width(),
625 static_cast<float>(source_y) / atlas.height());
626 const ImVec2 uv1(uv0.x + 16.0f / atlas.width(),
627 uv0.y + 16.0f / atlas.height());
628 draw->AddImage((ImTextureID)(intptr_t)atlas.texture(), start,
629 ImVec2(start.x + size, start.y + size), uv0, uv1,
630 ImGui::GetColorU32(IM_COL32(255, 255, 255, alpha)));
631 }
632 }
633 draw->PopClipRect();
634}
635
636// ---------------------------------------------------------------------------
637// PickTile16FromHoveredCanvas - eyedropper tool
638// ---------------------------------------------------------------------------
641 return false;
642 }
643
644 const auto tile = ReadTile(HoveredTile());
645 if (!tile.ok()) {
646 return false;
647 }
648 const int tile_id = *tile;
649
652 } else {
653 *deps_.current_tile16 = tile_id;
654 auto set_tile_status =
656 if (!set_tile_status.ok()) {
657 util::logf("Failed to sync Tile16 editor after eyedropper: %s",
658 set_tile_status.message().data());
659 }
660 }
661
663 return true;
664}
665
666// ---------------------------------------------------------------------------
667// ToggleBrushTool - switch between DRAW_TILE and MOUSE modes
668// ---------------------------------------------------------------------------
682
683// ---------------------------------------------------------------------------
684// ActivateFillTool - toggle FILL_TILE mode on/off
685// ---------------------------------------------------------------------------
696
697} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:201
absl::Status SetCurrentTile(int id)
std::vector< uint8_t > Tile16PixelsForMap(int map_id, int tile_id) const
bool IsValidMapGridPosition(int world, int map_x, int map_y) const
bool PaintTile(TilePosition position, int tile_id, ChangedMaps &changed_maps)
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
bool IsValidTile(TilePosition position) const
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > map_brush_previews_
void CheckForOverworldEdits()
Main entry point: check for tile edits (paint, fill, stamp).
bool IsProjectTile(TilePosition position) const
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 RenderMapTile(int map_id, TilePosition position, const std::vector< uint8_t > &tile_data)
void ActivateFillTool()
Toggle FILL_TILE mode on/off.
absl::StatusOr< int > ApplyGuardedCells(int map_id, const std::vector< GuardedTileCell > &cells)
absl::Status PasteBrush(const TileBrush &brush)
const TileBrush * selection_brush() const
The captured selection, independent of its source or preview position.
void ToggleBrushTool()
Toggle between DRAW_TILE and MOUSE modes.
void RefreshChangedMaps(const ChangedMaps &changed_maps)
absl::StatusOr< uint16_t > ReadTile(TilePosition position) const
void DrawBrushPreview(const TileBrush &brush, TilePosition anchor, bool mark_selection, int alpha)
absl::Status PaintPattern(const TileBrush &brush, TilePosition anchor, int width, int height, bool finalize=true)
std::vector< std::vector< uint16_t > > & WorldTiles() const
std::array< bool, zelda3::kNumOverworldMaps > ChangedMaps
TilePaintingManager(const TilePaintingDependencies &deps, const TilePaintingCallbacks &callbacks)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:39
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
bool is_active() const
Definition bitmap.h:414
int width() const
Definition bitmap.h:398
auto global_scale() const
Definition canvas.h:413
auto select_rect_active() const
Definition canvas.h:409
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:386
auto mutable_selected_points()
Definition canvas.h:473
void ClearSelection()
Definition canvas.cc:358
bool IsMouseHovering() const
Definition canvas.h:354
auto selected_points() const
Definition canvas.h:474
auto overworld_map(int i) const
Definition overworld.h:770
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:754
Editors are the view controllers for the application.
MapBrushPreview BuildMapBrushPreview(const TileBrush &brush, int anchor_x, int anchor_y, int map_x, int map_y, std::span< const uint8_t > atlas, int atlas_width=128)
void SyncMapBrushPreview(const MapBrushPreview &piece, const gfx::SnesPalette &palette, gfx::Bitmap &preview)
constexpr unsigned int kOverworldMapSize
constexpr int kTile16Size
std::vector< uint8_t > Tile16PixelsFromBlockset(std::span< const uint8_t > atlas, int tile_id, int atlas_width=128)
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:268
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:146
constexpr int kNumTile16Individual
Definition overworld.h:244
constexpr int kNumOverworldMaps
Definition common.h:85
int at(int x, int y) const
Definition tile_brush.h:24
std::vector< int > tile_ids
Definition tile_brush.h:15
Callbacks for undo integration and map refresh.
std::function< absl::Status(const TileBrush &, int x, int y, int width, int height, bool finalize) paint_project_pattern)
std::function< void(int tile_id)> request_tile16_selection
When set, eyedropper routes here (guarded RequestTileSwitch path).
std::function< absl::StatusOr< uint16_t >(int world, int x, int y)> read_project_tile
std::function< void(int map_index)> refresh_overworld_map_on_demand
std::function< void()> scroll_blockset_to_current_tile
std::function< int(int world)> allocated_rows
std::function< void()> finalize_paint_operation
std::function< void(int map_id)> select_map_for_editing
std::function< bool(int world, int x, int y)> is_project_tile
std::function< const gfx::Bitmap *(int map_id)> project_tile_atlas
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.
std::array< gfx::Bitmap, zelda3::kExpandedMapCount > * maps_bmp
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119