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 "imgui/imgui_internal.h"
2
3#include "canvas.h"
4#include "util/i18n/tr.h"
5
6#include <algorithm>
7#include <cmath>
8#include <string>
9
13#include "app/gfx/core/bitmap.h"
21#include "app/gui/core/style.h"
22#include "imgui/imgui.h"
23
24namespace yaze::gui {
25
26namespace {
27
28const ImGuiID kViewportWheelOwner = ImHashStr("yaze.canvas.viewport-wheel");
29
30void PreventViewportWheelPropagation(ImGuiContext* ctx, ImGuiContextHook*) {
31 auto* hovered = ctx->HoveredWindow;
32 auto* scrolled = ctx->WheelingWindow;
33 if (!hovered || !scrolled ||
34 ctx->WheelingWindowScrolledFrame != ctx->FrameCount ||
35 hovered->StateStorage.GetInt(kViewportWheelOwner, -2) !=
36 ctx->FrameCount - 1) {
37 return;
38 }
39 const auto& storage = hovered->StateStorage;
40 const ImRect viewport(storage.GetFloat(kViewportWheelOwner + 1),
41 storage.GetFloat(kViewportWheelOwner + 2),
42 storage.GetFloat(kViewportWheelOwner + 3),
43 storage.GetFloat(kViewportWheelOwner + 4));
44 if (!viewport.Contains(ctx->IO.MousePos)) {
45 return;
46 }
47 auto* ancestor = hovered;
48 while (ancestor && ancestor != scrolled) {
49 ancestor = ancestor->ParentWindow;
50 }
51 if (!ancestor) {
52 return;
53 }
54
55 // Pointer entry and wheel can arrive together, before SetItemKeyOwner has
56 // seen a hovered frame. NewFrame has queued native scrolling, but Begin has
57 // not applied it yet. Cancel only the wheel axes; keep other scroll requests.
58 const ImVec2 wheel = ctx->IO.MouseWheelRequestAxisSwap
59 ? ImVec2(ctx->IO.MouseWheel, 0)
60 : ImVec2(ctx->IO.MouseWheelH, ctx->IO.MouseWheel);
61 bool scroll_x = wheel.x != 0 && scrolled->ScrollMax.x != 0;
62 bool scroll_y = wheel.y != 0 && scrolled->ScrollMax.y != 0;
63 // ImGui resolves a diagonal gesture to one native axis. Do not cancel a
64 // programmatic request on the other axis merely because IO has both deltas.
65 if (scroll_x && scroll_y) {
66 if (ctx->WheelingAxisAvg.x > ctx->WheelingAxisAvg.y)
67 scroll_y = false;
68 else
69 scroll_x = false;
70 }
71 if (scroll_x)
72 scrolled->ScrollTarget.x = FLT_MAX;
73 if (scroll_y)
74 scrolled->ScrollTarget.y = FLT_MAX;
75 ctx->WheelingWindow = nullptr;
76 ctx->WheelingWindowReleaseTimer = 0;
77}
78
79void RegisterViewportWheelOwner(const ImRect& bounds) {
80 auto* ctx = ImGui::GetCurrentContext();
81 auto* window = ImGui::GetCurrentWindow();
82 ImRect visible = bounds;
83 visible.ClipWith(window->InnerClipRect);
84 auto& storage = window->StateStorage;
85 storage.SetInt(kViewportWheelOwner, ctx->FrameCount);
86 storage.SetFloat(kViewportWheelOwner + 1, visible.Min.x);
87 storage.SetFloat(kViewportWheelOwner + 2, visible.Min.y);
88 storage.SetFloat(kViewportWheelOwner + 3, visible.Max.x);
89 storage.SetFloat(kViewportWheelOwner + 4, visible.Max.y);
90 for (const auto& hook : ctx->Hooks) {
91 if (hook.Owner == kViewportWheelOwner)
92 return;
93 }
94 // Context-owned, with no Canvas pointers: closed views expire after a frame,
95 // and destroying an ImGui context also removes its hook.
96 ImGuiContextHook hook;
97 hook.Owner = kViewportWheelOwner;
98 hook.Type = ImGuiContextHookType_NewFramePost;
99 hook.Callback = PreventViewportWheelPropagation;
100 ImGui::AddContextHook(ctx, &hook);
101}
102
103} // namespace
104
105// Define constructors and destructor in .cc to avoid incomplete type issues
106// with unique_ptr
107
108// Default constructor
109Canvas::Canvas() : renderer_(nullptr) {
111}
112
114 // The outer ImVector never runs its elements' destructors, so each nested
115 // ImVector<std::string>'s backing array leaks when labels_ is destroyed.
116 // clear() releases that array (flagged by AddressSanitizer/LeakSanitizer
117 // otherwise). Do NOT destroy the individual std::strings: labels are copied
118 // in via ImVector::operator= (a shallow memcpy), so their internal pointers
119 // are not independently owned and destroying them would be a bad free.
120 for (auto& label_group : labels_) {
121 label_group.clear();
122 }
123}
124
125Canvas::Canvas(const std::string& id) : Canvas() {
126 Init(id, ImVec2(0, 0));
127}
128
129Canvas::Canvas(const std::string& id, ImVec2 canvas_size) : Canvas() {
130 Init(id, canvas_size);
131}
132
133Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
134 CanvasGridSize grid_size)
135 : Canvas() {
136 Init(id, canvas_size);
138}
139
140Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
141 CanvasGridSize grid_size, float global_scale)
142 : Canvas() {
143 Init(id, canvas_size);
147}
148
152
153Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id) : Canvas() {
155 Init(id, ImVec2(0, 0));
156}
157
158Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
159 ImVec2 canvas_size)
160 : Canvas() {
162 Init(id, canvas_size);
163}
164
165Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
166 ImVec2 canvas_size, CanvasGridSize grid_size)
167 : Canvas() {
169 Init(id, canvas_size);
171}
172
173Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
174 ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
175 : Canvas() {
177 Init(id, canvas_size);
181}
182
189
192 return;
193 }
195 return;
196 }
197 performance_integration_ = std::make_shared<CanvasPerformanceIntegration>();
200 performance_integration_->StartMonitoring();
201}
202
216
217void Canvas::Init(const std::string& id, ImVec2 canvas_size) {
218 canvas_id_ = id;
219 context_id_ = id + "Context";
220 if (canvas_size.x > 0 || canvas_size.y > 0) {
224 }
226}
227
229 if (!extensions_) {
230 extensions_ = std::make_unique<CanvasExtensions>();
231 }
232 return *extensions_;
233}
234
235using ImGui::GetContentRegionAvail;
236using ImGui::GetCursorScreenPos;
237using ImGui::GetIO;
238using ImGui::GetWindowDrawList;
239using ImGui::IsItemActive;
240using ImGui::IsItemHovered;
241using ImGui::IsMouseClicked;
242using ImGui::IsMouseDragging;
243using ImGui::Text;
244
245constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
246constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
247
248constexpr ImGuiButtonFlags kMouseFlags =
249 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
250
251namespace {
252ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
253 return ImVec2(std::floor(pos.x / scale) * scale,
254 std::floor(pos.y / scale) * scale);
255}
256} // namespace
257
258// Canvas class implementation begins here
259
261 // Initialize configuration with sensible defaults
262 config_.enable_grid = true;
266 config_.is_draggable = false;
267 config_.grid_step = 32.0f;
268 config_.global_scale = 1.0f;
269 config_.canvas_size = ImVec2(0, 0);
272
273 // Initialize selection state
275
276 // Note: palette_editor is now in CanvasExtensions (lazy-initialized)
277
278 // Initialize interaction handler
280
281 // Initialize enhanced components
283
284 // Initialize legacy compatibility variables to match config
295}
296
298 float old_scale = global_scale_;
299 global_scale_ += 0.25f;
301
302 // Publish zoom changed event
304 bus->Publish(
306 }
307}
308
310 float old_scale = global_scale_;
311 // Fit can produce a scale below the normal zoom floor. Preserve that scale
312 // rather than zooming in when the user requests Zoom Out.
313 const float min_scale = old_scale > 0.0f ? std::min(old_scale, 0.25f) : 0.25f;
314 global_scale_ = std::max(min_scale, old_scale - 0.25f);
316
317 // Publish zoom changed event
319 bus->Publish(
321 }
322}
323
324void Canvas::set_global_scale(float scale) {
325 float old_scale = global_scale_;
326 global_scale_ = scale;
327 config_.global_scale = scale;
328
329 // Publish zoom changed event only if scale actually changed
330 if (old_scale != scale) {
332 bus->Publish(
334 }
335 }
336}
337
339 // Cleanup extensions (if initialized)
340 if (extensions_) {
341 extensions_->Cleanup();
342 }
343 extensions_.reset();
344
346
347 // Stop performance monitoring before cleanup to prevent segfault
349 performance_integration_->StopMonitoring();
350 }
351
352 // Cleanup enhanced components (non-extension ones)
353 context_menu_.reset();
354 usage_tracker_.reset();
356}
357
361 points_.clear();
362 selected_tiles_.clear();
363 selected_points_.clear();
364 selected_tile_pos_ = ImVec2(-1, -1);
365 select_rect_active_ = false;
366 drawn_tile_pos_ = ImVec2(-1, -1);
367}
368
370 // Note: modals is now in CanvasExtensions (lazy-initialized on first use)
371
372 // Initialize context menu system
373 context_menu_ = std::make_unique<CanvasContextMenu>();
374 context_menu_->Initialize(canvas_id_);
375
376 // Initialize usage tracker (optional, controlled by config.enable_metrics)
378 usage_tracker_ = std::make_shared<CanvasUsageTracker>();
379 usage_tracker_->Initialize(canvas_id_);
380 usage_tracker_->StartSession();
381 // CanvasPerformanceIntegration is created lazily on first use
382 // (ShowPerformanceUI / RecordCanvasOperation) to avoid idle overhead.
383 }
384}
385
387 if (usage_tracker_) {
388 usage_tracker_->SetUsageMode(usage);
389 }
390 if (context_menu_) {
391 context_menu_->SetUsageMode(usage);
392 }
393 config_.usage_mode = usage;
394}
395
396void Canvas::RecordCanvasOperation(const std::string& operation_name,
397 double time_ms) {
398 if (usage_tracker_) {
399 usage_tracker_->RecordOperation(operation_name, time_ms);
400 }
403 performance_integration_->RecordOperation(operation_name, time_ms,
404 usage_mode());
405 }
406}
407
411 performance_integration_->RenderPerformanceUI();
412 }
413}
414
416 if (usage_tracker_) {
417 std::string report = usage_tracker_->ExportUsageReport();
418 // Show report in a modal or window (uses ImGui directly, no modals_ needed)
419 ImGui::OpenPopup("Canvas Usage Report");
420 if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr,
421 ImGuiWindowFlags_AlwaysAutoResize)) {
422 ImGui::Text(tr("Canvas Usage Report"));
423 ImGui::Separator();
424 ImGui::TextWrapped("%s", report.c_str());
425 ImGui::Separator();
426 if (ImGui::Button(tr("Close"))) {
427 ImGui::CloseCurrentPopup();
428 }
429 ImGui::EndPopup();
430 }
431 }
432}
433
435 rom_ = rom;
436 auto& ext = EnsureExtensions();
437 ext.InitializePaletteEditor();
438 if (ext.palette_editor) {
439 ext.palette_editor->Initialize(rom);
440 }
441}
442
445 if (extensions_ && extensions_->palette_editor && game_data) {
446 extensions_->palette_editor->Initialize(game_data);
447 }
448}
449
451 if (bitmap_ && !bitmap_->IsIndexed())
452 return; // Composite colors are derived.
453 if (bitmap_) {
454 auto& ext = EnsureExtensions();
455 ext.InitializePaletteEditor();
456 if (ext.palette_editor) {
457 auto mutable_palette = bitmap_->mutable_palette();
458 ext.palette_editor->ShowPaletteEditor(*mutable_palette,
459 "Canvas Palette Editor");
460 }
461 }
462}
463
465 if (bitmap_ && !bitmap_->IsIndexed())
466 return; // Composite colors are derived.
467 if (bitmap_) {
468 auto& ext = EnsureExtensions();
469 ext.InitializePaletteEditor();
470 if (ext.palette_editor) {
471 ext.palette_editor->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
472 }
473 }
474}
475
476bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
477 if (bitmap_ && bitmap_->IsIndexed() && extensions_ &&
478 extensions_->palette_editor) {
479 return extensions_->palette_editor->ApplyROMPalette(bitmap_, group_index,
480 palette_index);
481 }
482 return false;
483}
484
485// Size reporting methods for table integration
490
495
496void Canvas::ReserveTableSpace(const std::string& label) {
499}
500
501bool Canvas::BeginTableCanvas(const std::string& label) {
502 if (config_.auto_resize) {
503 ImVec2 preferred_size = GetPreferredSize();
504 CanvasUtils::SetNextCanvasSize(preferred_size, true);
505 }
506
507 // Begin child window that properly reports size to tables
508 std::string child_id = canvas_id_ + "_TableChild";
509 ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
510
511 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
512 bool result =
513 ImGui::BeginChild(child_id.c_str(), child_size,
514 true, // Always show border for table integration
515 ImGuiWindowFlags_NoScrollbar);
516
517 if (!label.empty()) {
518 ImGui::Text("%s", label.c_str());
519 }
520
521 return result;
522}
523
525 ImGui::EndChild();
526}
527
528CanvasRuntime Canvas::BeginInTable(const std::string& label,
529 const CanvasFrameOptions& options) {
530 // Calculate child size from options or auto-resize
531 ImVec2 child_size = options.canvas_size;
532 if (child_size.x <= 0 || child_size.y <= 0) {
534 }
535
536 if (config_.auto_resize && child_size.x > 0 && child_size.y > 0) {
537 CanvasUtils::SetNextCanvasSize(child_size, true);
538 }
539
540 // Begin child window for table integration
541 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
542 std::string child_id = canvas_id_ + "_TableChild";
543 ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoScrollbar;
544 if (options.show_scrollbar) {
545 child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
546 }
547 ImGui::BeginChild(child_id.c_str(), child_size, true, child_flags);
548
549 if (!label.empty()) {
550 ImGui::Text("%s", label.c_str());
551 }
552
553 // Draw background and set up canvas state
554 Begin(options);
555
556 // Build and return runtime
558 if (options.grid_step.has_value()) {
559 rt.grid_step = options.grid_step.value();
560 }
561 return rt;
562}
563
565 const CanvasFrameOptions& options) {
566 // Draw grid if enabled
567 if (options.draw_grid) {
568 float step = options.grid_step.value_or(config_.grid_step);
569 DrawGrid(step);
570 }
571
572 // Draw overlay
573 if (options.draw_overlay) {
574 DrawOverlay();
575 }
576
577 // Render persistent popups if enabled
578 if (options.render_popups) {
580 }
581
582 ImGui::EndChild();
583}
584
585// Improved interaction detection methods
587 return !points_.empty() && points_.size() >= 2;
588}
589
590bool Canvas::WasClicked(ImGuiMouseButton button) const {
591 return ImGui::IsItemClicked(button) && HasValidSelection();
592}
593
594bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
595 return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) &&
597}
598
600 if (HasValidSelection()) {
601 return points_[0]; // Return the first point of the selection
602 }
603 return ImVec2(-1, -1); // Invalid position
604}
605
606// ==================== Modern ImGui-Style Interface ====================
607
608void Canvas::Begin(ImVec2 canvas_size) {
609 // Modern ImGui-style begin - combines DrawBackground + DrawContextMenu
612}
613
615 // Modern ImGui-style end - automatically draws grid and overlay
616 if (config_.enable_grid) {
617 DrawGrid();
618 }
619 DrawOverlay();
620
621 // Render any persistent popups from context menu actions
623}
624
625void Canvas::Begin(const CanvasFrameOptions& options) {
627
628 // Only wrap in child window if explicitly requested
629 if (options.use_child_window) {
630 // Calculate effective size
631 ImVec2 effective_size = options.canvas_size;
632 if (effective_size.x == 0 && effective_size.y == 0) {
633 if (IsAutoResize()) {
634 effective_size = GetPreferredSize();
635 } else {
636 effective_size = GetCurrentSize();
637 }
638 }
639
640 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
641 if (options.show_scrollbar) {
642 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
643 }
644 ImGui::BeginChild(canvas_id().c_str(), effective_size, true, child_flags);
645 }
646
647 // Apply grid step from options if specified
648 if (options.grid_step.has_value()) {
649 SetCustomGridStep(options.grid_step.value());
650 }
651
652 DrawBackground(options.canvas_size, options.viewport_size);
654
655 if (options.draw_context_menu) {
657 }
658}
659
660void Canvas::End(const CanvasFrameOptions& options) {
661 if (options.draw_grid) {
662 DrawGrid(options.grid_step.value_or(GetGridStep()));
663 }
664 if (options.draw_overlay) {
665 DrawOverlay();
666 }
667 if (options.render_popups) {
669 }
670 // Only end child if we started one
671 if (options.use_child_window) {
672 ImGui::EndChild();
673 }
674}
675
676// ==================== Legacy Interface ====================
677
679 gfx::Bitmap& bitmap, const ImVec4& color,
680 const std::function<void()>& event,
681 int tile_size, float scale) {
682 config_.global_scale = scale;
683 global_scale_ = scale; // Legacy compatibility
686 DrawBitmap(bitmap, 2, scale);
687 if (DrawSolidTilePainter(color, tile_size)) {
688 event();
689 bitmap.UpdateTexture();
690 }
691 DrawGrid();
692 DrawOverlay();
693}
694
695void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
697 enable_custom_labels_ = true; // Legacy compatibility
698 DrawBackground(bg_size);
699 DrawInfoGrid(grid_size, 8, label_id);
700 DrawOverlay();
701}
702
703void Canvas::DrawBackground(ImVec2 canvas_size,
704 std::optional<ImVec2> viewport_size) {
705 draw_list_ = GetWindowDrawList();
706
707 // Capture the visible host before drawing an oversized content item or
708 // entering a popup. Legacy canvases still scale their content bounds.
709 const ImVec2 cursor = GetCursorScreenPos();
710 const ImVec2 available = GetContentRegionAvail();
711 ImRect visible_host(cursor,
712 ImVec2(cursor.x + available.x, cursor.y + available.y));
713 visible_host.ClipWith(ImGui::GetCurrentWindow()->InnerClipRect);
715 CalculateCanvasGeometry(config_, canvas_size, cursor, available);
717 viewport_size.value_or(ImVec2(std::max(1.0f, visible_host.GetWidth()),
718 std::max(1.0f, visible_host.GetHeight())));
719
720 if (viewport_size) {
721 // Preserve the legacy logical-size convention while fixing the hit area
722 // and clipping bounds in screen pixels. Bitmap drawing uses its own size.
723 const ImVec2 visible(std::max(1.0f, viewport_size->x),
724 std::max(1.0f, viewport_size->y));
725 const float scale = std::max(0.01f, global_scale_);
726 state_.geometry.canvas_sz = ImVec2(visible.x / scale, visible.y / scale);
727 state_.geometry.scaled_size = visible;
728 state_.geometry.canvas_p1 = ImVec2(state_.geometry.canvas_p0.x + visible.x,
729 state_.geometry.canvas_p0.y + visible.y);
730 }
731
733
734 // Update config if explicit size provided
735 if (canvas_size.x != 0) {
737 }
738
739 // Phase 1: Render background using helper
741
742 ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size,
744
745 if (viewport_size) {
746 // Ownership is registered on hover, before next frame's native window
747 // scrolling. Clearing IO wheel deltas in the editor alone is too late.
748 ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelX);
749 ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
750 RegisterViewportWheelOwner(
752 }
753
754 // CRITICAL FIX: Always update hover mouse position when hovering over canvas
755 // This fixes the regression where CheckForCurrentMap() couldn't track hover
756 // Phase 1: Use geometry helper for mouse calculation
757 if (IsItemHovered()) {
758 const ImGuiIO& io = GetIO();
761 state_.is_hovered = true;
762 is_hovered_ = true;
763 } else {
764 state_.is_hovered = false;
765 is_hovered_ = false;
766 }
767
768 // iOS/tablet gestures currently synthesize ImGui wheel deltas (see src/ios/main.mm).
769 // Consume them here to pan/zoom the canvas without triggering ImGui window scrolling.
770 if (LayoutHelpers::IsTouchDevice() && IsItemHovered()) {
771 ImGuiIO& io = GetIO();
772 const float wheel_x = io.MouseWheelH;
773 const float wheel_y = io.MouseWheel;
774
775 if (wheel_x != 0.0f || wheel_y != 0.0f) {
776 // Prevent parent windows/child regions from scrolling on touch gestures.
777 io.MouseWheelH = 0.0f;
778 io.MouseWheel = 0.0f;
779
780 if (io.KeyCtrl && wheel_y != 0.0f) {
781 // Ctrl+wheel: zoom (pinch on iOS).
782 constexpr float kMinScale = 0.25f;
783 constexpr float kMaxScale = 8.0f;
784 const float unclamped = global_scale_ * (1.0f + wheel_y);
785 const float new_scale = std::clamp(unclamped, kMinScale, kMaxScale);
786
787 if (new_scale != global_scale_) {
788 const ImVec2 new_scroll = ComputeScrollForZoomAtScreenPos(
789 state_.geometry, global_scale_, new_scale, io.MousePos);
790 set_global_scale(new_scale);
791 state_.geometry.scrolling = new_scroll;
794 }
795 } else {
796 // Plain wheel: pan (two-finger pan on iOS).
797 constexpr float kTouchWheelToPixels = 10.0f;
799 ImVec2(wheel_x * kTouchWheelToPixels,
800 wheel_y * kTouchWheelToPixels));
803 }
804 }
805 }
806
807 // Pan handling (Phase 1: Use geometry helper)
808 if (config_.is_draggable && IsItemHovered()) {
809 const ImGuiIO& io = GetIO();
810 const bool is_active = IsItemActive(); // Held
811
812 // Pan (we use a zero mouse threshold when there's no context menu)
813 if (const float mouse_threshold_for_pan =
814 enable_context_menu_ ? -1.0f : 0.0f;
815 is_active &&
816 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
817 ApplyScrollDelta(state_.geometry, io.MouseDelta);
820 }
821 }
822}
823
825 const ImGuiIO& io = GetIO();
826 const ImVec2 scaled_sz(canvas_sz_.x * global_scale_,
828 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
829 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
830 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
831
832 // Update canvas state for enhanced components
833 if (usage_tracker_) {
834 usage_tracker_->UpdateCanvasState(
837 }
838
839 // Use enhanced context menu if available
840 if (context_menu_) {
841 CanvasConfig snapshot = config_;
842 snapshot.canvas_size = canvas_sz_;
844 snapshot.global_scale = global_scale_;
845 snapshot.grid_step = custom_step_;
846 snapshot.enable_grid = enable_grid_;
850 snapshot.is_draggable = draggable_;
852 snapshot.scrolling = scrolling_;
853
854 context_menu_->SetCanvasState(
858
859 context_menu_->Render(
860 context_id_, mouse_pos, rom_, bitmap_,
861 bitmap_ ? bitmap_->mutable_palette() : nullptr,
862 [this](CanvasContextMenu::Command command,
863 const CanvasConfig& updated_config) {
864 switch (command) {
866 ResetView();
867 break;
869 if (bitmap_) {
871 }
872 break;
875 break;
878 break;
882 break;
886 break;
890 break;
894 break;
897 break;
901 break;
903 config_.grid_step = updated_config.grid_step;
905 break;
907 config_.global_scale = updated_config.global_scale;
909 break;
911 auto& ext = EnsureExtensions();
912 ext.InitializeModals();
913 if (ext.modals) {
914 CanvasConfig modal_config = updated_config;
915 modal_config.on_config_changed =
916 [this](const CanvasConfig& cfg) {
918 };
919 modal_config.on_scale_changed =
920 [this](const CanvasConfig& cfg) {
922 };
923 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config,
924 bitmap_);
925 }
926 } break;
928 auto& ext = EnsureExtensions();
929 ext.InitializeModals();
930 if (ext.modals) {
931 CanvasConfig modal_config = updated_config;
932 modal_config.on_config_changed =
933 [this](const CanvasConfig& cfg) {
935 };
936 modal_config.on_scale_changed =
937 [this](const CanvasConfig& cfg) {
939 };
940 ext.modals->ShowScalingControls(canvas_id_, modal_config,
941 bitmap_);
942 }
943 } break;
944 default:
945 break;
946 }
947 },
948 snapshot, this); // Phase 4: Pass Canvas* for editor menu integration
949
950 if (extensions_ && extensions_->modals) {
951 extensions_->modals->Render();
952 }
953
954 return;
955 }
956
957 // Draw enhanced property dialogs
960}
961
963 // Phase 4: Use RenderMenuItem from canvas_menu.h for consistent rendering
964 auto popup_callback = [this](const std::string& id,
965 std::function<void()> callback) {
966 popup_registry_.Open(id, callback);
967 };
968
969 gui::RenderMenuItem(item, popup_callback);
970}
971
973 // Phase 4: Add to editor menu definition
974 // Items are added to a default section with editor-specific priority
975 if (editor_menu_.sections.empty()) {
976 CanvasMenuSection section;
978 section.separator_after = true;
979 editor_menu_.sections.push_back(section);
980 }
981
982 // Add to the last section (or create new if the last isn't editor-specific)
983 auto& last_section = editor_menu_.sections.back();
984 if (last_section.priority != MenuSectionPriority::kEditorSpecific) {
985 CanvasMenuSection new_section;
987 new_section.separator_after = true;
988 editor_menu_.sections.push_back(new_section);
989 editor_menu_.sections.back().items.push_back(item);
990 } else {
991 last_section.items.push_back(item);
992 }
993}
994
998
999void Canvas::OpenPersistentPopup(const std::string& popup_id,
1000 std::function<void()> render_callback) {
1001 // Phase 4: Simplified popup management (no legacy synchronization)
1002 popup_registry_.Open(popup_id, render_callback);
1003}
1004
1005void Canvas::ClosePersistentPopup(const std::string& popup_id) {
1006 // Phase 4: Simplified popup management (no legacy synchronization)
1007 popup_registry_.Close(popup_id);
1008}
1009
1011 // Phase 4: Simplified rendering (no legacy synchronization)
1013}
1014
1016 if (!bitmap.is_active())
1017 return;
1018
1019 // Menu callbacks run inside a popup. Its available space is unrelated to the
1020 // viewport that opened it. Only fall back before the canvas's first frame.
1021 ImVec2 available = state_.geometry.viewport_size;
1022 if (available.x <= 0 || available.y <= 0)
1023 available = ImGui::GetContentRegionAvail();
1024 const auto fit =
1025 ComputeZoomToFit(ImVec2(bitmap.width(), bitmap.height()), available, 0);
1026 const float scale = std::max(0.25f, fit.scale);
1027 set_global_scale(scale);
1028 set_scrolling(ImVec2((available.x - bitmap.width() * scale) * 0.5f,
1029 (available.y - bitmap.height() * scale) * 0.5f));
1030}
1031
1033 config_.global_scale = 1.0f;
1034 global_scale_ = 1.0f; // Legacy compatibility
1035 scrolling_ = ImVec2(0, 0);
1036 config_.scrolling = ImVec2(0, 0); // Sync config for persistence
1037}
1038
1062
1068
1069bool Canvas::DrawTilePainter(const Bitmap& bitmap, int size, float scale) {
1070 const ImGuiIO& io = GetIO();
1071 const bool is_hovered = IsItemHovered();
1072 is_hovered_ = is_hovered;
1073 // Lock scrolled origin
1074 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1075 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1076 const auto scaled_size = size * scale;
1077
1078 // Erase the hover when the mouse is not in the canvas window.
1079 if (!is_hovered) {
1080 points_.clear();
1081 return false;
1082 }
1083
1084 // Reset the previous tile hover
1085 if (!points_.empty()) {
1086 points_.clear();
1087 }
1088
1089 // Calculate the coordinates of the mouse
1090 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
1091 mouse_pos_in_canvas_ = paint_pos;
1092 auto paint_pos_end =
1093 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
1094 points_.push_back(paint_pos);
1095 points_.push_back(paint_pos_end);
1096
1097 if (bitmap.is_active()) {
1098 ScopedNearestSampling nearest_sampling(draw_list_);
1099 draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
1100 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
1101 ImVec2(origin.x + paint_pos.x + scaled_size,
1102 origin.y + paint_pos.y + scaled_size),
1103 ImVec2(0, 0), ImVec2(1, 1),
1104 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
1105 }
1106
1107 if (IsMouseClicked(ImGuiMouseButton_Left) &&
1108 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
1109 // Draw the currently selected tile on the overworld here
1110 // Save the coordinates of the selected tile.
1111 drawn_tile_pos_ = paint_pos;
1112 return true;
1113 }
1114
1115 return false;
1116}
1117
1118bool Canvas::DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile) {
1119 // Update hover state for backward compatibility
1120 is_hovered_ = IsItemHovered();
1121
1122 // Clear points if not hovered (legacy behavior)
1123 if (!is_hovered_) {
1124 points_.clear();
1125 return false;
1126 }
1127
1128 // Build runtime and delegate to stateless helper
1130 ImVec2 drawn_pos;
1131 bool result = gui::DrawTilemapPainter(rt, tilemap, current_tile, &drawn_pos);
1132
1133 // Sync legacy state from stateless call
1134 if (is_hovered_) {
1135 const ImGuiIO& io = GetIO();
1136 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
1137 canvas_p0_.y + scrolling_.y);
1138 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1139 const float scaled_size = tilemap.tile_size.x * global_scale_;
1140 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
1141 mouse_pos_in_canvas_ = paint_pos;
1142
1143 points_.clear();
1144 points_.push_back(paint_pos);
1145 points_.push_back(
1146 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
1147 }
1148
1149 if (result) {
1150 drawn_tile_pos_ = drawn_pos;
1151 }
1152
1153 return result;
1154}
1155
1156bool Canvas::DrawSolidTilePainter(const ImVec4& color, int tile_size) {
1157 const ImGuiIO& io = GetIO();
1158 const bool is_hovered = IsItemHovered();
1159 is_hovered_ = is_hovered;
1160 // Lock scrolled origin
1161 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1162 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1163 auto scaled_tile_size = tile_size * global_scale_;
1164 static bool is_dragging = false;
1165 static ImVec2 start_drag_pos;
1166
1167 // Erase the hover when the mouse is not in the canvas window.
1168 if (!is_hovered) {
1169 points_.clear();
1170 return false;
1171 }
1172
1173 // Reset the previous tile hover
1174 if (!points_.empty()) {
1175 points_.clear();
1176 }
1177
1178 // Calculate the coordinates of the mouse
1179 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
1180 mouse_pos_in_canvas_ = paint_pos;
1181
1182 // Clamp the size to a grid
1183 paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_);
1184 paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_);
1185
1186 points_.push_back(paint_pos);
1187 points_.push_back(
1188 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
1189
1190 draw_list_->AddRectFilled(
1191 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
1192 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
1193 origin.y + paint_pos.y + scaled_tile_size),
1194 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
1195
1196 if (IsMouseClicked(ImGuiMouseButton_Left)) {
1197 is_dragging = true;
1198 start_drag_pos = paint_pos;
1199 }
1200
1201 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
1202 is_dragging = false;
1203 drawn_tile_pos_ = start_drag_pos;
1204 return true;
1205 }
1206
1207 return false;
1208}
1209
1210void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap,
1211 ImVec4 color) {
1212 const ImVec2 position = drawn_tile_pos_;
1213 int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
1214 int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
1215
1216 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
1217
1218 // Update the bitmap's pixel data based on the start_position and color
1219 for (int y = 0; y < tile_size; ++y) {
1220 for (int x = 0; x < tile_size; ++x) {
1221 // Calculate the actual pixel index in the bitmap
1222 int pixel_index =
1223 (start_position.y + y) * bitmap->width() + (start_position.x + x);
1224
1225 // Write the color to the pixel
1226 bitmap->WriteColor(pixel_index, color);
1227 }
1228 }
1229}
1230
1231bool Canvas::DrawTileSelector(int size, int size_y) {
1232 // Update hover state for backward compatibility
1233 is_hovered_ = IsItemHovered();
1234
1235 if (size_y == 0) {
1236 size_y = size;
1237 }
1238
1239 // Build runtime and delegate to stateless helper
1241 ImVec2 selected_pos;
1242 bool double_clicked = gui::DrawTileSelector(rt, size, size_y, &selected_pos);
1243
1244 // Sync legacy state: update points_ on click
1245 if (is_hovered_ && IsMouseClicked(ImGuiMouseButton_Left)) {
1246 const ImGuiIO& io = GetIO();
1247 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
1248 canvas_p0_.y + scrolling_.y);
1249 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1250 ImVec2 painter_pos = AlignPosToGrid(mouse_pos, static_cast<float>(size));
1251
1252 points_.clear();
1253 points_.push_back(painter_pos);
1254 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
1255 mouse_pos_in_canvas_ = painter_pos;
1256 }
1257
1258 return double_clicked;
1259}
1260
1261void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
1262 gfx::ScopedTimer timer("canvas_select_rect");
1263
1264 // Update hover state
1265 is_hovered_ = IsItemHovered();
1266 if (!is_hovered_) {
1267 return;
1268 }
1269
1270 // Build runtime and delegate to stateless helper
1272 rt.scale = scale; // Use the passed scale, not global_scale_
1273
1274 // Use a temporary selection to capture output from stateless helper
1275 CanvasSelection temp_selection;
1276 temp_selection.selected_tiles = selected_tiles_;
1277 temp_selection.selected_tile_pos = selected_tile_pos_;
1278 temp_selection.select_rect_active = select_rect_active_;
1279 for (int i = 0; i < selected_points_.size(); ++i) {
1280 temp_selection.selected_points.push_back(selected_points_[i]);
1281 }
1282
1283 gui::DrawSelectRect(rt, current_map, tile_size, scale, temp_selection);
1284
1285 // Sync back to legacy members
1286 selected_tiles_ = temp_selection.selected_tiles;
1287 selected_tile_pos_ = temp_selection.selected_tile_pos;
1288 select_rect_active_ = temp_selection.select_rect_active;
1289 selected_points_.clear();
1290 for (const auto& pt : temp_selection.selected_points) {
1291 selected_points_.push_back(pt);
1292 }
1293}
1294
1295void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) {
1296 if (!bitmap.is_active()) {
1297 return;
1298 }
1299 bitmap_ = &bitmap;
1300
1301 // Update content size for table integration
1302 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1303
1304 // Phase 1: Use rendering helper
1305 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset,
1306 scale);
1307}
1308
1309void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
1310 int alpha) {
1311 if (!bitmap.is_active()) {
1312 return;
1313 }
1314 bitmap_ = &bitmap;
1315
1316 // Update content size for table integration
1317 // CRITICAL: Store UNSCALED bitmap size as content - scale is applied during
1318 // rendering
1319 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1320
1321 // Phase 1: Use rendering helper
1322 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset,
1323 scale, alpha);
1324}
1325
1326void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size,
1327 ImVec2 src_pos, ImVec2 src_size) {
1328 if (!bitmap.is_active()) {
1329 return;
1330 }
1331 bitmap_ = &bitmap;
1332
1333 // Update content size for table integration
1334 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1335
1336 // Phase 1: Use rendering helper
1337 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size,
1338 src_pos, src_size);
1339}
1340
1341// TODO: Add parameters for sizing and positioning
1342void Canvas::DrawBitmapTable(const BitmapTable& gfx_bin) {
1343 ScopedNearestSampling nearest_sampling(draw_list_);
1344 for (const auto& [key, value] : gfx_bin) {
1345 // Skip null or inactive bitmaps without valid textures
1346 if (!value || !value->is_active() || !value->texture()) {
1347 continue;
1348 }
1349 int offset = 0x40 * (key + 1);
1350 int top_left_y = canvas_p0_.y + 2;
1351 if (key >= 1) {
1352 top_left_y = canvas_p0_.y + 0x40 * key;
1353 }
1354 draw_list_->AddImage((ImTextureID)(intptr_t)value->texture(),
1355 ImVec2(canvas_p0_.x + 2, top_left_y),
1356 ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset),
1357 ImVec2(0, 0), ImVec2(1, 1),
1358 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
1359 }
1360}
1361
1362void Canvas::DrawOutline(int x, int y, int w, int h) {
1363 const float scale = global_scale() > 0.0f ? global_scale() : 1.0f;
1365 IM_COL32(255, 255, 255, 200), scale);
1366}
1367
1368void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
1369 const float scale = global_scale() > 0.0f ? global_scale() : 1.0f;
1371 y, w, h, color, scale);
1372}
1373
1374void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
1375 const float scale = global_scale() > 0.0f ? global_scale() : 1.0f;
1377 color, scale);
1378}
1379
1380void Canvas::DrawBitmapGroup(std::vector<int>& group, gfx::Tilemap& tilemap,
1381 int tile_size, float /*scale*/, int local_map_size,
1382 ImVec2 total_map_size) {
1383 if (selected_points_.size() != 2) {
1384 // points_ should contain exactly two points
1385 return;
1386 }
1387 if (group.empty()) {
1388 // group should not be empty
1389 return;
1390 }
1391
1392 // CRITICAL: Use config_.global_scale for consistency with DrawOverlay
1393 // which also uses config_.global_scale for the selection rectangle outline.
1394 // Using the passed 'scale' parameter would cause misalignment if they differ.
1395 const float effective_scale = config_.global_scale;
1396
1397 // OPTIMIZATION: Use optimized rendering for large groups to improve
1398 // performance
1399 bool use_optimized_rendering =
1400 group.size() > 128; // Optimize for large selections
1401
1402 // Use provided map sizes for proper boundary handling
1403 const int small_map = local_map_size;
1404 const float large_map_width = total_map_size.x;
1405 const float large_map_height = total_map_size.y;
1406
1407 // Pre-calculate common values to avoid repeated computation
1408 const float tile_scale = tile_size * effective_scale;
1409 const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
1410
1411 // Top-left and bottom-right corners of the rectangle (in world coordinates)
1412 ImVec2 rect_top_left = selected_points_[0];
1413 ImVec2 rect_bottom_right = selected_points_[1];
1414
1415 // Calculate the start and end tiles in the grid
1416 // selected_points are now in world coordinates, so divide by tile_size only
1417 int start_tile_x = static_cast<int>(std::floor(rect_top_left.x / tile_size));
1418 int start_tile_y = static_cast<int>(std::floor(rect_top_left.y / tile_size));
1419 int end_tile_x =
1420 static_cast<int>(std::floor(rect_bottom_right.x / tile_size));
1421 int end_tile_y =
1422 static_cast<int>(std::floor(rect_bottom_right.y / tile_size));
1423
1424 if (start_tile_x > end_tile_x)
1425 std::swap(start_tile_x, end_tile_x);
1426 if (start_tile_y > end_tile_y)
1427 std::swap(start_tile_y, end_tile_y);
1428
1429 // Calculate the size of the rectangle in 16x16 grid form
1430 int rect_width = (end_tile_x - start_tile_x) * tile_size;
1431 int rect_height = (end_tile_y - start_tile_y) * tile_size;
1432
1433 int tiles_per_row = rect_width / tile_size;
1434 int tiles_per_col = rect_height / tile_size;
1435
1436 // The rest of this function draws only these atlas tiles, so the scope can
1437 // end with the function. The tiles share a texture and stay in one command.
1438 ScopedNearestSampling nearest_sampling(draw_list_);
1439 int i = 0;
1440 for (int y = 0; y < tiles_per_col + 1; ++y) {
1441 for (int x = 0; x < tiles_per_row + 1; ++x) {
1442 // Check bounds to prevent access violations
1443 if (i >= static_cast<int>(group.size())) {
1444 break;
1445 }
1446
1447 int tile_id = group[i];
1448
1449 // Check if tile_id is within the range of tile16_individual_
1450 auto tilemap_size = tilemap.map_size.x;
1451 if (tile_id >= 0 && tile_id < tilemap_size) {
1452 // Calculate the position of the tile within the rectangle
1453 int tile_pos_x = (x + start_tile_x) * tile_size * effective_scale;
1454 int tile_pos_y = (y + start_tile_y) * tile_size * effective_scale;
1455
1456 // OPTIMIZATION: Use pre-calculated values for better performance with
1457 // large selections
1458 if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
1459 atlas_tiles_per_row > 0) {
1460 int atlas_tile_x =
1461 (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
1462 int atlas_tile_y =
1463 (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
1464
1465 // Simple bounds check
1466 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
1467 atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
1468 // Calculate UV coordinates once for efficiency
1469 const float atlas_width = static_cast<float>(tilemap.atlas.width());
1470 const float atlas_height =
1471 static_cast<float>(tilemap.atlas.height());
1472 ImVec2 uv0 =
1473 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
1474 ImVec2 uv1 =
1475 ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
1476 (atlas_tile_y + tilemap.tile_size.y) / atlas_height);
1477
1478 // Calculate screen positions
1479 float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x;
1480 float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y;
1481 float screen_w = tilemap.tile_size.x * effective_scale;
1482 float screen_h = tilemap.tile_size.y * effective_scale;
1483
1484 // Use higher alpha for large selections to make them more visible
1485 uint32_t alpha_color = use_optimized_rendering
1486 ? IM_COL32(255, 255, 255, 200)
1487 : IM_COL32(255, 255, 255, 150);
1488
1489 // Draw from atlas texture with optimized parameters
1490 draw_list_->AddImage(
1491 (ImTextureID)(intptr_t)tilemap.atlas.texture(),
1492 ImVec2(screen_x, screen_y),
1493 ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1,
1494 ImGui::GetColorU32(alpha_color));
1495 }
1496 }
1497 }
1498 i++;
1499 }
1500 // Break outer loop if we've run out of tiles
1501 if (i >= static_cast<int>(group.size())) {
1502 break;
1503 }
1504 }
1505
1506 // Performance optimization completed - tiles are now rendered with
1507 // pre-calculated values
1508
1509 // Reposition rectangle to follow mouse, but clamp to prevent wrapping across
1510 // map boundaries
1511 const ImGuiIO& io = GetIO();
1512 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1513 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1514
1515 // CRITICAL FIX: Clamp BEFORE grid alignment for smoother dragging behavior
1516 // This prevents the rectangle from even attempting to cross boundaries during
1517 // drag
1518 ImVec2 clamped_mouse_pos = mouse_pos;
1519
1521 // Calculate which local map the mouse is in
1522 int mouse_local_map_x = static_cast<int>(mouse_pos.x) / small_map;
1523 int mouse_local_map_y = static_cast<int>(mouse_pos.y) / small_map;
1524
1525 // Calculate where the rectangle END would be if we place it at mouse
1526 // position
1527 float potential_end_x = mouse_pos.x + rect_width;
1528 float potential_end_y = mouse_pos.y + rect_height;
1529
1530 // Check if this would cross local map boundary (512x512 blocks)
1531 int potential_end_map_x = static_cast<int>(potential_end_x) / small_map;
1532 int potential_end_map_y = static_cast<int>(potential_end_y) / small_map;
1533
1534 // Clamp mouse position to prevent crossing during drag
1535 if (potential_end_map_x != mouse_local_map_x) {
1536 // Would cross horizontal boundary - clamp mouse to safe zone
1537 float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width;
1538 clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x);
1539 }
1540
1541 if (potential_end_map_y != mouse_local_map_y) {
1542 // Would cross vertical boundary - clamp mouse to safe zone
1543 float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height;
1544 clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y);
1545 }
1546 }
1547
1548 // Now grid-align the clamped position (in screen coords)
1549 auto new_start_pos_screen =
1550 AlignPosToGrid(clamped_mouse_pos, tile_size * effective_scale);
1551
1552 // Convert to world coordinates for storage (selected_points_ stores world coords)
1553 ImVec2 new_start_pos_world(new_start_pos_screen.x / effective_scale,
1554 new_start_pos_screen.y / effective_scale);
1555
1556 // Additional safety: clamp to overall map bounds (in world coordinates)
1557 new_start_pos_world.x =
1558 std::clamp(new_start_pos_world.x, 0.0f, large_map_width - rect_width);
1559 new_start_pos_world.y =
1560 std::clamp(new_start_pos_world.y, 0.0f, large_map_height - rect_height);
1561
1562 selected_points_.clear();
1563 selected_points_.push_back(new_start_pos_world);
1564 selected_points_.push_back(ImVec2(new_start_pos_world.x + rect_width,
1565 new_start_pos_world.y + rect_height));
1566 select_rect_active_ = true;
1567}
1568
1569void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
1571 color, config_.global_scale);
1572}
1573
1574void Canvas::DrawText(const std::string& text, int x, int y) {
1577}
1578
1583
1584void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
1585 // Draw grid + all lines in the canvas
1586 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1587 if (enable_grid_) {
1588 if (custom_step_ != 0.f)
1589 grid_step = custom_step_;
1590 grid_step *= global_scale_; // Apply global scale to grid step
1591
1592 DrawGridLines(grid_step);
1593 DrawCustomHighlight(grid_step);
1594
1595 // Draw the contents of labels on the grid
1597 for (float x = fmodf(scrolling_.x, grid_step);
1598 x < canvas_sz_.x * global_scale_; x += grid_step) {
1599 for (float y = fmodf(scrolling_.y, grid_step);
1600 y < canvas_sz_.y * global_scale_; y += grid_step) {
1601 int tile_x = (x - scrolling_.x) / grid_step;
1602 int tile_y = (y - scrolling_.y) / grid_step;
1603 int tile_id = tile_x + (tile_y * tile_id_offset);
1604
1605 if (tile_id >= labels_[label_id].size()) {
1606 break;
1607 }
1608 const std::string& label = labels_[label_id][tile_id];
1609 draw_list_->AddText(
1610 ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
1611 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
1612 kWhiteColor, label.c_str());
1613 }
1614 }
1615 }
1616 }
1617 // Every path must pop: an unbalanced clip rect clips everything drawn
1618 // after this canvas into the same window's draw list.
1619 draw_list_->PopClipRect();
1620}
1621
1626
1627void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
1628 if (config_.grid_step != 0.f)
1629 grid_step = config_.grid_step;
1630
1631 // Create render context for utilities
1634 .canvas_p0 = canvas_p0_,
1635 .canvas_p1 = canvas_p1_,
1636 .scrolling = scrolling_,
1637 .global_scale = config_.global_scale,
1638 .enable_grid = config_.enable_grid,
1639 .enable_hex_labels = config_.enable_hex_labels,
1640 .grid_step = grid_step};
1641
1642 // Use high-level utility function
1644
1645 // Draw custom labels if enabled
1647 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1649 tile_id_offset);
1650 draw_list_->PopClipRect();
1651 }
1652}
1653
1655 // Create render context for utilities
1658 .canvas_p0 = canvas_p0_,
1659 .canvas_p1 = canvas_p1_,
1660 .scrolling = scrolling_,
1661 .global_scale = config_.global_scale,
1662 .enable_grid = config_.enable_grid,
1663 .enable_hex_labels = config_.enable_hex_labels,
1664 .grid_step = config_.grid_step};
1665
1666 // Use high-level utility function with local points (synchronized from
1667 // interaction handler)
1669
1670 // Render any persistent popups from context menu actions
1672}
1673
1675 // Based on ImGui demo, should be adapted to use for OAM
1676 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1677 {
1678 Text(tr("Blue shape is drawn first: appears in back"));
1679 Text(tr("Red shape is drawn after: appears in front"));
1680 ImVec2 p0 = ImGui::GetCursorScreenPos();
1681 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
1682 IM_COL32(0, 0, 255, 255)); // Blue
1683 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
1684 ImVec2(p0.x + 75, p0.y + 75),
1685 IM_COL32(255, 0, 0, 255)); // Red
1686 ImGui::Dummy(ImVec2(75, 75));
1687 }
1688 ImGui::Separator();
1689 {
1690 Text(tr("Blue shape is drawn first, into channel 1: appears in front"));
1691 Text(tr("Red shape is drawn after, into channel 0: appears in back"));
1692 ImVec2 p1 = ImGui::GetCursorScreenPos();
1693
1694 // Create 2 channels and draw a Blue shape THEN a Red shape.
1695 // You can create any number of channels. Tables API use 1 channel per
1696 // column in order to better batch draw calls.
1697 draw_list->ChannelsSplit(2);
1698 draw_list->ChannelsSetCurrent(1);
1699 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
1700 IM_COL32(0, 0, 255, 255)); // Blue
1701 draw_list->ChannelsSetCurrent(0);
1702 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
1703 ImVec2(p1.x + 75, p1.y + 75),
1704 IM_COL32(255, 0, 0, 255)); // Red
1705
1706 // Flatten/reorder channels. Red shape is in channel 0 and it appears
1707 // below the Blue shape in channel 1. This works by copying draw indices
1708 // only (vertices are not copied).
1709 draw_list->ChannelsMerge();
1710 ImGui::Dummy(ImVec2(75, 75));
1711 Text(
1712 tr("After reordering, contents of channel 0 appears below channel 1."));
1713 }
1714}
1715
1717 // Use the new modal system (lazy-initialized via extensions)
1718 auto& ext = EnsureExtensions();
1719 ext.InitializeModals();
1720 if (ext.modals) {
1721 CanvasConfig modal_config;
1722 modal_config.canvas_size = canvas_sz_;
1723 modal_config.content_size = config_.content_size;
1724 modal_config.global_scale = global_scale_;
1725 modal_config.grid_step = custom_step_;
1726 modal_config.enable_grid = enable_grid_;
1730 modal_config.is_draggable = draggable_;
1731 modal_config.auto_resize = config_.auto_resize;
1732 modal_config.scrolling = scrolling_;
1733 modal_config.on_config_changed =
1734 [this](const CanvasConfig& updated_config) {
1735 // Update legacy variables when config changes
1736 enable_grid_ = updated_config.enable_grid;
1737 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1738 enable_custom_labels_ = updated_config.enable_custom_labels;
1739 };
1740 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1741 global_scale_ = updated_config.global_scale;
1742 scrolling_ = updated_config.scrolling;
1743 };
1744
1745 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_);
1746 return;
1747 }
1748
1749 // Fallback to legacy modal system
1750 if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr,
1751 ImGuiWindowFlags_AlwaysAutoResize)) {
1752 ImGui::Text(tr("Advanced Canvas Configuration"));
1753 ImGui::Separator();
1754
1755 // Canvas properties (read-only info)
1756 ImGui::Text(tr("Canvas Properties"));
1757 ImGui::Text(tr("ID: %s"), canvas_id_.c_str());
1758 ImGui::Text(tr("Canvas Size: %.0f x %.0f"), config_.canvas_size.x,
1760 ImGui::Text(tr("Content Size: %.0f x %.0f"), config_.content_size.x,
1762 ImGui::Text(tr("Global Scale: %.3f"), config_.global_scale);
1763 ImGui::Text(tr("Grid Step: %.1f"), config_.grid_step);
1764
1765 if (config_.content_size.x > 0 && config_.content_size.y > 0) {
1766 ImVec2 min_size = GetMinimumSize();
1767 ImVec2 preferred_size = GetPreferredSize();
1768 ImGui::Text(tr("Minimum Size: %.0f x %.0f"), min_size.x, min_size.y);
1769 ImGui::Text(tr("Preferred Size: %.0f x %.0f"), preferred_size.x,
1770 preferred_size.y);
1771 }
1772
1773 // Editable properties using new config system
1774 ImGui::Separator();
1775 ImGui::Text(tr("View Settings"));
1776 if (ImGui::Checkbox(tr("Enable Grid"), &config_.enable_grid)) {
1777 enable_grid_ = config_.enable_grid; // Legacy sync
1778 }
1779 if (ImGui::Checkbox(tr("Enable Hex Labels"), &config_.enable_hex_labels)) {
1781 }
1782 if (ImGui::Checkbox(tr("Enable Custom Labels"),
1785 }
1786 if (ImGui::Checkbox(tr("Enable Context Menu"),
1789 }
1790 if (ImGui::Checkbox(tr("Draggable"), &config_.is_draggable)) {
1791 draggable_ = config_.is_draggable; // Legacy sync
1792 }
1793 if (ImGui::Checkbox(tr("Auto Resize for Tables"), &config_.auto_resize)) {
1794 // Auto resize setting changed
1795 }
1796
1797 // Grid controls
1798 ImGui::Separator();
1799 ImGui::Text(tr("Grid Configuration"));
1800 if (ImGui::SliderFloat(tr("Grid Step"), &config_.grid_step, 1.0f, 128.0f,
1801 "%.1f")) {
1802 custom_step_ = config_.grid_step; // Legacy sync
1803 }
1804
1805 // Scale controls
1806 ImGui::Separator();
1807 ImGui::Text(tr("Scale Configuration"));
1808 if (ImGui::SliderFloat(tr("Global Scale"), &config_.global_scale, 0.1f,
1809 10.0f, "%.2f")) {
1810 global_scale_ = config_.global_scale; // Legacy sync
1811 }
1812
1813 // Scrolling controls
1814 ImGui::Separator();
1815 ImGui::Text(tr("Scrolling Configuration"));
1816 ImGui::Text(tr("Current Scroll: %.1f, %.1f"), scrolling_.x, scrolling_.y);
1817 if (ImGui::Button(tr("Reset Scroll"))) {
1818 scrolling_ = ImVec2(0, 0);
1819 }
1820 ImGui::SameLine();
1821 if (ImGui::Button(tr("Center View"))) {
1822 if (bitmap_) {
1823 scrolling_ = ImVec2(
1825 2.0f,
1827 config_.canvas_size.y) /
1828 2.0f);
1829 }
1830 }
1831
1832 if (ImGui::Button(tr("Close"))) {
1833 ImGui::CloseCurrentPopup();
1834 }
1835 ImGui::EndPopup();
1836 }
1837}
1838
1839// Old ShowPaletteManager method removed - now handled by PaletteWidget
1840
1842 // Use the new modal system (lazy-initialized via extensions)
1843 auto& ext = EnsureExtensions();
1844 ext.InitializeModals();
1845 if (ext.modals) {
1846 CanvasConfig modal_config;
1847 modal_config.canvas_size = canvas_sz_;
1848 modal_config.content_size = config_.content_size;
1849 modal_config.global_scale = global_scale_;
1850 modal_config.grid_step = custom_step_;
1851 modal_config.enable_grid = enable_grid_;
1855 modal_config.is_draggable = draggable_;
1856 modal_config.auto_resize = config_.auto_resize;
1857 modal_config.scrolling = scrolling_;
1858 modal_config.on_config_changed =
1859 [this](const CanvasConfig& updated_config) {
1860 // Update legacy variables when config changes
1861 enable_grid_ = updated_config.enable_grid;
1862 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1863 enable_custom_labels_ = updated_config.enable_custom_labels;
1864 enable_context_menu_ = updated_config.enable_context_menu;
1865 };
1866 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1867 draggable_ = updated_config.is_draggable;
1868 custom_step_ = updated_config.grid_step;
1869 global_scale_ = updated_config.global_scale;
1870 scrolling_ = updated_config.scrolling;
1871 };
1872
1873 ext.modals->ShowScalingControls(canvas_id_, modal_config);
1874 return;
1875 }
1876
1877 // Fallback to legacy modal system
1878 if (ImGui::BeginPopupModal("Scaling Controls", nullptr,
1879 ImGuiWindowFlags_AlwaysAutoResize)) {
1880 ImGui::Text(tr("Canvas Scaling and Display Controls"));
1881 ImGui::Separator();
1882
1883 // Global scale with new config system
1884 ImGui::Text(tr("Global Scale: %.3f"), config_.global_scale);
1885 if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f,
1886 "%.2f")) {
1887 global_scale_ = config_.global_scale; // Legacy sync
1888 }
1889
1890 // Preset scale buttons
1891 ImGui::Text(tr("Preset Scales:"));
1892 if (ImGui::Button(tr("0.25x"))) {
1893 config_.global_scale = 0.25f;
1895 }
1896 ImGui::SameLine();
1897 if (ImGui::Button(tr("0.5x"))) {
1898 config_.global_scale = 0.5f;
1900 }
1901 ImGui::SameLine();
1902 if (ImGui::Button(tr("1x"))) {
1903 config_.global_scale = 1.0f;
1905 }
1906 ImGui::SameLine();
1907 if (ImGui::Button(tr("2x"))) {
1908 config_.global_scale = 2.0f;
1910 }
1911 ImGui::SameLine();
1912 if (ImGui::Button(tr("4x"))) {
1913 config_.global_scale = 4.0f;
1915 }
1916 ImGui::SameLine();
1917 if (ImGui::Button(tr("8x"))) {
1918 config_.global_scale = 8.0f;
1920 }
1921
1922 // Grid configuration
1923 ImGui::Separator();
1924 ImGui::Text(tr("Grid Configuration"));
1925 ImGui::Text(tr("Grid Step: %.1f"), config_.grid_step);
1926 if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f,
1927 "%.1f")) {
1928 custom_step_ = config_.grid_step; // Legacy sync
1929 }
1930
1931 // Grid size presets
1932 ImGui::Text(tr("Grid Presets:"));
1933 if (ImGui::Button(tr("8x8"))) {
1934 config_.grid_step = 8.0f;
1936 }
1937 ImGui::SameLine();
1938 if (ImGui::Button(tr("16x16"))) {
1939 config_.grid_step = 16.0f;
1941 }
1942 ImGui::SameLine();
1943 if (ImGui::Button(tr("32x32"))) {
1944 config_.grid_step = 32.0f;
1946 }
1947 ImGui::SameLine();
1948 if (ImGui::Button(tr("64x64"))) {
1949 config_.grid_step = 64.0f;
1951 }
1952
1953 // Canvas size info
1954 ImGui::Separator();
1955 ImGui::Text(tr("Canvas Information"));
1956 ImGui::Text(tr("Canvas Size: %.0f x %.0f"), config_.canvas_size.x,
1958 ImGui::Text(tr("Scaled Size: %.0f x %.0f"),
1961 if (bitmap_) {
1962 ImGui::Text(tr("Bitmap Size: %d x %d"), bitmap_->width(),
1963 bitmap_->height());
1964 ImGui::Text(
1965 tr("Effective Scale: %.3f x %.3f"),
1968 }
1969
1970 if (ImGui::Button(tr("Close"))) {
1971 ImGui::CloseCurrentPopup();
1972 }
1973 ImGui::EndPopup();
1974 }
1975}
1976
1977// BPP format management methods
1979 if (bitmap_ && !bitmap_->IsIndexed())
1980 return; // Composite colors are derived.
1981 auto& ext = EnsureExtensions();
1982 ext.InitializeBppUI(canvas_id_);
1983
1984 if (bitmap_ && ext.bpp_format_ui) {
1985 ext.bpp_format_ui->RenderFormatSelector(
1987 [this](gfx::BppFormat format) { ConvertBitmapFormat(format); });
1988 }
1989}
1990
1992 if (bitmap_ && !bitmap_->IsIndexed())
1993 return;
1994 auto& ext = EnsureExtensions();
1995 ext.InitializeBppUI(canvas_id_);
1996
1997 if (bitmap_ && ext.bpp_format_ui) {
1998 ext.bpp_format_ui->RenderAnalysisPanel(*bitmap_, bitmap_->palette());
1999 }
2000}
2001
2003 if (bitmap_ && !bitmap_->IsIndexed())
2004 return;
2005 auto& ext = EnsureExtensions();
2006 if (!ext.bpp_conversion_dialog) {
2007 ext.bpp_conversion_dialog = std::make_unique<gui::BppConversionDialog>(
2008 canvas_id_ + "_bpp_conversion");
2009 }
2010
2011 if (bitmap_ && ext.bpp_conversion_dialog) {
2012 ext.bpp_conversion_dialog->Show(
2013 *bitmap_, bitmap_->palette(),
2014 [this](gfx::BppFormat format, bool /*preserve_palette*/) {
2015 ConvertBitmapFormat(format);
2016 });
2017 }
2018
2019 if (ext.bpp_conversion_dialog) {
2020 ext.bpp_conversion_dialog->Render();
2021 }
2022}
2023
2025 if (!bitmap_ || !bitmap_->IsIndexed())
2026 return false;
2027
2028 gfx::BppFormat current_format = GetCurrentBppFormat();
2029 if (current_format == target_format) {
2030 return true; // No conversion needed
2031 }
2032
2033 try {
2034 // Convert the bitmap data
2035 auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
2036 bitmap_->vector(), current_format, target_format, bitmap_->width(),
2037 bitmap_->height());
2038
2039 // Update the bitmap with converted data
2040 bitmap_->set_data(converted_data);
2041
2042 // Update the renderer
2044
2045 return true;
2046 } catch (const std::exception& e) {
2047 SDL_Log("Failed to convert bitmap format: %s", e.what());
2048 return false;
2049 }
2050}
2051
2059
2060// Phase 4A: Canvas Automation API
2062 auto& ext = EnsureExtensions();
2063 ext.InitializeAutomation(this);
2064 return ext.automation_api.get();
2065}
2066
2067// =============================================================================
2068// Canvas::AddXxxAt Methods
2069// =============================================================================
2070
2071void Canvas::AddImageAt(ImTextureID texture, ImVec2 local_top_left,
2072 ImVec2 size) {
2073 if (draw_list_ == nullptr)
2074 return;
2075 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
2076 canvas_p0_.y + local_top_left.y * global_scale_);
2077 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
2078 screen_pos.y + size.y * global_scale_);
2079 draw_list_->AddImage(texture, screen_pos, screen_end, ImVec2(0, 0),
2080 ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
2081}
2082
2083void Canvas::AddRectFilledAt(ImVec2 local_top_left, ImVec2 size,
2084 uint32_t color) {
2085 if (draw_list_ == nullptr)
2086 return;
2087 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
2088 canvas_p0_.y + local_top_left.y * global_scale_);
2089 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
2090 screen_pos.y + size.y * global_scale_);
2091 draw_list_->AddRectFilled(screen_pos, screen_end, color);
2092}
2093
2094void Canvas::AddTextAt(ImVec2 local_pos, const std::string& text,
2095 uint32_t color) {
2096 if (draw_list_ == nullptr)
2097 return;
2098 ImVec2 screen_pos(canvas_p0_.x + local_pos.x * global_scale_,
2099 canvas_p0_.y + local_pos.y * global_scale_);
2100 draw_list_->AddText(screen_pos, color, text.c_str());
2101}
2102
2103// =============================================================================
2104// CanvasFrame RAII Class
2105// =============================================================================
2106
2108 : canvas_(&canvas), options_(options), active_(true) {
2110}
2111
2113 if (active_) {
2115 }
2116}
2117
2119 : canvas_(other.canvas_), options_(other.options_), active_(other.active_) {
2120 other.active_ = false;
2121}
2122
2124 if (this != &other) {
2125 if (active_) {
2126 canvas_->End(options_);
2127 }
2128 canvas_ = other.canvas_;
2129 options_ = other.options_;
2130 active_ = other.active_;
2131 other.active_ = false;
2132 }
2133 return *this;
2134}
2135
2136} // 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:52
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
const SnesPalette & palette() const
Definition bitmap.h:393
TextureHandle texture() const
Definition bitmap.h:410
const std::vector< uint8_t > & vector() const
Definition bitmap.h:411
bool IsIndexed() const
Definition bitmap.h:401
bool is_active() const
Definition bitmap.h:414
SnesPalette * mutable_palette()
Definition bitmap.h:394
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
Definition bitmap.cc:705
int height() const
Definition bitmap.h:399
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:901
int width() const
Definition bitmap.h:398
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Definition bitmap.cc:301
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:724
CanvasFrameOptions options_
Definition canvas.h:741
CanvasFrame & operator=(const CanvasFrame &)=delete
CanvasFrame(Canvas &canvas, CanvasFrameOptions options=CanvasFrameOptions())
Definition canvas.cc:2107
void ClearState()
Clear all interaction state.
void Initialize(const std::string &canvas_id)
Initialize the interaction handler.
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:64
ImVec2 scrolling_
Definition canvas.h:548
void set_scrolling(ImVec2 scroll)
Definition canvas.h:367
CanvasState state_
Definition canvas.h:520
ImVector< ImVec2 > points_
Definition canvas.h:558
int highlight_tile_id
Definition canvas.h:536
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1295
PopupRegistry popup_registry_
Definition canvas.h:532
Rom * rom() const
Definition canvas.h:479
std::string canvas_id_
Definition canvas.h:562
void ShowScalingControls()
Definition canvas.cc:1841
bool WasDoubleClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:594
CanvasConfig config_
Definition canvas.h:510
ImVec2 selected_tile_pos_
Definition canvas.h:568
auto global_scale() const
Definition canvas.h:413
ImVec2 canvas_p1_
Definition canvas.h:551
void ShowBppAnalysis()
Definition canvas.cc:1991
gfx::IRenderer * renderer() const
Definition canvas.h:96
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1368
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:386
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:1380
bool BeginTableCanvas(const std::string &label="")
Definition canvas.cc:501
void InitializeEnhancedComponents()
Definition canvas.cc:369
CanvasRuntime BuildCurrentRuntime() const
Definition canvas.h:494
void ShowBppConversionDialog()
Definition canvas.cc:2002
CanvasAutomationAPI * GetAutomationAPI()
Definition canvas.cc:2061
void ShowAdvancedCanvasProperties()
Definition canvas.cc:1716
void ApplyScaleSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:1063
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:695
void DrawContextMenu()
Definition canvas.cc:824
void EnsurePerformanceIntegration()
Definition canvas.cc:190
ImVec2 mouse_pos_in_canvas_
Definition canvas.h:553
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:1118
bool DrawSolidTilePainter(const ImVec4 &color, int size)
Definition canvas.cc:1156
bool enable_context_menu_
Definition canvas.h:575
auto draw_list() const
Definition canvas.h:363
CanvasMenuDefinition editor_menu_
Definition canvas.h:527
void ApplyConfigSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:1039
void DrawLayeredElements()
Definition canvas.cc:1674
void ReserveTableSpace(const std::string &label="")
Definition canvas.cc:496
bool enable_custom_labels_
Definition canvas.h:574
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
Definition canvas.cc:2094
void ShowUsageReport()
Definition canvas.cc:415
void SetRenderer(gfx::IRenderer *renderer)
Definition canvas.h:95
ImVec2 GetMinimumSize() const
Definition canvas.cc:486
void AddRectFilledAt(ImVec2 local_top_left, ImVec2 size, uint32_t color)
Definition canvas.cc:2083
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1231
void ClearSelection()
Definition canvas.cc:358
bool ConvertBitmapFormat(gfx::BppFormat target_format)
Definition canvas.cc:2024
void DrawGridLines(float grid_step)
Definition canvas.cc:1579
void SetCustomGridStep(float step)
Definition canvas.h:118
void ShowPerformanceUI()
Definition canvas.cc:408
zelda3::GameData * game_data() const
Definition canvas.h:481
bool custom_canvas_size_
Definition canvas.h:576
void ClearContextMenuItems()
Definition canvas.cc:995
void AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size)
Definition canvas.cc:2071
void SetGameData(zelda3::GameData *game_data)
Definition canvas.cc:443
void DrawRect(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1569
bool HasValidSelection() const
Definition canvas.cc:586
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:1069
ImDrawList * draw_list_
Definition canvas.h:545
ImVector< ImVec2 > selected_points_
Definition canvas.h:567
ImVec2 GetCurrentSize() const
Definition canvas.h:290
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:678
void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color)
Definition canvas.cc:1210
void DrawCustomHighlight(float grid_step)
Definition canvas.cc:1622
bool select_rect_active_
Definition canvas.h:569
Bitmap * bitmap_
Definition canvas.h:542
std::unique_ptr< CanvasExtensions > extensions_
Definition canvas.h:516
CanvasGridSize grid_size() const
Definition canvas.h:126
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:972
ImVec2 GetPreferredSize() const
Definition canvas.cc:491
float GetGridStep() const
Definition canvas.h:396
CanvasInteractionHandler interaction_handler_
Definition canvas.h:198
void InitializePaletteEditor(Rom *rom)
Definition canvas.cc:434
void Begin(ImVec2 canvas_size=ImVec2(0, 0))
Begin canvas rendering (ImGui-style)
Definition canvas.cc:608
auto canvas_size() const
Definition canvas.h:373
void SetZoomToFit(const gfx::Bitmap &bitmap)
Definition canvas.cc:1015
bool WasClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:590
ImVector< ImVector< std::string > > labels_
Definition canvas.h:559
gfx::BppFormat GetCurrentBppFormat() const
Definition canvas.cc:2052
auto canvas_id() const
Definition canvas.h:420
void ClosePersistentPopup(const std::string &popup_id)
Definition canvas.cc:1005
void ShowBppFormatSelector()
Definition canvas.cc:1978
void RecordCanvasOperation(const std::string &operation_name, double time_ms)
Definition canvas.cc:396
void RenderPersistentPopups()
Definition canvas.cc:1010
void set_global_scale(float scale)
Definition canvas.cc:324
void SetGridSize(CanvasGridSize grid_size)
Definition canvas.h:99
bool IsAutoResize() const
Definition canvas.h:292
std::shared_ptr< CanvasUsageTracker > usage_tracker_
Definition canvas.h:196
void End()
End canvas rendering (ImGui-style)
Definition canvas.cc:614
void EndInTable(CanvasRuntime &runtime, const CanvasFrameOptions &options)
Definition canvas.cc:564
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:1261
std::unique_ptr< CanvasContextMenu > context_menu_
Definition canvas.h:195
auto usage_mode() const
Definition canvas.h:266
ImVec2 GetLastClickPosition() const
Definition canvas.cc:599
zelda3::GameData * game_data_
Definition canvas.h:544
void ShowPaletteEditor()
Definition canvas.cc:450
float global_scale_
Definition canvas.h:571
void DrawOutline(int x, int y, int w, int h)
Definition canvas.cc:1362
float custom_step_
Definition canvas.h:570
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), std::optional< ImVec2 > viewport_size=std::nullopt)
Definition canvas.cc:703
void DrawInfoGrid(float grid_step=64.0f, int tile_id_offset=8, int label_id=0)
Definition canvas.cc:1584
CanvasSelection selection_
Definition canvas.h:511
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:528
bool enable_hex_tile_labels_
Definition canvas.h:573
ImVec2 canvas_p0_
Definition canvas.h:550
void OpenPersistentPopup(const std::string &popup_id, std::function< void()> render_callback)
Definition canvas.cc:999
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1342
std::string context_id_
Definition canvas.h:563
ImVec2 canvas_sz_
Definition canvas.h:549
void InitializeDefaults()
Definition canvas.cc:260
void EndTableCanvas()
Definition canvas.cc:524
std::shared_ptr< CanvasPerformanceIntegration > performance_integration_
Definition canvas.h:197
void Init(const CanvasConfig &config)
Initialize canvas with configuration (post-construction) Preferred over constructor parameters for ne...
Definition canvas.cc:203
void SetGlobalScale(float scale)
Definition canvas.h:394
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1627
void DrawContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:962
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1574
ImVec2 drawn_tile_pos_
Definition canvas.h:552
void SyncLegacyGeometryFromState()
Definition canvas.cc:183
CanvasExtensions & EnsureExtensions()
Definition canvas.cc:228
std::vector< ImVec2 > selected_tiles_
Definition canvas.h:566
bool ApplyROMPalette(int group_index, int palette_index)
Definition canvas.cc:476
void ShowColorAnalysis()
Definition canvas.cc:464
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.
RAII scope that samples the images drawn inside it with nearest-neighbor filtering.
::yaze::EventBus * event_bus()
Get the current EventBus instance.
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)
void DrawCanvasOutlineWithColor(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color, float global_scale)
void DrawCanvasOutline(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, uint32_t color, float global_scale)
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding)
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)
void PreventViewportWheelPropagation(ImGuiContext *ctx, ImGuiContextHook *)
Definition canvas.cc:30
void RegisterViewportWheelOwner(const ImRect &bounds)
Definition canvas.cc:79
ImVec2 AlignPosToGrid(ImVec2 pos, float scale)
Definition canvas.cc:252
Graphical User Interface (GUI) components for the application.
constexpr uint32_t kWhiteColor
Definition canvas.cc:246
constexpr uint32_t kRectangleColor
Definition canvas.cc:245
CanvasUsage
Canvas usage patterns and tracking.
void BeginPadding(int i)
Definition style.cc:299
bool DrawTileSelector(const CanvasRuntime &rt, int size, int size_y, ImVec2 *out_selected_pos)
bool DrawTilemapPainter(const CanvasRuntime &rt, gfx::Tilemap &tilemap, int current_tile, ImVec2 *out_drawn_pos)
void ApplyScrollDelta(CanvasGeometry &geometry, ImVec2 delta)
Apply scroll delta to geometry.
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:303
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.
ZoomToFitResult ComputeZoomToFit(ImVec2 content_px, ImVec2 canvas_px, float padding_px)
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
constexpr ImGuiButtonFlags kMouseFlags
Definition canvas.cc:248
void DrawSelectRect(const CanvasRuntime &rt, int current_map, int tile_size, float scale, CanvasSelection &selection)
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< ImVec2 > viewport_size
std::optional< float > grid_step
std::vector< CanvasMenuSection > sections
Declarative menu item definition.
Definition canvas_menu.h:64
Menu section grouping related menu items.
MenuSectionPriority priority
Selection state for canvas interactions.
std::vector< ImVec2 > selected_tiles
std::vector< ImVec2 > selected_points
CanvasGeometry geometry