#include "canvas.h" #include #include #include "app/core/window.h" #include "app/gfx/atlas_renderer.h" #include "app/gfx/bitmap.h" #include "app/gfx/performance_profiler.h" #include "app/gui/color.h" #include "app/gui/style.h" #include "app/gui/canvas_utils.h" #include "util/log.h" #include "imgui/imgui.h" #include "imgui_memory_editor.h" namespace yaze::gui { using core::Renderer; using ImGui::BeginMenu; using ImGui::EndMenu; using ImGui::GetContentRegionAvail; using ImGui::GetCursorScreenPos; using ImGui::GetIO; using ImGui::GetMouseDragDelta; using ImGui::GetWindowDrawList; using ImGui::IsItemActive; using ImGui::IsItemHovered; using ImGui::IsMouseClicked; using ImGui::IsMouseDragging; using ImGui::MenuItem; using ImGui::OpenPopupOnItemClick; using ImGui::Selectable; using ImGui::Text; constexpr uint32_t kBlackColor = IM_COL32(0, 0, 0, 255); constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255); constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255); constexpr uint32_t kOutlineRect = IM_COL32(255, 255, 255, 200); constexpr ImGuiButtonFlags kMouseFlags = ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight; namespace { ImVec2 AlignPosToGrid(ImVec2 pos, float scale) { return ImVec2(std::floor(pos.x / scale) * scale, std::floor(pos.y / scale) * scale); } } // namespace // Canvas class implementation begins here void Canvas::InitializeDefaults() { // Initialize configuration with sensible defaults config_.enable_grid = true; config_.enable_hex_labels = false; config_.enable_custom_labels = false; config_.enable_context_menu = true; config_.is_draggable = false; config_.grid_step = 32.0f; config_.global_scale = 1.0f; config_.canvas_size = ImVec2(0, 0); config_.custom_canvas_size = false; // Initialize selection state selection_.Clear(); // Initialize palette editor palette_editor_ = std::make_unique(); // Initialize legacy compatibility variables to match config enable_grid_ = config_.enable_grid; enable_hex_tile_labels_ = config_.enable_hex_labels; enable_custom_labels_ = config_.enable_custom_labels; enable_context_menu_ = config_.enable_context_menu; draggable_ = config_.is_draggable; custom_step_ = config_.grid_step; global_scale_ = config_.global_scale; custom_canvas_size_ = config_.custom_canvas_size; select_rect_active_ = selection_.select_rect_active; selected_tile_pos_ = selection_.selected_tile_pos; } void Canvas::Cleanup() { palette_editor_.reset(); selection_.Clear(); } void Canvas::InitializePaletteEditor(Rom* rom) { rom_ = rom; if (palette_editor_) { palette_editor_->Initialize(rom); } } void Canvas::ShowPaletteEditor() { if (palette_editor_ && bitmap_) { auto mutable_palette = bitmap_->mutable_palette(); palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor"); } } void Canvas::ShowColorAnalysis() { if (palette_editor_ && bitmap_) { palette_editor_->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis"); } } bool Canvas::ApplyROMPalette(int group_index, int palette_index) { if (palette_editor_ && bitmap_) { return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index); } return false; } // Size reporting methods for table integration ImVec2 Canvas::GetMinimumSize() const { return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale); } ImVec2 Canvas::GetPreferredSize() const { return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale); } void Canvas::ReserveTableSpace(const std::string& label) { ImVec2 size = config_.auto_resize ? GetPreferredSize() : config_.canvas_size; CanvasUtils::ReserveCanvasSpace(size, label); } bool Canvas::BeginTableCanvas(const std::string& label) { if (config_.auto_resize) { ImVec2 preferred_size = GetPreferredSize(); CanvasUtils::SetNextCanvasSize(preferred_size, true); } // Begin child window that properly reports size to tables std::string child_id = canvas_id_ + "_TableChild"; ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size; bool result = ImGui::BeginChild(child_id.c_str(), child_size, true, // Always show border for table integration ImGuiWindowFlags_AlwaysVerticalScrollbar); if (!label.empty()) { ImGui::Text("%s", label.c_str()); } return result; } void Canvas::EndTableCanvas() { ImGui::EndChild(); } // Improved interaction detection methods bool Canvas::HasValidSelection() const { return !points_.empty() && points_.size() >= 2; } bool Canvas::WasClicked(ImGuiMouseButton button) const { return ImGui::IsItemClicked(button) && HasValidSelection(); } bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const { return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection(); } ImVec2 Canvas::GetLastClickPosition() const { if (HasValidSelection()) { return points_[0]; // Return the first point of the selection } return ImVec2(-1, -1); // Invalid position } void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color, const std::function &event, int tile_size, float scale) { config_.global_scale = scale; global_scale_ = scale; // Legacy compatibility DrawBackground(); DrawContextMenu(); DrawBitmap(bitmap, 2, scale); if (DrawSolidTilePainter(color, tile_size)) { event(); } DrawGrid(); DrawOverlay(); } void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) { config_.enable_custom_labels = true; enable_custom_labels_ = true; // Legacy compatibility DrawBackground(bg_size); DrawInfoGrid(grid_size, 8, label_id); DrawOverlay(); } void Canvas::DrawBackground(ImVec2 canvas_size) { draw_list_ = GetWindowDrawList(); canvas_p0_ = GetCursorScreenPos(); // Calculate canvas size using utility function ImVec2 content_region = GetContentRegionAvail(); canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size); if (canvas_size.x != 0) { canvas_sz_ = canvas_size; config_.canvas_size = canvas_size; } // Calculate scaled canvas bounds ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale); // CRITICAL FIX: Ensure minimum size to prevent ImGui assertions if (scaled_size.x <= 0.0f) scaled_size.x = 1.0f; if (scaled_size.y <= 0.0f) scaled_size.y = 1.0f; canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y); // Draw border and background color draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor); draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor); ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags); if (config_.is_draggable && IsItemHovered()) { const ImGuiIO &io = GetIO(); const bool is_active = IsItemActive(); // Held const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); // Pan (we use a zero mouse threshold when there's no context menu) if (const float mouse_threshold_for_pan = enable_context_menu_ ? -1.0f : 0.0f; is_active && IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { scrolling_.x += io.MouseDelta.x; scrolling_.y += io.MouseDelta.y; } } } void Canvas::DrawContextMenu() { const ImGuiIO &io = GetIO(); const ImVec2 scaled_sz(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); // Lock scrolled origin const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); static bool show_bitmap_data = false; if (show_bitmap_data && bitmap_ != nullptr) { static MemoryEditor mem_edit; mem_edit.DrawWindow("Bitmap Data", (void *)bitmap_->data(), bitmap_->size(), 0); } // Context menu (under default mouse threshold) if (ImVec2 drag_delta = GetMouseDragDelta(ImGuiMouseButton_Right); enable_context_menu_ && drag_delta.x == 0.0f && drag_delta.y == 0.0f) OpenPopupOnItemClick(context_id_.c_str(), ImGuiPopupFlags_MouseButtonRight); // Contents of the Context Menu if (ImGui::BeginPopup(context_id_.c_str())) { // Draw custom context menu items first for (const auto& item : context_menu_items_) { DrawContextMenuItem(item); } // Add separator if there are custom items if (!context_menu_items_.empty()) { ImGui::Separator(); } // Default canvas menu items if (MenuItem("Reset View", nullptr, false)) { ResetView(); } if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) { SetZoomToFit(*bitmap_); } if (MenuItem("Advanced Properties", nullptr, false)) { ImGui::OpenPopup("Advanced Canvas Properties"); } ImGui::Separator(); MenuItem("Show Grid", nullptr, &enable_grid_); Selectable("Show Position Labels", &enable_hex_tile_labels_); if (MenuItem("Edit Palette", nullptr, false) && bitmap_) { ShowPaletteEditor(); } if (MenuItem("Color Analysis", nullptr, false) && bitmap_) { ShowColorAnalysis(); } if (MenuItem("Scaling Controls", nullptr, false)) { ImGui::OpenPopup("Scaling Controls"); } if (BeginMenu("Canvas Properties")) { Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y); Text("Global Scale: %.1f", global_scale_); Text("Mouse Position: %.0f x %.0f", mouse_pos.x, mouse_pos.y); EndMenu(); } if (bitmap_ != nullptr) { if (BeginMenu("Bitmap Properties")) { Text("Size: %.0f x %.0f", scaled_sz.x, scaled_sz.y); Text("Pitch: %d", bitmap_->surface()->pitch); Text("BitsPerPixel: %d", bitmap_->surface()->format->BitsPerPixel); Text("BytesPerPixel: %d", bitmap_->surface()->format->BytesPerPixel); MenuItem("Data", nullptr, &show_bitmap_data); if (BeginMenu("Format")) { if (MenuItem("Indexed")) { bitmap_->Reformat(gfx::BitmapFormat::kIndexed); Renderer::Get().UpdateBitmap(bitmap_); } if (MenuItem("4BPP")) { bitmap_->Reformat(gfx::BitmapFormat::k4bpp); Renderer::Get().UpdateBitmap(bitmap_); } if (MenuItem("8BPP")) { bitmap_->Reformat(gfx::BitmapFormat::k8bpp); Renderer::Get().UpdateBitmap(bitmap_); } EndMenu(); } if (BeginMenu("ROM Palette Selection") && rom_) { Text("Select ROM Palette Group:"); // Enhanced ROM palette group selection if (palette_editor_) { // Use our enhanced palette editor's ROM selection if (MenuItem("Open Enhanced Palette Manager")) { palette_editor_->ShowROMPaletteManager(); } ImGui::Separator(); // Quick palette group selection const char* palette_groups[] = { "Overworld Main", "Overworld Aux", "Overworld Animated", "Dungeon Main", "Global Sprites", "Armor", "Swords" }; if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_, palette_groups, IM_ARRAYSIZE(palette_groups))) { // Group selection changed } ImGui::SetNextItemWidth(100.f); if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) { // Palette index changed } // Apply button with enhanced functionality if (ImGui::Button("Apply to Canvas") && bitmap_) { if (palette_editor_->ApplyROMPalette(bitmap_, edit_palette_group_name_index_, edit_palette_index_)) { util::logf("Applied ROM palette group %d, index %d via context menu", edit_palette_group_name_index_, edit_palette_index_); } } // Direct palette editing with SelectablePalettePipeline if (ImGui::TreeNode("Interactive Palette Editor")) { if (rom_ && bitmap_) { ImGui::Text("Interactive ROM Palette Editing"); ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]); // Get the enhanced palette editor's ROM palette if available if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) { auto editable_palette = const_cast(*rom_palette); if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) { // Use the existing SelectablePalettePipeline for interactive editing gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_, editable_palette); if (refresh_graphics_) { bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_); Renderer::Get().UpdateBitmap(bitmap_); refresh_graphics_ = false; util::logf("Applied interactive palette changes to canvas"); } } ImGui::EndChild(); } else { ImGui::Text("Load ROM palettes first using Enhanced Palette Manager"); } } ImGui::TreePop(); } } EndMenu(); } if (BeginMenu("View Palette")) { (void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8); EndMenu(); } EndMenu(); } } ImGui::Separator(); if (BeginMenu("Grid Tile Size")) { if (MenuItem("8x8", nullptr, custom_step_ == 8.0f)) { custom_step_ = 8.0f; } if (MenuItem("16x16", nullptr, custom_step_ == 16.0f)) { custom_step_ = 16.0f; } if (MenuItem("32x32", nullptr, custom_step_ == 32.0f)) { custom_step_ = 32.0f; } if (MenuItem("64x64", nullptr, custom_step_ == 64.0f)) { custom_step_ = 64.0f; } EndMenu(); } ImGui::EndPopup(); } // Draw enhanced property dialogs ShowAdvancedCanvasProperties(); ShowScalingControls(); } void Canvas::DrawContextMenuItem(const ContextMenuItem& item) { if (!item.enabled_condition()) { ImGui::BeginDisabled(); } if (item.subitems.empty()) { // Simple menu item if (ImGui::MenuItem(item.label.c_str(), item.shortcut.empty() ? nullptr : item.shortcut.c_str())) { item.callback(); } } else { // Menu with subitems if (ImGui::BeginMenu(item.label.c_str())) { for (const auto& subitem : item.subitems) { DrawContextMenuItem(subitem); } ImGui::EndMenu(); } } if (!item.enabled_condition()) { ImGui::EndDisabled(); } } void Canvas::AddContextMenuItem(const ContextMenuItem& item) { context_menu_items_.push_back(item); } void Canvas::ClearContextMenuItems() { context_menu_items_.clear(); } // Old ShowPaletteEditor method removed - now handled by EnhancedPaletteEditor void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) { if (!bitmap.is_active()) return; ImVec2 available = ImGui::GetContentRegionAvail(); float scale_x = available.x / bitmap.width(); float scale_y = available.y / bitmap.height(); config_.global_scale = std::min(scale_x, scale_y); // Ensure minimum readable scale if (config_.global_scale < 0.25f) config_.global_scale = 0.25f; global_scale_ = config_.global_scale; // Legacy compatibility // Center the view scrolling_ = ImVec2(0, 0); } void Canvas::ResetView() { config_.global_scale = 1.0f; global_scale_ = 1.0f; // Legacy compatibility scrolling_ = ImVec2(0, 0); } bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); const auto scaled_size = size * scale; // Erase the hover when the mouse is not in the canvas window. if (!is_hovered) { points_.clear(); return false; } // Reset the previous tile hover if (!points_.empty()) { points_.clear(); } // Calculate the coordinates of the mouse ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size); mouse_pos_in_canvas_ = paint_pos; auto paint_pos_end = ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size); points_.push_back(paint_pos); points_.push_back(paint_pos_end); if (bitmap.is_active()) { draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(), ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size)); } if (IsMouseClicked(ImGuiMouseButton_Left) && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { // Draw the currently selected tile on the overworld here // Save the coordinates of the selected tile. drawn_tile_pos_ = paint_pos; return true; } return false; } bool Canvas::DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); // Safety check: ensure tilemap is properly initialized if (!tilemap.atlas.is_active() || tilemap.tile_size.x <= 0) { return false; } const auto scaled_size = tilemap.tile_size.x * global_scale_; if (!is_hovered) { points_.clear(); return false; } if (!points_.empty()) { points_.clear(); } ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_size); mouse_pos_in_canvas_ = paint_pos; points_.push_back(paint_pos); points_.push_back( ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size)); // CRITICAL FIX: Disable tile cache system to prevent crashes // Just draw a simple preview tile using the atlas directly if (tilemap.atlas.is_active() && tilemap.atlas.texture()) { // Draw the tile directly from the atlas without caching int tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; if (tiles_per_row > 0) { int tile_x = (current_tile % tiles_per_row) * tilemap.tile_size.x; int tile_y = (current_tile / tiles_per_row) * tilemap.tile_size.y; // Simple bounds check if (tile_x >= 0 && tile_x < tilemap.atlas.width() && tile_y >= 0 && tile_y < tilemap.atlas.height()) { // Draw directly from atlas texture ImVec2 uv0 = ImVec2(static_cast(tile_x) / tilemap.atlas.width(), static_cast(tile_y) / tilemap.atlas.height()); ImVec2 uv1 = ImVec2(static_cast(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(), static_cast(tile_y + tilemap.tile_size.y) / tilemap.atlas.height()); draw_list_->AddImage( (ImTextureID)(intptr_t)tilemap.atlas.texture(), ImVec2(origin.x + paint_pos.x, origin.y + paint_pos.y), ImVec2(origin.x + paint_pos.x + scaled_size, origin.y + paint_pos.y + scaled_size), uv0, uv1); } } } if (IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { drawn_tile_pos_ = paint_pos; return true; } return false; } bool Canvas::DrawSolidTilePainter(const ImVec4 &color, int tile_size) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; // Lock scrolled origin const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); auto scaled_tile_size = tile_size * global_scale_; static bool is_dragging = false; static ImVec2 start_drag_pos; // Erase the hover when the mouse is not in the canvas window. if (!is_hovered) { points_.clear(); return false; } // Reset the previous tile hover if (!points_.empty()) { points_.clear(); } // Calculate the coordinates of the mouse ImVec2 paint_pos = AlignPosToGrid(mouse_pos, scaled_tile_size); mouse_pos_in_canvas_ = paint_pos; // Clamp the size to a grid paint_pos.x = std::clamp(paint_pos.x, 0.0f, canvas_sz_.x * global_scale_); paint_pos.y = std::clamp(paint_pos.y, 0.0f, canvas_sz_.y * global_scale_); points_.push_back(paint_pos); points_.push_back( ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size)); draw_list_->AddRectFilled( ImVec2(origin.x + paint_pos.x + 1, origin.y + paint_pos.y + 1), ImVec2(origin.x + paint_pos.x + scaled_tile_size, origin.y + paint_pos.y + scaled_tile_size), IM_COL32(color.x * 255, color.y * 255, color.z * 255, 255)); if (IsMouseClicked(ImGuiMouseButton_Left)) { is_dragging = true; start_drag_pos = paint_pos; } if (is_dragging && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { is_dragging = false; drawn_tile_pos_ = start_drag_pos; return true; } return false; } void Canvas::DrawTileOnBitmap(int tile_size, gfx::Bitmap *bitmap, ImVec4 color) { const ImVec2 position = drawn_tile_pos_; int tile_index_x = static_cast(position.x / global_scale_) / tile_size; int tile_index_y = static_cast(position.y / global_scale_) / tile_size; ImVec2 start_position(tile_index_x * tile_size, tile_index_y * tile_size); // Update the bitmap's pixel data based on the start_position and color for (int y = 0; y < tile_size; ++y) { for (int x = 0; x < tile_size; ++x) { // Calculate the actual pixel index in the bitmap int pixel_index = (start_position.y + y) * bitmap->width() + (start_position.x + x); // Write the color to the pixel bitmap->WriteColor(pixel_index, color); } } } bool Canvas::DrawTileSelector(int size, int size_y) { const ImGuiIO &io = GetIO(); const bool is_hovered = IsItemHovered(); is_hovered_ = is_hovered; const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); if (size_y == 0) { size_y = size; } if (is_hovered && IsMouseClicked(ImGuiMouseButton_Left)) { if (!points_.empty()) { points_.clear(); } ImVec2 painter_pos = AlignPosToGrid(mouse_pos, size); points_.push_back(painter_pos); points_.push_back(ImVec2(painter_pos.x + size, painter_pos.y + size_y)); mouse_pos_in_canvas_ = painter_pos; } if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { return true; } return false; } void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { gfx::ScopedTimer timer("canvas_select_rect"); const ImGuiIO &io = GetIO(); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); static ImVec2 drag_start_pos; const float scaled_size = tile_size * scale; static bool dragging = false; constexpr int small_map_size = 0x200; // Only handle mouse events if the canvas is hovered const bool is_hovered = IsItemHovered(); if (!is_hovered) { return; } // Calculate superX and superY accounting for world offset int superY, superX; if (current_map < 0x40) { // Light World superY = current_map / 8; superX = current_map % 8; } else if (current_map < 0x80) { // Dark World superY = (current_map - 0x40) / 8; superX = (current_map - 0x40) % 8; } else { // Special World superY = (current_map - 0x80) / 8; superX = (current_map - 0x80) % 8; } // Handle right click for single tile selection if (IsMouseClicked(ImGuiMouseButton_Right)) { ImVec2 painter_pos = AlignPosToGrid(mouse_pos, scaled_size); int painter_x = painter_pos.x; int painter_y = painter_pos.y; auto tile16_x = (painter_x % small_map_size) / (small_map_size / 0x20); auto tile16_y = (painter_y % small_map_size) / (small_map_size / 0x20); int index_x = superX * 0x20 + tile16_x; int index_y = superY * 0x20 + tile16_y; selected_tile_pos_ = ImVec2(index_x, index_y); selected_points_.clear(); select_rect_active_ = false; // Start drag position for rectangle selection drag_start_pos = AlignPosToGrid(mouse_pos, scaled_size); } // Calculate the rectangle's top-left and bottom-right corners ImVec2 drag_end_pos = AlignPosToGrid(mouse_pos, scaled_size); if (ImGui::IsMouseDragging(ImGuiMouseButton_Right)) { auto start = ImVec2(canvas_p0_.x + drag_start_pos.x, canvas_p0_.y + drag_start_pos.y); auto end = ImVec2(canvas_p0_.x + drag_end_pos.x + tile_size, canvas_p0_.y + drag_end_pos.y + tile_size); draw_list_->AddRect(start, end, kWhiteColor); dragging = true; } if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) { // Release dragging mode dragging = false; // Calculate the bounds of the rectangle in terms of 16x16 tile indices constexpr int tile16_size = 16; int start_x = std::floor(drag_start_pos.x / scaled_size) * tile16_size; int start_y = std::floor(drag_start_pos.y / scaled_size) * tile16_size; int end_x = std::floor(drag_end_pos.x / scaled_size) * tile16_size; int end_y = std::floor(drag_end_pos.y / scaled_size) * tile16_size; // Swap the start and end positions if they are in the wrong order if (start_x > end_x) std::swap(start_x, end_x); if (start_y > end_y) std::swap(start_y, end_y); selected_tiles_.clear(); selected_tiles_.reserve(((end_x - start_x) / tile16_size + 1) * ((end_y - start_y) / tile16_size + 1)); // Number of tiles per local map (since each tile is 16x16) constexpr int tiles_per_local_map = small_map_size / 16; // Loop through the tiles in the rectangle and store their positions for (int y = start_y; y <= end_y; y += tile16_size) { for (int x = start_x; x <= end_x; x += tile16_size) { // Determine which local map (512x512) the tile is in int local_map_x = x / small_map_size; int local_map_y = y / small_map_size; // Calculate the tile's position within its local map int tile16_x = (x % small_map_size) / tile16_size; int tile16_y = (y % small_map_size) / tile16_size; // Calculate the index within the overall map structure int index_x = local_map_x * tiles_per_local_map + tile16_x; int index_y = local_map_y * tiles_per_local_map + tile16_y; selected_tiles_.emplace_back(index_x, index_y); } } // Clear and add the calculated rectangle points selected_points_.clear(); selected_points_.push_back(drag_start_pos); selected_points_.push_back(drag_end_pos); select_rect_active_ = true; } } void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x, canvas_p0_.y), ImVec2(canvas_p0_.x + (bitmap.width() * scale), canvas_p0_.y + (bitmap.height() * scale))); draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor); } void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale, int alpha) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); // Calculate the actual rendered size including scale and offsets ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale); ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y); draw_list_->AddImage( (ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x + x_offset + scrolling_.x, canvas_p0_.y + y_offset + scrolling_.y), ImVec2( canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x, canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y), ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha)); // Note: Content size for child windows should be set before BeginChild, not here } void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) { if (!bitmap.is_active()) { return; } bitmap_ = &bitmap; // Update content size for table integration config_.content_size = ImVec2(bitmap.width(), bitmap.height()); draw_list_->AddImage( (ImTextureID)(intptr_t)bitmap.texture(), ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y), ImVec2(canvas_p0_.x + dest_pos.x + dest_size.x, canvas_p0_.y + dest_pos.y + dest_size.y), ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()), ImVec2((src_pos.x + src_size.x) / bitmap.width(), (src_pos.y + src_size.y) / bitmap.height())); } // TODO: Add parameters for sizing and positioning void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) { for (const auto &[key, value] : gfx_bin) { int offset = 0x40 * (key + 1); int top_left_y = canvas_p0_.y + 2; if (key >= 1) { top_left_y = canvas_p0_.y + 0x40 * key; } draw_list_->AddImage((ImTextureID)(intptr_t)value.texture(), ImVec2(canvas_p0_.x + 2, top_left_y), ImVec2(canvas_p0_.x + 0x100, canvas_p0_.y + offset)); } } void Canvas::DrawOutline(int x, int y, int w, int h) { CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200)); } void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) { CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color); } void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) { CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color); } void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, int tile_size, float scale) { if (selected_points_.size() != 2) { // points_ should contain exactly two points return; } if (group.empty()) { // group should not be empty return; } // OPTIMIZATION: Use optimized rendering for large groups to improve performance bool use_optimized_rendering = group.size() > 16; // Optimize for large selections // Pre-calculate common values to avoid repeated computation const float tile_scale = tile_size * scale; const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x; // Top-left and bottom-right corners of the rectangle ImVec2 rect_top_left = selected_points_[0]; ImVec2 rect_bottom_right = selected_points_[1]; // Calculate the start and end tiles in the grid int start_tile_x = static_cast(std::floor(rect_top_left.x / (tile_size * scale))); int start_tile_y = static_cast(std::floor(rect_top_left.y / (tile_size * scale))); int end_tile_x = static_cast(std::floor(rect_bottom_right.x / (tile_size * scale))); int end_tile_y = static_cast(std::floor(rect_bottom_right.y / (tile_size * scale))); if (start_tile_x > end_tile_x) std::swap(start_tile_x, end_tile_x); if (start_tile_y > end_tile_y) std::swap(start_tile_y, end_tile_y); // Calculate the size of the rectangle in 16x16 grid form int rect_width = (end_tile_x - start_tile_x) * tile_size; int rect_height = (end_tile_y - start_tile_y) * tile_size; int tiles_per_row = rect_width / tile_size; int tiles_per_col = rect_height / tile_size; int i = 0; for (int y = 0; y < tiles_per_col + 1; ++y) { for (int x = 0; x < tiles_per_row + 1; ++x) { // Check bounds to prevent access violations if (i >= static_cast(group.size())) { break; } int tile_id = group[i]; // Check if tile_id is within the range of tile16_individual_ auto tilemap_size = tilemap.atlas.width() * tilemap.atlas.height() / tilemap.map_size.x; if (tile_id >= 0 && tile_id < tilemap_size) { // Calculate the position of the tile within the rectangle int tile_pos_x = (x + start_tile_x) * tile_size * scale; int tile_pos_y = (y + start_tile_y) * tile_size * scale; // OPTIMIZATION: Use pre-calculated values for better performance with large selections if (tilemap.atlas.is_active() && tilemap.atlas.texture() && atlas_tiles_per_row > 0) { int atlas_tile_x = (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x; int atlas_tile_y = (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y; // Simple bounds check if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() && atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) { // Calculate UV coordinates once for efficiency const float atlas_width = static_cast(tilemap.atlas.width()); const float atlas_height = static_cast(tilemap.atlas.height()); ImVec2 uv0 = ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height); ImVec2 uv1 = ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width, (atlas_tile_y + tilemap.tile_size.y) / atlas_height); // Calculate screen positions float screen_x = canvas_p0_.x + scrolling_.x + tile_pos_x; float screen_y = canvas_p0_.y + scrolling_.y + tile_pos_y; float screen_w = tilemap.tile_size.x * scale; float screen_h = tilemap.tile_size.y * scale; // Use higher alpha for large selections to make them more visible uint32_t alpha_color = use_optimized_rendering ? IM_COL32(255, 255, 255, 200) : IM_COL32(255, 255, 255, 150); // Draw from atlas texture with optimized parameters draw_list_->AddImage( (ImTextureID)(intptr_t)tilemap.atlas.texture(), ImVec2(screen_x, screen_y), ImVec2(screen_x + screen_w, screen_y + screen_h), uv0, uv1, alpha_color); } } } i++; } // Break outer loop if we've run out of tiles if (i >= static_cast(group.size())) { break; } } // Performance optimization completed - tiles are now rendered with pre-calculated values const ImGuiIO &io = GetIO(); const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); auto new_start_pos = AlignPosToGrid(mouse_pos, tile_size * scale); selected_points_.clear(); selected_points_.push_back(new_start_pos); selected_points_.push_back( ImVec2(new_start_pos.x + rect_width, new_start_pos.y + rect_height)); select_rect_active_ = true; } void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) { CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale); } void Canvas::DrawText(std::string text, int x, int y) { CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale); } void Canvas::DrawGridLines(float grid_step) { CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale); } void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) { // Draw grid + all lines in the canvas draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); if (enable_grid_) { if (custom_step_ != 0.f) grid_step = custom_step_; grid_step *= global_scale_; // Apply global scale to grid step DrawGridLines(grid_step); DrawCustomHighlight(grid_step); if (!enable_custom_labels_) { return; } // Draw the contents of labels on the grid for (float x = fmodf(scrolling_.x, grid_step); x < canvas_sz_.x * global_scale_; x += grid_step) { for (float y = fmodf(scrolling_.y, grid_step); y < canvas_sz_.y * global_scale_; y += grid_step) { int tile_x = (x - scrolling_.x) / grid_step; int tile_y = (y - scrolling_.y) / grid_step; int tile_id = tile_x + (tile_y * tile_id_offset); if (tile_id >= labels_[label_id].size()) { break; } std::string label = labels_[label_id][tile_id]; draw_list_->AddText( ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset, canvas_p0_.y + y + (grid_step / 2) - tile_id_offset), kWhiteColor, label.data()); } } } } void Canvas::DrawCustomHighlight(float grid_step) { CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step); } void Canvas::DrawGrid(float grid_step, int tile_id_offset) { if (config_.grid_step != 0.f) grid_step = config_.grid_step; // Create render context for utilities CanvasUtils::CanvasRenderContext ctx = { .draw_list = draw_list_, .canvas_p0 = canvas_p0_, .canvas_p1 = canvas_p1_, .scrolling = scrolling_, .global_scale = config_.global_scale, .enable_grid = config_.enable_grid, .enable_hex_labels = config_.enable_hex_labels, .grid_step = grid_step }; // Use high-level utility function CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id); // Draw custom labels if enabled if (config_.enable_custom_labels) { draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true); CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset); draw_list_->PopClipRect(); } } void Canvas::DrawOverlay() { // Create render context for utilities CanvasUtils::CanvasRenderContext ctx = { .draw_list = draw_list_, .canvas_p0 = canvas_p0_, .canvas_p1 = canvas_p1_, .scrolling = scrolling_, .global_scale = config_.global_scale, .enable_grid = config_.enable_grid, .enable_hex_labels = config_.enable_hex_labels, .grid_step = config_.grid_step }; // Use high-level utility function CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_); } void Canvas::DrawLayeredElements() { // Based on ImGui demo, should be adapted to use for OAM ImDrawList *draw_list = ImGui::GetWindowDrawList(); { Text("Blue shape is drawn first: appears in back"); Text("Red shape is drawn after: appears in front"); ImVec2 p0 = ImGui::GetCursorScreenPos(); draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), IM_COL32(0, 0, 255, 255)); // Blue draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), ImVec2(p0.x + 75, p0.y + 75), IM_COL32(255, 0, 0, 255)); // Red ImGui::Dummy(ImVec2(75, 75)); } ImGui::Separator(); { Text("Blue shape is drawn first, into channel 1: appears in front"); Text("Red shape is drawn after, into channel 0: appears in back"); ImVec2 p1 = ImGui::GetCursorScreenPos(); // Create 2 channels and draw a Blue shape THEN a Red shape. // You can create any number of channels. Tables API use 1 channel per // column in order to better batch draw calls. draw_list->ChannelsSplit(2); draw_list->ChannelsSetCurrent(1); draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), IM_COL32(0, 0, 255, 255)); // Blue draw_list->ChannelsSetCurrent(0); draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), ImVec2(p1.x + 75, p1.y + 75), IM_COL32(255, 0, 0, 255)); // Red // Flatten/reorder channels. Red shape is in channel 0 and it appears // below the Blue shape in channel 1. This works by copying draw indices // only (vertices are not copied). draw_list->ChannelsMerge(); ImGui::Dummy(ImVec2(75, 75)); Text("After reordering, contents of channel 0 appears below channel 1."); } } void BeginCanvas(Canvas &canvas, ImVec2 child_size) { gui::BeginPadding(1); // Use improved canvas sizing for table integration ImVec2 effective_size = child_size; if (child_size.x == 0 && child_size.y == 0) { // Auto-size based on canvas configuration if (canvas.IsAutoResize()) { effective_size = canvas.GetPreferredSize(); } else { effective_size = canvas.GetCurrentSize(); } } ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar); canvas.DrawBackground(); gui::EndPadding(); canvas.DrawContextMenu(); } void EndCanvas(Canvas &canvas) { canvas.DrawGrid(); canvas.DrawOverlay(); ImGui::EndChild(); } void GraphicsBinCanvasPipeline(int width, int height, int tile_size, int num_sheets_to_load, int canvas_id, bool is_loaded, gfx::BitmapTable &graphics_bin) { gui::Canvas canvas; if (ImGuiID child_id = ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { canvas.DrawBackground(ImVec2(width + 1, num_sheets_to_load * height + 1)); canvas.DrawContextMenu(); if (is_loaded) { for (const auto &[key, value] : graphics_bin) { int offset = height * (key + 1); int top_left_y = canvas.zero_point().y + 2; if (key >= 1) { top_left_y = canvas.zero_point().y + height * key; } canvas.draw_list()->AddImage( (ImTextureID)(intptr_t)value.texture(), ImVec2(canvas.zero_point().x + 2, top_left_y), ImVec2(canvas.zero_point().x + 0x100, canvas.zero_point().y + offset)); } } canvas.DrawTileSelector(tile_size); canvas.DrawGrid(tile_size); canvas.DrawOverlay(); } ImGui::EndChild(); } void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded, bool scrollbar, int canvas_id) { auto draw_canvas = [&](gui::Canvas &canvas, gfx::Bitmap &bitmap, int width, int height, int tile_size, bool is_loaded) { canvas.DrawBackground(ImVec2(width + 1, height + 1)); canvas.DrawContextMenu(); canvas.DrawBitmap(bitmap, 2, is_loaded); canvas.DrawTileSelector(tile_size); canvas.DrawGrid(tile_size); canvas.DrawOverlay(); }; if (scrollbar) { if (ImGuiID child_id = ImGui::GetID((ImTextureID)(intptr_t)(intptr_t)canvas_id); ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); } ImGui::EndChild(); } else { draw_canvas(canvas, bitmap, width, height, tile_size, is_loaded); } } void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, const std::string& label, bool auto_resize) { // Configure canvas for table integration canvas.SetAutoResize(auto_resize); if (auto_resize && bitmap.is_active()) { // Auto-calculate size based on bitmap content ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height()); ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale()); canvas.SetCanvasSize(preferred_size); } // Begin table-aware canvas if (canvas.BeginTableCanvas(label)) { // Draw the canvas content canvas.DrawBackground(); canvas.DrawContextMenu(); if (bitmap.is_active()) { canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale()); } canvas.DrawGrid(); canvas.DrawOverlay(); } canvas.EndTableCanvas(); } void Canvas::ShowAdvancedCanvasProperties() { if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Advanced Canvas Configuration"); ImGui::Separator(); // Canvas properties (read-only info) ImGui::Text("Canvas Properties"); ImGui::Text("ID: %s", canvas_id_.c_str()); ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y); ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y); ImGui::Text("Global Scale: %.3f", config_.global_scale); ImGui::Text("Grid Step: %.1f", config_.grid_step); if (config_.content_size.x > 0 && config_.content_size.y > 0) { ImVec2 min_size = GetMinimumSize(); ImVec2 preferred_size = GetPreferredSize(); ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y); ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y); } // Editable properties using new config system ImGui::Separator(); ImGui::Text("View Settings"); if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) { enable_grid_ = config_.enable_grid; // Legacy sync } if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) { enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync } if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) { enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync } if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) { enable_context_menu_ = config_.enable_context_menu; // Legacy sync } if (ImGui::Checkbox("Draggable", &config_.is_draggable)) { draggable_ = config_.is_draggable; // Legacy sync } if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) { // Auto resize setting changed } // Grid controls ImGui::Separator(); ImGui::Text("Grid Configuration"); if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) { custom_step_ = config_.grid_step; // Legacy sync } // Scale controls ImGui::Separator(); ImGui::Text("Scale Configuration"); if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) { global_scale_ = config_.global_scale; // Legacy sync } // Scrolling controls ImGui::Separator(); ImGui::Text("Scrolling Configuration"); ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y); if (ImGui::Button("Reset Scroll")) { scrolling_ = ImVec2(0, 0); } ImGui::SameLine(); if (ImGui::Button("Center View")) { if (bitmap_) { scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f, -(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f); } } if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } // Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor void Canvas::ShowScalingControls() { if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Canvas Scaling and Display Controls"); ImGui::Separator(); // Global scale with new config system ImGui::Text("Global Scale: %.3f", config_.global_scale); if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) { global_scale_ = config_.global_scale; // Legacy sync } // Preset scale buttons ImGui::Text("Preset Scales:"); if (ImGui::Button("0.25x")) { config_.global_scale = 0.25f; global_scale_ = config_.global_scale; } ImGui::SameLine(); if (ImGui::Button("0.5x")) { config_.global_scale = 0.5f; global_scale_ = config_.global_scale; } ImGui::SameLine(); if (ImGui::Button("1x")) { config_.global_scale = 1.0f; global_scale_ = config_.global_scale; } ImGui::SameLine(); if (ImGui::Button("2x")) { config_.global_scale = 2.0f; global_scale_ = config_.global_scale; } ImGui::SameLine(); if (ImGui::Button("4x")) { config_.global_scale = 4.0f; global_scale_ = config_.global_scale; } ImGui::SameLine(); if (ImGui::Button("8x")) { config_.global_scale = 8.0f; global_scale_ = config_.global_scale; } // Grid configuration ImGui::Separator(); ImGui::Text("Grid Configuration"); ImGui::Text("Grid Step: %.1f", config_.grid_step); if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) { custom_step_ = config_.grid_step; // Legacy sync } // Grid size presets ImGui::Text("Grid Presets:"); if (ImGui::Button("8x8")) { config_.grid_step = 8.0f; custom_step_ = config_.grid_step; } ImGui::SameLine(); if (ImGui::Button("16x16")) { config_.grid_step = 16.0f; custom_step_ = config_.grid_step; } ImGui::SameLine(); if (ImGui::Button("32x32")) { config_.grid_step = 32.0f; custom_step_ = config_.grid_step; } ImGui::SameLine(); if (ImGui::Button("64x64")) { config_.grid_step = 64.0f; custom_step_ = config_.grid_step; } // Canvas size info ImGui::Separator(); ImGui::Text("Canvas Information"); ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y); ImGui::Text("Scaled Size: %.0f x %.0f", config_.canvas_size.x * config_.global_scale, config_.canvas_size.y * config_.global_scale); if (bitmap_) { ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height()); ImGui::Text("Effective Scale: %.3f x %.3f", (config_.canvas_size.x * config_.global_scale) / bitmap_->width(), (config_.canvas_size.y * config_.global_scale) / bitmap_->height()); } if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } // Old ROM palette management methods removed - now handled by EnhancedPaletteEditor } // namespace yaze::gui