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