yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas.cc
Go to the documentation of this file.
1#include "canvas.h"
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <cmath>
6#include <string>
7
11#include "app/gfx/core/bitmap.h"
18#include "app/gui/core/style.h"
19#include "imgui/imgui.h"
20
21namespace yaze::gui {
22
23// Define constructors and destructor in .cc to avoid incomplete type issues
24// with unique_ptr
25
26// Default constructor
27Canvas::Canvas() : renderer_(nullptr) {
29}
30
32 // The outer ImVector never runs its elements' destructors, so each nested
33 // ImVector<std::string>'s backing array leaks when labels_ is destroyed.
34 // clear() releases that array (flagged by AddressSanitizer/LeakSanitizer
35 // otherwise). Do NOT destroy the individual std::strings: labels are copied
36 // in via ImVector::operator= (a shallow memcpy), so their internal pointers
37 // are not independently owned and destroying them would be a bad free.
38 for (auto& label_group : labels_) {
39 label_group.clear();
40 }
41}
42
43Canvas::Canvas(const std::string& id) : Canvas() {
44 Init(id, ImVec2(0, 0));
45}
46
47Canvas::Canvas(const std::string& id, ImVec2 canvas_size) : Canvas() {
48 Init(id, canvas_size);
49}
50
51Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
52 CanvasGridSize grid_size)
53 : Canvas() {
54 Init(id, canvas_size);
56}
57
58Canvas::Canvas(const std::string& id, ImVec2 canvas_size,
59 CanvasGridSize grid_size, float global_scale)
60 : Canvas() {
61 Init(id, canvas_size);
65}
66
70
71Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id) : Canvas() {
73 Init(id, ImVec2(0, 0));
74}
75
76Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
77 ImVec2 canvas_size)
78 : Canvas() {
80 Init(id, canvas_size);
81}
82
83Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
84 ImVec2 canvas_size, CanvasGridSize grid_size)
85 : Canvas() {
87 Init(id, canvas_size);
89}
90
91Canvas::Canvas(gfx::IRenderer* renderer, const std::string& id,
92 ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
93 : Canvas() {
95 Init(id, canvas_size);
99}
100
107
110 return;
111 }
113 return;
114 }
115 performance_integration_ = std::make_shared<CanvasPerformanceIntegration>();
118 performance_integration_->StartMonitoring();
119}
120
134
135void Canvas::Init(const std::string& id, ImVec2 canvas_size) {
136 canvas_id_ = id;
137 context_id_ = id + "Context";
138 if (canvas_size.x > 0 || canvas_size.y > 0) {
142 }
144}
145
147 if (!extensions_) {
148 extensions_ = std::make_unique<CanvasExtensions>();
149 }
150 return *extensions_;
151}
152
153using ImGui::GetContentRegionAvail;
154using ImGui::GetCursorScreenPos;
155using ImGui::GetIO;
156using ImGui::GetWindowDrawList;
157using ImGui::IsItemActive;
158using ImGui::IsItemHovered;
159using ImGui::IsMouseClicked;
160using ImGui::IsMouseDragging;
161using ImGui::Text;
162
163constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
164constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
165
166constexpr ImGuiButtonFlags kMouseFlags =
167 ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight;
168
169namespace {
170ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
171 return ImVec2(std::floor(pos.x / scale) * scale,
172 std::floor(pos.y / scale) * scale);
173}
174} // namespace
175
176// Canvas class implementation begins here
177
179 // Initialize configuration with sensible defaults
180 config_.enable_grid = true;
184 config_.is_draggable = false;
185 config_.grid_step = 32.0f;
186 config_.global_scale = 1.0f;
187 config_.canvas_size = ImVec2(0, 0);
190
191 // Initialize selection state
193
194 // Note: palette_editor is now in CanvasExtensions (lazy-initialized)
195
196 // Initialize interaction handler
198
199 // Initialize enhanced components
201
202 // Initialize legacy compatibility variables to match config
213}
214
216 float old_scale = global_scale_;
217 global_scale_ += 0.25f;
219
220 // Publish zoom changed event
222 bus->Publish(
224 }
225}
226
228 float old_scale = global_scale_;
229 global_scale_ -= 0.25f;
231
232 // Publish zoom changed event
234 bus->Publish(
236 }
237}
238
239void Canvas::set_global_scale(float scale) {
240 float old_scale = global_scale_;
241 global_scale_ = scale;
242 config_.global_scale = scale;
243
244 // Publish zoom changed event only if scale actually changed
245 if (old_scale != scale) {
247 bus->Publish(
249 }
250 }
251}
252
254 // Cleanup extensions (if initialized)
255 if (extensions_) {
256 extensions_->Cleanup();
257 }
258 extensions_.reset();
259
261
262 // Stop performance monitoring before cleanup to prevent segfault
264 performance_integration_->StopMonitoring();
265 }
266
267 // Cleanup enhanced components (non-extension ones)
268 context_menu_.reset();
269 usage_tracker_.reset();
271}
272
274 // Note: modals is now in CanvasExtensions (lazy-initialized on first use)
275
276 // Initialize context menu system
277 context_menu_ = std::make_unique<CanvasContextMenu>();
278 context_menu_->Initialize(canvas_id_);
279
280 // Initialize usage tracker (optional, controlled by config.enable_metrics)
282 usage_tracker_ = std::make_shared<CanvasUsageTracker>();
283 usage_tracker_->Initialize(canvas_id_);
284 usage_tracker_->StartSession();
285 // CanvasPerformanceIntegration is created lazily on first use
286 // (ShowPerformanceUI / RecordCanvasOperation) to avoid idle overhead.
287 }
288}
289
291 if (usage_tracker_) {
292 usage_tracker_->SetUsageMode(usage);
293 }
294 if (context_menu_) {
295 context_menu_->SetUsageMode(usage);
296 }
297 config_.usage_mode = usage;
298}
299
300void Canvas::RecordCanvasOperation(const std::string& operation_name,
301 double time_ms) {
302 if (usage_tracker_) {
303 usage_tracker_->RecordOperation(operation_name, time_ms);
304 }
307 performance_integration_->RecordOperation(operation_name, time_ms,
308 usage_mode());
309 }
310}
311
315 performance_integration_->RenderPerformanceUI();
316 }
317}
318
320 if (usage_tracker_) {
321 std::string report = usage_tracker_->ExportUsageReport();
322 // Show report in a modal or window (uses ImGui directly, no modals_ needed)
323 ImGui::OpenPopup("Canvas Usage Report");
324 if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr,
325 ImGuiWindowFlags_AlwaysAutoResize)) {
326 ImGui::Text(tr("Canvas Usage Report"));
327 ImGui::Separator();
328 ImGui::TextWrapped("%s", report.c_str());
329 ImGui::Separator();
330 if (ImGui::Button(tr("Close"))) {
331 ImGui::CloseCurrentPopup();
332 }
333 ImGui::EndPopup();
334 }
335 }
336}
337
339 rom_ = rom;
340 auto& ext = EnsureExtensions();
341 ext.InitializePaletteEditor();
342 if (ext.palette_editor) {
343 ext.palette_editor->Initialize(rom);
344 }
345}
346
349 if (extensions_ && extensions_->palette_editor && game_data) {
350 extensions_->palette_editor->Initialize(game_data);
351 }
352}
353
355 if (bitmap_) {
356 auto& ext = EnsureExtensions();
357 ext.InitializePaletteEditor();
358 if (ext.palette_editor) {
359 auto mutable_palette = bitmap_->mutable_palette();
360 ext.palette_editor->ShowPaletteEditor(*mutable_palette,
361 "Canvas Palette Editor");
362 }
363 }
364}
365
367 if (bitmap_) {
368 auto& ext = EnsureExtensions();
369 ext.InitializePaletteEditor();
370 if (ext.palette_editor) {
371 ext.palette_editor->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
372 }
373 }
374}
375
376bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
377 if (bitmap_ && extensions_ && extensions_->palette_editor) {
378 return extensions_->palette_editor->ApplyROMPalette(bitmap_, group_index,
379 palette_index);
380 }
381 return false;
382}
383
384// Size reporting methods for table integration
389
394
395void Canvas::ReserveTableSpace(const std::string& label) {
398}
399
400bool Canvas::BeginTableCanvas(const std::string& label) {
401 if (config_.auto_resize) {
402 ImVec2 preferred_size = GetPreferredSize();
403 CanvasUtils::SetNextCanvasSize(preferred_size, true);
404 }
405
406 // Begin child window that properly reports size to tables
407 std::string child_id = canvas_id_ + "_TableChild";
408 ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
409
410 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
411 bool result =
412 ImGui::BeginChild(child_id.c_str(), child_size,
413 true, // Always show border for table integration
414 ImGuiWindowFlags_NoScrollbar);
415
416 if (!label.empty()) {
417 ImGui::Text("%s", label.c_str());
418 }
419
420 return result;
421}
422
424 ImGui::EndChild();
425}
426
427CanvasRuntime Canvas::BeginInTable(const std::string& label,
428 const CanvasFrameOptions& options) {
429 // Calculate child size from options or auto-resize
430 ImVec2 child_size = options.canvas_size;
431 if (child_size.x <= 0 || child_size.y <= 0) {
433 }
434
435 if (config_.auto_resize && child_size.x > 0 && child_size.y > 0) {
436 CanvasUtils::SetNextCanvasSize(child_size, true);
437 }
438
439 // Begin child window for table integration
440 // Use NoScrollbar - canvas handles its own scrolling via internal mechanism
441 std::string child_id = canvas_id_ + "_TableChild";
442 ImGuiWindowFlags child_flags = ImGuiWindowFlags_NoScrollbar;
443 if (options.show_scrollbar) {
444 child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar;
445 }
446 ImGui::BeginChild(child_id.c_str(), child_size, true, child_flags);
447
448 if (!label.empty()) {
449 ImGui::Text("%s", label.c_str());
450 }
451
452 // Draw background and set up canvas state
453 Begin(options);
454
455 // Build and return runtime
457 if (options.grid_step.has_value()) {
458 rt.grid_step = options.grid_step.value();
459 }
460 return rt;
461}
462
464 const CanvasFrameOptions& options) {
465 // Draw grid if enabled
466 if (options.draw_grid) {
467 float step = options.grid_step.value_or(config_.grid_step);
468 DrawGrid(step);
469 }
470
471 // Draw overlay
472 if (options.draw_overlay) {
473 DrawOverlay();
474 }
475
476 // Render persistent popups if enabled
477 if (options.render_popups) {
479 }
480
481 ImGui::EndChild();
482}
483
484// Improved interaction detection methods
486 return !points_.empty() && points_.size() >= 2;
487}
488
489bool Canvas::WasClicked(ImGuiMouseButton button) const {
490 return ImGui::IsItemClicked(button) && HasValidSelection();
491}
492
493bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
494 return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) &&
496}
497
499 if (HasValidSelection()) {
500 return points_[0]; // Return the first point of the selection
501 }
502 return ImVec2(-1, -1); // Invalid position
503}
504
505// ==================== Modern ImGui-Style Interface ====================
506
507void Canvas::Begin(ImVec2 canvas_size) {
508 // Modern ImGui-style begin - combines DrawBackground + DrawContextMenu
511}
512
514 // Modern ImGui-style end - automatically draws grid and overlay
515 if (config_.enable_grid) {
516 DrawGrid();
517 }
518 DrawOverlay();
519
520 // Render any persistent popups from context menu actions
522}
523
524void Canvas::Begin(const CanvasFrameOptions& options) {
526
527 // Only wrap in child window if explicitly requested
528 if (options.use_child_window) {
529 // Calculate effective size
530 ImVec2 effective_size = options.canvas_size;
531 if (effective_size.x == 0 && effective_size.y == 0) {
532 if (IsAutoResize()) {
533 effective_size = GetPreferredSize();
534 } else {
535 effective_size = GetCurrentSize();
536 }
537 }
538
539 ImGuiWindowFlags child_flags = ImGuiWindowFlags_None;
540 if (options.show_scrollbar) {
541 child_flags |= ImGuiWindowFlags_AlwaysVerticalScrollbar;
542 }
543 ImGui::BeginChild(canvas_id().c_str(), effective_size, true, child_flags);
544 }
545
546 // Apply grid step from options if specified
547 if (options.grid_step.has_value()) {
548 SetCustomGridStep(options.grid_step.value());
549 }
550
553
554 if (options.draw_context_menu) {
556 }
557}
558
559void Canvas::End(const CanvasFrameOptions& options) {
560 if (options.draw_grid) {
561 DrawGrid(options.grid_step.value_or(GetGridStep()));
562 }
563 if (options.draw_overlay) {
564 DrawOverlay();
565 }
566 if (options.render_popups) {
568 }
569 // Only end child if we started one
570 if (options.use_child_window) {
571 ImGui::EndChild();
572 }
573}
574
575// ==================== Legacy Interface ====================
576
578 gfx::Bitmap& bitmap, const ImVec4& color,
579 const std::function<void()>& event,
580 int tile_size, float scale) {
581 config_.global_scale = scale;
582 global_scale_ = scale; // Legacy compatibility
585 DrawBitmap(bitmap, 2, scale);
586 if (DrawSolidTilePainter(color, tile_size)) {
587 event();
588 bitmap.UpdateTexture();
589 }
590 DrawGrid();
591 DrawOverlay();
592}
593
594void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
596 enable_custom_labels_ = true; // Legacy compatibility
597 DrawBackground(bg_size);
598 DrawInfoGrid(grid_size, 8, label_id);
599 DrawOverlay();
600}
601
602void Canvas::DrawBackground(ImVec2 canvas_size) {
603 draw_list_ = GetWindowDrawList();
604
605 // Phase 1: Calculate geometry using new helper
607 config_, canvas_size, GetCursorScreenPos(), GetContentRegionAvail());
608
610
611 // Update config if explicit size provided
612 if (canvas_size.x != 0) {
614 }
615
616 // Phase 1: Render background using helper
618
619 ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size,
621
622 // CRITICAL FIX: Always update hover mouse position when hovering over canvas
623 // This fixes the regression where CheckForCurrentMap() couldn't track hover
624 // Phase 1: Use geometry helper for mouse calculation
625 if (IsItemHovered()) {
626 const ImGuiIO& io = GetIO();
629 state_.is_hovered = true;
630 is_hovered_ = true;
631 } else {
632 state_.is_hovered = false;
633 is_hovered_ = false;
634 }
635
636 // iOS/tablet gestures currently synthesize ImGui wheel deltas (see src/ios/main.mm).
637 // Consume them here to pan/zoom the canvas without triggering ImGui window scrolling.
638 if (LayoutHelpers::IsTouchDevice() && IsItemHovered()) {
639 ImGuiIO& io = GetIO();
640 const float wheel_x = io.MouseWheelH;
641 const float wheel_y = io.MouseWheel;
642
643 if (wheel_x != 0.0f || wheel_y != 0.0f) {
644 // Prevent parent windows/child regions from scrolling on touch gestures.
645 io.MouseWheelH = 0.0f;
646 io.MouseWheel = 0.0f;
647
648 if (io.KeyCtrl && wheel_y != 0.0f) {
649 // Ctrl+wheel: zoom (pinch on iOS).
650 constexpr float kMinScale = 0.25f;
651 constexpr float kMaxScale = 8.0f;
652 const float unclamped = global_scale_ * (1.0f + wheel_y);
653 const float new_scale = std::clamp(unclamped, kMinScale, kMaxScale);
654
655 if (new_scale != global_scale_) {
656 const ImVec2 new_scroll = ComputeScrollForZoomAtScreenPos(
657 state_.geometry, global_scale_, new_scale, io.MousePos);
658 set_global_scale(new_scale);
659 state_.geometry.scrolling = new_scroll;
662 }
663 } else {
664 // Plain wheel: pan (two-finger pan on iOS).
665 constexpr float kTouchWheelToPixels = 10.0f;
667 ImVec2(wheel_x * kTouchWheelToPixels,
668 wheel_y * kTouchWheelToPixels));
671 }
672 }
673 }
674
675 // Pan handling (Phase 1: Use geometry helper)
676 if (config_.is_draggable && IsItemHovered()) {
677 const ImGuiIO& io = GetIO();
678 const bool is_active = IsItemActive(); // Held
679
680 // Pan (we use a zero mouse threshold when there's no context menu)
681 if (const float mouse_threshold_for_pan =
682 enable_context_menu_ ? -1.0f : 0.0f;
683 is_active &&
684 IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
685 ApplyScrollDelta(state_.geometry, io.MouseDelta);
688 }
689 }
690}
691
693 const ImGuiIO& io = GetIO();
694 const ImVec2 scaled_sz(canvas_sz_.x * global_scale_,
696 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
697 canvas_p0_.y + scrolling_.y); // Lock scrolled origin
698 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
699
700 // Update canvas state for enhanced components
701 if (usage_tracker_) {
702 usage_tracker_->UpdateCanvasState(
705 }
706
707 // Use enhanced context menu if available
708 if (context_menu_) {
709 CanvasConfig snapshot;
710 snapshot.canvas_size = canvas_sz_;
712 snapshot.global_scale = global_scale_;
713 snapshot.grid_step = custom_step_;
714 snapshot.enable_grid = enable_grid_;
718 snapshot.is_draggable = draggable_;
720 snapshot.scrolling = scrolling_;
721
722 context_menu_->SetCanvasState(
726
727 context_menu_->Render(
728 context_id_, mouse_pos, rom_, bitmap_,
729 bitmap_ ? bitmap_->mutable_palette() : nullptr,
730 [this](CanvasContextMenu::Command command,
731 const CanvasConfig& updated_config) {
732 switch (command) {
734 ResetView();
735 break;
737 if (bitmap_) {
739 }
740 break;
743 break;
746 break;
750 break;
754 break;
758 break;
762 break;
765 break;
769 break;
771 config_.grid_step = updated_config.grid_step;
773 break;
775 config_.global_scale = updated_config.global_scale;
777 break;
779 auto& ext = EnsureExtensions();
780 ext.InitializeModals();
781 if (ext.modals) {
782 CanvasConfig modal_config = updated_config;
783 modal_config.on_config_changed =
784 [this](const CanvasConfig& cfg) {
786 };
787 modal_config.on_scale_changed =
788 [this](const CanvasConfig& cfg) {
790 };
791 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config,
792 bitmap_);
793 }
794 } break;
796 auto& ext = EnsureExtensions();
797 ext.InitializeModals();
798 if (ext.modals) {
799 CanvasConfig modal_config = updated_config;
800 modal_config.on_config_changed =
801 [this](const CanvasConfig& cfg) {
803 };
804 modal_config.on_scale_changed =
805 [this](const CanvasConfig& cfg) {
807 };
808 ext.modals->ShowScalingControls(canvas_id_, modal_config,
809 bitmap_);
810 }
811 } break;
812 default:
813 break;
814 }
815 },
816 snapshot, this); // Phase 4: Pass Canvas* for editor menu integration
817
818 if (extensions_ && extensions_->modals) {
819 extensions_->modals->Render();
820 }
821
822 return;
823 }
824
825 // Draw enhanced property dialogs
828}
829
831 // Phase 4: Use RenderMenuItem from canvas_menu.h for consistent rendering
832 auto popup_callback = [this](const std::string& id,
833 std::function<void()> callback) {
834 popup_registry_.Open(id, callback);
835 };
836
837 gui::RenderMenuItem(item, popup_callback);
838}
839
841 // Phase 4: Add to editor menu definition
842 // Items are added to a default section with editor-specific priority
843 if (editor_menu_.sections.empty()) {
844 CanvasMenuSection section;
846 section.separator_after = true;
847 editor_menu_.sections.push_back(section);
848 }
849
850 // Add to the last section (or create new if the last isn't editor-specific)
851 auto& last_section = editor_menu_.sections.back();
852 if (last_section.priority != MenuSectionPriority::kEditorSpecific) {
853 CanvasMenuSection new_section;
855 new_section.separator_after = true;
856 editor_menu_.sections.push_back(new_section);
857 editor_menu_.sections.back().items.push_back(item);
858 } else {
859 last_section.items.push_back(item);
860 }
861}
862
866
867void Canvas::OpenPersistentPopup(const std::string& popup_id,
868 std::function<void()> render_callback) {
869 // Phase 4: Simplified popup management (no legacy synchronization)
870 popup_registry_.Open(popup_id, render_callback);
871}
872
873void Canvas::ClosePersistentPopup(const std::string& popup_id) {
874 // Phase 4: Simplified popup management (no legacy synchronization)
875 popup_registry_.Close(popup_id);
876}
877
879 // Phase 4: Simplified rendering (no legacy synchronization)
881}
882
884 if (!bitmap.is_active())
885 return;
886
887 ImVec2 available = ImGui::GetContentRegionAvail();
888 float scale_x = available.x / bitmap.width();
889 float scale_y = available.y / bitmap.height();
890 config_.global_scale = std::min(scale_x, scale_y);
891
892 // Ensure minimum readable scale
893 if (config_.global_scale < 0.25f)
894 config_.global_scale = 0.25f;
895
896 global_scale_ = config_.global_scale; // Legacy compatibility
897
898 // Center the view
899 scrolling_ = ImVec2(0, 0);
900}
901
903 config_.global_scale = 1.0f;
904 global_scale_ = 1.0f; // Legacy compatibility
905 scrolling_ = ImVec2(0, 0);
906 config_.scrolling = ImVec2(0, 0); // Sync config for persistence
907}
908
932
938
939bool Canvas::DrawTilePainter(const Bitmap& bitmap, int size, float scale) {
940 const ImGuiIO& io = GetIO();
941 const bool is_hovered = IsItemHovered();
942 is_hovered_ = is_hovered;
943 // Lock scrolled origin
944 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
945 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
946 const auto scaled_size = size * scale;
947
948 // Erase the hover when the mouse is not in the canvas window.
949 if (!is_hovered) {
950 points_.clear();
951 return false;
952 }
953
954 // Reset the previous tile hover
955 if (!points_.empty()) {
956 points_.clear();
957 }
958
959 // Calculate the coordinates of the mouse
960 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
961 mouse_pos_in_canvas_ = paint_pos;
962 auto paint_pos_end =
963 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size);
964 points_.push_back(paint_pos);
965 points_.push_back(paint_pos_end);
966
967 if (bitmap.is_active()) {
968 draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
969 ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y),
970 ImVec2(origin.x + paint_pos.x + scaled_size,
971 origin.y + paint_pos.y + scaled_size));
972 }
973
974 if (IsMouseClicked(ImGuiMouseButton_Left) &&
975 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
976 // Draw the currently selected tile on the overworld here
977 // Save the coordinates of the selected tile.
978 drawn_tile_pos_ = paint_pos;
979 return true;
980 }
981
982 return false;
983}
984
985bool Canvas::DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile) {
986 // Update hover state for backward compatibility
987 is_hovered_ = IsItemHovered();
988
989 // Clear points if not hovered (legacy behavior)
990 if (!is_hovered_) {
991 points_.clear();
992 return false;
993 }
994
995 // Build runtime and delegate to stateless helper
997 ImVec2 drawn_pos;
998 bool result = gui::DrawTilemapPainter(rt, tilemap, current_tile, &drawn_pos);
999
1000 // Sync legacy state from stateless call
1001 if (is_hovered_) {
1002 const ImGuiIO& io = GetIO();
1003 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
1004 canvas_p0_.y + scrolling_.y);
1005 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1006 const float scaled_size = tilemap.tile_size.x * global_scale_;
1007 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size);
1008 mouse_pos_in_canvas_ = paint_pos;
1009
1010 points_.clear();
1011 points_.push_back(paint_pos);
1012 points_.push_back(
1013 ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
1014 }
1015
1016 if (result) {
1017 drawn_tile_pos_ = drawn_pos;
1018 }
1019
1020 return result;
1021}
1022
1023bool Canvas::DrawSolidTilePainter(const ImVec4& color, int tile_size) {
1024 const ImGuiIO& io = GetIO();
1025 const bool is_hovered = IsItemHovered();
1026 is_hovered_ = is_hovered;
1027 // Lock scrolled origin
1028 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1029 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1030 auto scaled_tile_size = tile_size * global_scale_;
1031 static bool is_dragging = false;
1032 static ImVec2 start_drag_pos;
1033
1034 // Erase the hover when the mouse is not in the canvas window.
1035 if (!is_hovered) {
1036 points_.clear();
1037 return false;
1038 }
1039
1040 // Reset the previous tile hover
1041 if (!points_.empty()) {
1042 points_.clear();
1043 }
1044
1045 // Calculate the coordinates of the mouse
1046 ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size);
1047 mouse_pos_in_canvas_ = paint_pos;
1048
1049 // Clamp the size to a grid
1050 paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_);
1051 paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_);
1052
1053 points_.push_back(paint_pos);
1054 points_.push_back(
1055 ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
1056
1057 draw_list_->AddRectFilled(
1058 ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1),
1059 ImVec2(origin.x + paint_pos.x + scaled_tile_size,
1060 origin.y + paint_pos.y + scaled_tile_size),
1061 IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255));
1062
1063 if (IsMouseClicked(ImGuiMouseButton_Left)) {
1064 is_dragging = true;
1065 start_drag_pos = paint_pos;
1066 }
1067
1068 if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
1069 is_dragging = false;
1070 drawn_tile_pos_ = start_drag_pos;
1071 return true;
1072 }
1073
1074 return false;
1075}
1076
1077void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap* bitmap,
1078 ImVec4 color) {
1079 const ImVec2 position = drawn_tile_pos_;
1080 int tile_index_x = static_cast<int>(position.x / global_scale_) / tile_size;
1081 int tile_index_y = static_cast<int>(position.y / global_scale_) / tile_size;
1082
1083 ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size);
1084
1085 // Update the bitmap's pixel data based on the start_position and color
1086 for (int y = 0; y < tile_size; ++y) {
1087 for (int x = 0; x < tile_size; ++x) {
1088 // Calculate the actual pixel index in the bitmap
1089 int pixel_index =
1090 (start_position.y + y) * bitmap->width() + (start_position.x + x);
1091
1092 // Write the color to the pixel
1093 bitmap->WriteColor(pixel_index, color);
1094 }
1095 }
1096}
1097
1098bool Canvas::DrawTileSelector(int size, int size_y) {
1099 // Update hover state for backward compatibility
1100 is_hovered_ = IsItemHovered();
1101
1102 if (size_y == 0) {
1103 size_y = size;
1104 }
1105
1106 // Build runtime and delegate to stateless helper
1108 ImVec2 selected_pos;
1109 bool double_clicked = gui::DrawTileSelector(rt, size, size_y, &selected_pos);
1110
1111 // Sync legacy state: update points_ on click
1112 if (is_hovered_ && IsMouseClicked(ImGuiMouseButton_Left)) {
1113 const ImGuiIO& io = GetIO();
1114 const ImVec2 origin(canvas_p0_.x + scrolling_.x,
1115 canvas_p0_.y + scrolling_.y);
1116 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1117 ImVec2 painter_pos = AlignPosToGrid(mouse_pos, static_cast<float>(size));
1118
1119 points_.clear();
1120 points_.push_back(painter_pos);
1121 points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y));
1122 mouse_pos_in_canvas_ = painter_pos;
1123 }
1124
1125 return double_clicked;
1126}
1127
1128void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
1129 gfx::ScopedTimer timer("canvas_select_rect");
1130
1131 // Update hover state
1132 is_hovered_ = IsItemHovered();
1133 if (!is_hovered_) {
1134 return;
1135 }
1136
1137 // Build runtime and delegate to stateless helper
1139 rt.scale = scale; // Use the passed scale, not global_scale_
1140
1141 // Use a temporary selection to capture output from stateless helper
1142 CanvasSelection temp_selection;
1143 temp_selection.selected_tiles = selected_tiles_;
1144 temp_selection.selected_tile_pos = selected_tile_pos_;
1145 temp_selection.select_rect_active = select_rect_active_;
1146 for (int i = 0; i < selected_points_.size(); ++i) {
1147 temp_selection.selected_points.push_back(selected_points_[i]);
1148 }
1149
1150 gui::DrawSelectRect(rt, current_map, tile_size, scale, temp_selection);
1151
1152 // Sync back to legacy members
1153 selected_tiles_ = temp_selection.selected_tiles;
1154 selected_tile_pos_ = temp_selection.selected_tile_pos;
1155 select_rect_active_ = temp_selection.select_rect_active;
1156 selected_points_.clear();
1157 for (const auto& pt : temp_selection.selected_points) {
1158 selected_points_.push_back(pt);
1159 }
1160}
1161
1162void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) {
1163 if (!bitmap.is_active()) {
1164 return;
1165 }
1166 bitmap_ = &bitmap;
1167
1168 // Update content size for table integration
1169 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1170
1171 // Phase 1: Use rendering helper
1172 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset,
1173 scale);
1174}
1175
1176void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
1177 int alpha) {
1178 if (!bitmap.is_active()) {
1179 return;
1180 }
1181 bitmap_ = &bitmap;
1182
1183 // Update content size for table integration
1184 // CRITICAL: Store UNSCALED bitmap size as content - scale is applied during
1185 // rendering
1186 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1187
1188 // Phase 1: Use rendering helper
1189 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset,
1190 scale, alpha);
1191}
1192
1193void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size,
1194 ImVec2 src_pos, ImVec2 src_size) {
1195 if (!bitmap.is_active()) {
1196 return;
1197 }
1198 bitmap_ = &bitmap;
1199
1200 // Update content size for table integration
1201 config_.content_size = ImVec2(bitmap.width(), bitmap.height());
1202
1203 // Phase 1: Use rendering helper
1204 RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size,
1205 src_pos, src_size);
1206}
1207
1208// TODO: Add parameters for sizing and positioning
1209void Canvas::DrawBitmapTable(const BitmapTable& gfx_bin) {
1210 for (const auto& [key, value] : gfx_bin) {
1211 // Skip null or inactive bitmaps without valid textures
1212 if (!value || !value->is_active() || !value->texture()) {
1213 continue;
1214 }
1215 int offset = 0x40 * (key + 1);
1216 int top_left_y = canvas_p0_.y + 2;
1217 if (key >= 1) {
1218 top_left_y = canvas_p0_.y + 0x40 * key;
1219 }
1220 draw_list_->AddImage((ImTextureID)(intptr_t)value->texture(),
1221 ImVec2(canvas_p0_.x + 2, top_left_y),
1222 ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset));
1223 }
1224}
1225
1226void Canvas::DrawOutline(int x, int y, int w, int h) {
1228 IM_COL32(255, 255, 255, 200));
1229}
1230
1231void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
1233 y, w, h, color);
1234}
1235
1236void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
1238 color);
1239}
1240
1241void Canvas::DrawBitmapGroup(std::vector<int>& group, gfx::Tilemap& tilemap,
1242 int tile_size, float /*scale*/, int local_map_size,
1243 ImVec2 total_map_size) {
1244 if (selected_points_.size() != 2) {
1245 // points_ should contain exactly two points
1246 return;
1247 }
1248 if (group.empty()) {
1249 // group should not be empty
1250 return;
1251 }
1252
1253 // CRITICAL: Use config_.global_scale for consistency with DrawOverlay
1254 // which also uses config_.global_scale for the selection rectangle outline.
1255 // Using the passed 'scale' parameter would cause misalignment if they differ.
1256 const float effective_scale = config_.global_scale;
1257
1258 // OPTIMIZATION: Use optimized rendering for large groups to improve
1259 // performance
1260 bool use_optimized_rendering =
1261 group.size() > 128; // Optimize for large selections
1262
1263 // Use provided map sizes for proper boundary handling
1264 const int small_map = local_map_size;
1265 const float large_map_width = total_map_size.x;
1266 const float large_map_height = total_map_size.y;
1267
1268 // Pre-calculate common values to avoid repeated computation
1269 const float tile_scale = tile_size * effective_scale;
1270 const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
1271
1272 // Top-left and bottom-right corners of the rectangle (in world coordinates)
1273 ImVec2 rect_top_left = selected_points_[0];
1274 ImVec2 rect_bottom_right = selected_points_[1];
1275
1276 // Calculate the start and end tiles in the grid
1277 // selected_points are now in world coordinates, so divide by tile_size only
1278 int start_tile_x = static_cast<int>(std::floor(rect_top_left.x / tile_size));
1279 int start_tile_y = static_cast<int>(std::floor(rect_top_left.y / tile_size));
1280 int end_tile_x =
1281 static_cast<int>(std::floor(rect_bottom_right.x / tile_size));
1282 int end_tile_y =
1283 static_cast<int>(std::floor(rect_bottom_right.y / tile_size));
1284
1285 if (start_tile_x > end_tile_x)
1286 std::swap(start_tile_x, end_tile_x);
1287 if (start_tile_y > end_tile_y)
1288 std::swap(start_tile_y, end_tile_y);
1289
1290 // Calculate the size of the rectangle in 16x16 grid form
1291 int rect_width = (end_tile_x - start_tile_x) * tile_size;
1292 int rect_height = (end_tile_y - start_tile_y) * tile_size;
1293
1294 int tiles_per_row = rect_width / tile_size;
1295 int tiles_per_col = rect_height / tile_size;
1296
1297 int i = 0;
1298 for (int y = 0; y < tiles_per_col + 1; ++y) {
1299 for (int x = 0; x < tiles_per_row + 1; ++x) {
1300 // Check bounds to prevent access violations
1301 if (i >= static_cast<int>(group.size())) {
1302 break;
1303 }
1304
1305 int tile_id = group[i];
1306
1307 // Check if tile_id is within the range of tile16_individual_
1308 auto tilemap_size = tilemap.map_size.x;
1309 if (tile_id >= 0 && tile_id < tilemap_size) {
1310 // Calculate the position of the tile within the rectangle
1311 int tile_pos_x = (x + start_tile_x) * tile_size * effective_scale;
1312 int tile_pos_y = (y + start_tile_y) * tile_size * effective_scale;
1313
1314 // OPTIMIZATION: Use pre-calculated values for better performance with
1315 // large selections
1316 if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
1317 atlas_tiles_per_row > 0) {
1318 int atlas_tile_x =
1319 (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
1320 int atlas_tile_y =
1321 (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
1322
1323 // Simple bounds check
1324 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
1325 atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
1326 // Calculate UV coordinates once for efficiency
1327 const float atlas_width = static_cast<float>(tilemap.atlas.width());
1328 const float atlas_height =
1329 static_cast<float>(tilemap.atlas.height());
1330 ImVec2 uv0 =
1331 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
1332 ImVec2 uv1 =
1333 ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
1334 (atlas_tile_y + tilemap.tile_size.y) / atlas_height);
1335
1336 // Calculate screen positions
1337 float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x;
1338 float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y;
1339 float screen_w = tilemap.tile_size.x * effective_scale;
1340 float screen_h = tilemap.tile_size.y * effective_scale;
1341
1342 // Use higher alpha for large selections to make them more visible
1343 uint32_t alpha_color = use_optimized_rendering
1344 ? IM_COL32(255, 255, 255, 200)
1345 : IM_COL32(255, 255, 255, 150);
1346
1347 // Draw from atlas texture with optimized parameters
1348 draw_list_->AddImage(
1349 (ImTextureID)(intptr_t)tilemap.atlas.texture(),
1350 ImVec2(screen_x, screen_y),
1351 ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1,
1352 alpha_color);
1353 }
1354 }
1355 }
1356 i++;
1357 }
1358 // Break outer loop if we've run out of tiles
1359 if (i >= static_cast<int>(group.size())) {
1360 break;
1361 }
1362 }
1363
1364 // Performance optimization completed - tiles are now rendered with
1365 // pre-calculated values
1366
1367 // Reposition rectangle to follow mouse, but clamp to prevent wrapping across
1368 // map boundaries
1369 const ImGuiIO& io = GetIO();
1370 const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y);
1371 const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
1372
1373 // CRITICAL FIX: Clamp BEFORE grid alignment for smoother dragging behavior
1374 // This prevents the rectangle from even attempting to cross boundaries during
1375 // drag
1376 ImVec2 clamped_mouse_pos = mouse_pos;
1377
1379 // Calculate which local map the mouse is in
1380 int mouse_local_map_x = static_cast<int>(mouse_pos.x) / small_map;
1381 int mouse_local_map_y = static_cast<int>(mouse_pos.y) / small_map;
1382
1383 // Calculate where the rectangle END would be if we place it at mouse
1384 // position
1385 float potential_end_x = mouse_pos.x + rect_width;
1386 float potential_end_y = mouse_pos.y + rect_height;
1387
1388 // Check if this would cross local map boundary (512x512 blocks)
1389 int potential_end_map_x = static_cast<int>(potential_end_x) / small_map;
1390 int potential_end_map_y = static_cast<int>(potential_end_y) / small_map;
1391
1392 // Clamp mouse position to prevent crossing during drag
1393 if (potential_end_map_x != mouse_local_map_x) {
1394 // Would cross horizontal boundary - clamp mouse to safe zone
1395 float max_mouse_x = (mouse_local_map_x + 1) * small_map - rect_width;
1396 clamped_mouse_pos.x = std::min(mouse_pos.x, max_mouse_x);
1397 }
1398
1399 if (potential_end_map_y != mouse_local_map_y) {
1400 // Would cross vertical boundary - clamp mouse to safe zone
1401 float max_mouse_y = (mouse_local_map_y + 1) * small_map - rect_height;
1402 clamped_mouse_pos.y = std::min(mouse_pos.y, max_mouse_y);
1403 }
1404 }
1405
1406 // Now grid-align the clamped position (in screen coords)
1407 auto new_start_pos_screen =
1408 AlignPosToGrid(clamped_mouse_pos, tile_size * effective_scale);
1409
1410 // Convert to world coordinates for storage (selected_points_ stores world coords)
1411 ImVec2 new_start_pos_world(new_start_pos_screen.x / effective_scale,
1412 new_start_pos_screen.y / effective_scale);
1413
1414 // Additional safety: clamp to overall map bounds (in world coordinates)
1415 new_start_pos_world.x =
1416 std::clamp(new_start_pos_world.x, 0.0f, large_map_width - rect_width);
1417 new_start_pos_world.y =
1418 std::clamp(new_start_pos_world.y, 0.0f, large_map_height - rect_height);
1419
1420 selected_points_.clear();
1421 selected_points_.push_back(new_start_pos_world);
1422 selected_points_.push_back(ImVec2(new_start_pos_world.x + rect_width,
1423 new_start_pos_world.y + rect_height));
1424 select_rect_active_ = true;
1425}
1426
1427void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
1429 color, config_.global_scale);
1430}
1431
1432void Canvas::DrawText(const std::string& text, int x, int y) {
1435}
1436
1441
1442void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
1443 // Draw grid + all lines in the canvas
1444 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1445 if (enable_grid_) {
1446 if (custom_step_ != 0.f)
1447 grid_step = custom_step_;
1448 grid_step *= global_scale_; // Apply global scale to grid step
1449
1450 DrawGridLines(grid_step);
1451 DrawCustomHighlight(grid_step);
1452
1453 if (!enable_custom_labels_) {
1454 return;
1455 }
1456
1457 // Draw the contents of labels on the grid
1458 for (float x = fmodf(scrolling_.x, grid_step);
1459 x < canvas_sz_.x * global_scale_; x += grid_step) {
1460 for (float y = fmodf(scrolling_.y, grid_step);
1461 y < canvas_sz_.y * global_scale_; y += grid_step) {
1462 int tile_x = (x - scrolling_.x) / grid_step;
1463 int tile_y = (y - scrolling_.y) / grid_step;
1464 int tile_id = tile_x + (tile_y * tile_id_offset);
1465
1466 if (tile_id >= labels_[label_id].size()) {
1467 break;
1468 }
1469 std::string label = labels_[label_id][tile_id];
1470 draw_list_->AddText(
1471 ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
1472 canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
1473 kWhiteColor, label.data());
1474 }
1475 }
1476 }
1477}
1478
1483
1484void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
1485 if (config_.grid_step != 0.f)
1486 grid_step = config_.grid_step;
1487
1488 // Create render context for utilities
1491 .canvas_p0 = canvas_p0_,
1492 .canvas_p1 = canvas_p1_,
1493 .scrolling = scrolling_,
1494 .global_scale = config_.global_scale,
1495 .enable_grid = config_.enable_grid,
1496 .enable_hex_labels = config_.enable_hex_labels,
1497 .grid_step = grid_step};
1498
1499 // Use high-level utility function
1501
1502 // Draw custom labels if enabled
1504 draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
1506 tile_id_offset);
1507 draw_list_->PopClipRect();
1508 }
1509}
1510
1512 // Create render context for utilities
1515 .canvas_p0 = canvas_p0_,
1516 .canvas_p1 = canvas_p1_,
1517 .scrolling = scrolling_,
1518 .global_scale = config_.global_scale,
1519 .enable_grid = config_.enable_grid,
1520 .enable_hex_labels = config_.enable_hex_labels,
1521 .grid_step = config_.grid_step};
1522
1523 // Use high-level utility function with local points (synchronized from
1524 // interaction handler)
1526
1527 // Render any persistent popups from context menu actions
1529}
1530
1532 // Based on ImGui demo, should be adapted to use for OAM
1533 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1534 {
1535 Text(tr("Blue shape is drawn first: appears in back"));
1536 Text(tr("Red shape is drawn after: appears in front"));
1537 ImVec2 p0 = ImGui::GetCursorScreenPos();
1538 draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50),
1539 IM_COL32(0, 0, 255, 255)); // Blue
1540 draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25),
1541 ImVec2(p0.x + 75, p0.y + 75),
1542 IM_COL32(255, 0, 0, 255)); // Red
1543 ImGui::Dummy(ImVec2(75, 75));
1544 }
1545 ImGui::Separator();
1546 {
1547 Text(tr("Blue shape is drawn first, into channel 1: appears in front"));
1548 Text(tr("Red shape is drawn after, into channel 0: appears in back"));
1549 ImVec2 p1 = ImGui::GetCursorScreenPos();
1550
1551 // Create 2 channels and draw a Blue shape THEN a Red shape.
1552 // You can create any number of channels. Tables API use 1 channel per
1553 // column in order to better batch draw calls.
1554 draw_list->ChannelsSplit(2);
1555 draw_list->ChannelsSetCurrent(1);
1556 draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50),
1557 IM_COL32(0, 0, 255, 255)); // Blue
1558 draw_list->ChannelsSetCurrent(0);
1559 draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25),
1560 ImVec2(p1.x + 75, p1.y + 75),
1561 IM_COL32(255, 0, 0, 255)); // Red
1562
1563 // Flatten/reorder channels. Red shape is in channel 0 and it appears
1564 // below the Blue shape in channel 1. This works by copying draw indices
1565 // only (vertices are not copied).
1566 draw_list->ChannelsMerge();
1567 ImGui::Dummy(ImVec2(75, 75));
1568 Text(
1569 tr("After reordering, contents of channel 0 appears below channel 1."));
1570 }
1571}
1572
1574 // Use the new modal system (lazy-initialized via extensions)
1575 auto& ext = EnsureExtensions();
1576 ext.InitializeModals();
1577 if (ext.modals) {
1578 CanvasConfig modal_config;
1579 modal_config.canvas_size = canvas_sz_;
1580 modal_config.content_size = config_.content_size;
1581 modal_config.global_scale = global_scale_;
1582 modal_config.grid_step = custom_step_;
1583 modal_config.enable_grid = enable_grid_;
1587 modal_config.is_draggable = draggable_;
1588 modal_config.auto_resize = config_.auto_resize;
1589 modal_config.scrolling = scrolling_;
1590 modal_config.on_config_changed =
1591 [this](const CanvasConfig& updated_config) {
1592 // Update legacy variables when config changes
1593 enable_grid_ = updated_config.enable_grid;
1594 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1595 enable_custom_labels_ = updated_config.enable_custom_labels;
1596 };
1597 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1598 global_scale_ = updated_config.global_scale;
1599 scrolling_ = updated_config.scrolling;
1600 };
1601
1602 ext.modals->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_);
1603 return;
1604 }
1605
1606 // Fallback to legacy modal system
1607 if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr,
1608 ImGuiWindowFlags_AlwaysAutoResize)) {
1609 ImGui::Text(tr("Advanced Canvas Configuration"));
1610 ImGui::Separator();
1611
1612 // Canvas properties (read-only info)
1613 ImGui::Text(tr("Canvas Properties"));
1614 ImGui::Text(tr("ID: %s"), canvas_id_.c_str());
1615 ImGui::Text(tr("Canvas Size: %.0f x %.0f"), config_.canvas_size.x,
1617 ImGui::Text(tr("Content Size: %.0f x %.0f"), config_.content_size.x,
1619 ImGui::Text(tr("Global Scale: %.3f"), config_.global_scale);
1620 ImGui::Text(tr("Grid Step: %.1f"), config_.grid_step);
1621
1622 if (config_.content_size.x > 0 && config_.content_size.y > 0) {
1623 ImVec2 min_size = GetMinimumSize();
1624 ImVec2 preferred_size = GetPreferredSize();
1625 ImGui::Text(tr("Minimum Size: %.0f x %.0f"), min_size.x, min_size.y);
1626 ImGui::Text(tr("Preferred Size: %.0f x %.0f"), preferred_size.x,
1627 preferred_size.y);
1628 }
1629
1630 // Editable properties using new config system
1631 ImGui::Separator();
1632 ImGui::Text(tr("View Settings"));
1633 if (ImGui::Checkbox(tr("Enable Grid"), &config_.enable_grid)) {
1634 enable_grid_ = config_.enable_grid; // Legacy sync
1635 }
1636 if (ImGui::Checkbox(tr("Enable Hex Labels"), &config_.enable_hex_labels)) {
1638 }
1639 if (ImGui::Checkbox(tr("Enable Custom Labels"),
1642 }
1643 if (ImGui::Checkbox(tr("Enable Context Menu"),
1646 }
1647 if (ImGui::Checkbox(tr("Draggable"), &config_.is_draggable)) {
1648 draggable_ = config_.is_draggable; // Legacy sync
1649 }
1650 if (ImGui::Checkbox(tr("Auto Resize for Tables"), &config_.auto_resize)) {
1651 // Auto resize setting changed
1652 }
1653
1654 // Grid controls
1655 ImGui::Separator();
1656 ImGui::Text(tr("Grid Configuration"));
1657 if (ImGui::SliderFloat(tr("Grid Step"), &config_.grid_step, 1.0f, 128.0f,
1658 "%.1f")) {
1659 custom_step_ = config_.grid_step; // Legacy sync
1660 }
1661
1662 // Scale controls
1663 ImGui::Separator();
1664 ImGui::Text(tr("Scale Configuration"));
1665 if (ImGui::SliderFloat(tr("Global Scale"), &config_.global_scale, 0.1f,
1666 10.0f, "%.2f")) {
1667 global_scale_ = config_.global_scale; // Legacy sync
1668 }
1669
1670 // Scrolling controls
1671 ImGui::Separator();
1672 ImGui::Text(tr("Scrolling Configuration"));
1673 ImGui::Text(tr("Current Scroll: %.1f, %.1f"), scrolling_.x, scrolling_.y);
1674 if (ImGui::Button(tr("Reset Scroll"))) {
1675 scrolling_ = ImVec2(0, 0);
1676 }
1677 ImGui::SameLine();
1678 if (ImGui::Button(tr("Center View"))) {
1679 if (bitmap_) {
1680 scrolling_ = ImVec2(
1682 2.0f,
1684 config_.canvas_size.y) /
1685 2.0f);
1686 }
1687 }
1688
1689 if (ImGui::Button(tr("Close"))) {
1690 ImGui::CloseCurrentPopup();
1691 }
1692 ImGui::EndPopup();
1693 }
1694}
1695
1696// Old ShowPaletteManager method removed - now handled by PaletteWidget
1697
1699 // Use the new modal system (lazy-initialized via extensions)
1700 auto& ext = EnsureExtensions();
1701 ext.InitializeModals();
1702 if (ext.modals) {
1703 CanvasConfig modal_config;
1704 modal_config.canvas_size = canvas_sz_;
1705 modal_config.content_size = config_.content_size;
1706 modal_config.global_scale = global_scale_;
1707 modal_config.grid_step = custom_step_;
1708 modal_config.enable_grid = enable_grid_;
1712 modal_config.is_draggable = draggable_;
1713 modal_config.auto_resize = config_.auto_resize;
1714 modal_config.scrolling = scrolling_;
1715 modal_config.on_config_changed =
1716 [this](const CanvasConfig& updated_config) {
1717 // Update legacy variables when config changes
1718 enable_grid_ = updated_config.enable_grid;
1719 enable_hex_tile_labels_ = updated_config.enable_hex_labels;
1720 enable_custom_labels_ = updated_config.enable_custom_labels;
1721 enable_context_menu_ = updated_config.enable_context_menu;
1722 };
1723 modal_config.on_scale_changed = [this](const CanvasConfig& updated_config) {
1724 draggable_ = updated_config.is_draggable;
1725 custom_step_ = updated_config.grid_step;
1726 global_scale_ = updated_config.global_scale;
1727 scrolling_ = updated_config.scrolling;
1728 };
1729
1730 ext.modals->ShowScalingControls(canvas_id_, modal_config);
1731 return;
1732 }
1733
1734 // Fallback to legacy modal system
1735 if (ImGui::BeginPopupModal("Scaling Controls", nullptr,
1736 ImGuiWindowFlags_AlwaysAutoResize)) {
1737 ImGui::Text(tr("Canvas Scaling and Display Controls"));
1738 ImGui::Separator();
1739
1740 // Global scale with new config system
1741 ImGui::Text(tr("Global Scale: %.3f"), config_.global_scale);
1742 if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f,
1743 "%.2f")) {
1744 global_scale_ = config_.global_scale; // Legacy sync
1745 }
1746
1747 // Preset scale buttons
1748 ImGui::Text(tr("Preset Scales:"));
1749 if (ImGui::Button(tr("0.25x"))) {
1750 config_.global_scale = 0.25f;
1752 }
1753 ImGui::SameLine();
1754 if (ImGui::Button(tr("0.5x"))) {
1755 config_.global_scale = 0.5f;
1757 }
1758 ImGui::SameLine();
1759 if (ImGui::Button(tr("1x"))) {
1760 config_.global_scale = 1.0f;
1762 }
1763 ImGui::SameLine();
1764 if (ImGui::Button(tr("2x"))) {
1765 config_.global_scale = 2.0f;
1767 }
1768 ImGui::SameLine();
1769 if (ImGui::Button(tr("4x"))) {
1770 config_.global_scale = 4.0f;
1772 }
1773 ImGui::SameLine();
1774 if (ImGui::Button(tr("8x"))) {
1775 config_.global_scale = 8.0f;
1777 }
1778
1779 // Grid configuration
1780 ImGui::Separator();
1781 ImGui::Text(tr("Grid Configuration"));
1782 ImGui::Text(tr("Grid Step: %.1f"), config_.grid_step);
1783 if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f,
1784 "%.1f")) {
1785 custom_step_ = config_.grid_step; // Legacy sync
1786 }
1787
1788 // Grid size presets
1789 ImGui::Text(tr("Grid Presets:"));
1790 if (ImGui::Button(tr("8x8"))) {
1791 config_.grid_step = 8.0f;
1793 }
1794 ImGui::SameLine();
1795 if (ImGui::Button(tr("16x16"))) {
1796 config_.grid_step = 16.0f;
1798 }
1799 ImGui::SameLine();
1800 if (ImGui::Button(tr("32x32"))) {
1801 config_.grid_step = 32.0f;
1803 }
1804 ImGui::SameLine();
1805 if (ImGui::Button(tr("64x64"))) {
1806 config_.grid_step = 64.0f;
1808 }
1809
1810 // Canvas size info
1811 ImGui::Separator();
1812 ImGui::Text(tr("Canvas Information"));
1813 ImGui::Text(tr("Canvas Size: %.0f x %.0f"), config_.canvas_size.x,
1815 ImGui::Text(tr("Scaled Size: %.0f x %.0f"),
1818 if (bitmap_) {
1819 ImGui::Text(tr("Bitmap Size: %d x %d"), bitmap_->width(),
1820 bitmap_->height());
1821 ImGui::Text(
1822 tr("Effective Scale: %.3f x %.3f"),
1825 }
1826
1827 if (ImGui::Button(tr("Close"))) {
1828 ImGui::CloseCurrentPopup();
1829 }
1830 ImGui::EndPopup();
1831 }
1832}
1833
1834// BPP format management methods
1836 auto& ext = EnsureExtensions();
1837 ext.InitializeBppUI(canvas_id_);
1838
1839 if (bitmap_ && ext.bpp_format_ui) {
1840 ext.bpp_format_ui->RenderFormatSelector(
1842 [this](gfx::BppFormat format) { ConvertBitmapFormat(format); });
1843 }
1844}
1845
1847 auto& ext = EnsureExtensions();
1848 ext.InitializeBppUI(canvas_id_);
1849
1850 if (bitmap_ && ext.bpp_format_ui) {
1851 ext.bpp_format_ui->RenderAnalysisPanel(*bitmap_, bitmap_->palette());
1852 }
1853}
1854
1856 auto& ext = EnsureExtensions();
1857 if (!ext.bpp_conversion_dialog) {
1858 ext.bpp_conversion_dialog = std::make_unique<gui::BppConversionDialog>(
1859 canvas_id_ + "_bpp_conversion");
1860 }
1861
1862 if (bitmap_ && ext.bpp_conversion_dialog) {
1863 ext.bpp_conversion_dialog->Show(
1864 *bitmap_, bitmap_->palette(),
1865 [this](gfx::BppFormat format, bool /*preserve_palette*/) {
1866 ConvertBitmapFormat(format);
1867 });
1868 }
1869
1870 if (ext.bpp_conversion_dialog) {
1871 ext.bpp_conversion_dialog->Render();
1872 }
1873}
1874
1876 if (!bitmap_)
1877 return false;
1878
1879 gfx::BppFormat current_format = GetCurrentBppFormat();
1880 if (current_format == target_format) {
1881 return true; // No conversion needed
1882 }
1883
1884 try {
1885 // Convert the bitmap data
1886 auto converted_data = gfx::BppFormatManager::Get().ConvertFormat(
1887 bitmap_->vector(), current_format, target_format, bitmap_->width(),
1888 bitmap_->height());
1889
1890 // Update the bitmap with converted data
1891 bitmap_->set_data(converted_data);
1892
1893 // Update the renderer
1895
1896 return true;
1897 } catch (const std::exception& e) {
1898 SDL_Log("Failed to convert bitmap format: %s", e.what());
1899 return false;
1900 }
1901}
1902
1910
1911// Phase 4A: Canvas Automation API
1913 auto& ext = EnsureExtensions();
1914 ext.InitializeAutomation(this);
1915 return ext.automation_api.get();
1916}
1917
1918// =============================================================================
1919// Canvas::AddXxxAt Methods
1920// =============================================================================
1921
1922void Canvas::AddImageAt(ImTextureID texture, ImVec2 local_top_left,
1923 ImVec2 size) {
1924 if (draw_list_ == nullptr)
1925 return;
1926 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
1927 canvas_p0_.y + local_top_left.y * global_scale_);
1928 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
1929 screen_pos.y + size.y * global_scale_);
1930 draw_list_->AddImage(texture, screen_pos, screen_end);
1931}
1932
1933void Canvas::AddRectFilledAt(ImVec2 local_top_left, ImVec2 size,
1934 uint32_t color) {
1935 if (draw_list_ == nullptr)
1936 return;
1937 ImVec2 screen_pos(canvas_p0_.x + local_top_left.x * global_scale_,
1938 canvas_p0_.y + local_top_left.y * global_scale_);
1939 ImVec2 screen_end(screen_pos.x + size.x * global_scale_,
1940 screen_pos.y + size.y * global_scale_);
1941 draw_list_->AddRectFilled(screen_pos, screen_end, color);
1942}
1943
1944void Canvas::AddTextAt(ImVec2 local_pos, const std::string& text,
1945 uint32_t color) {
1946 if (draw_list_ == nullptr)
1947 return;
1948 ImVec2 screen_pos(canvas_p0_.x + local_pos.x * global_scale_,
1949 canvas_p0_.y + local_pos.y * global_scale_);
1950 draw_list_->AddText(screen_pos, color, text.c_str());
1951}
1952
1953// =============================================================================
1954// CanvasFrame RAII Class
1955// =============================================================================
1956
1958 : canvas_(&canvas), options_(options), active_(true) {
1960}
1961
1963 if (active_) {
1965 }
1966}
1967
1969 : canvas_(other.canvas_), options_(other.options_), active_(other.active_) {
1970 other.active_ = false;
1971}
1972
1974 if (this != &other) {
1975 if (active_) {
1976 canvas_->End(options_);
1977 }
1978 canvas_ = other.canvas_;
1979 options_ = other.options_;
1980 active_ = other.active_;
1981 other.active_ = false;
1982 }
1983 return *this;
1984}
1985
1986} // namespace yaze::gui
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:389
TextureHandle texture() const
Definition bitmap.h:401
const std::vector< uint8_t > & vector() const
Definition bitmap.h:402
bool is_active() const
Definition bitmap.h:405
SnesPalette * mutable_palette()
Definition bitmap.h:390
void WriteColor(int position, const ImVec4 &color)
Write a color to a pixel at the given position.
Definition bitmap.cc:632
int height() const
Definition bitmap.h:395
void set_data(const std::vector< uint8_t > &data)
Definition bitmap.cc:853
int width() const
Definition bitmap.h:394
void UpdateTexture()
Updates the underlying SDL_Texture when it already exists.
Definition bitmap.cc:297
BppFormat DetectFormat(const std::vector< uint8_t > &data, int width, int height)
Detect BPP format from bitmap data.
std::vector< uint8_t > ConvertFormat(const std::vector< uint8_t > &data, BppFormat from_format, BppFormat to_format, int width, int height)
Convert bitmap data between BPP formats.
static BppFormatManager & Get()
Defines an abstract interface for all rendering operations.
Definition irenderer.h:60
RAII timer for automatic timing management.
Programmatic interface for controlling canvas operations.
Lightweight RAII guard for existing Canvas instances.
Definition canvas.h:707
CanvasFrameOptions options_
Definition canvas.h:724
CanvasFrame & operator=(const CanvasFrame &)=delete
CanvasFrame(Canvas &canvas, CanvasFrameOptions options=CanvasFrameOptions())
Definition canvas.cc:1957
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:531
CanvasState state_
Definition canvas.h:504
ImVector< ImVec2 > points_
Definition canvas.h:541
int highlight_tile_id
Definition canvas.h:519
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1162
PopupRegistry popup_registry_
Definition canvas.h:515
Rom * rom() const
Definition canvas.h:463
std::string canvas_id_
Definition canvas.h:545
void ShowScalingControls()
Definition canvas.cc:1698
bool WasDoubleClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:493
CanvasConfig config_
Definition canvas.h:494
ImVec2 selected_tile_pos_
Definition canvas.h:551
auto global_scale() const
Definition canvas.h:397
ImVec2 canvas_p1_
Definition canvas.h:534
void ShowBppAnalysis()
Definition canvas.cc:1846
gfx::IRenderer * renderer() const
Definition canvas.h:96
void DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1231
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:290
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:1241
bool BeginTableCanvas(const std::string &label="")
Definition canvas.cc:400
void InitializeEnhancedComponents()
Definition canvas.cc:273
CanvasRuntime BuildCurrentRuntime() const
Definition canvas.h:478
void ShowBppConversionDialog()
Definition canvas.cc:1855
CanvasAutomationAPI * GetAutomationAPI()
Definition canvas.cc:1912
void ShowAdvancedCanvasProperties()
Definition canvas.cc:1573
void ApplyScaleSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:933
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:594
void DrawContextMenu()
Definition canvas.cc:692
void EnsurePerformanceIntegration()
Definition canvas.cc:108
ImVec2 mouse_pos_in_canvas_
Definition canvas.h:536
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:985
bool DrawSolidTilePainter(const ImVec4 &color, int size)
Definition canvas.cc:1023
bool enable_context_menu_
Definition canvas.h:558
auto draw_list() const
Definition canvas.h:347
CanvasMenuDefinition editor_menu_
Definition canvas.h:511
void ApplyConfigSnapshot(const CanvasConfig &snapshot)
Definition canvas.cc:909
void DrawLayeredElements()
Definition canvas.cc:1531
void ReserveTableSpace(const std::string &label="")
Definition canvas.cc:395
bool enable_custom_labels_
Definition canvas.h:557
void AddTextAt(ImVec2 local_pos, const std::string &text, uint32_t color)
Definition canvas.cc:1944
void ShowUsageReport()
Definition canvas.cc:319
void SetRenderer(gfx::IRenderer *renderer)
Definition canvas.h:95
ImVec2 GetMinimumSize() const
Definition canvas.cc:385
void AddRectFilledAt(ImVec2 local_top_left, ImVec2 size, uint32_t color)
Definition canvas.cc:1933
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:1098
bool ConvertBitmapFormat(gfx::BppFormat target_format)
Definition canvas.cc:1875
void DrawGridLines(float grid_step)
Definition canvas.cc:1437
void SetCustomGridStep(float step)
Definition canvas.h:118
void ShowPerformanceUI()
Definition canvas.cc:312
zelda3::GameData * game_data() const
Definition canvas.h:465
bool custom_canvas_size_
Definition canvas.h:559
void ClearContextMenuItems()
Definition canvas.cc:863
void AddImageAt(ImTextureID texture, ImVec2 local_top_left, ImVec2 size)
Definition canvas.cc:1922
void SetGameData(zelda3::GameData *game_data)
Definition canvas.cc:347
void DrawRect(int x, int y, int w, int h, ImVec4 color)
Definition canvas.cc:1427
bool HasValidSelection() const
Definition canvas.cc:485
bool DrawTilePainter(const Bitmap &bitmap, int size, float scale=1.0f)
Definition canvas.cc:939
ImDrawList * draw_list_
Definition canvas.h:528
ImVector< ImVec2 > selected_points_
Definition canvas.h:550
ImVec2 GetCurrentSize() const
Definition canvas.h:274
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:577
void DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color)
Definition canvas.cc:1077
void DrawCustomHighlight(float grid_step)
Definition canvas.cc:1479
bool select_rect_active_
Definition canvas.h:552
Bitmap * bitmap_
Definition canvas.h:525
std::unique_ptr< CanvasExtensions > extensions_
Definition canvas.h:500
CanvasGridSize grid_size() const
Definition canvas.h:126
void AddContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:840
ImVec2 GetPreferredSize() const
Definition canvas.cc:390
float GetGridStep() const
Definition canvas.h:380
CanvasInteractionHandler interaction_handler_
Definition canvas.h:197
void InitializePaletteEditor(Rom *rom)
Definition canvas.cc:338
void Begin(ImVec2 canvas_size=ImVec2(0, 0))
Begin canvas rendering (ImGui-style)
Definition canvas.cc:507
auto canvas_size() const
Definition canvas.h:357
void SetZoomToFit(const gfx::Bitmap &bitmap)
Definition canvas.cc:883
bool WasClicked(ImGuiMouseButton button=ImGuiMouseButton_Left) const
Definition canvas.cc:489
ImVector< ImVector< std::string > > labels_
Definition canvas.h:542
gfx::BppFormat GetCurrentBppFormat() const
Definition canvas.cc:1903
auto canvas_id() const
Definition canvas.h:404
void ClosePersistentPopup(const std::string &popup_id)
Definition canvas.cc:873
void ShowBppFormatSelector()
Definition canvas.cc:1835
void RecordCanvasOperation(const std::string &operation_name, double time_ms)
Definition canvas.cc:300
void RenderPersistentPopups()
Definition canvas.cc:878
void set_global_scale(float scale)
Definition canvas.cc:239
void SetGridSize(CanvasGridSize grid_size)
Definition canvas.h:99
bool IsAutoResize() const
Definition canvas.h:276
std::shared_ptr< CanvasUsageTracker > usage_tracker_
Definition canvas.h:195
void End()
End canvas rendering (ImGui-style)
Definition canvas.cc:513
void EndInTable(CanvasRuntime &runtime, const CanvasFrameOptions &options)
Definition canvas.cc:463
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:1128
std::unique_ptr< CanvasContextMenu > context_menu_
Definition canvas.h:194
auto usage_mode() const
Definition canvas.h:250
ImVec2 GetLastClickPosition() const
Definition canvas.cc:498
zelda3::GameData * game_data_
Definition canvas.h:527
void ShowPaletteEditor()
Definition canvas.cc:354
float global_scale_
Definition canvas.h:554
void DrawOutline(int x, int y, int w, int h)
Definition canvas.cc:1226
float custom_step_
Definition canvas.h:553
void DrawInfoGrid(float grid_step=64.0f, int tile_id_offset=8, int label_id=0)
Definition canvas.cc:1442
CanvasSelection selection_
Definition canvas.h:495
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:427
bool enable_hex_tile_labels_
Definition canvas.h:556
ImVec2 canvas_p0_
Definition canvas.h:533
void OpenPersistentPopup(const std::string &popup_id, std::function< void()> render_callback)
Definition canvas.cc:867
void DrawBitmapTable(const BitmapTable &gfx_bin)
Definition canvas.cc:1209
std::string context_id_
Definition canvas.h:546
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:602
ImVec2 canvas_sz_
Definition canvas.h:532
void InitializeDefaults()
Definition canvas.cc:178
void EndTableCanvas()
Definition canvas.cc:423
std::shared_ptr< CanvasPerformanceIntegration > performance_integration_
Definition canvas.h:196
void Init(const CanvasConfig &config)
Initialize canvas with configuration (post-construction) Preferred over constructor parameters for ne...
Definition canvas.cc:121
void SetGlobalScale(float scale)
Definition canvas.h:378
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1484
void DrawContextMenuItem(const gui::CanvasMenuItem &item)
Definition canvas.cc:830
void DrawText(const std::string &text, int x, int y)
Definition canvas.cc:1432
ImVec2 drawn_tile_pos_
Definition canvas.h:535
void SyncLegacyGeometryFromState()
Definition canvas.cc:101
CanvasExtensions & EnsureExtensions()
Definition canvas.cc:146
std::vector< ImVec2 > selected_tiles_
Definition canvas.h:549
bool ApplyROMPalette(int group_index, int palette_index)
Definition canvas.cc:376
void ShowColorAnalysis()
Definition canvas.cc:366
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)
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding)
void DrawCanvasOutline(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, uint32_t color)
void DrawCanvasOutlineWithColor(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int x, int y, int w, int h, ImVec4 color)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale)
void DrawCanvasText(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, const std::string &text, int x, int y, float global_scale)
void DrawCustomHighlight(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 scrolling, int highlight_tile_id, float grid_step)
void DrawCanvasGridLines(ImDrawList *draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1, ImVec2 scrolling, float grid_step, float global_scale)
ImVec2 AlignPosToGrid(ImVec2 pos, float scale)
Definition canvas.cc:170
Graphical User Interface (GUI) components for the application.
constexpr uint32_t kWhiteColor
Definition canvas.cc:164
constexpr uint32_t kRectangleColor
Definition canvas.cc:163
CanvasUsage
Canvas usage patterns and tracking.
void BeginPadding(int i)
Definition style.cc:277
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:281
void RenderCanvasBackground(ImDrawList *draw_list, const CanvasGeometry &geometry)
Render canvas background and border.
CanvasGeometry CalculateCanvasGeometry(const CanvasConfig &config, ImVec2 requested_size, ImVec2 cursor_screen_pos, ImVec2 content_region_avail)
Calculate canvas geometry from configuration and ImGui context.
void RenderMenuItem(const CanvasMenuItem &item, std::function< void(const std::string &, std::function< void()>)> popup_opened_callback)
Render a single menu item.
Definition canvas_menu.cc:6
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
constexpr ImGuiButtonFlags kMouseFlags
Definition canvas.cc:166
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< 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