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