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"
13#include "core/asar_wrapper.h"
15#include "app/platform/window.h"
20#include "app/gfx/core/bitmap.h"
25#include "app/gui/core/icons.h"
26#include "app/gui/core/input.h"
27#include "app/gui/core/style.h"
28#include "app/rom.h"
29#include "zelda3/common.h"
32#include "imgui/imgui.h"
33#include "imgui_memory_editor.h"
34#include "util/hex.h"
35#include "util/log.h"
36#include "util/macro.h"
37
38namespace yaze::editor {
39
40using namespace ImGui;
41
42// Scratch space canvas methods
44 // Slot selector
45 Text("Scratch Space Slot:");
46 for (int i = 0; i < 4; i++) {
47 if (i > 0)
48 SameLine();
49 bool is_current = (current_scratch_slot_ == i);
50 if (is_current)
51 PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f));
52 if (Button(std::to_string(i + 1).c_str(), ImVec2(25, 25))) {
54 }
55 if (is_current)
56 PopStyleColor();
57 }
58
59 SameLine();
60 if (Button("Save Selection")) {
62 }
63 SameLine();
64 if (Button("Load")) {
66 }
67 SameLine();
68 if (Button("Clear")) {
70 }
71
72 // Selection transfer buttons
73 Separator();
74 Text("Selection Transfer:");
75 if (Button(ICON_MD_DOWNLOAD " From Overworld")) {
76 // Transfer current overworld selection to scratch space
78 !ow_map_canvas_.selected_tiles().empty()) {
80 }
81 }
82 HOVER_HINT("Copy current overworld selection to this scratch slot");
83
84 SameLine();
85 if (Button(ICON_MD_UPLOAD " To Clipboard")) {
86 // Copy scratch selection to clipboard for pasting in overworld
89 // Copy scratch selection to clipboard
90 std::vector<int> scratch_tile_ids;
91 for (const auto& tile_pos : scratch_canvas_.selected_tiles()) {
92 int tile_x = static_cast<int>(tile_pos.x) / 32;
93 int tile_y = static_cast<int>(tile_pos.y) / 32;
94 if (tile_x >= 0 && tile_x < 32 && tile_y >= 0 && tile_y < 32) {
95 scratch_tile_ids.push_back(
96 scratch_spaces_[current_scratch_slot_].tile_data[tile_x][tile_y]);
97 }
98 }
99 if (!scratch_tile_ids.empty() && dependencies_.shared_clipboard) {
100 const auto& points = scratch_canvas_.selected_points();
101 int width =
102 std::abs(static_cast<int>((points[1].x - points[0].x) / 32)) + 1;
103 int height =
104 std::abs(static_cast<int>((points[1].y - points[0].y) / 32)) + 1;
106 std::move(scratch_tile_ids);
110 }
111 }
112 }
113 HOVER_HINT("Copy scratch selection to clipboard for pasting in overworld");
114
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
229 return;
230 }
231
232 const auto& tile_ids =
235 int pattern_height = dependencies_.shared_clipboard->overworld_height;
236
237 if (tile_ids.empty())
238 return;
239
240 auto& current_slot = scratch_spaces_[current_scratch_slot_];
241 int max_width = current_slot.width > 0 ? current_slot.width : 20;
242 int max_height = current_slot.height > 0 ? current_slot.height : 30;
243
244 // Draw the pattern to scratch space
245 int idx = 0;
246 for (int py = 0; py < pattern_height && (start_tile_y + py) < max_height;
247 ++py) {
248 for (int px = 0; px < pattern_width && (start_tile_x + px) < max_width;
249 ++px) {
250 if (idx < static_cast<int>(tile_ids.size())) {
251 int tile_id = tile_ids[idx];
252 int scratch_x = start_tile_x + px;
253 int scratch_y = start_tile_y + py;
254
255 // Bounds check for tile_data array
256 if (scratch_x >= 0 && scratch_x < 32 && scratch_y >= 0 &&
257 scratch_y < 32) {
258 current_slot.tile_data[scratch_x][scratch_y] = tile_id;
259 UpdateScratchBitmapTile(scratch_x, scratch_y, tile_id);
260 }
261 idx++;
262 }
263 }
264 }
265
266 // Mark scratch space as modified
267 current_slot.in_use = true;
268 if (current_slot.name == "Empty") {
269 current_slot.name =
270 absl::StrFormat("Pattern %dx%d", pattern_width, pattern_height);
271 }
272}
273
275 int tile_id, int slot) {
276 gfx::ScopedTimer timer("overworld_update_scratch_tile");
277
278 // Use current slot if not specified
279 if (slot == -1)
281
282 // Get the tile data from the tile16 blockset
283 auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
284 if (tile_data.empty())
285 return;
286
287 auto& scratch_slot = scratch_spaces_[slot];
288
289 // Use canvas grid size (32x32) for consistent scaling
290 const int grid_size = 32;
291 int scratch_bitmap_width = scratch_slot.scratch_bitmap.width();
292 int scratch_bitmap_height = scratch_slot.scratch_bitmap.height();
293
294 // Calculate pixel position in scratch bitmap
295 for (int y = 0; y < 16; ++y) {
296 for (int x = 0; x < 16; ++x) {
297 int src_index = y * 16 + x;
298
299 // Scale to grid size - each tile takes up grid_size x grid_size pixels
300 int dst_x = tile_x * grid_size + x + x; // Double scaling for 32x32 grid
301 int dst_y = tile_y * grid_size + y + y;
302
303 // Bounds check for scratch bitmap
304 if (dst_x >= 0 && dst_x < scratch_bitmap_width && dst_y >= 0 &&
305 dst_y < scratch_bitmap_height &&
306 src_index < static_cast<int>(tile_data.size())) {
307
308 // Write 2x2 pixel blocks to fill the 32x32 grid space
309 for (int py = 0; py < 2 && (dst_y + py) < scratch_bitmap_height; ++py) {
310 for (int px = 0; px < 2 && (dst_x + px) < scratch_bitmap_width;
311 ++px) {
312 int dst_index = (dst_y + py) * scratch_bitmap_width + (dst_x + px);
313 scratch_slot.scratch_bitmap.WriteToPixel(dst_index,
314 tile_data[src_index]);
315 }
316 }
317 }
318 }
319 }
320
321 scratch_slot.scratch_bitmap.set_modified(true);
322 // Queue texture update via Arena's deferred system
324 gfx::Arena::TextureCommandType::UPDATE, &scratch_slot.scratch_bitmap);
325 scratch_slot.in_use = true;
326}
327
329 gfx::ScopedTimer timer("overworld_save_selection_to_scratch");
330
331 if (slot < 0 || slot >= 4) {
332 return absl::InvalidArgumentError("Invalid scratch slot");
333 }
334
336 !ow_map_canvas_.selected_tiles().empty()) {
337 // Calculate actual selection dimensions from overworld rectangle
338 const auto& selected_points = ow_map_canvas_.selected_points();
339 if (selected_points.size() >= 2) {
340 const auto start = selected_points[0];
341 const auto end = selected_points[1];
342
343 // Calculate width and height in tiles
344 int selection_width =
345 std::abs(static_cast<int>((end.x - start.x) / 16)) + 1;
346 int selection_height =
347 std::abs(static_cast<int>((end.y - start.y) / 16)) + 1;
348
349 // Update scratch space dimensions to match selection
350 scratch_spaces_[slot].width = std::max(1, std::min(selection_width, 32));
351 scratch_spaces_[slot].height =
352 std::max(1, std::min(selection_height, 32));
353 scratch_spaces_[slot].in_use = true;
354 scratch_spaces_[slot].name =
355 absl::StrFormat("Selection %dx%d", scratch_spaces_[slot].width,
356 scratch_spaces_[slot].height);
357
358 // Recreate bitmap with new dimensions
359 int bitmap_width = scratch_spaces_[slot].width * 16;
360 int bitmap_height = scratch_spaces_[slot].height * 16;
361 std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
362 scratch_spaces_[slot].scratch_bitmap.Create(bitmap_width, bitmap_height,
363 8, empty_data);
364 if (all_gfx_loaded_) {
366 scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_);
367 // Queue texture creation via Arena's deferred system
370 &scratch_spaces_[slot].scratch_bitmap);
371 }
372
373 // Save selected tiles to scratch data with proper layout
376
377 int idx = 0;
378 for (int y = 0;
379 y < scratch_spaces_[slot].height &&
380 idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
381 ++y) {
382 for (int x = 0;
383 x < scratch_spaces_[slot].width &&
384 idx < static_cast<int>(ow_map_canvas_.selected_tiles().size());
385 ++x) {
386 if (idx < static_cast<int>(ow_map_canvas_.selected_tiles().size())) {
387 int tile_id = overworld_.GetTileFromPosition(
389 if (x < 32 && y < 32) {
390 scratch_spaces_[slot].tile_data[x][y] = tile_id;
391 }
392 // Update the bitmap immediately
393 UpdateScratchBitmapTile(x, y, tile_id, slot);
394 idx++;
395 }
396 }
397 }
398 }
399 } else {
400 // Default single-tile scratch space
401 scratch_spaces_[slot].width = 16; // Default size
402 scratch_spaces_[slot].height = 16;
403 scratch_spaces_[slot].name = absl::StrFormat("Map %d Area", current_map_);
404 scratch_spaces_[slot].in_use = true;
405 }
406
407
408 return absl::OkStatus();
409}
410
412 if (slot < 0 || slot >= 4) {
413 return absl::InvalidArgumentError("Invalid scratch slot");
414 }
415
416 if (!scratch_spaces_[slot].in_use) {
417 return absl::FailedPreconditionError("Scratch slot is empty");
418 }
419
420 // Placeholder - could restore tiles to current map position
421 util::logf("Loading scratch slot %d: %s", slot,
422 scratch_spaces_[slot].name.c_str());
423
424 return absl::OkStatus();
425}
426
428 if (slot < 0 || slot >= 4) {
429 return absl::InvalidArgumentError("Invalid scratch slot");
430 }
431
432 scratch_spaces_[slot].in_use = false;
433 scratch_spaces_[slot].name = "Empty";
434
435 // Clear the bitmap
436 if (scratch_spaces_[slot].scratch_bitmap.is_active()) {
437 auto& data = scratch_spaces_[slot].scratch_bitmap.mutable_data();
438 std::fill(data.begin(), data.end(), 0);
439 scratch_spaces_[slot].scratch_bitmap.set_modified(true);
440 // Queue texture update via Arena's deferred system
443 }
444
445 return absl::OkStatus();
446}
447
448}
EditorDependencies dependencies_
Definition editor.h:165
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:1004
auto global_scale() const
Definition canvas.h:329
auto select_rect_active() const
Definition canvas.h:325
auto selected_tiles() const
Definition canvas.h:326
auto drawn_tile_position() const
Definition canvas.h:297
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:862
void SetContextMenuEnabled(bool enabled)
Definition canvas.h:177
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:372
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1304
auto selected_points() const
Definition canvas.h:384
auto current_area_palette() const
Definition overworld.h:267
void set_current_world(int world)
Definition overworld.h:281
int GetTileFromPosition(ImVec2 position) const
Definition overworld.h:223
void set_current_map(int i)
Definition overworld.h:280
#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
SharedClipboard * shared_clipboard
Definition editor.h:84