yaze 0.2.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas.cc
Go to the documentation of this file.
1#include "canvas.h"
2
3#include <cmath>
4#include <string>
5
7#include "app/gfx/bitmap.h"
8#include "app/gui/color.h"
9#include "app/gui/input.h"
10#include "app/gui/style.h"
11#include "app/rom.h"
12#include "imgui/imgui.h"
13#include "imgui_memory_editor.h"
14
15namespace yaze {
16namespace gui {
17
18using core::Renderer;
19
20using ImGui::BeginMenu;
21using ImGui::EndMenu;
22using ImGui::GetContentRegionAvail;
23using ImGui::GetCursorScreenPos;
24using ImGui::GetIO;
25using ImGui::GetMouseDragDelta;
26using ImGui::GetWindowDrawList;
27using ImGui::IsItemActive;
28using ImGui::IsItemHovered;
29using ImGui::IsMouseClicked;
30using ImGui::IsMouseDragging;
31using ImGui::MenuItem;
32using ImGui::OpenPopupOnItemClick;
33using ImGui::Selectable;
34using ImGui::Text;
35
36constexpr uint32_t kBlackColor = IM_COL32(0, 0, 0, 255);
37constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
38constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
39constexpr uint32_t kOutlineRect = IM_COL32(255, 255, 255, 200);
40
41constexpr ImGuiButtonFlags kMouseFlags =
42 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
43
44namespace {
45ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
46 return ImVec2(std::floor((double)pos.x / scale) * scale,
47 std::floor((double)pos.y / scale) * scale);
48}
49} // namespace
50
51void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
52 const std::function<void()> &event,
53 int tile_size, float scale) {
54 global_scale_ = scale;
57 DrawBitmap(bitmap, 2, scale);
58 if (DrawSolidTilePainter(color, tile_size)) {
59 event();
60 }
61 DrawGrid();
63}
64
65void Canvas::UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale,
66 float grid_size, int label_id) {
68 DrawBackground(bg_size);
69 DrawInfoGrid(grid_size, 8, label_id);
71}
72
73void Canvas::DrawBackground(ImVec2 canvas_size, bool can_drag) {
74 draw_list_ = GetWindowDrawList();
75 canvas_p0_ = GetCursorScreenPos();
76 if (!custom_canvas_size_) canvas_sz_ = GetContentRegionAvail();
80
81 // Draw border and background color
84
85 ImGui::InvisibleButton(
86 canvas_id_.c_str(),
89
90 if (draggable_ && IsItemHovered()) {
91 const ImGuiIO &io = GetIO();
92 const bool is_active = IsItemActive(); // Held
93 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
94 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
95 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
96
97 // Pan (we use a zero mouse threshold when there's no context menu)
98 if (const float mouse_threshold_for_pan =
99 enable_context_menu_ ? -1.0f : 0.0f;
100 is_active &&
101 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
102 scrolling_.x += io.MouseDelta.x;
103 scrolling_.y += io.MouseDelta.y;
104 }
105 }
106}
107
109 const ImGuiIO &io = GetIO();
110 const ImVec2 scaled_sz(canvas_sz_.x * global_scale_,
112 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
113 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
114 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
115
116 static bool show_bitmap_data = false;
117 if (show_bitmap_data && bitmap_ != nullptr) {
118 MemoryEditor mem_edit;
119 mem_edit.DrawWindow("Bitmap Data", (void *)bitmap_->data(), bitmap_->size(),
120 0);
121 }
122
123 // Context menu (under default mouse threshold)
124 if (ImVec2 drag_delta = GetMouseDragDelta(ImGuiMouseButton_Right);
125 enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f)
126 OpenPopupOnItemClick(context_id_.c_str(), ImGuiPopupFlags_MouseButtonRight);
127
128 // Contents of the Context Menu
129 if (ImGui::BeginPopup(context_id_.c_str())) {
130 if (MenuItem("Reset Position", nullptr, false)) {
131 scrolling_.x = 0;
132 scrolling_.y = 0;
133 }
134 MenuItem("Show Grid", nullptr, &enable_grid_);
135 Selectable("Show Position Labels", &enable_hex_tile_labels_);
136 if (BeginMenu("Canvas Properties")) {
137 Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
138 Text("Global Scale: %.1f", global_scale_);
139 Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y);
140 EndMenu();
141 }
142 if (bitmap_ != nullptr) {
143 if (BeginMenu("Bitmap Properties")) {
144 Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y);
145 Text("Pitch: %d", bitmap_->surface()->pitch);
146 Text("BitsPerPixel: %d", bitmap_->surface()->format->BitsPerPixel);
147 Text("BytesPerPixel: %d", bitmap_->surface()->format->BytesPerPixel);
148 EndMenu();
149 }
150 if (BeginMenu("Bitmap Format")) {
151 if (MenuItem("Indexed")) {
154 }
155 if (MenuItem("4BPP")) {
158 }
159 if (MenuItem("8BPP")) {
162 }
163
164 EndMenu();
165 }
166 if (BeginMenu("View Palette")) {
167 DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8);
168 EndMenu();
169 }
170
171 if (BeginMenu("Bitmap Palette")) {
172 if (rom()->is_loaded()) {
173 gui::TextWithSeparators("ROM Palette");
174 ImGui::SetNextItemWidth(100.f);
175 ImGui::Combo("Palette Group", (int *)&edit_palette_group_name_index_,
177 IM_ARRAYSIZE(gfx::kPaletteGroupAddressesKeys));
178 ImGui::SetNextItemWidth(100.f);
179 gui::InputHexWord("Palette Group Index", &edit_palette_index_);
180
181 auto palette_group = rom()->mutable_palette_group()->get_group(
183 auto palette = palette_group->mutable_palette(edit_palette_index_);
184
185 if (ImGui::BeginChild("Palette", ImVec2(0, 300), true)) {
187 refresh_graphics_, *palette);
188
189 if (refresh_graphics_) {
190 bitmap_->SetPaletteWithTransparent(*palette,
193 refresh_graphics_ = false;
194 }
195 ImGui::EndChild();
196 }
197 }
198 EndMenu();
199 }
200 MenuItem("Bitmap Data", nullptr, &show_bitmap_data);
201 }
202 ImGui::Separator();
203 if (BeginMenu("Grid Tile Size")) {
204 if (MenuItem("8x8", nullptr, custom_step_ == 8.0f)) {
205 custom_step_ = 8.0f;
206 }
207 if (MenuItem("16x16", nullptr, custom_step_ == 16.0f)) {
208 custom_step_ = 16.0f;
209 }
210 if (MenuItem("32x32", nullptr, custom_step_ == 32.0f)) {
211 custom_step_ = 32.0f;
212 }
213 if (MenuItem("64x64", nullptr, custom_step_ == 64.0f)) {
214 custom_step_ = 64.0f;
215 }
216 EndMenu();
217 }
218
219 ImGui::EndPopup();
220 }
221}
222
223bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) {
224 const ImGuiIO &io = GetIO();
225 const bool is_hovered = IsItemHovered();
226 is_hovered_ = is_hovered;
227 // Lock scrolled origin
228 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
229 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
230 const auto scaled_size = size * scale;
231
232 // Erase the hover when the mouse is not in the canvas window.
233 if (!is_hovered) {
234 points_.clear();
235 return false;
236 }
237
238 // Reset the previous tile hover
239 if (!points_.empty()) {
240 points_.clear();
241 }
242
243 // Calculate the coordinates of the mouse
244 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
245 mouse_pos_in_canvas_ = paint_pos;
246 auto paint_pos_end =
247 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
248 points_.push_back(paint_pos);
249 points_.push_back(paint_pos_end);
250
251 if (bitmap.is_active()) {
252 draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
253 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
254 ImVec2(origin.x + paint_pos.x + scaled_size,
255 origin.y + paint_pos.y + scaled_size));
256 }
257
258 if (IsMouseClicked(ImGuiMouseButton_Left)) {
259 // Draw the currently selected tile on the overworld here
260 // Save the coordinates of the selected tile.
261 drawn_tile_pos_ = paint_pos;
262 return true;
263 } else if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
264 // Draw the currently selected tile on the overworld here
265 // Save the coordinates of the selected tile.
266 drawn_tile_pos_ = paint_pos;
267 return true;
268 }
269
270 return false;
271}
272
273bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) {
274 const ImGuiIO &io = GetIO();
275 const bool is_hovered = IsItemHovered();
276 is_hovered_ = is_hovered;
277 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
278 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
279 const auto scaled_size = tilemap.tile_size.x * global_scale_;
280
281 if (!is_hovered) {
282 points_.clear();
283 return false;
284 }
285
286 if (!points_.empty()) {
287 points_.clear();
288 }
289
290 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
291 mouse_pos_in_canvas_ = paint_pos;
292
293 points_.push_back(paint_pos);
294 points_.push_back(
295 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
296
297 if (tilemap.tile_bitmaps.find(current_tile) == tilemap.tile_bitmaps.end()) {
298 tilemap.tile_bitmaps[current_tile] = gfx::Bitmap(
299 tilemap.tile_size.x, tilemap.tile_size.y, 8,
300 gfx::GetTilemapData(tilemap, current_tile), tilemap.atlas.palette());
301 auto bitmap_ptr = &tilemap.tile_bitmaps[current_tile];
302 Renderer::Get().RenderBitmap(bitmap_ptr);
303 }
304
305 draw_list_->AddImage(
306 (ImTextureID)(intptr_t)tilemap.tile_bitmaps[current_tile].texture(),
307 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
308 ImVec2(origin.x + paint_pos.x + scaled_size,
309 origin.y + paint_pos.y + scaled_size));
310
311 if (IsMouseClicked(ImGuiMouseButton_Left)) {
312 drawn_tile_pos_ = paint_pos;
313 return true;
314 } else if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
315 drawn_tile_pos_ = paint_pos;
316 return true;
317 }
318
319 return false;
320}
321
322bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) {
323 const ImGuiIO &io = GetIO();
324 const bool is_hovered = IsItemHovered();
325 is_hovered_ = is_hovered;
326 // Lock scrolled origin
327 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
328 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
329 auto scaled_tile_size = tile_size * global_scale_;
330 static bool is_dragging = false;
331 static ImVec2 start_drag_pos;
332
333 // Erase the hover when the mouse is not in the canvas window.
334 if (!is_hovered) {
335 points_.clear();
336 return false;
337 }
338
339 // Reset the previous tile hover
340 if (!points_.empty()) {
341 points_.clear();
342 }
343
344 // Calculate the coordinates of the mouse
345 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
346 mouse_pos_in_canvas_ = paint_pos;
347
348 // Clamp the size to a grid
349 paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_);
350 paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_);
351
352 points_.push_back(paint_pos);
353 points_.push_back(
354 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
355
356 draw_list_->AddRectFilled(
357 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
358 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
359 origin.y + paint_pos.y + scaled_tile_size),
360 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
361
362 if (IsMouseClicked(ImGuiMouseButton_Left)) {
363 is_dragging = true;
364 start_drag_pos = paint_pos;
365 }
366
367 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
368 is_dragging = false;
369 drawn_tile_pos_ = start_drag_pos;
370 return true;
371 }
372
373 return false;
374}
375
376void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap,
377 ImVec4 color) {
378 const ImVec2 position = drawn_tile_pos_;
379 int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
380 int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
381
382 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
383
384 // Update the bitmap's pixel data based on the start_position and color
385 for (int y = 0; y < tile_size; ++y) {
386 for (int x = 0; x < tile_size; ++x) {
387 // Calculate the actual pixel index in the bitmap
388 int pixel_index =
389 (start_position.y + y) * bitmap->width() + (start_position.x + x);
390
391 // Write the color to the pixel
392 bitmap->WriteColor(pixel_index, color);
393 }
394 }
395}
396
397bool Canvas::DrawTileSelector(int size, int size_y) {
398 const ImGuiIO &io = GetIO();
399 const bool is_hovered = IsItemHovered();
400 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
401 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
402 if (size_y == 0) {
403 size_y = size;
404 }
405
406 if (is_hovered && IsMouseClicked(ImGuiMouseButton_Left)) {
407 if (!points_.empty()) {
408 points_.clear();
409 }
410 ImVec2 painter_pos;
411 painter_pos.x = std::floor((double)mouse_pos.x / size) * size;
412 painter_pos.y = std::floor((double)mouse_pos.y / size) * size;
413
414 points_.push_back(painter_pos);
415 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
416 mouse_pos_in_canvas_ = painter_pos;
417 }
418
419 if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
420 return true;
421 }
422
423 return false;
424}
425
426void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
427 const ImGuiIO &io = GetIO();
428 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
429 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
430 static ImVec2 drag_start_pos;
431 const float scaled_size = tile_size * scale;
432 static bool dragging = false;
433 constexpr int small_map_size = 0x200;
434 int superY = current_map / 8;
435 int superX = current_map % 8;
436
437 // Handle right click for single tile selection
438 if (IsMouseClicked(ImGuiMouseButton_Right)) {
439 ImVec2 painter_pos = AlignPosToGrid(mouse_pos, scaled_size);
440 int painter_x = painter_pos.x;
441 int painter_y = painter_pos.y;
442
443 auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20);
444 auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20);
445
446 int index_x = superX * 0x20 + tile16_x;
447 int index_y = superY * 0x20 + tile16_y;
448 selected_tile_pos_ = ImVec2(index_x, index_y);
449 selected_points_.clear();
450 select_rect_active_ = false;
451
452 // Start drag position for rectangle selection
453 drag_start_pos = AlignPosToGrid(mouse_pos, scaled_size);
454 }
455
456 // Calculate the rectangle's top-left and bottom-right corners
457 ImVec2 drag_end_pos = AlignPosToGrid(mouse_pos, scaled_size);
458 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
459 auto start = ImVec2(canvas_p0_.x + drag_start_pos.x,
460 canvas_p0_.y + drag_start_pos.y);
461 auto end = ImVec2(canvas_p0_.x + drag_end_pos.x + tile_size,
462 canvas_p0_.y + drag_end_pos.y + tile_size);
463 draw_list_->AddRect(start, end, kWhiteColor);
464 dragging = true;
465 }
466
467 if (dragging) {
468 // Release dragging mode
469 if (!ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
470 dragging = false;
471
472 // Calculate the bounds of the rectangle in terms of 16x16 tile indices
473 constexpr int tile16_size = 16;
474 int start_x = std::floor(drag_start_pos.x / scaled_size) * tile16_size;
475 int start_y = std::floor(drag_start_pos.y / scaled_size) * tile16_size;
476 int end_x = std::floor(drag_end_pos.x / scaled_size) * tile16_size;
477 int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size;
478
479 // Swap the start and end positions if they are in the wrong order
480 if (start_x > end_x) std::swap(start_x, end_x);
481 if (start_y > end_y) std::swap(start_y, end_y);
482
483 selected_tiles_.clear();
484 // Number of tiles per local map (since each tile is 16x16)
485 constexpr int tiles_per_local_map = small_map_size / 16;
486
487 // Loop through the tiles in the rectangle and store their positions
488 for (int y = start_y; y <= end_y; y += tile16_size) {
489 for (int x = start_x; x <= end_x; x += tile16_size) {
490 // Determine which local map (512x512) the tile is in
491 int local_map_x = x / small_map_size;
492 int local_map_y = y / small_map_size;
493
494 // Calculate the tile's position within its local map
495 int tile16_x = (x % small_map_size) / tile16_size;
496 int tile16_y = (y % small_map_size) / tile16_size;
497
498 // Calculate the index within the overall map structure
499 int index_x = local_map_x * tiles_per_local_map + tile16_x;
500 int index_y = local_map_y * tiles_per_local_map + tile16_y;
501
502 selected_tiles_.push_back(ImVec2(index_x, index_y));
503 }
504 }
505 // Clear and add the calculated rectangle points
506 selected_points_.clear();
507 selected_points_.push_back(drag_start_pos);
508 selected_points_.push_back(drag_end_pos);
509 select_rect_active_ = true;
510 }
511 }
512}
513
514void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) {
515 if (!bitmap.is_active()) {
516 return;
517 }
518 bitmap_ = &bitmap;
519 draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
520 ImVec2(canvas_p0_.x, canvas_p0_.y),
521 ImVec2(canvas_p0_.x + (bitmap.width() * scale),
522 canvas_p0_.y + (bitmap.height() * scale)));
524}
525
526void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale,
527 int alpha) {
528 if (!bitmap.is_active()) {
529 return;
530 }
531 bitmap_ = &bitmap;
532 draw_list_->AddImage(
533 (ImTextureID)(intptr_t)bitmap.texture(),
534 ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
535 canvas_p0_.y + y_offset + scrolling_.y),
536 ImVec2(
537 canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale),
538 canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)),
539 ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
540}
541
542void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
543 ImVec2 src_pos, ImVec2 src_size) {
544 if (!bitmap.is_active()) {
545 return;
546 }
547 bitmap_ = &bitmap;
548 draw_list_->AddImage(
549 (ImTextureID)(intptr_t)bitmap.texture(),
550 ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
551 ImVec2(canvas_p0_.x + dest_pos.x + dest_size.x,
552 canvas_p0_.y + dest_pos.y + dest_size.y),
553 ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
554 ImVec2((src_pos.x + src_size.x) / bitmap.width(),
555 (src_pos.y + src_size.y) / bitmap.height()));
556}
557
558// TODO: Add parameters for sizing and positioning
560 for (const auto &[key, value] : gfx_bin) {
561 int offset = 0x40 * (key + 1);
562 int top_left_y = canvas_p0_.y + 2;
563 if (key >= 1) {
564 top_left_y = canvas_p0_.y + 0x40 * key;
565 }
566 draw_list_->AddImage((ImTextureID)(intptr_t)value.texture(),
567 ImVec2(canvas_p0_.x + 2, top_left_y),
568 ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset));
569 }
570}
571
572void Canvas::DrawOutline(int x, int y, int w, int h) {
573 ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
574 canvas_p0_.y + scrolling_.y + y);
575 ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
576 canvas_p0_.y + scrolling_.y + y + h);
577 draw_list_->AddRect(origin, size, kOutlineRect, 0, 0, 1.5f);
578}
579
580void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
581 ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
582 canvas_p0_.y + scrolling_.y + y);
583 ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
584 canvas_p0_.y + scrolling_.y + y + h);
585 draw_list_->AddRect(origin, size,
586 IM_COL32(color.x, color.y, color.z, color.w));
587}
588
589void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
590 ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
591 canvas_p0_.y + scrolling_.y + y);
592 ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
593 canvas_p0_.y + scrolling_.y + y + h);
594 draw_list_->AddRect(origin, size, color);
595}
596
597void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
598 int tile_size, float scale) {
599 if (selected_points_.size() != 2) {
600 // points_ should contain exactly two points
601 return;
602 }
603 if (group.empty()) {
604 // group should not be empty
605 return;
606 }
607
608 // Top-left and bottom-right corners of the rectangle
609 ImVec2 rect_top_left = selected_points_[0];
610 ImVec2 rect_bottom_right = selected_points_[1];
611
612 // Calculate the start and end tiles in the grid
613 int start_tile_x =
614 static_cast<int>(std::floor(rect_top_left.x / (tile_size * scale)));
615 int start_tile_y =
616 static_cast<int>(std::floor(rect_top_left.y / (tile_size * scale)));
617 int end_tile_x =
618 static_cast<int>(std::floor(rect_bottom_right.x / (tile_size * scale)));
619 int end_tile_y =
620 static_cast<int>(std::floor(rect_bottom_right.y / (tile_size * scale)));
621
622 if (start_tile_x > end_tile_x) std::swap(start_tile_x, end_tile_x);
623 if (start_tile_y > end_tile_y) std::swap(start_tile_y, end_tile_y);
624
625 // Calculate the size of the rectangle in 16x16 grid form
626 int rect_width = (end_tile_x - start_tile_x) * tile_size;
627 int rect_height = (end_tile_y - start_tile_y) * tile_size;
628
629 int tiles_per_row = rect_width / tile_size;
630 int tiles_per_col = rect_height / tile_size;
631
632 int i = 0;
633 for (int y = 0; y < tiles_per_col + 1; ++y) {
634 for (int x = 0; x < tiles_per_row + 1; ++x) {
635 int tile_id = group[i];
636
637 // Check if tile_id is within the range of tile16_individual_
638 auto tilemap_size =
639 tilemap.atlas.width() * tilemap.atlas.height() / tilemap.map_size.x;
640 if (tile_id >= 0 && tile_id < tilemap_size) {
641 // Calculate the position of the tile within the rectangle
642 int tile_pos_x = (x + start_tile_x) * tile_size * scale;
643 int tile_pos_y = (y + start_tile_y) * tile_size * scale;
644
645 // Draw the tile bitmap at the calculated position
646 gfx::RenderTile(tilemap, tile_id);
647 DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale,
648 150.0f);
649 i++;
650 }
651 }
652 }
653
654 const ImGuiIO &io = GetIO();
655 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
656 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
657 auto new_start_pos = AlignPosToGrid(mouse_pos, tile_size * scale);
658 selected_points_.clear();
659 selected_points_.push_back(new_start_pos);
660 selected_points_.push_back(
661 ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height));
662 select_rect_active_ = true;
663}
664
665void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
666 ImVec2 origin(canvas_p0_.x + scrolling_.x + x,
667 canvas_p0_.y + scrolling_.y + y);
668 ImVec2 size(canvas_p0_.x + scrolling_.x + x + w,
669 canvas_p0_.y + scrolling_.y + y + h);
670 draw_list_->AddRectFilled(origin, size,
671 IM_COL32(color.x, color.y, color.z, color.w));
672 // Add a black outline
673 ImVec2 outline_origin(origin.x - 1, origin.y - 1);
674 ImVec2 outline_size(size.x + 1, size.y + 1);
675 draw_list_->AddRect(outline_origin, outline_size, kBlackColor);
676}
677
678void Canvas::DrawText(std::string text, int x, int y) {
679 draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + x + 1,
680 canvas_p0_.y + scrolling_.y + y + 1),
681 kBlackColor, text.data());
682 draw_list_->AddText(
683 ImVec2(canvas_p0_.x + scrolling_.x + x, canvas_p0_.y + scrolling_.y + y),
684 kWhiteColor, text.data());
685}
686
687void Canvas::DrawGridLines(float grid_step) {
688 const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
689 const float grid_thickness = 0.5f;
690 for (float x = fmodf(scrolling_.x, grid_step);
691 x < canvas_sz_.x * global_scale_; x += grid_step)
692 draw_list_->AddLine(ImVec2(canvas_p0_.x + x, canvas_p0_.y),
693 ImVec2(canvas_p0_.x + x, canvas_p1_.y), grid_color,
694 grid_thickness);
695 for (float y = fmodf(scrolling_.y, grid_step);
696 y < canvas_sz_.y * global_scale_; y += grid_step)
697 draw_list_->AddLine(ImVec2(canvas_p0_.x, canvas_p0_.y + y),
698 ImVec2(canvas_p1_.x, canvas_p0_.y + y), grid_color,
699 grid_thickness);
700}
701
702void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
703 // Draw grid + all lines in the canvas
704 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
705 if (enable_grid_) {
706 if (custom_step_ != 0.f) grid_step = custom_step_;
707 grid_step *= global_scale_; // Apply global scale to grid step
708
709 DrawGridLines(grid_step);
710 DrawCustomHighlight(grid_step);
711
713 // Draw the contents of labels on the grid
714 for (float x = fmodf(scrolling_.x, grid_step);
715 x < canvas_sz_.x * global_scale_; x += grid_step) {
716 for (float y = fmodf(scrolling_.y, grid_step);
717 y < canvas_sz_.y * global_scale_; y += grid_step) {
718 int tile_x = (x - scrolling_.x) / grid_step;
719 int tile_y = (y - scrolling_.y) / grid_step;
720 int tile_id = tile_x + (tile_y * tile_id_offset);
721
722 if (tile_id >= labels_[label_id].size()) {
723 break;
724 }
725 std::string label = labels_[label_id][tile_id];
726 draw_list_->AddText(
727 ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
728 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
729 kWhiteColor, label.data());
730 }
731 }
732 }
733 }
734}
735
736void Canvas::DrawCustomHighlight(float grid_step) {
737 if (highlight_tile_id != -1) {
738 int tile_x = highlight_tile_id % 8;
739 int tile_y = highlight_tile_id / 8;
740 ImVec2 tile_pos(canvas_p0_.x + scrolling_.x + tile_x * grid_step,
741 canvas_p0_.y + scrolling_.y + tile_y * grid_step);
742 ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
743
744 draw_list_->AddRectFilled(tile_pos, tile_pos_end,
745 IM_COL32(255, 0, 255, 255));
746 }
747}
748
749void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
750 // Draw grid + all lines in the canvas
751 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
752 if (enable_grid_) {
753 if (custom_step_ != 0.f) grid_step = custom_step_;
754 grid_step *= global_scale_; // Apply global scale to grid step
755
756 DrawGridLines(grid_step);
757 DrawCustomHighlight(grid_step);
758
760 // Draw the hex ID of the tile in the center of the tile square
761 for (float x = fmodf(scrolling_.x, grid_step);
762 x < canvas_sz_.x * global_scale_; x += grid_step) {
763 for (float y = fmodf(scrolling_.y, grid_step);
764 y < canvas_sz_.y * global_scale_; y += grid_step) {
765 int tile_x = (x - scrolling_.x) / grid_step;
766 int tile_y = (y - scrolling_.y) / grid_step;
767 int tile_id = tile_x + (tile_y * 16);
768 std::string hex_id = absl::StrFormat("%02X", tile_id);
769 draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 4,
770 canvas_p0_.y + y + (grid_step / 2) - 4),
771 kWhiteColor, hex_id.data());
772 }
773 }
774 }
775
777 // Draw the contents of labels on the grid
778 for (float x = fmodf(scrolling_.x, grid_step);
779 x < canvas_sz_.x * global_scale_; x += grid_step) {
780 for (float y = fmodf(scrolling_.y, grid_step);
781 y < canvas_sz_.y * global_scale_; y += grid_step) {
782 int tile_x = (x - scrolling_.x) / grid_step;
783 int tile_y = (y - scrolling_.y) / grid_step;
784 int tile_id = tile_x + (tile_y * tile_id_offset);
785
786 if (tile_id >= labels_[current_labels_].size()) {
787 break;
788 }
789 std::string label = labels_[current_labels_][tile_id];
790 draw_list_->AddText(
791 ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
792 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
793 kWhiteColor, label.data());
794 }
795 }
796 }
797 }
798}
799
801 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
802 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
803 for (int n = 0; n < points_.Size; n += 2) {
804 draw_list_->AddRect(
805 ImVec2(origin.x + points_[n].x, origin.y + points_[n].y),
806 ImVec2(origin.x + points_[n + 1].x, origin.y + points_[n + 1].y),
807 kWhiteColor, 1.0f);
808 }
809
810 if (!selected_points_.empty()) {
811 for (int n = 0; n < selected_points_.size(); n += 2) {
812 draw_list_->AddRect(ImVec2(origin.x + selected_points_[n].x,
813 origin.y + selected_points_[n].y),
814 ImVec2(origin.x + selected_points_[n + 1].x + 0x10,
815 origin.y + selected_points_[n + 1].y + 0x10),
816 kWhiteColor, 1.0f);
817 }
818 }
819
820 draw_list_->PopClipRect();
821}
822
824 // Based on ImGui demo, should be adapted to use for OAM
825 ImDrawList *draw_list = ImGui::GetWindowDrawList();
826 {
827 Text("Blue shape is drawn first: appears in back");
828 Text("Red shape is drawn after: appears in front");
829 ImVec2 p0 = ImGui::GetCursorScreenPos();
830 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
831 IM_COL32(0, 0, 255, 255)); // Blue
832 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
833 ImVec2(p0.x + 75, p0.y + 75),
834 IM_COL32(255, 0, 0, 255)); // Red
835 ImGui::Dummy(ImVec2(75, 75));
836 }
837 ImGui::Separator();
838 {
839 Text("Blue shape is drawn first, into channel 1: appears in front");
840 Text("Red shape is drawn after, into channel 0: appears in back");
841 ImVec2 p1 = ImGui::GetCursorScreenPos();
842
843 // Create 2 channels and draw a Blue shape THEN a Red shape.
844 // You can create any number of channels. Tables API use 1 channel per
845 // column in order to better batch draw calls.
846 draw_list->ChannelsSplit(2);
847 draw_list->ChannelsSetCurrent(1);
848 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
849 IM_COL32(0, 0, 255, 255)); // Blue
850 draw_list->ChannelsSetCurrent(0);
851 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
852 ImVec2(p1.x + 75, p1.y + 75),
853 IM_COL32(255, 0, 0, 255)); // Red
854
855 // Flatten/reorder channels. Red shape is in channel 0 and it appears
856 // below the Blue shape in channel 1. This works by copying draw indices
857 // only (vertices are not copied).
858 draw_list->ChannelsMerge();
859 ImGui::Dummy(ImVec2(75, 75));
860 Text("After reordering, contents of channel 0 appears below channel 1.");
861 }
862}
863
864void BeginCanvas(Canvas &canvas, ImVec2 child_size) {
866 ImGui::BeginChild(canvas.canvas_id().c_str(), child_size, true);
867 canvas.DrawBackground();
869 canvas.DrawContextMenu();
870}
871
872void EndCanvas(Canvas &canvas) {
873 canvas.DrawGrid();
874 canvas.DrawOverlay();
875 ImGui::EndChild();
876}
877
878void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
879 int num_sheets_to_load, int canvas_id,
880 bool is_loaded, gfx::BitmapTable &graphics_bin) {
881 gui::Canvas canvas;
882 if (ImGuiID child_id =
883 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
884 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
885 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
886 canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
887 canvas.DrawContextMenu();
888 if (is_loaded) {
889 for (const auto &[key, value] : graphics_bin) {
890 int offset = height * (key + 1);
891 int top_left_y = canvas.zero_point().y + 2;
892 if (key >= 1) {
893 top_left_y = canvas.zero_point().y + height * key;
894 }
895 canvas.draw_list()->AddImage(
896 (ImTextureID)(intptr_t)value.texture(),
897 ImVec2(canvas.zero_point().x + 2, top_left_y),
898 ImVec2(canvas.zero_point().x + 0x100,
899 canvas.zero_point().y + offset));
900 }
901 }
902 canvas.DrawTileSelector(tile_size);
903 canvas.DrawGrid(tile_size);
904 canvas.DrawOverlay();
905 }
906 ImGui::EndChild();
907}
908
909void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
910 int height, int tile_size, bool is_loaded,
911 bool scrollbar, int canvas_id) {
912 auto draw_canvas = [](gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
913 int height, int tile_size, bool is_loaded) {
914 canvas.DrawBackground(ImVec2(width + 1, height + 1));
915 canvas.DrawContextMenu();
916 canvas.DrawBitmap(bitmap, 2, is_loaded);
917 canvas.DrawTileSelector(tile_size);
918 canvas.DrawGrid(tile_size);
919 canvas.DrawOverlay();
920 };
921
922 if (scrollbar) {
923 if (ImGuiID child_id =
924 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
925 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
926 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
927 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
928 }
929 ImGui::EndChild();
930 } else {
931 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
932 }
933}
934
935} // namespace gui
936} // namespace yaze
auto rom()
Definition rom.h:290
The Renderer class represents the renderer for the Yaze application.
Definition renderer.h:24
void UpdateBitmap(gfx::Bitmap *bitmap)
Definition renderer.h:49
void RenderBitmap(gfx::Bitmap *bitmap)
Definition renderer.h:45
Represents a bitmap image.
Definition bitmap.h:59
const SnesPalette & palette() const
Definition bitmap.h:149
SDL_Texture * texture() const
Definition bitmap.h:158
bool is_active() const
Definition bitmap.h:162
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
Definition bitmap.cc:392
int height() const
Definition bitmap.h:152
int width() const
Definition bitmap.h:151
Represents a canvas for drawing and manipulating graphics.
Definition canvas.h:37
ImVec2 scrolling_
Definition canvas.h:234
ImVector< ImVec2 > points_
Definition canvas.h:242
int highlight_tile_id
Definition canvas.h:224
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:514
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f)
Definition canvas.cc:597
std::string canvas_id_
Definition canvas.h:246
ImVec2 selected_tile_pos_
Definition canvas.h:240
ImVec2 canvas_p1_
Definition canvas.h:237
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:580
uint64_t edit_palette_group_name_index_
Definition canvas.h:227
void DrawOverlay()
Definition canvas.cc:800
void DrawContextMenu()
Definition canvas.cc:108
ImVec2 mouse_pos_in_canvas_
Definition canvas.h:239
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:273
bool DrawSolidTilePainter(const ImVec4 &color, int size)
Definition canvas.cc:322
bool enable_context_menu_
Definition canvas.h:215
auto draw_list() const
Definition canvas.h:165
void DrawLayeredElements()
Definition canvas.cc:823
bool enable_custom_labels_
Definition canvas.h:214
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:397
void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, const std::function< void()> &event, int tile_size, float scale=1.0f)
Definition canvas.cc:51
void DrawGridLines(float grid_step)
Definition canvas.cc:687
uint64_t edit_palette_sub_index_
Definition canvas.h:228
bool custom_canvas_size_
Definition canvas.h:216
void DrawRect(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:665
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:223
ImDrawList * draw_list_
Definition canvas.h:232
ImVector< ImVec2 > selected_points_
Definition canvas.h:243
void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color)
Definition canvas.cc:376
void DrawCustomHighlight(float grid_step)
Definition canvas.cc:736
bool select_rect_active_
Definition canvas.h:217
Bitmap * bitmap_
Definition canvas.h:230
uint16_t edit_palette_index_
Definition canvas.h:226
auto canvas_size() const
Definition canvas.h:169
ImVector< ImVector< std::string > > labels_
Definition canvas.h:244
auto canvas_id() const
Definition canvas.h:177
void DrawText(std::string text, int x, int y)
Definition canvas.cc:678
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:426
auto zero_point() const
Definition canvas.h:166
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), bool drag=false)
Definition canvas.cc:73
bool refresh_graphics_
Definition canvas.h:218
void UpdateInfoGrid(ImVec2 bg_size, int tile_size, float scale=1.0f, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:65
float global_scale_
Definition canvas.h:221
void DrawOutline(int x, int y, int w, int h)
Definition canvas.cc:572
float custom_step_
Definition canvas.h:220
void DrawInfoGrid(float grid_step=64.0f, int tile_id_offset=8, int label_id=0)
Definition canvas.cc:702
bool enable_hex_tile_labels_
Definition canvas.h:213
ImVec2 canvas_p0_
Definition canvas.h:236
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:559
std::string context_id_
Definition canvas.h:247
ImVec2 canvas_sz_
Definition canvas.h:235
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:749
ImVec2 drawn_tile_pos_
Definition canvas.h:238
std::vector< ImVec2 > selected_tiles_
Definition canvas.h:248
static Renderer & Get()
Definition renderer.h:26
@ kIndexed
Definition bitmap.h:33
@ k8bpp
Definition bitmap.h:35
@ k4bpp
Definition bitmap.h:34
constexpr const char * kPaletteGroupAddressesKeys[]
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:193
std::unordered_map< int, gfx::Bitmap > BitmapTable
Definition bitmap.h:199
void RenderTile(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:30
ImVec2 AlignPosToGrid(ImVec2 pos, float scale)
Definition canvas.cc:45
Graphical User Interface (GUI) components for the application.
Definition canvas.cc:16
constexpr uint32_t kWhiteColor
Definition canvas.cc:38
constexpr uint32_t kRectangleColor
Definition canvas.cc:37
void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id)
Definition canvas.cc:909
void EndCanvas(Canvas &canvas)
Definition canvas.cc:872
void BeginPadding(int i)
Definition style.cc:372
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:162
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:864
void GraphicsBinCanvasPipeline(int width, int height, int tile_size, int num_sheets_to_load, int canvas_id, bool is_loaded, gfx::BitmapTable &graphics_bin)
Definition canvas.cc:878
constexpr uint32_t kOutlineRect
Definition canvas.cc:39
void EndPadding()
Definition style.cc:376
void SelectablePalettePipeline(uint64_t &palette_id, bool &refresh_graphics, gfx::SnesPalette &palette)
Definition color.cc:127
constexpr uint32_t kBlackColor
Definition canvas.cc:36
constexpr ImGuiButtonFlags kMouseFlags
Definition canvas.cc:41
void TextWithSeparators(const absl::string_view &text)
Definition style.cc:753
absl::Status DisplayEditablePalette(gfx::SnesPalette &palette, const std::string &title, bool show_color_picker, int colors_per_row, ImGuiColorEditFlags flags)
Definition color.cc:170
Main namespace for the application.
Definition controller.cc:18
absl::flat_hash_map< int, Bitmap > tile_bitmaps
Definition tilemap.h:18