yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
scratch_space.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <filesystem>
6#include <future>
7#include <set>
8#include <unordered_map>
9#include <vector>
10
11#include "absl/status/status.h"
12#include "absl/strings/str_format.h"
14#include "app/core/features.h"
16#include "app/core/window.h"
20#include "app/gfx/arena.h"
21#include "app/gfx/bitmap.h"
24#include "app/gfx/tilemap.h"
25#include "app/gui/canvas.h"
26#include "app/gui/icons.h"
27#include "app/gui/input.h"
28#include "app/gui/style.h"
29#include "app/rom.h"
30#include "app/zelda3/common.h"
33#include "imgui/imgui.h"
34#include "imgui_memory_editor.h"
35#include "util/hex.h"
36#include "util/log.h"
37#include "util/macro.h"
38
39namespace yaze::editor {
40
41using namespace ImGui;
42
43// Scratch space canvas methods
45 // Slot selector
46 Text("Scratch Space Slot:");
47 for (int i = 0; i < 4; i++) {
48 if (i > 0)
49 SameLine();
50 bool is_current = (current_scratch_slot_ == i);
51 if (is_current)
52 PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f));
53 if (Button(std::to_string(i + 1).c_str(), ImVec2(25, 25))) {
55 }
56 if (is_current)
57 PopStyleColor();
58 }
59
60 SameLine();
61 if (Button("Save Selection")) {
63 }
64 SameLine();
65 if (Button("Load")) {
67 }
68 SameLine();
69 if (Button("Clear")) {
71 }
72
73 // Selection transfer buttons
74 Separator();
75 Text("Selection Transfer:");
76 if (Button(ICON_MD_DOWNLOAD " From Overworld")) {
77 // Transfer current overworld selection to scratch space
79 !ow_map_canvas_.selected_tiles().empty()) {
81 }
82 }
83 HOVER_HINT("Copy current overworld selection to this scratch slot");
84
85 SameLine();
86 if (Button(ICON_MD_UPLOAD " To Clipboard")) {
87 // Copy scratch selection to clipboard for pasting in overworld
90 // Copy scratch selection to clipboard
91 std::vector<int> scratch_tile_ids;
92 for (const auto& tile_pos : scratch_canvas_.selected_tiles()) {
93 int tile_x = static_cast<int>(tile_pos.x) / 32;
94 int tile_y = static_cast<int>(tile_pos.y) / 32;
95 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
96 scratch_tile_ids.push_back(
97 scratch_spaces_[current_scratch_slot_].tile_data[tile_x][tile_y]);
98 }
99 }
100 if (!scratch_tile_ids.empty() && context_) {
101 const auto& points = scratch_canvas_.selected_points();
102 int width =
103 std::abs(static_cast<int>((points[1].x - points[0].x) / 32)) + 1;
104 int height =
105 std::abs(static_cast<int>((points[1].y - points[0].y) / 32)) + 1;
107 std::move(scratch_tile_ids);
111 }
112 }
113 }
114 HOVER_HINT("Copy scratch selection to clipboard for pasting in overworld");
115
118 " Pattern ready! Use Shift+Click to stamp, or paste in overworld");
119 }
120
121 Text("Slot %d: %s (%dx%d)", current_scratch_slot_ + 1,
125 Text(
126 "Select tiles from Tile16 tab or make selections in overworld, then draw "
127 "here!");
128
129 // Initialize scratch bitmap with proper size based on scratch space dimensions
130 auto& current_slot = scratch_spaces_[current_scratch_slot_];
131 if (!current_slot.scratch_bitmap.is_active()) {
132 // Create bitmap based on scratch space dimensions (each tile is 16x16)
133 int bitmap_width = current_slot.width * 16;
134 int bitmap_height = current_slot.height * 16;
135 std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
136 current_slot.scratch_bitmap.Create(bitmap_width, bitmap_height, 8,
137 empty_data);
138 if (all_gfx_loaded_) {
140 current_slot.scratch_bitmap.SetPalette(palette_);
141 // Queue texture creation via Arena's deferred system
143 gfx::Arena::TextureCommandType::CREATE, &current_slot.scratch_bitmap);
144 }
145 }
146
147 // Draw the scratch space canvas with dynamic sizing
149 ImGui::BeginGroup();
150
151 // Set proper content size for scrolling based on scratch space dimensions
152 ImVec2 scratch_content_size(current_slot.width * 16 + 4,
153 current_slot.height * 16 + 4);
154 gui::BeginChildWithScrollbar("##ScratchSpaceScrollRegion",
155 scratch_content_size);
158
159 // Disable context menu for scratch space to allow right-click selection
161
162 // Draw the scratch bitmap with proper scaling
163 if (current_slot.scratch_bitmap.is_active()) {
164 scratch_canvas_.DrawBitmap(current_slot.scratch_bitmap, 2, 2, 1.0f);
165 }
166
167 // Simplified scratch space - just basic tile drawing like the original
170 }
171
174
175 EndChild();
176 ImGui::EndGroup();
177
178 return absl::OkStatus();
179}
180
182 // Handle painting like the main overworld - continuous drawing
183 auto mouse_position = scratch_canvas_.drawn_tile_position();
184
185 // Use the scratch canvas scale and grid settings
186 float canvas_scale = scratch_canvas_.global_scale();
187 int grid_size =
188 32; // 32x32 grid for scratch space (matches kOverworldCanvasSize)
189
190 // Calculate tile position using proper canvas scaling
191 int tile_x = static_cast<int>(mouse_position.x) / grid_size;
192 int tile_y = static_cast<int>(mouse_position.y) / grid_size;
193
194 // Get current scratch slot dimensions
195 auto& current_slot = scratch_spaces_[current_scratch_slot_];
196 int max_width = current_slot.width > 0 ? current_slot.width : 20;
197 int max_height = current_slot.height > 0 ? current_slot.height : 30;
198
199 // Bounds check for current scratch space dimensions
200 if (tile_x >= 0 && tile_x < max_width && tile_y >= 0 && tile_y < max_height) {
201 // Bounds check for our tile_data array (always 32x32 max)
202 if (tile_x < 32 && tile_y < 32) {
203 current_slot.tile_data[tile_x][tile_y] = current_tile16_;
204 }
205
206 // Update the bitmap immediately for visual feedback
208
209 // Mark this scratch space as in use
210 if (!current_slot.in_use) {
211 current_slot.in_use = true;
212 current_slot.name =
213 absl::StrFormat("Layout %d", current_scratch_slot_ + 1);
214 }
215 }
216}
217
219 // Handle drawing patterns from overworld selections
220 auto mouse_position = scratch_canvas_.drawn_tile_position();
221
222 // Use 32x32 grid size (same as scratch canvas grid)
223 int start_tile_x = static_cast<int>(mouse_position.x) / 32;
224 int start_tile_y = static_cast<int>(mouse_position.y) / 32;
225
226 // Get the selected tiles from overworld via clipboard
228 return;
229 }
230
231 const auto& tile_ids = context_->shared_clipboard.overworld_tile16_ids;
232 int pattern_width = context_->shared_clipboard.overworld_width;
233 int pattern_height = context_->shared_clipboard.overworld_height;
234
235 if (tile_ids.empty())
236 return;
237
238 auto& current_slot = scratch_spaces_[current_scratch_slot_];
239 int max_width = current_slot.width > 0 ? current_slot.width : 20;
240 int max_height = current_slot.height > 0 ? current_slot.height : 30;
241
242 // Draw the pattern to scratch space
243 int idx = 0;
244 for (int py = 0; py < pattern_height && (start_tile_y + py) < max_height;
245 ++py) {
246 for (int px = 0; px < pattern_width && (start_tile_x + px) < max_width;
247 ++px) {
248 if (idx < static_cast<int>(tile_ids.size())) {
249 int tile_id = tile_ids[idx];
250 int scratch_x = start_tile_x + px;
251 int scratch_y = start_tile_y + py;
252
253 // Bounds check for tile_data array
254 if (scratch_x >= 0 && scratch_x < 32 && scratch_y >= 0 &&
255 scratch_y < 32) {
256 current_slot.tile_data[scratch_x][scratch_y] = tile_id;
257 UpdateScratchBitmapTile(scratch_x, scratch_y, tile_id);
258 }
259 idx++;
260 }
261 }
262 }
263
264 // Mark scratch space as modified
265 current_slot.in_use = true;
266 if (current_slot.name == "Empty") {
267 current_slot.name =
268 absl::StrFormat("Pattern %dx%d", pattern_width, pattern_height);
269 }
270}
271
273 int tile_id, int slot) {
274 gfx::ScopedTimer timer("overworld_update_scratch_tile");
275
276 // Use current slot if not specified
277 if (slot == -1)
279
280 // Get the tile data from the tile16 blockset
281 auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
282 if (tile_data.empty())
283 return;
284
285 auto& scratch_slot = scratch_spaces_[slot];
286
287 // Use canvas grid size (32x32) for consistent scaling
288 const int grid_size = 32;
289 int scratch_bitmap_width = scratch_slot.scratch_bitmap.width();
290 int scratch_bitmap_height = scratch_slot.scratch_bitmap.height();
291
292 // Calculate pixel position in scratch bitmap
293 for (int y = 0; y < 16; ++y) {
294 for (int x = 0; x < 16; ++x) {
295 int src_index = y * 16 + x;
296
297 // Scale to grid size - each tile takes up grid_size x grid_size pixels
298 int dst_x = tile_x * grid_size + x + x; // Double scaling for 32x32 grid
299 int dst_y = tile_y * grid_size + y + y;
300
301 // Bounds check for scratch bitmap
302 if (dst_x >= 0 && dst_x < scratch_bitmap_width && dst_y >= 0 &&
303 dst_y < scratch_bitmap_height &&
304 src_index < static_cast<int>(tile_data.size())) {
305
306 // Write 2x2 pixel blocks to fill the 32x32 grid space
307 for (int py = 0; py < 2 && (dst_y + py) < scratch_bitmap_height; ++py) {
308 for (int px = 0; px < 2 && (dst_x + px) < scratch_bitmap_width;
309 ++px) {
310 int dst_index = (dst_y + py) * scratch_bitmap_width + (dst_x + px);
311 scratch_slot.scratch_bitmap.WriteToPixel(dst_index,
312 tile_data[src_index]);
313 }
314 }
315 }
316 }
317 }
318
319 scratch_slot.scratch_bitmap.set_modified(true);
320 // Queue texture update via Arena's deferred system
322 gfx::Arena::TextureCommandType::UPDATE, &scratch_slot.scratch_bitmap);
323 scratch_slot.in_use = true;
324}
325
327 gfx::ScopedTimer timer("overworld_save_selection_to_scratch");
328
329 if (slot < 0 || slot >= 4) {
330 return absl::InvalidArgumentError("Invalid scratch slot");
331 }
332
334 !ow_map_canvas_.selected_tiles().empty()) {
335 // Calculate actual selection dimensions from overworld rectangle
336 const auto& selected_points = ow_map_canvas_.selected_points();
337 if (selected_points.size() >= 2) {
338 const auto start = selected_points[0];
339 const auto end = selected_points[1];
340
341 // Calculate width and height in tiles
342 int selection_width =
343 std::abs(static_cast<int>((end.x - start.x) / 16)) + 1;
344 int selection_height =
345 std::abs(static_cast<int>((end.y - start.y) / 16)) + 1;
346
347 // Update scratch space dimensions to match selection
348 scratch_spaces_[slot].width = std::max(1, std::min(selection_width, 32));
349 scratch_spaces_[slot].height =
350 std::max(1, std::min(selection_height, 32));
351 scratch_spaces_[slot].in_use = true;
352 scratch_spaces_[slot].name =
353 absl::StrFormat("Selection %dx%d", scratch_spaces_[slot].width,
354 scratch_spaces_[slot].height);
355
356 // Recreate bitmap with new dimensions
357 int bitmap_width = scratch_spaces_[slot].width * 16;
358 int bitmap_height = scratch_spaces_[slot].height * 16;
359 std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
360 scratch_spaces_[slot].scratch_bitmap.Create(bitmap_width, bitmap_height,
361 8, empty_data);
362 if (all_gfx_loaded_) {
364 scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_);
365 // Queue texture creation via Arena's deferred system
368 &scratch_spaces_[slot].scratch_bitmap);
369 }
370
371 // Save selected tiles to scratch data with proper layout
374
375 int idx = 0;
376 for (int y = 0;
377 y < scratch_spaces_[slot].height &&
378 idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
379 ++y) {
380 for (int x = 0;
381 x < scratch_spaces_[slot].width &&
382 idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
383 ++x) {
384 if (idx < static_cast<int>(ow_map_canvas_.selected_tiles().size())) {
385 int tile_id = overworld_.GetTileFromPosition(
387 if (x < 32 && y < 32) {
388 scratch_spaces_[slot].tile_data[x][y] = tile_id;
389 }
390 // Update the bitmap immediately
391 UpdateScratchBitmapTile(x, y, tile_id, slot);
392 idx++;
393 }
394 }
395 }
396 }
397 } else {
398 // Default single-tile scratch space
399 scratch_spaces_[slot].width = 16; // Default size
400 scratch_spaces_[slot].height = 16;
401 scratch_spaces_[slot].name = absl::StrFormat("Map %d Area", current_map_);
402 scratch_spaces_[slot].in_use = true;
403 }
404
405
406 return absl::OkStatus();
407}
408
410 if (slot < 0 || slot >= 4) {
411 return absl::InvalidArgumentError("Invalid scratch slot");
412 }
413
414 if (!scratch_spaces_[slot].in_use) {
415 return absl::FailedPreconditionError("Scratch slot is empty");
416 }
417
418 // Placeholder - could restore tiles to current map position
419 util::logf("Loading scratch slot %d: %s", slot,
420 scratch_spaces_[slot].name.c_str());
421
422 return absl::OkStatus();
423}
424
426 if (slot < 0 || slot >= 4) {
427 return absl::InvalidArgumentError("Invalid scratch slot");
428 }
429
430 scratch_spaces_[slot].in_use = false;
431 scratch_spaces_[slot].name = "Empty";
432
433 // Clear the bitmap
434 if (scratch_spaces_[slot].scratch_bitmap.is_active()) {
435 auto& data = scratch_spaces_[slot].scratch_bitmap.mutable_data();
436 std::fill(data.begin(), data.end(), 0);
437 scratch_spaces_[slot].scratch_bitmap.set_modified(true);
438 // Queue texture update via Arena's deferred system
441 }
442
443 return absl::OkStatus();
444}
445
446}
EditorContext * context_
Definition editor.h:124
std::array< ScratchSpaceSlot, 4 > scratch_spaces_
absl::Status ClearScratchSpace(int slot)
absl::Status SaveCurrentSelectionToScratch(int slot)
absl::Status LoadScratchToSelection(int slot)
void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot=-1)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
static Arena & Get()
Definition arena.cc:15
RAII timer for automatic timing management.
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1062
auto global_scale() const
Definition canvas.h:345
auto select_rect_active() const
Definition canvas.h:341
auto selected_tiles() const
Definition canvas.h:342
auto drawn_tile_position() const
Definition canvas.h:313
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:920
void SetContextMenuEnabled(bool enabled)
Definition canvas.h:198
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:381
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1386
auto selected_points() const
Definition canvas.h:400
auto current_area_palette() const
Definition overworld.h:278
void set_current_world(int world)
Definition overworld.h:292
int GetTileFromPosition(ImVec2 position) const
Definition overworld.h:234
void set_current_map(int i)
Definition overworld.h:291
#define ICON_MD_CONTENT_PASTE
Definition icons.h:465
#define ICON_MD_UPLOAD
Definition icons.h:2046
#define ICON_MD_DOWNLOAD
Definition icons.h:616
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define HOVER_HINT(string)
Definition macro.h:24
Definition input.cc:20
Editors are the view controllers for the application.
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:235
void BeginPadding(int i)
Definition style.cc:272
void EndPadding()
Definition style.cc:276
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:284
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:116
struct yaze::editor::EditorContext::SharedClipboard shared_clipboard