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