yaze 0.3.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
12#include "app/gui/core/style.h"
13#include "imgui/imgui.h"
14
15namespace yaze::gui {
16
17// Define constructors and destructor in .cc to avoid incomplete type issues
18// with unique_ptr
19
20// Default constructor
21Canvas::Canvas() : renderer_(nullptr) {
23}
24
25// Legacy constructors (renderer is optional for backward compatibility)
26Canvas::Canvas(const std::string& id)
27 : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
29}
30
31Canvas::Canvas(const std::string& id, ImVec2 canvas_size)
32 : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
37}
38
39Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
40 CanvasGridSize grid_size)
41 : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
47}
48
49Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
50 CanvasGridSize grid_size, float global_scale)
51 : renderer_(nullptr), canvas_id_(id), context_id_(id + "Context") {
58}
59
60// New constructors with renderer support (for migration to IRenderer pattern)
61Canvas::Canvas(gfx::IRenderer* renderer) : renderer_(renderer) {
63}
64
65Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id)
66 : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
68}
69
70Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
71 ImVec2 canvas_size)
72 : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
77}
78
79Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
80 ImVec2 canvas_size, CanvasGridSize grid_size)
81 : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
87}
88
89Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
90 ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
91 : renderer_(renderer), canvas_id_(id), context_id_(id + "Context") {
98}
99
100Canvas::~Canvas() = default;
101
115
116void Canvas::Init(const std::string& id, ImVec2 canvas_size) {
117 canvas_id_ = id;
118 context_id_ = id + "Context";
119 if (canvas_size.x > 0 || canvas_size.y > 0) {
123 }
125}
126
128 if (!extensions_) {
129 extensions_ = std::make_unique<CanvasExtensions>();
130 }
131 return *extensions_;
132}
133
134using ImGui::GetContentRegionAvail;
135using ImGui::GetCursorScreenPos;
136using ImGui::GetIO;
137using ImGui::GetWindowDrawList;
138using ImGui::IsItemActive;
139using ImGui::IsItemHovered;
140using ImGui::IsMouseClicked;
141using ImGui::IsMouseDragging;
142using ImGui::Text;
143
144constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
145constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
146
147constexpr ImGuiButtonFlags kMouseFlags =
148 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
149
150namespace {
151ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
152 return ImVec2(std::floor(pos.x / scale) * scale,
153 std::floor(pos.y / scale) * scale);
154}
155} // namespace
156
157// Canvas class implementation begins here
158
160 // Initialize configuration with sensible defaults
161 config_.enable_grid = true;
165 config_.is_draggable = false;
166 config_.grid_step = 32.0f;
167 config_.global_scale = 1.0f;
168 config_.canvas_size = ImVec2(0, 0);
171
172 // Initialize selection state
174
175 // Note: palette_editor is now in CanvasExtensions (lazy-initialized)
176
177 // Initialize interaction handler
179
180 // Initialize enhanced components
182
183 // Initialize legacy compatibility variables to match config
194}
195
197 // Cleanup extensions (if initialized)
198 if (extensions_) {
199 extensions_->Cleanup();
200 }
201 extensions_.reset();
202
204
205 // Stop performance monitoring before cleanup to prevent segfault
207 performance_integration_->StopMonitoring();
208 }
209
210 // Cleanup enhanced components (non-extension ones)
211 context_menu_.reset();
212 usage_tracker_.reset();
214}
215
217 // Note: modals is now in CanvasExtensions (lazy-initialized on first use)
218
219 // Initialize context menu system
220 context_menu_ = std::make_unique<CanvasContextMenu>();
221 context_menu_->Initialize(canvas_id_);
222
223 // Initialize usage tracker (optional, controlled by config.enable_metrics)
225 usage_tracker_ = std::make_shared<CanvasUsageTracker>();
226 usage_tracker_->Initialize(canvas_id_);
227 usage_tracker_->StartSession();
228
229 // Initialize performance integration
230 performance_integration_ = std::make_shared<CanvasPerformanceIntegration>();
233 performance_integration_->StartMonitoring();
234 }
235}
236
238 if (usage_tracker_) {
239 usage_tracker_->SetUsageMode(usage);
240 }
241 if (context_menu_) {
242 context_menu_->SetUsageMode(usage);
243 }
244 config_.usage_mode = usage;
245}
246
247void Canvas::RecordCanvasOperation(const std::string& operation_name,
248 double time_ms) {
249 if (usage_tracker_) {
250 usage_tracker_->RecordOperation(operation_name, time_ms);
251 }
253 performance_integration_->RecordOperation(operation_name, time_ms,
254 usage_mode());
255 }
256}
257
260 performance_integration_->RenderPerformanceUI();
261 }
262}
263
265 if (usage_tracker_) {
266 std::string report = usage_tracker_->ExportUsageReport();
267 // Show report in a modal or window (uses ImGui directly, no modals_ needed)
268 ImGui::OpenPopup("Canvas Usage Report");
269 if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr,
270 ImGuiWindowFlags_AlwaysAutoResize)) {
271 ImGui::Text("Canvas Usage Report");
272 ImGui::Separator();
273 ImGui::TextWrapped("%s", report.c_str());
274 ImGui::Separator();
275 if (ImGui::Button("Close")) {
276 ImGui::CloseCurrentPopup();
277 }
278 ImGui::EndPopup();
279 }
280 }
281}
282
284 rom_ = rom;
285 auto& ext = EnsureExtensions();
286 ext.InitializePaletteEditor();
287 if (ext.palette_editor) {
288 ext.palette_editor->Initialize(rom);
289 }
290}
291
294 if (extensions_ && extensions_->palette_editor && game_data) {
295 extensions_->palette_editor->Initialize(game_data);
296 }
297}
298
300 if (bitmap_) {
301 auto& ext = EnsureExtensions();
302 ext.InitializePaletteEditor();
303 if (ext.palette_editor) {
304 auto mutable_palette = bitmap_->mutable_palette();
305 ext.palette_editor->ShowPaletteEditor(*mutable_palette,
306 "Canvas Palette Editor");
307 }
308 }
309}
310
312 if (bitmap_) {
313 auto& ext = EnsureExtensions();
314 ext.InitializePaletteEditor();
315 if (ext.palette_editor) {
316 ext.palette_editor->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
317 }
318 }
319}
320
321bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
322 if (bitmap_ && extensions_ && extensions_->palette_editor) {
323 return extensions_->palette_editor->ApplyROMPalette(bitmap_, group_index,
324 palette_index);
325 }
326 return false;
327}
328
329// Size reporting methods for table integration
334
339
340void Canvas::ReserveTableSpace(const std::string& label) {
343}
344
345bool Canvas::BeginTableCanvas(const std::string& label) {
346 if (config_.auto_resize) {
347 ImVec2 preferred_size = GetPreferredSize();
348 CanvasUtils::SetNextCanvasSize(preferred_size, true);
349 }
350
351 // Begin child window that properly reports size to tables
352 std::string child_id = canvas_id_ + "_TableChild";
353 ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
354
355 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
356 bool result =
357 ImGui::BeginChild(child_id.c_str(), child_size,
358 true, // Always show border for table integration
359 ImGuiWindowFlags_NoScrollbar);
360
361 if (!label.empty()) {
362 ImGui::Text("%s", label.c_str());
363 }
364
365 return result;
366}
367
369 ImGui::EndChild();
370}
371
372CanvasRuntime Canvas::BeginInTable(const std::string& label,
373 const CanvasFrameOptions& options) {
374 // Calculate child size from options or auto-resize
375 ImVec2 child_size = options.canvas_size;
376 if (child_size.x <= 0 || child_size.y <= 0) {
378 }
379
380 if (config_.auto_resize && child_size.x > 0 && child_size.y > 0) {
381 CanvasUtils::SetNextCanvasSize(child_size, true);
382 }
383
384 // Begin child window for table integration
385 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
386 std::string child_id = canvas_id_ + "_TableChild";
387 ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoScrollbar;
388 if (options.show_scrollbar) {
389 child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
390 }
391 ImGui::BeginChild(child_id.c_str(), child_size, true, child_flags);
392
393 if (!label.empty()) {
394 ImGui::Text("%s", label.c_str());
395 }
396
397 // Draw background and set up canvas state
398 Begin(options);
399
400 // Build and return runtime
402 if (options.grid_step.has_value()) {
403 rt.grid_step = options.grid_step.value();
404 }
405 return rt;
406}
407
409 const CanvasFrameOptions& options) {
410 // Draw grid if enabled
411 if (options.draw_grid) {
412 float step = options.grid_step.value_or(config_.grid_step);
413 DrawGrid(step);
414 }
415
416 // Draw overlay
417 if (options.draw_overlay) {
418 DrawOverlay();
419 }
420
421 // Render persistent popups if enabled
422 if (options.render_popups) {
424 }
425
426 ImGui::EndChild();
427}
428
429// Improved interaction detection methods
431 return !points_.empty() && points_.size() >= 2;
432}
433
434bool Canvas::WasClicked(ImGuiMouseButton button) const {
435 return ImGui::IsItemClicked(button) && HasValidSelection();
436}
437
438bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
439 return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) &&
441}
442
444 if (HasValidSelection()) {
445 return points_[0]; // Return the first point of the selection
446 }
447 return ImVec2(-1, -1); // Invalid position
448}
449
450// ==================== Modern ImGui-Style Interface ====================
451
452void Canvas::Begin(ImVec2 canvas_size) {
453 // Modern ImGui-style begin - combines DrawBackground + DrawContextMenu
456}
457
459 // Modern ImGui-style end - automatically draws grid and overlay
460 if (config_.enable_grid) {
461 DrawGrid();
462 }
463 DrawOverlay();
464
465 // Render any persistent popups from context menu actions
467}
468
469void Canvas::Begin(const CanvasFrameOptions& options) {
471
472 // Only wrap in child window if explicitly requested
473 if (options.use_child_window) {
474 // Calculate effective size
475 ImVec2 effective_size = options.canvas_size;
476 if (effective_size.x == 0 && effective_size.y == 0) {
477 if (IsAutoResize()) {
478 effective_size = GetPreferredSize();
479 } else {
480 effective_size = GetCurrentSize();
481 }
482 }
483
484 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
485 if (options.show_scrollbar) {
486 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
487 }
488 ImGui::BeginChild(canvas_id().c_str(), effective_size, true, child_flags);
489 }
490
491 // Apply grid step from options if specified
492 if (options.grid_step.has_value()) {
493 SetCustomGridStep(options.grid_step.value());
494 }
495
498
499 if (options.draw_context_menu) {
501 }
502}
503
504void Canvas::End(const CanvasFrameOptions& options) {
505 if (options.draw_grid) {
506 DrawGrid(options.grid_step.value_or(GetGridStep()));
507 }
508 if (options.draw_overlay) {
509 DrawOverlay();
510 }
511 if (options.render_popups) {
513 }
514 // Only end child if we started one
515 if (options.use_child_window) {
516 ImGui::EndChild();
517 }
518}
519
520// ==================== Legacy Interface ====================
521
523 gfx::Bitmap& bitmap, const ImVec4& color,
524 const std::function<void()>& event,
525 int tile_size, float scale) {
526 config_.global_scale = scale;
527 global_scale_ = scale; // Legacy compatibility
530 DrawBitmap(bitmap, 2, scale);
531 if (DrawSolidTilePainter(color, tile_size)) {
532 event();
533 bitmap.UpdateTexture();
534 }
535 DrawGrid();
536 DrawOverlay();
537}
538
539void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
541 enable_custom_labels_ = true; // Legacy compatibility
542 DrawBackground(bg_size);
543 DrawInfoGrid(grid_size, 8, label_id);
544 DrawOverlay();
545}
546
547void Canvas::DrawBackground(ImVec2 canvas_size) {
548 draw_list_ = GetWindowDrawList();
549
550 // Phase 1: Calculate geometry using new helper
552 config_, canvas_size, GetCursorScreenPos(), GetContentRegionAvail());
553
554 // Sync legacy fields for backward compatibility
559
560 // Update config if explicit size provided
561 if (canvas_size.x != 0) {
563 }
564
565 // Phase 1: Render background using helper
567
568 ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size,
570
571 // CRITICAL FIX: Always update hover mouse position when hovering over canvas
572 // This fixes the regression where CheckForCurrentMap() couldn't track hover
573 // Phase 1: Use geometry helper for mouse calculation
574 if (IsItemHovered()) {
575 const ImGuiIO& io = GetIO();
578 state_.is_hovered = true;
579 is_hovered_ = true;
580 } else {
581 state_.is_hovered = false;
582 is_hovered_ = false;
583 }
584
585 // Pan handling (Phase 1: Use geometry helper)
586 if (config_.is_draggable && IsItemHovered()) {
587 const ImGuiIO& io = GetIO();
588 const bool is_active = IsItemActive(); // Held
589
590 // Pan (we use a zero mouse threshold when there's no context menu)
591 if (const float mouse_threshold_for_pan =
592 enable_context_menu_ ? -1.0f : 0.0f;
593 is_active &&
594 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
595 ApplyScrollDelta(state_.geometry, io.MouseDelta);
596 scrolling_ = state_.geometry.scrolling; // Sync legacy field
597 config_.scrolling = scrolling_; // Sync config
598 }
599 }
600}
601
603 const ImGuiIO& io = GetIO();
604 const ImVec2 scaled_sz(canvas_sz_.x * global_scale_,
606 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
607 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
608 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
609
610 // Update canvas state for enhanced components
611 if (usage_tracker_) {
612 usage_tracker_->UpdateCanvasState(
615 }
616
617 // Use enhanced context menu if available
618 if (context_menu_) {
619 CanvasConfig snapshot;
620 snapshot.canvas_size = canvas_sz_;
622 snapshot.global_scale = global_scale_;
623 snapshot.grid_step = custom_step_;
624 snapshot.enable_grid = enable_grid_;
628 snapshot.is_draggable = draggable_;
630 snapshot.scrolling = scrolling_;
631
632 context_menu_->SetCanvasState(
636
637 context_menu_->Render(
638 context_id_, mouse_pos, rom_, bitmap_,
639 bitmap_ ? bitmap_->mutable_palette() : nullptr,
640 [this](CanvasContextMenu::Command command,
641 const CanvasConfig& updated_config) {
642 switch (command) {
644 ResetView();
645 break;
647 if (bitmap_) {
649 }
650 break;
653 break;
656 break;
660 break;
664 break;
668 break;
672 break;
675 break;
679 break;
681 config_.grid_step = updated_config.grid_step;
683 break;
685 config_.global_scale = updated_config.global_scale;
687 break;
689 {
690 auto& ext = EnsureExtensions();
691 ext.InitializeModals();
692 if (ext.modals) {
693 CanvasConfig modal_config = updated_config;
694 modal_config.on_config_changed =
695 [this](const CanvasConfig& cfg) {
697 };
698 modal_config.on_scale_changed =
699 [this](const CanvasConfig& cfg) {
701 };
702 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config,
703 bitmap_);
704 }
705 }
706 break;
708 {
709 auto& ext = EnsureExtensions();
710 ext.InitializeModals();
711 if (ext.modals) {
712 CanvasConfig modal_config = updated_config;
713 modal_config.on_config_changed =
714 [this](const CanvasConfig& cfg) {
716 };
717 modal_config.on_scale_changed =
718 [this](const CanvasConfig& cfg) {
720 };
721 ext.modals->ShowScalingControls(canvas_id_, modal_config, bitmap_);
722 }
723 }
724 break;
725 default:
726 break;
727 }
728 },
729 snapshot, this); // Phase 4: Pass Canvas* for editor menu integration
730
731 if (extensions_ && extensions_->modals) {
732 extensions_->modals->Render();
733 }
734
735 return;
736 }
737
738 // Draw enhanced property dialogs
741}
742
744 // Phase 4: Use RenderMenuItem from canvas_menu.h for consistent rendering
745 auto popup_callback = [this](const std::string& id,
746 std::function<void()> callback) {
747 popup_registry_.Open(id, callback);
748 };
749
750 gui::RenderMenuItem(item, popup_callback);
751}
752
754 // Phase 4: Add to editor menu definition
755 // Items are added to a default section with editor-specific priority
756 if (editor_menu_.sections.empty()) {
757 CanvasMenuSection section;
759 section.separator_after = true;
760 editor_menu_.sections.push_back(section);
761 }
762
763 // Add to the last section (or create new if the last isn't editor-specific)
764 auto& last_section = editor_menu_.sections.back();
765 if (last_section.priority != MenuSectionPriority::kEditorSpecific) {
766 CanvasMenuSection new_section;
768 new_section.separator_after = true;
769 editor_menu_.sections.push_back(new_section);
770 editor_menu_.sections.back().items.push_back(item);
771 } else {
772 last_section.items.push_back(item);
773 }
774}
775
779
780void Canvas::OpenPersistentPopup(const std::string& popup_id,
781 std::function<void()> render_callback) {
782 // Phase 4: Simplified popup management (no legacy synchronization)
783 popup_registry_.Open(popup_id, render_callback);
784}
785
786void Canvas::ClosePersistentPopup(const std::string& popup_id) {
787 // Phase 4: Simplified popup management (no legacy synchronization)
788 popup_registry_.Close(popup_id);
789}
790
792 // Phase 4: Simplified rendering (no legacy synchronization)
794}
795
797 if (!bitmap.is_active())
798 return;
799
800 ImVec2 available = ImGui::GetContentRegionAvail();
801 float scale_x = available.x / bitmap.width();
802 float scale_y = available.y / bitmap.height();
803 config_.global_scale = std::min(scale_x, scale_y);
804
805 // Ensure minimum readable scale
806 if (config_.global_scale < 0.25f)
807 config_.global_scale = 0.25f;
808
809 global_scale_ = config_.global_scale; // Legacy compatibility
810
811 // Center the view
812 scrolling_ = ImVec2(0, 0);
813}
814
816 config_.global_scale = 1.0f;
817 global_scale_ = 1.0f; // Legacy compatibility
818 scrolling_ = ImVec2(0, 0);
819 config_.scrolling = ImVec2(0, 0); // Sync config for persistence
820}
821
845
851
852bool Canvas::DrawTilePainter(const Bitmap& bitmap, int size, float scale) {
853 const ImGuiIO& io = GetIO();
854 const bool is_hovered = IsItemHovered();
855 is_hovered_ = is_hovered;
856 // Lock scrolled origin
857 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
858 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
859 const auto scaled_size = size * scale;
860
861 // Erase the hover when the mouse is not in the canvas window.
862 if (!is_hovered) {
863 points_.clear();
864 return false;
865 }
866
867 // Reset the previous tile hover
868 if (!points_.empty()) {
869 points_.clear();
870 }
871
872 // Calculate the coordinates of the mouse
873 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
874 mouse_pos_in_canvas_ = paint_pos;
875 auto paint_pos_end =
876 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
877 points_.push_back(paint_pos);
878 points_.push_back(paint_pos_end);
879
880 if (bitmap.is_active()) {
881 draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
882 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
883 ImVec2(origin.x + paint_pos.x + scaled_size,
884 origin.y + paint_pos.y + scaled_size));
885 }
886
887 if (IsMouseClicked(ImGuiMouseButton_Left) &&
888 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
889 // Draw the currently selected tile on the overworld here
890 // Save the coordinates of the selected tile.
891 drawn_tile_pos_ = paint_pos;
892 return true;
893 }
894
895 return false;
896}
897
898bool Canvas::DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile) {
899 // Update hover state for backward compatibility
900 is_hovered_ = IsItemHovered();
901
902 // Clear points if not hovered (legacy behavior)
903 if (!is_hovered_) {
904 points_.clear();
905 return false;
906 }
907
908 // Build runtime and delegate to stateless helper
910 ImVec2 drawn_pos;
911 bool result = gui::DrawTilemapPainter(rt, tilemap, current_tile, &drawn_pos);
912
913 // Sync legacy state from stateless call
914 if (is_hovered_) {
915 const ImGuiIO& io = GetIO();
916 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
917 canvas_p0_.y + scrolling_.y);
918 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
919 const float scaled_size = tilemap.tile_size.x * global_scale_;
920 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
921 mouse_pos_in_canvas_ = paint_pos;
922
923 points_.clear();
924 points_.push_back(paint_pos);
925 points_.push_back(
926 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
927 }
928
929 if (result) {
930 drawn_tile_pos_ = drawn_pos;
931 }
932
933 return result;
934}
935
936bool Canvas::DrawSolidTilePainter(const ImVec4& color, int tile_size) {
937 const ImGuiIO& io = GetIO();
938 const bool is_hovered = IsItemHovered();
939 is_hovered_ = is_hovered;
940 // Lock scrolled origin
941 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
942 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
943 auto scaled_tile_size = tile_size * global_scale_;
944 static bool is_dragging = false;
945 static ImVec2 start_drag_pos;
946
947 // Erase the hover when the mouse is not in the canvas window.
948 if (!is_hovered) {
949 points_.clear();
950 return false;
951 }
952
953 // Reset the previous tile hover
954 if (!points_.empty()) {
955 points_.clear();
956 }
957
958 // Calculate the coordinates of the mouse
959 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
960 mouse_pos_in_canvas_ = paint_pos;
961
962 // Clamp the size to a grid
963 paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_);
964 paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_);
965
966 points_.push_back(paint_pos);
967 points_.push_back(
968 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
969
970 draw_list_->AddRectFilled(
971 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
972 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
973 origin.y + paint_pos.y + scaled_tile_size),
974 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
975
976 if (IsMouseClicked(ImGuiMouseButton_Left)) {
977 is_dragging = true;
978 start_drag_pos = paint_pos;
979 }
980
981 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
982 is_dragging = false;
983 drawn_tile_pos_ = start_drag_pos;
984 return true;
985 }
986
987 return false;
988}
989
990void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap,
991 ImVec4 color) {
992 const ImVec2 position = drawn_tile_pos_;
993 int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
994 int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
995
996 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
997
998 // Update the bitmap's pixel data based on the start_position and color
999 for (int y = 0; y < tile_size; ++y) {
1000 for (int x = 0; x < tile_size; ++x) {
1001 // Calculate the actual pixel index in the bitmap
1002 int pixel_index =
1003 (start_position.y + y) * bitmap->width() + (start_position.x + x);
1004
1005 // Write the color to the pixel
1006 bitmap->WriteColor(pixel_index, color);
1007 }
1008 }
1009}
1010
1011bool Canvas::DrawTileSelector(int size, int size_y) {
1012 // Update hover state for backward compatibility
1013 is_hovered_ = IsItemHovered();
1014
1015 if (size_y == 0) {
1016 size_y = size;
1017 }
1018
1019 // Build runtime and delegate to stateless helper
1021 ImVec2 selected_pos;
1022 bool double_clicked = gui::DrawTileSelector(rt, size, size_y, &selected_pos);
1023
1024 // Sync legacy state: update points_ on click
1025 if (is_hovered_ && IsMouseClicked(ImGuiMouseButton_Left)) {
1026 const ImGuiIO& io = GetIO();
1027 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
1028 canvas_p0_.y + scrolling_.y);
1029 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1030 ImVec2 painter_pos = AlignPosToGrid(mouse_pos, static_cast<float>(size));
1031
1032 points_.clear();
1033 points_.push_back(painter_pos);
1034 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
1035 mouse_pos_in_canvas_ = painter_pos;
1036 }
1037
1038 return double_clicked;
1039}
1040
1041void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
1042 gfx::ScopedTimer timer("canvas_select_rect");
1043
1044 // Update hover state
1045 is_hovered_ = IsItemHovered();
1046 if (!is_hovered_) {
1047 return;
1048 }
1049
1050 // Build runtime and delegate to stateless helper
1052 rt.scale = scale; // Use the passed scale, not global_scale_
1053
1054 // Use a temporary selection to capture output from stateless helper
1055 CanvasSelection temp_selection;
1056 temp_selection.selected_tiles = selected_tiles_;
1057 temp_selection.selected_tile_pos = selected_tile_pos_;
1058 temp_selection.select_rect_active = select_rect_active_;
1059 for (int i = 0; i < selected_points_.size(); ++i) {
1060 temp_selection.selected_points.push_back(selected_points_[i]);
1061 }
1062
1063 gui::DrawSelectRect(rt, current_map, tile_size, scale, temp_selection);
1064
1065 // Sync back to legacy members
1066 selected_tiles_ = temp_selection.selected_tiles;
1067 selected_tile_pos_ = temp_selection.selected_tile_pos;
1068 select_rect_active_ = temp_selection.select_rect_active;
1069 selected_points_.clear();
1070 for (const auto& pt : temp_selection.selected_points) {
1071 selected_points_.push_back(pt);
1072 }
1073}
1074
1075void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) {
1076 if (!bitmap.is_active()) {
1077 return;
1078 }
1079 bitmap_ = &bitmap;
1080
1081 // Update content size for table integration
1082 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1083
1084 // Phase 1: Use rendering helper
1085 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset,
1086 scale);
1087}
1088
1089void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
1090 int alpha) {
1091 if (!bitmap.is_active()) {
1092 return;
1093 }
1094 bitmap_ = &bitmap;
1095
1096 // Update content size for table integration
1097 // CRITICAL: Store UNSCALED bitmap size as content - scale is applied during
1098 // rendering
1099 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1100
1101 // Phase 1: Use rendering helper
1102 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset,
1103 scale, alpha);
1104}
1105
1106void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size,
1107 ImVec2 src_pos, ImVec2 src_size) {
1108 if (!bitmap.is_active()) {
1109 return;
1110 }
1111 bitmap_ = &bitmap;
1112
1113 // Update content size for table integration
1114 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1115
1116 // Phase 1: Use rendering helper
1117 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size,
1118 src_pos, src_size);
1119}
1120
1121// TODO: Add parameters for sizing and positioning
1122void Canvas::DrawBitmapTable(const BitmapTable& gfx_bin) {
1123 for (const auto& [key, value] : gfx_bin) {
1124 // Skip null or inactive bitmaps without valid textures
1125 if (!value || !value->is_active() || !value->texture()) {
1126 continue;
1127 }
1128 int offset = 0x40 * (key + 1);
1129 int top_left_y = canvas_p0_.y + 2;
1130 if (key >= 1) {
1131 top_left_y = canvas_p0_.y + 0x40 * key;
1132 }
1133 draw_list_->AddImage((ImTextureID)(intptr_t)value->texture(),
1134 ImVec2(canvas_p0_.x + 2, top_left_y),
1135 ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset));
1136 }
1137}
1138
1139void Canvas::DrawOutline(int x, int y, int w, int h) {
1141 IM_COL32(255, 255, 255, 200));
1142}
1143
1144void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
1146 y, w, h, color);
1147}
1148
1149void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
1151 color);
1152}
1153
1154void Canvas::DrawBitmapGroup(std::vector<int>& group, gfx::Tilemap& tilemap,
1155 int tile_size, float /*scale*/, int local_map_size,
1156 ImVec2 total_map_size) {
1157 if (selected_points_.size() != 2) {
1158 // points_ should contain exactly two points
1159 return;
1160 }
1161 if (group.empty()) {
1162 // group should not be empty
1163 return;
1164 }
1165
1166 // CRITICAL: Use config_.global_scale for consistency with DrawOverlay
1167 // which also uses config_.global_scale for the selection rectangle outline.
1168 // Using the passed 'scale' parameter would cause misalignment if they differ.
1169 const float effective_scale = config_.global_scale;
1170
1171 // OPTIMIZATION: Use optimized rendering for large groups to improve
1172 // performance
1173 bool use_optimized_rendering =
1174 group.size() > 128; // Optimize for large selections
1175
1176 // Use provided map sizes for proper boundary handling
1177 const int small_map = local_map_size;
1178 const float large_map_width = total_map_size.x;
1179 const float large_map_height = total_map_size.y;
1180
1181 // Pre-calculate common values to avoid repeated computation
1182 const float tile_scale = tile_size * effective_scale;
1183 const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
1184
1185 // Top-left and bottom-right corners of the rectangle (in world coordinates)
1186 ImVec2 rect_top_left = selected_points_[0];
1187 ImVec2 rect_bottom_right = selected_points_[1];
1188
1189 // Calculate the start and end tiles in the grid
1190 // selected_points are now in world coordinates, so divide by tile_size only
1191 int start_tile_x =
1192 static_cast<int>(std::floor(rect_top_left.x / tile_size));
1193 int start_tile_y =
1194 static_cast<int>(std::floor(rect_top_left.y / tile_size));
1195 int end_tile_x =
1196 static_cast<int>(std::floor(rect_bottom_right.x / tile_size));
1197 int end_tile_y =
1198 static_cast<int>(std::floor(rect_bottom_right.y / tile_size));
1199
1200 if (start_tile_x > end_tile_x)
1201 std::swap(start_tile_x, end_tile_x);
1202 if (start_tile_y > end_tile_y)
1203 std::swap(start_tile_y, end_tile_y);
1204
1205 // Calculate the size of the rectangle in 16x16 grid form
1206 int rect_width = (end_tile_x - start_tile_x) * tile_size;
1207 int rect_height = (end_tile_y - start_tile_y) * tile_size;
1208
1209 int tiles_per_row = rect_width / tile_size;
1210 int tiles_per_col = rect_height / tile_size;
1211
1212 int i = 0;
1213 for (int y = 0; y < tiles_per_col + 1; ++y) {
1214 for (int x = 0; x < tiles_per_row + 1; ++x) {
1215 // Check bounds to prevent access violations
1216 if (i >= static_cast<int>(group.size())) {
1217 break;
1218 }
1219
1220 int tile_id = group[i];
1221
1222 // Check if tile_id is within the range of tile16_individual_
1223 auto tilemap_size = tilemap.map_size.x;
1224 if (tile_id >= 0 && tile_id < tilemap_size) {
1225 // Calculate the position of the tile within the rectangle
1226 int tile_pos_x = (x + start_tile_x) * tile_size * effective_scale;
1227 int tile_pos_y = (y + start_tile_y) * tile_size * effective_scale;
1228
1229 // OPTIMIZATION: Use pre-calculated values for better performance with
1230 // large selections
1231 if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
1232 atlas_tiles_per_row > 0) {
1233 int atlas_tile_x =
1234 (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
1235 int atlas_tile_y =
1236 (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
1237
1238 // Simple bounds check
1239 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
1240 atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
1241 // Calculate UV coordinates once for efficiency
1242 const float atlas_width = static_cast<float>(tilemap.atlas.width());
1243 const float atlas_height =
1244 static_cast<float>(tilemap.atlas.height());
1245 ImVec2 uv0 =
1246 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
1247 ImVec2 uv1 =
1248 ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
1249 (atlas_tile_y + tilemap.tile_size.y) / atlas_height);
1250
1251 // Calculate screen positions
1252 float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x;
1253 float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y;
1254 float screen_w = tilemap.tile_size.x * effective_scale;
1255 float screen_h = tilemap.tile_size.y * effective_scale;
1256
1257 // Use higher alpha for large selections to make them more visible
1258 uint32_t alpha_color = use_optimized_rendering
1259 ? IM_COL32(255, 255, 255, 200)
1260 : IM_COL32(255, 255, 255, 150);
1261
1262 // Draw from atlas texture with optimized parameters
1263 draw_list_->AddImage(
1264 (ImTextureID)(intptr_t)tilemap.atlas.texture(),
1265 ImVec2(screen_x, screen_y),
1266 ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1,
1267 alpha_color);
1268 }
1269 }
1270 }
1271 i++;
1272 }
1273 // Break outer loop if we've run out of tiles
1274 if (i >= static_cast<int>(group.size())) {
1275 break;
1276 }
1277 }
1278
1279 // Performance optimization completed - tiles are now rendered with
1280 // pre-calculated values
1281
1282 // Reposition rectangle to follow mouse, but clamp to prevent wrapping across
1283 // map boundaries
1284 const ImGuiIO& io = GetIO();
1285 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1286 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1287
1288 // CRITICAL FIX: Clamp BEFORE grid alignment for smoother dragging behavior
1289 // This prevents the rectangle from even attempting to cross boundaries during
1290 // drag
1291 ImVec2 clamped_mouse_pos = mouse_pos;
1292
1294 // Calculate which local map the mouse is in
1295 int mouse_local_map_x = static_cast<int>(mouse_pos.x) / small_map;
1296 int mouse_local_map_y = static_cast<int>(mouse_pos.y) / small_map;
1297
1298 // Calculate where the rectangle END would be if we place it at mouse
1299 // position
1300 float potential_end_x = mouse_pos.x + rect_width;
1301 float potential_end_y = mouse_pos.y + rect_height;
1302
1303 // Check if this would cross local map boundary (512x512 blocks)
1304 int potential_end_map_x = static_cast<int>(potential_end_x) / small_map;
1305 int potential_end_map_y = static_cast<int>(potential_end_y) / small_map;
1306
1307 // Clamp mouse position to prevent crossing during drag
1308 if (potential_end_map_x != mouse_local_map_x) {
1309 // Would cross horizontal boundary - clamp mouse to safe zone
1310 float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width;
1311 clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x);
1312 }
1313
1314 if (potential_end_map_y != mouse_local_map_y) {
1315 // Would cross vertical boundary - clamp mouse to safe zone
1316 float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height;
1317 clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y);
1318 }
1319 }
1320
1321 // Now grid-align the clamped position (in screen coords)
1322 auto new_start_pos_screen = AlignPosToGrid(clamped_mouse_pos, tile_size * effective_scale);
1323
1324 // Convert to world coordinates for storage (selected_points_ stores world coords)
1325 ImVec2 new_start_pos_world(new_start_pos_screen.x / effective_scale,
1326 new_start_pos_screen.y / effective_scale);
1327
1328 // Additional safety: clamp to overall map bounds (in world coordinates)
1329 new_start_pos_world.x =
1330 std::clamp(new_start_pos_world.x, 0.0f, large_map_width - rect_width);
1331 new_start_pos_world.y =
1332 std::clamp(new_start_pos_world.y, 0.0f, large_map_height - rect_height);
1333
1334 selected_points_.clear();
1335 selected_points_.push_back(new_start_pos_world);
1336 selected_points_.push_back(
1337 ImVec2(new_start_pos_world.x + rect_width, new_start_pos_world.y + rect_height));
1338 select_rect_active_ = true;
1339}
1340
1341void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
1343 color, config_.global_scale);
1344}
1345
1346void Canvas::DrawText(const std::string& text, int x, int y) {
1349}
1350
1355
1356void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
1357 // Draw grid + all lines in the canvas
1358 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1359 if (enable_grid_) {
1360 if (custom_step_ != 0.f)
1361 grid_step = custom_step_;
1362 grid_step *= global_scale_; // Apply global scale to grid step
1363
1364 DrawGridLines(grid_step);
1365 DrawCustomHighlight(grid_step);
1366
1367 if (!enable_custom_labels_) {
1368 return;
1369 }
1370
1371 // Draw the contents of labels on the grid
1372 for (float x = fmodf(scrolling_.x, grid_step);
1373 x < canvas_sz_.x * global_scale_; x += grid_step) {
1374 for (float y = fmodf(scrolling_.y, grid_step);
1375 y < canvas_sz_.y * global_scale_; y += grid_step) {
1376 int tile_x = (x - scrolling_.x) / grid_step;
1377 int tile_y = (y - scrolling_.y) / grid_step;
1378 int tile_id = tile_x + (tile_y * tile_id_offset);
1379
1380 if (tile_id >= labels_[label_id].size()) {
1381 break;
1382 }
1383 std::string label = labels_[label_id][tile_id];
1384 draw_list_->AddText(
1385 ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
1386 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
1387 kWhiteColor, label.data());
1388 }
1389 }
1390 }
1391}
1392
1397
1398void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
1399 if (config_.grid_step != 0.f)
1400 grid_step = config_.grid_step;
1401
1402 // Create render context for utilities
1405 .canvas_p0 = canvas_p0_,
1406 .canvas_p1 = canvas_p1_,
1407 .scrolling = scrolling_,
1408 .global_scale = config_.global_scale,
1409 .enable_grid = config_.enable_grid,
1410 .enable_hex_labels = config_.enable_hex_labels,
1411 .grid_step = grid_step};
1412
1413 // Use high-level utility function
1415
1416 // Draw custom labels if enabled
1418 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1420 tile_id_offset);
1421 draw_list_->PopClipRect();
1422 }
1423}
1424
1426 // Create render context for utilities
1429 .canvas_p0 = canvas_p0_,
1430 .canvas_p1 = canvas_p1_,
1431 .scrolling = scrolling_,
1432 .global_scale = config_.global_scale,
1433 .enable_grid = config_.enable_grid,
1434 .enable_hex_labels = config_.enable_hex_labels,
1435 .grid_step = config_.grid_step};
1436
1437 // Use high-level utility function with local points (synchronized from
1438 // interaction handler)
1440
1441 // Render any persistent popups from context menu actions
1443}
1444
1446 // Based on ImGui demo, should be adapted to use for OAM
1447 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1448 {
1449 Text("Blue shape is drawn first: appears in back");
1450 Text("Red shape is drawn after: appears in front");
1451 ImVec2 p0 = ImGui::GetCursorScreenPos();
1452 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
1453 IM_COL32(0, 0, 255, 255)); // Blue
1454 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
1455 ImVec2(p0.x + 75, p0.y + 75),
1456 IM_COL32(255, 0, 0, 255)); // Red
1457 ImGui::Dummy(ImVec2(75, 75));
1458 }
1459 ImGui::Separator();
1460 {
1461 Text("Blue shape is drawn first, into channel 1: appears in front");
1462 Text("Red shape is drawn after, into channel 0: appears in back");
1463 ImVec2 p1 = ImGui::GetCursorScreenPos();
1464
1465 // Create 2 channels and draw a Blue shape THEN a Red shape.
1466 // You can create any number of channels. Tables API use 1 channel per
1467 // column in order to better batch draw calls.
1468 draw_list->ChannelsSplit(2);
1469 draw_list->ChannelsSetCurrent(1);
1470 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
1471 IM_COL32(0, 0, 255, 255)); // Blue
1472 draw_list->ChannelsSetCurrent(0);
1473 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
1474 ImVec2(p1.x + 75, p1.y + 75),
1475 IM_COL32(255, 0, 0, 255)); // Red
1476
1477 // Flatten/reorder channels. Red shape is in channel 0 and it appears
1478 // below the Blue shape in channel 1. This works by copying draw indices
1479 // only (vertices are not copied).
1480 draw_list->ChannelsMerge();
1481 ImGui::Dummy(ImVec2(75, 75));
1482 Text("After reordering, contents of channel 0 appears below channel 1.");
1483 }
1484}
1485
1486void BeginCanvas(Canvas& canvas, ImVec2 child_size) {
1488
1489 // Use improved canvas sizing for table integration
1490 ImVec2 effective_size = child_size;
1491 if (child_size.x == 0 && child_size.y == 0) {
1492 // Auto-size based on canvas configuration
1493 if (canvas.IsAutoResize()) {
1494 effective_size = canvas.GetPreferredSize();
1495 } else {
1496 effective_size = canvas.GetCurrentSize();
1497 }
1498 }
1499
1500 // Use NoScrollbar by default - content should fit in the child window
1501 // Scrolling is handled by the canvas's internal scrolling mechanism
1502 ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
1503 ImGuiWindowFlags_NoScrollbar);
1504 canvas.DrawBackground();
1506 canvas.DrawContextMenu();
1507}
1508
1509void EndCanvas(Canvas& canvas) {
1510 canvas.DrawGrid();
1511 canvas.DrawOverlay();
1512 ImGui::EndChild();
1513}
1514
1516 const CanvasFrameOptions& options) {
1518
1519 // Only wrap in child window if explicitly requested
1520 if (options.use_child_window) {
1521 // Calculate effective size
1522 ImVec2 effective_size = options.canvas_size;
1523 if (effective_size.x == 0 && effective_size.y == 0) {
1524 if (canvas.IsAutoResize()) {
1525 effective_size = canvas.GetPreferredSize();
1526 } else {
1527 effective_size = canvas.GetCurrentSize();
1528 }
1529 }
1530
1531 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
1532 if (options.show_scrollbar) {
1533 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
1534 }
1535 ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
1536 child_flags);
1537 }
1538
1539 // Apply grid step from options if specified
1540 if (options.grid_step.has_value()) {
1541 canvas.SetCustomGridStep(options.grid_step.value());
1542 }
1543
1544 canvas.DrawBackground(options.canvas_size);
1546
1547 if (options.draw_context_menu) {
1548 canvas.DrawContextMenu();
1549 }
1550
1551 // Build and return runtime
1552 CanvasRuntime runtime;
1553 runtime.draw_list = canvas.draw_list();
1554 runtime.canvas_p0 = canvas.zero_point();
1555 runtime.canvas_sz = canvas.canvas_size();
1556 runtime.scrolling = canvas.scrolling();
1557 runtime.hovered = canvas.IsMouseHovering();
1558 runtime.grid_step = options.grid_step.value_or(canvas.GetGridStep());
1559 runtime.scale = canvas.GetGlobalScale();
1560 runtime.content_size = canvas.GetCurrentSize();
1561
1562 return runtime;
1563}
1564
1565void EndCanvas(gui::Canvas& canvas, CanvasRuntime& /*runtime*/,
1566 const CanvasFrameOptions& options) {
1567 if (options.draw_grid) {
1568 canvas.DrawGrid(options.grid_step.value_or(canvas.GetGridStep()));
1569 }
1570 if (options.draw_overlay) {
1571 canvas.DrawOverlay();
1572 }
1573 if (options.render_popups) {
1574 canvas.RenderPersistentPopups();
1575 }
1576 // Only end child if we started one
1577 if (options.use_child_window) {
1578 ImGui::EndChild();
1579 }
1580}
1581
1582// =============================================================================
1583// Scroll and Zoom Helpers
1584// =============================================================================
1585
1586ZoomToFitResult ComputeZoomToFit(ImVec2 content_px, ImVec2 canvas_px,
1587 float padding_px) {
1588 ZoomToFitResult result;
1589 result.scale = 1.0f;
1590 result.scroll = ImVec2(0, 0);
1591
1592 if (content_px.x <= 0 || content_px.y <= 0) {
1593 return result;
1594 }
1595
1596 // Calculate available space after padding
1597 float available_x = canvas_px.x - padding_px * 2;
1598 float available_y = canvas_px.y - padding_px * 2;
1599
1600 if (available_x <= 0 || available_y <= 0) {
1601 return result;
1602 }
1603
1604 // Compute scale to fit content in available space
1605 float scale_x = available_x / content_px.x;
1606 float scale_y = available_y / content_px.y;
1607 result.scale = std::min(scale_x, scale_y);
1608
1609 // Center the content
1610 float scaled_w = content_px.x * result.scale;
1611 float scaled_h = content_px.y * result.scale;
1612 result.scroll.x = (canvas_px.x - scaled_w) / 2.0f;
1613 result.scroll.y = (canvas_px.y - scaled_h) / 2.0f;
1614
1615 return result;
1616}
1617
1618ImVec2 ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px) {
1619 // Scrolling is typically negative (content moves left/up as you scroll)
1620 // max_scroll is how far we can scroll before content edge leaves viewport
1621 float max_scroll_x = std::max(0.0f, content_px.x - canvas_px.x);
1622 float max_scroll_y = std::max(0.0f, content_px.y - canvas_px.y);
1623
1624 // Clamp scroll to valid range: [-max_scroll, 0]
1625 // At scroll=0, content top-left is at viewport top-left
1626 // At scroll=-max_scroll, content bottom-right is at viewport bottom-right
1627 return ImVec2(
1628 std::clamp(scroll.x, -max_scroll_x, 0.0f),
1629 std::clamp(scroll.y, -max_scroll_y, 0.0f));
1630}
1631
1632void GraphicsBinCanvasPipeline(int width, int height, int tile_size,
1633 int num_sheets_to_load, int canvas_id,
1634 bool is_loaded, gfx::BitmapTable& graphics_bin) {
1635 gui::Canvas canvas;
1636 if (ImGuiID child_id =
1637 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1638 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
1639 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1640 canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1));
1641 canvas.DrawContextMenu();
1642 if (is_loaded) {
1643 for (const auto& [key, value] : graphics_bin) {
1644 // Skip null bitmaps
1645 if (!value || !value->texture()) {
1646 continue;
1647 }
1648 int offset = height * (key + 1);
1649 int top_left_y = canvas.zero_point().y + 2;
1650 if (key >= 1) {
1651 top_left_y = canvas.zero_point().y + height * key;
1652 }
1653 canvas.draw_list()->AddImage(
1654 (ImTextureID)(intptr_t)value->texture(),
1655 ImVec2(canvas.zero_point().x + 2, top_left_y),
1656 ImVec2(canvas.zero_point().x + 0x100,
1657 canvas.zero_point().y + offset));
1658 }
1659 }
1660 canvas.DrawTileSelector(tile_size);
1661 canvas.DrawGrid(tile_size);
1662 canvas.DrawOverlay();
1663 // Phase 3: Render persistent popups (previously only available via End())
1664 canvas.RenderPersistentPopups();
1665 }
1666 ImGui::EndChild();
1667}
1668
1669void BitmapCanvasPipeline(gui::Canvas& canvas, gfx::Bitmap& bitmap, int width,
1670 int height, int tile_size, bool is_loaded,
1671 bool scrollbar, int canvas_id) {
1672 auto draw_canvas = [&](gui::Canvas& canvas, gfx::Bitmap& bitmap, int width,
1673 int height, int tile_size, bool is_loaded) {
1674 canvas.DrawBackground(ImVec2(width + 1, height + 1));
1675 canvas.DrawContextMenu();
1676 canvas.DrawBitmap(bitmap, 2, is_loaded);
1677 canvas.DrawTileSelector(tile_size);
1678 canvas.DrawGrid(tile_size);
1679 canvas.DrawOverlay();
1680 // Phase 3: Render persistent popups (previously only available via End())
1681 canvas.RenderPersistentPopups();
1682 };
1683
1684 if (scrollbar) {
1685 if (ImGuiID child_id =
1686 ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id);
1687 ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
1688 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
1689 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1690 }
1691 ImGui::EndChild();
1692 } else {
1693 draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded);
1694 }
1695}
1696
1698 const std::string& label, bool auto_resize) {
1699 // Configure canvas for table integration
1700 canvas.SetAutoResize(auto_resize);
1701
1702 if (auto_resize && bitmap.is_active()) {
1703 // Auto-calculate size based on bitmap content
1704 ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height());
1705 ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(
1706 content_size, canvas.GetGlobalScale());
1707 canvas.SetCanvasSize(preferred_size);
1708 }
1709
1710 // Begin table-aware canvas
1711 if (canvas.BeginTableCanvas(label)) {
1712 // Draw the canvas content
1713 canvas.DrawBackground();
1714 canvas.DrawContextMenu();
1715
1716 if (bitmap.is_active()) {
1717 canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale());
1718 }
1719
1720 canvas.DrawGrid();
1721 canvas.DrawOverlay();
1722 // Phase 3: Render persistent popups (previously only available via End())
1723 canvas.RenderPersistentPopups();
1724 }
1725 canvas.EndTableCanvas();
1726}
1727
1729 // Use the new modal system (lazy-initialized via extensions)
1730 auto& ext = EnsureExtensions();
1731 ext.InitializeModals();
1732 if (ext.modals) {
1733 CanvasConfig modal_config;
1734 modal_config.canvas_size = canvas_sz_;
1735 modal_config.content_size = config_.content_size;
1736 modal_config.global_scale = global_scale_;
1737 modal_config.grid_step = custom_step_;
1738 modal_config.enable_grid = enable_grid_;
1742 modal_config.is_draggable = draggable_;
1743 modal_config.auto_resize = config_.auto_resize;
1744 modal_config.scrolling = scrolling_;
1745 modal_config.on_config_changed =
1746 [this](const CanvasConfig& updated_config) {
1747 // Update legacy variables when config changes
1748 enable_grid_ = updated_config.enable_grid;
1749 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1750 enable_custom_labels_ = updated_config.enable_custom_labels;
1751 };
1752 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1753 global_scale_ = updated_config.global_scale;
1754 scrolling_ = updated_config.scrolling;
1755 };
1756
1757 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_);
1758 return;
1759 }
1760
1761 // Fallback to legacy modal system
1762 if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr,
1763 ImGuiWindowFlags_AlwaysAutoResize)) {
1764 ImGui::Text("Advanced Canvas Configuration");
1765 ImGui::Separator();
1766
1767 // Canvas properties (read-only info)
1768 ImGui::Text("Canvas Properties");
1769 ImGui::Text("ID: %s", canvas_id_.c_str());
1770 ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x,
1772 ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x,
1774 ImGui::Text("Global Scale: %.3f", config_.global_scale);
1775 ImGui::Text("Grid Step: %.1f", config_.grid_step);
1776
1777 if (config_.content_size.x > 0 && config_.content_size.y > 0) {
1778 ImVec2 min_size = GetMinimumSize();
1779 ImVec2 preferred_size = GetPreferredSize();
1780 ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
1781 ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x,
1782 preferred_size.y);
1783 }
1784
1785 // Editable properties using new config system
1786 ImGui::Separator();
1787 ImGui::Text("View Settings");
1788 if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) {
1789 enable_grid_ = config_.enable_grid; // Legacy sync
1790 }
1791 if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) {
1793 }
1794 if (ImGui::Checkbox("Enable Custom Labels",
1797 }
1798 if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) {
1800 }
1801 if (ImGui::Checkbox("Draggable", &config_.is_draggable)) {
1802 draggable_ = config_.is_draggable; // Legacy sync
1803 }
1804 if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) {
1805 // Auto resize setting changed
1806 }
1807
1808 // Grid controls
1809 ImGui::Separator();
1810 ImGui::Text("Grid Configuration");
1811 if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f,
1812 "%.1f")) {
1813 custom_step_ = config_.grid_step; // Legacy sync
1814 }
1815
1816 // Scale controls
1817 ImGui::Separator();
1818 ImGui::Text("Scale Configuration");
1819 if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f,
1820 "%.2f")) {
1821 global_scale_ = config_.global_scale; // Legacy sync
1822 }
1823
1824 // Scrolling controls
1825 ImGui::Separator();
1826 ImGui::Text("Scrolling Configuration");
1827 ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y);
1828 if (ImGui::Button("Reset Scroll")) {
1829 scrolling_ = ImVec2(0, 0);
1830 }
1831 ImGui::SameLine();
1832 if (ImGui::Button("Center View")) {
1833 if (bitmap_) {
1834 scrolling_ = ImVec2(
1836 2.0f,
1838 config_.canvas_size.y) /
1839 2.0f);
1840 }
1841 }
1842
1843 if (ImGui::Button("Close")) {
1844 ImGui::CloseCurrentPopup();
1845 }
1846 ImGui::EndPopup();
1847 }
1848}
1849
1850// Old ShowPaletteManager method removed - now handled by PaletteWidget
1851
1853 // Use the new modal system (lazy-initialized via extensions)
1854 auto& ext = EnsureExtensions();
1855 ext.InitializeModals();
1856 if (ext.modals) {
1857 CanvasConfig modal_config;
1858 modal_config.canvas_size = canvas_sz_;
1859 modal_config.content_size = config_.content_size;
1860 modal_config.global_scale = global_scale_;
1861 modal_config.grid_step = custom_step_;
1862 modal_config.enable_grid = enable_grid_;
1866 modal_config.is_draggable = draggable_;
1867 modal_config.auto_resize = config_.auto_resize;
1868 modal_config.scrolling = scrolling_;
1869 modal_config.on_config_changed =
1870 [this](const CanvasConfig& updated_config) {
1871 // Update legacy variables when config changes
1872 enable_grid_ = updated_config.enable_grid;
1873 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1874 enable_custom_labels_ = updated_config.enable_custom_labels;
1875 enable_context_menu_ = updated_config.enable_context_menu;
1876 };
1877 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1878 draggable_ = updated_config.is_draggable;
1879 custom_step_ = updated_config.grid_step;
1880 global_scale_ = updated_config.global_scale;
1881 scrolling_ = updated_config.scrolling;
1882 };
1883
1884 ext.modals->ShowScalingControls(canvas_id_, modal_config);
1885 return;
1886 }
1887
1888 // Fallback to legacy modal system
1889 if (ImGui::BeginPopupModal("Scaling Controls", nullptr,
1890 ImGuiWindowFlags_AlwaysAutoResize)) {
1891 ImGui::Text("Canvas Scaling and Display Controls");
1892 ImGui::Separator();
1893
1894 // Global scale with new config system
1895 ImGui::Text("Global Scale: %.3f", config_.global_scale);
1896 if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f,
1897 "%.2f")) {
1898 global_scale_ = config_.global_scale; // Legacy sync
1899 }
1900
1901 // Preset scale buttons
1902 ImGui::Text("Preset Scales:");
1903 if (ImGui::Button("0.25x")) {
1904 config_.global_scale = 0.25f;
1906 }
1907 ImGui::SameLine();
1908 if (ImGui::Button("0.5x")) {
1909 config_.global_scale = 0.5f;
1911 }
1912 ImGui::SameLine();
1913 if (ImGui::Button("1x")) {
1914 config_.global_scale = 1.0f;
1916 }
1917 ImGui::SameLine();
1918 if (ImGui::Button("2x")) {
1919 config_.global_scale = 2.0f;
1921 }
1922 ImGui::SameLine();
1923 if (ImGui::Button("4x")) {
1924 config_.global_scale = 4.0f;
1926 }
1927 ImGui::SameLine();
1928 if (ImGui::Button("8x")) {
1929 config_.global_scale = 8.0f;
1931 }
1932
1933 // Grid configuration
1934 ImGui::Separator();
1935 ImGui::Text("Grid Configuration");
1936 ImGui::Text("Grid Step: %.1f", config_.grid_step);
1937 if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f,
1938 "%.1f")) {
1939 custom_step_ = config_.grid_step; // Legacy sync
1940 }
1941
1942 // Grid size presets
1943 ImGui::Text("Grid Presets:");
1944 if (ImGui::Button("8x8")) {
1945 config_.grid_step = 8.0f;
1947 }
1948 ImGui::SameLine();
1949 if (ImGui::Button("16x16")) {
1950 config_.grid_step = 16.0f;
1952 }
1953 ImGui::SameLine();
1954 if (ImGui::Button("32x32")) {
1955 config_.grid_step = 32.0f;
1957 }
1958 ImGui::SameLine();
1959 if (ImGui::Button("64x64")) {
1960 config_.grid_step = 64.0f;
1962 }
1963
1964 // Canvas size info
1965 ImGui::Separator();
1966 ImGui::Text("Canvas Information");
1967 ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x,
1969 ImGui::Text("Scaled Size: %.0f x %.0f",
1972 if (bitmap_) {
1973 ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height());
1974 ImGui::Text(
1975 "Effective Scale: %.3f x %.3f",
1978 }
1979
1980 if (ImGui::Button("Close")) {
1981 ImGui::CloseCurrentPopup();
1982 }
1983 ImGui::EndPopup();
1984 }
1985}
1986
1987// BPP format management methods
1989 auto& ext = EnsureExtensions();
1990 ext.InitializeBppUI(canvas_id_);
1991
1992 if (bitmap_ && ext.bpp_format_ui) {
1993 ext.bpp_format_ui->RenderFormatSelector(
1995 [this](gfx::BppFormat format) { ConvertBitmapFormat(format); });
1996 }
1997}
1998
2000 auto& ext = EnsureExtensions();
2001 ext.InitializeBppUI(canvas_id_);
2002
2003 if (bitmap_ && ext.bpp_format_ui) {
2004 ext.bpp_format_ui->RenderAnalysisPanel(*bitmap_, bitmap_->palette());
2005 }
2006}
2007
2009 auto& ext = EnsureExtensions();
2010 if (!ext.bpp_conversion_dialog) {
2011 ext.bpp_conversion_dialog = std::make_unique<gui::BppConversionDialog>(
2012 canvas_id_ + "_bpp_conversion");
2013 }
2014
2015 if (bitmap_ && ext.bpp_conversion_dialog) {
2016 ext.bpp_conversion_dialog->Show(
2017 *bitmap_, bitmap_->palette(),
2018 [this](gfx::BppFormat format, bool /*preserve_palette*/) {
2019 ConvertBitmapFormat(format);
2020 });
2021 }
2022
2023 if (ext.bpp_conversion_dialog) {
2024 ext.bpp_conversion_dialog->Render();
2025 }
2026}
2027
2029 if (!bitmap_)
2030 return false;
2031
2032 gfx::BppFormat current_format = GetCurrentBppFormat();
2033 if (current_format == target_format) {
2034 return true; // No conversion needed
2035 }
2036
2037 try {
2038 // Convert the bitmap data
2039 auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
2040 bitmap_->vector(), current_format, target_format, bitmap_->width(),
2041 bitmap_->height());
2042
2043 // Update the bitmap with converted data
2044 bitmap_->set_data(converted_data);
2045
2046 // Update the renderer
2048
2049 return true;
2050 } catch (const std::exception& e) {
2051 SDL_Log("Failed to convert bitmap format: %s", e.what());
2052 return false;
2053 }
2054}
2055
2063
2064// Phase 4A: Canvas Automation API
2066 auto& ext = EnsureExtensions();
2067 ext.InitializeAutomation(this);
2068 return ext.automation_api.get();
2069}
2070
2071// Stateless Canvas Helpers
2072
2073namespace {
2075 CanvasGeometry geom;
2076 geom.canvas_p0 = rt.canvas_p0;
2077 geom.canvas_sz = rt.canvas_sz;
2078 geom.scrolling = rt.scrolling;
2079 geom.scaled_size =
2080 ImVec2(rt.canvas_sz.x * rt.scale, rt.canvas_sz.y * rt.scale);
2081 geom.canvas_p1 = ImVec2(geom.canvas_p0.x + geom.canvas_sz.x,
2082 geom.canvas_p0.y + geom.canvas_sz.y);
2083 return geom;
2084}
2085} // namespace
2086
2087void DrawBitmap(const CanvasRuntime& rt, gfx::Bitmap& bitmap, int border_offset,
2088 float scale) {
2089 if (!rt.draw_list) return;
2090 CanvasGeometry geom = GetGeometryFromRuntime(rt);
2091 RenderBitmapOnCanvas(rt.draw_list, geom, bitmap, border_offset, scale);
2092}
2093
2094void DrawBitmap(const CanvasRuntime& rt, gfx::Bitmap& bitmap, int x_offset,
2095 int y_offset, float scale, int alpha) {
2096 if (!rt.draw_list) return;
2097 CanvasGeometry geom = GetGeometryFromRuntime(rt);
2098 RenderBitmapOnCanvas(rt.draw_list, geom, bitmap, x_offset, y_offset, scale,
2099 alpha);
2100}
2101
2102void DrawBitmap(const CanvasRuntime& rt, gfx::Bitmap& bitmap, ImVec2 dest_pos,
2103 ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) {
2104 if (!rt.draw_list) return;
2105 CanvasGeometry geom = GetGeometryFromRuntime(rt);
2106 RenderBitmapOnCanvas(rt.draw_list, geom, bitmap, dest_pos, dest_size, src_pos,
2107 src_size);
2108}
2109
2110void DrawBitmap(const CanvasRuntime& rt, gfx::Bitmap& bitmap,
2111 const BitmapDrawOpts& opts) {
2112 if (!rt.draw_list) return;
2113
2114 // Ensure texture if requested
2115 if (opts.ensure_texture && !bitmap.texture() && bitmap.surface()) {
2118 }
2119
2120 // Determine which overload to use based on options
2121 if (opts.dest_size.x > 0 && opts.dest_size.y > 0) {
2122 ImVec2 src_size = opts.src_size;
2123 if (src_size.x <= 0 || src_size.y <= 0) {
2124 src_size = ImVec2(static_cast<float>(bitmap.width()),
2125 static_cast<float>(bitmap.height()));
2126 }
2127 DrawBitmap(rt, bitmap, opts.dest_pos, opts.dest_size, opts.src_pos,
2128 src_size);
2129 } else {
2130 DrawBitmap(rt, bitmap, static_cast<int>(opts.dest_pos.x),
2131 static_cast<int>(opts.dest_pos.y), opts.scale, opts.alpha);
2132 }
2133}
2134
2136 const BitmapPreviewOptions& options) {
2137 if (options.ensure_texture && !bitmap.texture() && bitmap.surface()) {
2140 }
2141
2142 if (options.dest_size.x > 0 && options.dest_size.y > 0) {
2143 ImVec2 src_size = options.src_size;
2144 if (src_size.x <= 0 || src_size.y <= 0) {
2145 src_size = ImVec2(bitmap.width(), bitmap.height());
2146 }
2147 DrawBitmap(rt, bitmap, options.dest_pos, options.dest_size, options.src_pos,
2148 src_size);
2149 } else {
2150 DrawBitmap(rt, bitmap, static_cast<int>(options.dest_pos.x),
2151 static_cast<int>(options.dest_pos.y), options.scale,
2152 options.alpha);
2153 }
2154}
2155
2157 const PreviewPanelOpts& opts) {
2158 if (!rt.draw_list) return false;
2159
2160 // Ensure texture if requested
2161 if (opts.ensure_texture && !bmp.texture() && bmp.surface()) {
2164 }
2165
2166 // Draw the bitmap using existing helpers
2167 if (opts.dest_size.x > 0 && opts.dest_size.y > 0) {
2168 DrawBitmap(rt, bmp, opts.dest_pos, opts.dest_size, ImVec2(0, 0),
2169 ImVec2(static_cast<float>(bmp.width()),
2170 static_cast<float>(bmp.height())));
2171 } else {
2172 DrawBitmap(rt, bmp, static_cast<int>(opts.dest_pos.x),
2173 static_cast<int>(opts.dest_pos.y), 1.0f, 255);
2174 }
2175 return true;
2176}
2177
2178// ============================================================================
2179// Stateless DrawRect/DrawText/DrawOutline Helpers
2180// ============================================================================
2181
2182void DrawRect(const CanvasRuntime& rt, int x, int y, int w, int h,
2183 ImVec4 color) {
2184 if (!rt.draw_list) return;
2186 h, color, rt.scale);
2187}
2188
2189void DrawText(const CanvasRuntime& rt, const std::string& text, int x, int y) {
2190 if (!rt.draw_list) return;
2192 y, rt.scale);
2193}
2194
2195void DrawOutline(const CanvasRuntime& rt, int x, int y, int w, int h,
2196 ImU32 color) {
2197 if (!rt.draw_list) return;
2199 w, h, color);
2200}
2201
2202// ============================================================================
2203// Stateless Interaction Helpers
2204// ============================================================================
2205
2206namespace {
2207ImVec2 AlignPosToGridHelper(ImVec2 pos, float scale) {
2208 return ImVec2(std::floor(pos.x / scale) * scale,
2209 std::floor(pos.y / scale) * scale);
2210}
2211} // namespace
2212
2214 int current_tile, ImVec2* out_drawn_pos) {
2215 if (!rt.draw_list) return false;
2216
2217 const ImGuiIO& io = ImGui::GetIO();
2218 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
2219 rt.canvas_p0.y + rt.scrolling.y);
2220 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2221
2222 // Safety check: ensure tilemap is properly initialized
2223 if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) {
2224 return false;
2225 }
2226
2227 const float scaled_size = tilemap.tile_size.x * rt.scale;
2228
2229 if (!rt.hovered) {
2230 return false;
2231 }
2232
2233 ImVec2 paint_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2234
2235 // Performance optimization: Draw preview tile directly from atlas texture
2236 if (tilemap.atlas.is_active() && tilemap.atlas.texture()) {
2237 int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
2238 if (tiles_per_row > 0) {
2239 int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x;
2240 int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y;
2241
2242 // Simple bounds check
2243 if (tile_x >= 0 && tile_x < tilemap.atlas.width() && tile_y >= 0 &&
2244 tile_y < tilemap.atlas.height()) {
2245 ImVec2 uv0 =
2246 ImVec2(static_cast<float>(tile_x) / tilemap.atlas.width(),
2247 static_cast<float>(tile_y) / tilemap.atlas.height());
2248 ImVec2 uv1 = ImVec2(static_cast<float>(tile_x + tilemap.tile_size.x) /
2249 tilemap.atlas.width(),
2250 static_cast<float>(tile_y + tilemap.tile_size.y) /
2251 tilemap.atlas.height());
2252
2253 rt.draw_list->AddImage(
2254 (ImTextureID)(intptr_t)tilemap.atlas.texture(),
2255 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
2256 ImVec2(origin.x + paint_pos.x + scaled_size,
2257 origin.y + paint_pos.y + scaled_size),
2258 uv0, uv1);
2259 }
2260 }
2261 }
2262
2263 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
2264 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
2265 if (out_drawn_pos) *out_drawn_pos = paint_pos;
2266 return true;
2267 }
2268
2269 return false;
2270}
2271
2272bool DrawTileSelector(const CanvasRuntime& rt, int size, int size_y,
2273 ImVec2* out_selected_pos) {
2274 const ImGuiIO& io = ImGui::GetIO();
2275 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
2276 rt.canvas_p0.y + rt.scrolling.y);
2277 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2278
2279 if (size_y == 0) {
2280 size_y = size;
2281 }
2282
2283 if (rt.hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
2284 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos, static_cast<float>(size));
2285 if (out_selected_pos) *out_selected_pos = painter_pos;
2286 }
2287
2288 // Return true on double-click for "confirm selection" semantics
2289 if (rt.hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
2290 return true;
2291 }
2292
2293 return false;
2294}
2295
2296void DrawSelectRect(const CanvasRuntime& rt, int current_map, int tile_size,
2297 float scale, CanvasSelection& selection) {
2298 if (!rt.draw_list) return;
2299
2300 const ImGuiIO& io = ImGui::GetIO();
2301 const ImVec2 origin(rt.canvas_p0.x + rt.scrolling.x,
2302 rt.canvas_p0.y + rt.scrolling.y);
2303 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
2304 static ImVec2 drag_start_pos;
2305 const float scaled_size = tile_size * scale;
2306 static bool dragging = false;
2307 constexpr int small_map_size = 0x200;
2308 constexpr uint32_t kWhite = IM_COL32(255, 255, 255, 255);
2309
2310 if (!rt.hovered) {
2311 return;
2312 }
2313
2314 // Calculate superX and superY accounting for world offset
2315 int superY, superX;
2316 if (current_map < 0x40) {
2317 superY = current_map / 8;
2318 superX = current_map % 8;
2319 } else if (current_map < 0x80) {
2320 superY = (current_map - 0x40) / 8;
2321 superX = (current_map - 0x40) % 8;
2322 } else {
2323 superY = (current_map - 0x80) / 8;
2324 superX = (current_map - 0x80) % 8;
2325 }
2326
2327 // Handle right click for single tile selection
2328 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
2329 ImVec2 painter_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2330 // Unscale to get world coordinates for tile calculation
2331 int world_x = static_cast<int>(painter_pos.x / scale);
2332 int world_y = static_cast<int>(painter_pos.y / scale);
2333
2334 auto tile16_x = (world_x % small_map_size) / (small_map_size / 0x20);
2335 auto tile16_y = (world_y % small_map_size) / (small_map_size / 0x20);
2336
2337 int index_x = superX * 0x20 + tile16_x;
2338 int index_y = superY * 0x20 + tile16_y;
2339 selection.selected_tile_pos = ImVec2(static_cast<float>(index_x),
2340 static_cast<float>(index_y));
2341 selection.selected_points.clear();
2342 selection.select_rect_active = false;
2343
2344 drag_start_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2345 }
2346
2347 // Calculate the rectangle's top-left and bottom-right corners
2348 ImVec2 drag_end_pos = AlignPosToGridHelper(mouse_pos, scaled_size);
2349 if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) {
2350 auto start =
2351 ImVec2(origin.x + drag_start_pos.x, origin.y + drag_start_pos.y);
2352 // Use scaled_size for visual rectangle to match zoom level
2353 auto end = ImVec2(origin.x + drag_end_pos.x + scaled_size,
2354 origin.y + drag_end_pos.y + scaled_size);
2355 rt.draw_list->AddRect(start, end, kWhite);
2356 dragging = true;
2357 }
2358
2359 if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
2360 dragging = false;
2361
2362 constexpr int tile16_size = 16;
2363 // Convert from scaled screen coords to world tile coords
2364 int start_x = static_cast<int>(std::floor(drag_start_pos.x / scaled_size)) * tile16_size;
2365 int start_y = static_cast<int>(std::floor(drag_start_pos.y / scaled_size)) * tile16_size;
2366 int end_x = static_cast<int>(std::floor(drag_end_pos.x / scaled_size)) * tile16_size;
2367 int end_y = static_cast<int>(std::floor(drag_end_pos.y / scaled_size)) * tile16_size;
2368
2369 if (start_x > end_x) std::swap(start_x, end_x);
2370 if (start_y > end_y) std::swap(start_y, end_y);
2371
2372 selection.selected_tiles.clear();
2373 selection.selected_tiles.reserve(
2374 static_cast<size_t>(((end_x - start_x) / tile16_size + 1) *
2375 ((end_y - start_y) / tile16_size + 1)));
2376
2377 constexpr int tiles_per_local_map = small_map_size / 16;
2378
2379 for (int y = start_y; y <= end_y; y += tile16_size) {
2380 for (int x = start_x; x <= end_x; x += tile16_size) {
2381 int local_map_x = (x / small_map_size) % 8;
2382 int local_map_y = (y / small_map_size) % 8;
2383 int tile16_x = (x % small_map_size) / tile16_size;
2384 int tile16_y = (y % small_map_size) / tile16_size;
2385 int index_x = local_map_x * tiles_per_local_map + tile16_x;
2386 int index_y = local_map_y * tiles_per_local_map + tile16_y;
2387 selection.selected_tiles.emplace_back(static_cast<float>(index_x),
2388 static_cast<float>(index_y));
2389 }
2390 }
2391
2392 // Store world coordinates (unscaled) so they work correctly at any zoom level
2393 // Divide by scale to convert from screen coords to world coords
2394 selection.selected_points.clear();
2395 selection.selected_points.push_back(
2396 ImVec2(drag_start_pos.x / scale, drag_start_pos.y / scale));
2397 selection.selected_points.push_back(
2398 ImVec2(drag_end_pos.x / scale, drag_end_pos.y / scale));
2399 selection.select_rect_active = true;
2400 }
2401}
2402
2403// =============================================================================
2404// Canvas::AddXxxAt Methods
2405// =============================================================================
2406
2407void Canvas::AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size) {
2408 if (draw_list_ == nullptr) return;
2409 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
2410 canvas_p0_.y + local_top_left.y * global_scale_);
2411 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
2412 screen_pos.y + size.y * global_scale_);
2413 draw_list_->AddImage(texture, screen_pos, screen_end);
2414}
2415
2416void Canvas::AddRectFilledAt(ImVec2 local_top_left, ImVec2 size, uint32_t color) {
2417 if (draw_list_ == nullptr) return;
2418 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
2419 canvas_p0_.y + local_top_left.y * global_scale_);
2420 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
2421 screen_pos.y + size.y * global_scale_);
2422 draw_list_->AddRectFilled(screen_pos, screen_end, color);
2423}
2424
2425void Canvas::AddTextAt(ImVec2 local_pos, const std::string& text, uint32_t color) {
2426 if (draw_list_ == nullptr) return;
2427 ImVec2 screen_pos(canvas_p0_.x + local_pos.x * global_scale_,
2428 canvas_p0_.y + local_pos.y * global_scale_);
2429 draw_list_->AddText(screen_pos, color, text.c_str());
2430}
2431
2432// =============================================================================
2433// CanvasFrame RAII Class
2434// =============================================================================
2435
2437 : canvas_(&canvas), options_(options), active_(true) {
2439}
2440
2442 if (active_) {
2444 }
2445}
2446
2448 : canvas_(other.canvas_), options_(other.options_), active_(other.active_) {
2449 other.active_ = false;
2450}
2451
2453 if (this != &other) {
2454 if (active_) {
2455 canvas_->End(options_);
2456 }
2457 canvas_ = other.canvas_;
2458 options_ = other.options_;
2459 active_ = other.active_;
2460 other.active_ = false;
2461 }
2462 return *this;
2463}
2464
2465} // namespace yaze::gui
2466
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:34
static Arena & Get()
Definition arena.cc:19
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
TextureHandle texture() const
Definition bitmap.h:380
const std::vector< uint8_t > & vector() const
Definition bitmap.h:381
bool is_active() const
Definition bitmap.h:384
SnesPalette * mutable_palette()
Definition bitmap.h:369
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
Definition bitmap.cc:630
int height() const
Definition bitmap.h:374
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:851
int width() const
Definition bitmap.h:373
SDL_Surface * surface() const
Definition bitmap.h:379
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Definition bitmap.cc:295
BppFormat DetectFormat(const std::vector< uint8_t > &data, int width, int height)
Detect BPP format from bitmap data.
std::vector< uint8_t > ConvertFormat(const std::vector< uint8_t > &data, BppFormat from_format, BppFormat to_format, int width, int height)
Convert bitmap data between BPP formats.
static BppFormatManager & Get()
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
RAII timer for automatic timing management.
Programmatic interface for controlling canvas operations.
Lightweight RAII guard for existing Canvas instances.
Definition canvas.h:872
CanvasFrameOptions options_
Definition canvas.h:889
CanvasFrame & operator=(const CanvasFrame &)=delete
CanvasFrame(Canvas &canvas, CanvasFrameOptions options=CanvasFrameOptions())
Definition canvas.cc:2436
void Initialize(const std::string &canvas_id)
Initialize the interaction handler.
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:150
ImVec2 scrolling_
Definition canvas.h:627
CanvasState state_
Definition canvas.h:600
ImVector< ImVec2 > points_
Definition canvas.h:637
int highlight_tile_id
Definition canvas.h:615
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1075
PopupRegistry popup_registry_
Definition canvas.h:611
Rom * rom() const
Definition canvas.h:561
std::string canvas_id_
Definition canvas.h:641
void ShowScalingControls()
Definition canvas.cc:1852
bool WasDoubleClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:438
CanvasConfig config_
Definition canvas.h:590
ImVec2 selected_tile_pos_
Definition canvas.h:647
auto global_scale() const
Definition canvas.h:494
ImVec2 canvas_p1_
Definition canvas.h:630
void ShowBppAnalysis()
Definition canvas.cc:1999
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1144
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:237
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
Definition canvas.cc:1154
bool BeginTableCanvas(const std::string &label="")
Definition canvas.cc:345
void InitializeEnhancedComponents()
Definition canvas.cc:216
CanvasRuntime BuildCurrentRuntime() const
Definition canvas.h:574
void ShowBppConversionDialog()
Definition canvas.cc:2008
CanvasAutomationAPI * GetAutomationAPI()
Definition canvas.cc:2065
void ShowAdvancedCanvasProperties()
Definition canvas.cc:1728
void ApplyScaleSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:846
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:539
void DrawContextMenu()
Definition canvas.cc:602
ImVec2 mouse_pos_in_canvas_
Definition canvas.h:632
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:898
bool DrawSolidTilePainter(const ImVec4 &color, int size)
Definition canvas.cc:936
bool enable_context_menu_
Definition canvas.h:654
auto draw_list() const
Definition canvas.h:442
CanvasMenuDefinition editor_menu_
Definition canvas.h:607
void ApplyConfigSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:822
void DrawLayeredElements()
Definition canvas.cc:1445
void ReserveTableSpace(const std::string &label="")
Definition canvas.cc:340
bool enable_custom_labels_
Definition canvas.h:653
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
Definition canvas.cc:2425
void ShowUsageReport()
Definition canvas.cc:264
ImVec2 GetMinimumSize() const
Definition canvas.cc:330
void AddRectFilledAt(ImVec2 local_top_left, ImVec2 size, uint32_t color)
Definition canvas.cc:2416
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1011
bool ConvertBitmapFormat(gfx::BppFormat target_format)
Definition canvas.cc:2028
void DrawGridLines(float grid_step)
Definition canvas.cc:1351
void SetCustomGridStep(float step)
Definition canvas.h:215
void ShowPerformanceUI()
Definition canvas.cc:258
zelda3::GameData * game_data() const
Definition canvas.h:563
bool custom_canvas_size_
Definition canvas.h:655
void ClearContextMenuItems()
Definition canvas.cc:776
void AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size)
Definition canvas.cc:2407
void SetGameData(zelda3::GameData *game_data)
Definition canvas.cc:292
void DrawRect(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1341
bool HasValidSelection() const
Definition canvas.cc:430
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:852
ImDrawList * draw_list_
Definition canvas.h:624
ImVector< ImVec2 > selected_points_
Definition canvas.h:646
ImVec2 GetCurrentSize() const
Definition canvas.h:369
void SetCanvasSize(ImVec2 canvas_size)
Definition canvas.h:469
void UpdateColorPainter(gfx::IRenderer *renderer, gfx::Bitmap &bitmap, const ImVec4 &color, const std::function< void()> &event, int tile_size, float scale=1.0f)
Definition canvas.cc:522
void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color)
Definition canvas.cc:990
void DrawCustomHighlight(float grid_step)
Definition canvas.cc:1393
bool select_rect_active_
Definition canvas.h:648
Bitmap * bitmap_
Definition canvas.h:621
std::unique_ptr< CanvasExtensions > extensions_
Definition canvas.h:596
CanvasGridSize grid_size() const
Definition canvas.h:223
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:753
ImVec2 GetPreferredSize() const
Definition canvas.cc:335
float GetGridStep() const
Definition canvas.h:477
CanvasInteractionHandler interaction_handler_
Definition canvas.h:292
void InitializePaletteEditor(Rom *rom)
Definition canvas.cc:283
void Begin(ImVec2 canvas_size=ImVec2(0, 0))
Begin canvas rendering (ImGui-style)
Definition canvas.cc:452
auto canvas_size() const
Definition canvas.h:451
void SetZoomToFit(const gfx::Bitmap &bitmap)
Definition canvas.cc:796
bool WasClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:434
ImVector< ImVector< std::string > > labels_
Definition canvas.h:638
gfx::BppFormat GetCurrentBppFormat() const
Definition canvas.cc:2056
auto canvas_id() const
Definition canvas.h:501
void ClosePersistentPopup(const std::string &popup_id)
Definition canvas.cc:786
void ShowBppFormatSelector()
Definition canvas.cc:1988
void RecordCanvasOperation(const std::string &operation_name, double time_ms)
Definition canvas.cc:247
void RenderPersistentPopups()
Definition canvas.cc:791
void SetGridSize(CanvasGridSize grid_size)
Definition canvas.h:196
bool IsAutoResize() const
Definition canvas.h:371
std::shared_ptr< CanvasUsageTracker > usage_tracker_
Definition canvas.h:290
void End()
End canvas rendering (ImGui-style)
Definition canvas.cc:458
float GetGlobalScale() const
Definition canvas.h:473
void EndInTable(CanvasRuntime &runtime, const CanvasFrameOptions &options)
Definition canvas.cc:408
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:1041
auto zero_point() const
Definition canvas.h:443
bool IsMouseHovering() const
Definition canvas.h:433
std::unique_ptr< CanvasContextMenu > context_menu_
Definition canvas.h:289
auto usage_mode() const
Definition canvas.h:345
ImVec2 GetLastClickPosition() const
Definition canvas.cc:443
zelda3::GameData * game_data_
Definition canvas.h:623
void ShowPaletteEditor()
Definition canvas.cc:299
float global_scale_
Definition canvas.h:650
void DrawOutline(int x, int y, int w, int h)
Definition canvas.cc:1139
float custom_step_
Definition canvas.h:649
void DrawInfoGrid(float grid_step=64.0f, int tile_id_offset=8, int label_id=0)
Definition canvas.cc:1356
CanvasSelection selection_
Definition canvas.h:591
CanvasRuntime BeginInTable(const std::string &label, const CanvasFrameOptions &options)
Begin canvas in table cell with frame options (modern API) Returns CanvasRuntime for stateless helper...
Definition canvas.cc:372
bool enable_hex_tile_labels_
Definition canvas.h:652
ImVec2 canvas_p0_
Definition canvas.h:629
auto scrolling() const
Definition canvas.h:445
void OpenPersistentPopup(const std::string &popup_id, std::function< void()> render_callback)
Definition canvas.cc:780
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1122
std::string context_id_
Definition canvas.h:642
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:547
ImVec2 canvas_sz_
Definition canvas.h:628
void InitializeDefaults()
Definition canvas.cc:159
void EndTableCanvas()
Definition canvas.cc:368
std::shared_ptr< CanvasPerformanceIntegration > performance_integration_
Definition canvas.h:291
void Init(const CanvasConfig &config)
Initialize canvas with configuration (post-construction) Preferred over constructor parameters for ne...
Definition canvas.cc:102
void SetAutoResize(bool auto_resize)
Definition canvas.h:370
void SetGlobalScale(float scale)
Definition canvas.h:475
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1398
void DrawContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:743
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1346
ImVec2 drawn_tile_pos_
Definition canvas.h:631
CanvasExtensions & EnsureExtensions()
Definition canvas.cc:127
std::vector< ImVec2 > selected_tiles_
Definition canvas.h:645
bool ApplyROMPalette(int group_index, int palette_index)
Definition canvas.cc:321
void ShowColorAnalysis()
Definition canvas.cc:311
void Close(const std::string &popup_id)
Close a persistent popup.
void RenderAll()
Render all active popups.
void Open(const std::string &popup_id, std::function< void()> render_callback)
Open a persistent popup.
std::unordered_map< int, std::unique_ptr< gfx::Bitmap > > BitmapTable
Definition bitmap.h:497
BppFormat
BPP format enumeration for SNES graphics.
@ kBpp8
8 bits per pixel (256 colors)
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string &label)
void SetNextCanvasSize(ImVec2 size, bool auto_resize)
void DrawCanvasRect(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color, float global_scale)
void DrawCanvasLabels(const CanvasRenderContext &ctx, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
void DrawCanvasOverlay(const CanvasRenderContext &ctx, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding)
void DrawCanvasOutline(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, uint32_t color)
void DrawCanvasOutlineWithColor(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale)
void DrawCanvasText(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, const std::string &text, int x, int y, float global_scale)
void DrawCustomHighlight(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int highlight_tile_id, float grid_step)
void DrawCanvasGridLines(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1, ImVec2 scrolling, float grid_step, float global_scale)
ImVec2 AlignPosToGridHelper(ImVec2 pos, float scale)
Definition canvas.cc:2207
CanvasGeometry GetGeometryFromRuntime(const CanvasRuntime &rt)
Definition canvas.cc:2074
ImVec2 AlignPosToGrid(ImVec2 pos, float scale)
Definition canvas.cc:151
Graphical User Interface (GUI) components for the application.
constexpr uint32_t kWhiteColor
Definition canvas.cc:145
constexpr uint32_t kRectangleColor
Definition canvas.cc:144
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:1669
CanvasUsage
Canvas usage patterns and tracking.
void EndCanvas(Canvas &canvas)
Definition canvas.cc:1509
void DrawBitmapPreview(const CanvasRuntime &rt, gfx::Bitmap &bitmap, const BitmapPreviewOptions &options)
Definition canvas.cc:2135
void BeginPadding(int i)
Definition style.cc:274
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
Definition canvas.cc:2272
bool DrawTilemapPainter(const CanvasRuntime &rt, gfx::Tilemap &tilemap, int current_tile, ImVec2 *out_drawn_pos)
Definition canvas.cc:2213
void DrawRect(const CanvasRuntime &rt, int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:2182
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
Definition canvas.cc:1486
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:1632
void ApplyScrollDelta(CanvasGeometry &geometry, ImVec2 delta)
Apply scroll delta to geometry.
ImVec2 ClampScroll(ImVec2 scroll, ImVec2 content_px, ImVec2 canvas_px)
Definition canvas.cc:1618
ImVec2 CalculateMouseInCanvas(const CanvasGeometry &geometry, ImVec2 mouse_screen_pos)
Calculate mouse position in canvas space.
void EndPadding()
Definition style.cc:278
void RenderCanvasBackground(ImDrawList *draw_list, const CanvasGeometry &geometry)
Render canvas background and border.
CanvasGeometry CalculateCanvasGeometry(const CanvasConfig &config, ImVec2 requested_size, ImVec2 cursor_screen_pos, ImVec2 content_region_avail)
Calculate canvas geometry from configuration and ImGui context.
void DrawText(const CanvasRuntime &rt, const std::string &text, int x, int y)
Definition canvas.cc:2189
ZoomToFitResult ComputeZoomToFit(ImVec2 content_px, ImVec2 canvas_px, float padding_px)
Definition canvas.cc:1586
CanvasGridSize
Definition canvas.h:52
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
Definition canvas_menu.cc:6
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
void DrawOutline(const CanvasRuntime &rt, int x, int y, int w, int h, ImU32 color)
Definition canvas.cc:2195
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, const std::string &label, bool auto_resize)
Definition canvas.cc:1697
constexpr ImGuiButtonFlags kMouseFlags
Definition canvas.cc:147
void DrawBitmap(const CanvasRuntime &rt, gfx::Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:2087
void DrawSelectRect(const CanvasRuntime &rt, int current_map, int tile_size, float scale, CanvasSelection &selection)
Definition canvas.cc:2296
bool RenderPreviewPanel(const CanvasRuntime &rt, gfx::Bitmap &bmp, const PreviewPanelOpts &opts)
Definition canvas.cc:2156
int y
Y coordinate or height.
Definition tilemap.h:21
int x
X coordinate or width.
Definition tilemap.h:20
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
Unified configuration for canvas display and interaction.
std::function< void(const CanvasConfig &) on_config_changed)
std::function< void(const CanvasConfig &) on_scale_changed)
Optional extension modules for Canvas.
std::optional< float > grid_step
Definition canvas.h:70
Canvas geometry calculated per-frame.
std::vector< CanvasMenuSection > sections
Declarative menu item definition.
Definition canvas_menu.h:64
Menu section grouping related menu items.
MenuSectionPriority priority
ImDrawList * draw_list
Definition canvas.h:55
Selection state for canvas interactions.
std::vector< ImVec2 > selected_tiles
std::vector< ImVec2 > selected_points
CanvasGeometry geometry