Update canvas system with enhanced interaction and performance tracking features
- Introduced a new CanvasInteractionHandler for managing tile interactions, including painting and selection modes. - Added CanvasContextMenu for improved user interaction with context-specific options. - Implemented CanvasPerformanceIntegration to monitor and report performance metrics during canvas operations. - Developed CanvasUsageTracker to track user interactions and usage patterns within the canvas. - Refactored existing canvas utilities and integrated new modal systems for enhanced user experience. - Updated CMake configuration to include new canvas components and ensure proper linking with existing libraries.
This commit is contained in:
@@ -12,7 +12,6 @@
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/core/asar_wrapper.h"
|
||||
#include "app/core/features.h"
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/core/window.h"
|
||||
#include "app/editor/overworld/entity.h"
|
||||
#include "app/editor/overworld/map_properties.h"
|
||||
@@ -36,7 +35,6 @@
|
||||
#include "util/log.h"
|
||||
#include "util/macro.h"
|
||||
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
using core::Renderer;
|
||||
@@ -143,7 +141,7 @@ absl::Status OverworldEditor::Load() {
|
||||
RETURN_IF_ERROR(
|
||||
tile16_editor_.Initialize(tile16_blockset_bmp_, current_gfx_bmp_,
|
||||
*overworld_.mutable_all_tiles_types()));
|
||||
|
||||
|
||||
// CRITICAL FIX: Initialize tile16 editor with the correct overworld palette
|
||||
tile16_editor_.set_palette(palette_);
|
||||
tile16_editor_.set_rom(rom_);
|
||||
@@ -949,8 +947,8 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
current_tile16_);
|
||||
|
||||
// Apply the current selected tile to each position in the rectangle
|
||||
for (int y = start_y; y <= end_y; y += kTile16Size) {
|
||||
for (int x = start_x; x <= end_x; x += kTile16Size) {
|
||||
for (int y = start_y, i = 0; y <= end_y; y += kTile16Size) {
|
||||
for (int x = start_x; x <= end_x; x += kTile16Size, ++i) {
|
||||
// Determine which local map (512x512) the tile is in
|
||||
int local_map_x = x / local_map_size;
|
||||
int local_map_y = y / local_map_size;
|
||||
@@ -963,37 +961,36 @@ void OverworldEditor::CheckForOverworldEdits() {
|
||||
int index_x = local_map_x * tiles_per_local_map + tile16_x;
|
||||
int index_y = local_map_y * tiles_per_local_map + tile16_y;
|
||||
|
||||
overworld_.set_current_world(current_world_);
|
||||
overworld_.set_current_map(current_map_);
|
||||
int tile16_id = overworld_.GetTileFromPosition(
|
||||
ow_map_canvas_.selected_tiles()[i]);
|
||||
// Bounds check for the selected world array
|
||||
if (index_x >= 0 && index_x < 0x200 && index_y >= 0 &&
|
||||
index_y < 0x200) {
|
||||
// CRITICAL FIX: Set the tile to the currently selected tile16, not read from canvas
|
||||
selected_world[index_x][index_y] = current_tile16_;
|
||||
selected_world[index_x][index_y] = tile16_id;
|
||||
|
||||
// CRITICAL FIX: Also update the bitmap directly like single tile drawing
|
||||
ImVec2 tile_position(x, y);
|
||||
auto tile_data =
|
||||
gfx::GetTilemapData(tile16_blockset_, current_tile16_);
|
||||
auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile16_id);
|
||||
if (!tile_data.empty()) {
|
||||
RenderUpdatedMapBitmap(tile_position, tile_data);
|
||||
util::logf(
|
||||
"CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
|
||||
"with tile16_id=%d",
|
||||
x, y, current_tile16_);
|
||||
x, y, tile16_id);
|
||||
} else {
|
||||
util::logf("ERROR: Failed to get tile data for tile16_id=%d",
|
||||
current_tile16_);
|
||||
tile16_id);
|
||||
}
|
||||
} else {
|
||||
util::logf(
|
||||
"ERROR: Rectangle selection position [%d,%d] out of bounds",
|
||||
index_x, index_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefreshOverworldMap();
|
||||
// Clear the rectangle selection after applying
|
||||
ow_map_canvas_.mutable_selected_tiles()->clear();
|
||||
ow_map_canvas_.mutable_points()->clear();
|
||||
// ow_map_canvas_.mutable_selected_tiles()->clear();
|
||||
// ow_map_canvas_.mutable_points()->clear();
|
||||
util::logf(
|
||||
"CheckForOverworldEdits: Rectangle selection applied and cleared");
|
||||
}
|
||||
@@ -1008,7 +1005,7 @@ void OverworldEditor::CheckForSelectRectangle() {
|
||||
current_tile16_ =
|
||||
overworld_.GetTileFromPosition(ow_map_canvas_.selected_tile_pos());
|
||||
ow_map_canvas_.set_selected_tile_pos(ImVec2(-1, -1));
|
||||
|
||||
|
||||
// Scroll blockset canvas to show the selected tile
|
||||
ScrollBlocksetCanvasToCurrentTile();
|
||||
}
|
||||
@@ -1228,8 +1225,8 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
||||
const int parent_map_x = highlight_parent % 8;
|
||||
const int parent_map_y = highlight_parent / 8;
|
||||
ow_map_canvas_.DrawOutline(parent_map_x * kOverworldMapSize,
|
||||
parent_map_y * kOverworldMapSize, large_map_size,
|
||||
large_map_size);
|
||||
parent_map_y * kOverworldMapSize,
|
||||
large_map_size, large_map_size);
|
||||
} else {
|
||||
// Calculate map coordinates accounting for world offset
|
||||
int current_map_x;
|
||||
@@ -1375,7 +1372,7 @@ absl::Status OverworldEditor::DrawTile16Selector() {
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
|
||||
map_blockset_loaded_, /*scale=*/2);
|
||||
bool tile_selected = false;
|
||||
bool tile_selected = false;
|
||||
|
||||
// Call DrawTileSelector after event detection for visual feedback
|
||||
if (blockset_canvas_.DrawTileSelector(32.0f)) {
|
||||
@@ -1385,7 +1382,7 @@ absl::Status OverworldEditor::DrawTile16Selector() {
|
||||
|
||||
// Then check for single click (if not double-click)
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
blockset_canvas_.IsMouseHovering()) {
|
||||
blockset_canvas_.IsMouseHovering()) {
|
||||
tile_selected = true;
|
||||
}
|
||||
|
||||
@@ -1404,7 +1401,7 @@ absl::Status OverworldEditor::DrawTile16Selector() {
|
||||
if (id != current_tile16_ && id >= 0 && id < 512) {
|
||||
current_tile16_ = id;
|
||||
RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id));
|
||||
|
||||
|
||||
// Scroll blockset canvas to show the selected tile
|
||||
ScrollBlocksetCanvasToCurrentTile();
|
||||
}
|
||||
@@ -2122,7 +2119,7 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
|
||||
// Check if ZSCustomOverworld v3 is present
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
|
||||
|
||||
|
||||
if (use_v3_area_sizes) {
|
||||
// Use v3 multi-area coordination
|
||||
RefreshMultiAreaMapsSafely(map_index, map);
|
||||
@@ -2251,7 +2248,8 @@ void OverworldEditor::RefreshMultiAreaMapsSafely(int map_index,
|
||||
overworld_.GetMapTiles(current_world_));
|
||||
if (status.ok()) {
|
||||
maps_bmp_[sibling].set_data(sibling_map->bitmap_data());
|
||||
maps_bmp_[sibling].SetPalette(overworld_.current_area_palette());
|
||||
maps_bmp_[sibling].SetPalette(
|
||||
overworld_.current_area_palette());
|
||||
maps_bmp_[sibling].set_modified(false);
|
||||
|
||||
// Update texture if it exists
|
||||
@@ -2297,16 +2295,17 @@ absl::Status OverworldEditor::RefreshMapPalette() {
|
||||
// Use v3 area size system
|
||||
using zelda3::AreaSizeEnum;
|
||||
auto area_size = overworld_.overworld_map(current_map_)->area_size();
|
||||
|
||||
|
||||
if (area_size != AreaSizeEnum::SmallArea) {
|
||||
// Get all sibling maps that need palette updates
|
||||
std::vector<int> sibling_maps;
|
||||
int parent_id = overworld_.overworld_map(current_map_)->parent();
|
||||
|
||||
|
||||
switch (area_size) {
|
||||
case AreaSizeEnum::LargeArea:
|
||||
// 2x2 grid: parent, parent+1, parent+8, parent+9
|
||||
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||
sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
|
||||
parent_id + 9};
|
||||
break;
|
||||
case AreaSizeEnum::WideArea:
|
||||
// 2x1 grid: parent, parent+1
|
||||
@@ -2319,7 +2318,7 @@ absl::Status OverworldEditor::RefreshMapPalette() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Update palette for all siblings
|
||||
for (int sibling_index : sibling_maps) {
|
||||
if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
|
||||
@@ -2338,7 +2337,8 @@ absl::Status OverworldEditor::RefreshMapPalette() {
|
||||
if (overworld_.overworld_map(current_map_)->is_large_map()) {
|
||||
// We need to update the map and its siblings if it's a large map
|
||||
for (int i = 1; i < 4; i++) {
|
||||
int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
|
||||
int sibling_index =
|
||||
overworld_.overworld_map(current_map_)->parent() + i;
|
||||
if (i >= 2)
|
||||
sibling_index += 6;
|
||||
RETURN_IF_ERROR(
|
||||
@@ -2354,7 +2354,7 @@ absl::Status OverworldEditor::RefreshMapPalette() {
|
||||
|
||||
void OverworldEditor::RefreshMapProperties() {
|
||||
const auto& current_ow_map = *overworld_.mutable_overworld_map(current_map_);
|
||||
|
||||
|
||||
// Check if ZSCustomOverworld v3 is present
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_v3_area_sizes = (asm_version >= 3);
|
||||
@@ -2363,12 +2363,12 @@ void OverworldEditor::RefreshMapProperties() {
|
||||
// Use v3 area size system
|
||||
using zelda3::AreaSizeEnum;
|
||||
auto area_size = current_ow_map.area_size();
|
||||
|
||||
|
||||
if (area_size != AreaSizeEnum::SmallArea) {
|
||||
// Get all sibling maps that need property updates
|
||||
std::vector<int> sibling_maps;
|
||||
int parent_id = current_ow_map.parent();
|
||||
|
||||
|
||||
switch (area_size) {
|
||||
case AreaSizeEnum::LargeArea:
|
||||
// 2x2 grid: parent+1, parent+8, parent+9 (skip parent itself)
|
||||
@@ -2385,7 +2385,7 @@ void OverworldEditor::RefreshMapProperties() {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Copy properties from parent map to all siblings
|
||||
for (int sibling_index : sibling_maps) {
|
||||
if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
|
||||
@@ -3198,11 +3198,12 @@ void OverworldEditor::SetupOverworldCanvasContextMenu() {
|
||||
RefreshOverworldMap();
|
||||
auto status = RefreshTile16Blockset();
|
||||
if (!status.ok()) {
|
||||
util::logf("Failed to refresh tile16 blockset: %s", status.message().data());
|
||||
util::logf("Failed to refresh tile16 blockset: %s",
|
||||
status.message().data());
|
||||
}
|
||||
};
|
||||
ow_map_canvas_.AddContextMenuItem(refresh_map_item);
|
||||
|
||||
|
||||
// Canvas controls
|
||||
gui::Canvas::ContextMenuItem reset_pos_item;
|
||||
reset_pos_item.label = "Reset Canvas Position";
|
||||
@@ -3224,33 +3225,36 @@ void OverworldEditor::ScrollBlocksetCanvasToCurrentTile() {
|
||||
// Calculate the position of the current tile in the blockset canvas
|
||||
// Blockset is arranged in an 8-tile-per-row grid, each tile is 16x16 pixels
|
||||
constexpr int kTilesPerRow = 8;
|
||||
constexpr int kTileDisplaySize = 32; // Each tile displayed at 32x32 (16x16 at 2x scale)
|
||||
|
||||
constexpr int kTileDisplaySize =
|
||||
32; // Each tile displayed at 32x32 (16x16 at 2x scale)
|
||||
|
||||
// Calculate tile position in canvas coordinates (absolute position in the grid)
|
||||
int tile_col = current_tile16_ % kTilesPerRow;
|
||||
int tile_row = current_tile16_ / kTilesPerRow;
|
||||
float tile_x = static_cast<float>(tile_col * kTileDisplaySize);
|
||||
float tile_y = static_cast<float>(tile_row * kTileDisplaySize);
|
||||
|
||||
|
||||
// Get the canvas dimensions
|
||||
ImVec2 canvas_size = blockset_canvas_.canvas_size();
|
||||
|
||||
|
||||
// Calculate the scroll position to center the tile in the viewport
|
||||
float scroll_x = tile_x - (canvas_size.x / 2.0F) + (kTileDisplaySize / 2.0F);
|
||||
float scroll_y = tile_y - (canvas_size.y / 2.0F) + (kTileDisplaySize / 2.0F);
|
||||
|
||||
|
||||
// Clamp scroll to valid ranges (don't scroll beyond bounds)
|
||||
if (scroll_x < 0) scroll_x = 0;
|
||||
if (scroll_y < 0) scroll_y = 0;
|
||||
|
||||
if (scroll_x < 0)
|
||||
scroll_x = 0;
|
||||
if (scroll_y < 0)
|
||||
scroll_y = 0;
|
||||
|
||||
// Update the blockset canvas scrolling position first
|
||||
blockset_canvas_.set_scrolling(ImVec2(-1, -scroll_y));
|
||||
|
||||
|
||||
// Set the points to draw the white outline box around the current tile
|
||||
// Points are in canvas coordinates (not screen coordinates)
|
||||
blockset_canvas_.mutable_points()->clear();
|
||||
blockset_canvas_.mutable_points()->push_back(ImVec2(tile_x, tile_y));
|
||||
blockset_canvas_.mutable_points()->push_back(ImVec2(tile_x + kTileDisplaySize, tile_y + kTileDisplaySize));
|
||||
// blockset_canvas_.mutable_points()->clear();
|
||||
// blockset_canvas_.mutable_points()->push_back(ImVec2(tile_x, tile_y));
|
||||
// blockset_canvas_.mutable_points()->push_back(ImVec2(tile_x + kTileDisplaySize, tile_y + kTileDisplaySize));
|
||||
}
|
||||
|
||||
void OverworldEditor::DrawOverworldProperties() {
|
||||
|
||||
@@ -70,6 +70,12 @@ void Canvas::InitializeDefaults() {
|
||||
// Initialize palette editor
|
||||
palette_editor_ = std::make_unique<EnhancedPaletteEditor>();
|
||||
|
||||
// Initialize interaction handler
|
||||
interaction_handler_.Initialize(canvas_id_);
|
||||
|
||||
// Initialize enhanced components
|
||||
InitializeEnhancedComponents();
|
||||
|
||||
// Initialize legacy compatibility variables to match config
|
||||
enable_grid_ = config_.enable_grid;
|
||||
enable_hex_tile_labels_ = config_.enable_hex_labels;
|
||||
@@ -86,6 +92,93 @@ void Canvas::InitializeDefaults() {
|
||||
void Canvas::Cleanup() {
|
||||
palette_editor_.reset();
|
||||
selection_.Clear();
|
||||
|
||||
// Stop performance monitoring before cleanup to prevent segfault
|
||||
if (performance_integration_) {
|
||||
performance_integration_->StopMonitoring();
|
||||
}
|
||||
|
||||
// Cleanup enhanced components
|
||||
modals_.reset();
|
||||
context_menu_.reset();
|
||||
usage_tracker_.reset();
|
||||
performance_integration_.reset();
|
||||
}
|
||||
|
||||
void Canvas::InitializeEnhancedComponents() {
|
||||
// Initialize modals system
|
||||
modals_ = std::make_unique<canvas::CanvasModals>();
|
||||
|
||||
// Initialize context menu system
|
||||
context_menu_ = std::make_unique<canvas::CanvasContextMenu>();
|
||||
context_menu_->Initialize(canvas_id_);
|
||||
|
||||
// Initialize usage tracker
|
||||
usage_tracker_ = std::make_shared<canvas::CanvasUsageTracker>();
|
||||
usage_tracker_->Initialize(canvas_id_);
|
||||
canvas::CanvasUsageManager::Get().RegisterTracker(canvas_id_, usage_tracker_);
|
||||
|
||||
// Initialize performance integration
|
||||
performance_integration_ = std::make_shared<canvas::CanvasPerformanceIntegration>();
|
||||
performance_integration_->Initialize(canvas_id_);
|
||||
performance_integration_->SetUsageTracker(usage_tracker_);
|
||||
canvas::CanvasPerformanceManager::Get().RegisterIntegration(canvas_id_, performance_integration_);
|
||||
|
||||
// Start performance monitoring
|
||||
performance_integration_->StartMonitoring();
|
||||
usage_tracker_->StartSession();
|
||||
}
|
||||
|
||||
void Canvas::SetUsageMode(canvas::CanvasUsage usage) {
|
||||
if (usage_tracker_) {
|
||||
usage_tracker_->SetUsageMode(usage);
|
||||
}
|
||||
if (context_menu_) {
|
||||
context_menu_->SetUsageMode(usage);
|
||||
}
|
||||
}
|
||||
|
||||
canvas::CanvasUsage Canvas::GetUsageMode() const {
|
||||
if (usage_tracker_) {
|
||||
return usage_tracker_->GetCurrentStats().usage_mode;
|
||||
}
|
||||
return canvas::CanvasUsage::kUnknown;
|
||||
}
|
||||
|
||||
void Canvas::RecordCanvasOperation(const std::string& operation_name, double time_ms) {
|
||||
if (usage_tracker_) {
|
||||
usage_tracker_->RecordOperation(operation_name, time_ms);
|
||||
}
|
||||
if (performance_integration_) {
|
||||
performance_integration_->RecordOperation(operation_name, time_ms, GetUsageMode());
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::ShowPerformanceUI() {
|
||||
if (performance_integration_) {
|
||||
performance_integration_->RenderPerformanceUI();
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::ShowUsageReport() {
|
||||
if (usage_tracker_) {
|
||||
std::string report = usage_tracker_->ExportUsageReport();
|
||||
// Show report in a modal or window
|
||||
if (modals_) {
|
||||
// Create a simple text display modal
|
||||
ImGui::OpenPopup("Canvas Usage Report");
|
||||
if (ImGui::BeginPopupModal("Canvas Usage Report", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Canvas Usage Report");
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", report.c_str());
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Canvas::InitializePaletteEditor(Rom* rom) {
|
||||
@@ -174,6 +267,24 @@ ImVec2 Canvas::GetLastClickPosition() const {
|
||||
return ImVec2(-1, -1); // Invalid position
|
||||
}
|
||||
|
||||
// ==================== Modern ImGui-Style Interface ====================
|
||||
|
||||
void Canvas::Begin(ImVec2 canvas_size) {
|
||||
// Modern ImGui-style begin - combines DrawBackground + DrawContextMenu
|
||||
DrawBackground(canvas_size);
|
||||
DrawContextMenu();
|
||||
}
|
||||
|
||||
void Canvas::End() {
|
||||
// Modern ImGui-style end - automatically draws grid and overlay
|
||||
if (config_.enable_grid) {
|
||||
DrawGrid();
|
||||
}
|
||||
DrawOverlay();
|
||||
}
|
||||
|
||||
// ==================== Legacy Interface ====================
|
||||
|
||||
void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
|
||||
const std::function<void()> &event,
|
||||
int tile_size, float scale) {
|
||||
@@ -250,6 +361,119 @@ void Canvas::DrawContextMenu() {
|
||||
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);
|
||||
|
||||
// Update canvas state for enhanced components
|
||||
if (usage_tracker_) {
|
||||
usage_tracker_->UpdateCanvasState(canvas_sz_, config_.content_size,
|
||||
global_scale_, custom_step_,
|
||||
enable_grid_, enable_hex_tile_labels_,
|
||||
enable_custom_labels_);
|
||||
}
|
||||
|
||||
// Use enhanced context menu if available
|
||||
if (context_menu_) {
|
||||
canvas::CanvasConfig snapshot;
|
||||
snapshot.canvas_size = canvas_sz_;
|
||||
snapshot.content_size = config_.content_size;
|
||||
snapshot.global_scale = global_scale_;
|
||||
snapshot.grid_step = custom_step_;
|
||||
snapshot.enable_grid = enable_grid_;
|
||||
snapshot.enable_hex_labels = enable_hex_tile_labels_;
|
||||
snapshot.enable_custom_labels = enable_custom_labels_;
|
||||
snapshot.enable_context_menu = enable_context_menu_;
|
||||
snapshot.is_draggable = draggable_;
|
||||
snapshot.auto_resize = config_.auto_resize;
|
||||
snapshot.scrolling = scrolling_;
|
||||
|
||||
context_menu_->SetCanvasState(canvas_sz_, config_.content_size,
|
||||
global_scale_, custom_step_, enable_grid_,
|
||||
enable_hex_tile_labels_, enable_custom_labels_,
|
||||
enable_context_menu_, draggable_,
|
||||
config_.auto_resize, scrolling_);
|
||||
|
||||
context_menu_->Render(
|
||||
context_id_, mouse_pos, bitmap_,
|
||||
bitmap_ ? bitmap_->mutable_palette() : nullptr,
|
||||
[this](canvas::CanvasContextMenu::Command command,
|
||||
const canvas::CanvasConfig& updated_config) {
|
||||
switch (command) {
|
||||
case canvas::CanvasContextMenu::Command::kResetView:
|
||||
ResetView();
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kZoomToFit:
|
||||
if (bitmap_) {
|
||||
SetZoomToFit(*bitmap_);
|
||||
}
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kZoomIn:
|
||||
SetGlobalScale(config_.global_scale * 1.25f);
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kZoomOut:
|
||||
SetGlobalScale(config_.global_scale * 0.8f);
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleGrid:
|
||||
config_.enable_grid = !config_.enable_grid;
|
||||
enable_grid_ = config_.enable_grid;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleHexLabels:
|
||||
config_.enable_hex_labels = !config_.enable_hex_labels;
|
||||
enable_hex_tile_labels_ = config_.enable_hex_labels;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleCustomLabels:
|
||||
config_.enable_custom_labels = !config_.enable_custom_labels;
|
||||
enable_custom_labels_ = config_.enable_custom_labels;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleContextMenu:
|
||||
config_.enable_context_menu = !config_.enable_context_menu;
|
||||
enable_context_menu_ = config_.enable_context_menu;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleAutoResize:
|
||||
config_.auto_resize = !config_.auto_resize;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kToggleDraggable:
|
||||
config_.is_draggable = !config_.is_draggable;
|
||||
draggable_ = config_.is_draggable;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kSetGridStep:
|
||||
config_.grid_step = updated_config.grid_step;
|
||||
custom_step_ = config_.grid_step;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kSetScale:
|
||||
config_.global_scale = updated_config.global_scale;
|
||||
global_scale_ = config_.global_scale;
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kOpenAdvancedProperties:
|
||||
if (modals_) {
|
||||
canvas::CanvasConfig modal_config = updated_config;
|
||||
modal_config.on_config_changed =
|
||||
[this](const canvas::CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); };
|
||||
modal_config.on_scale_changed =
|
||||
[this](const canvas::CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); };
|
||||
modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_);
|
||||
}
|
||||
break;
|
||||
case canvas::CanvasContextMenu::Command::kOpenScalingControls:
|
||||
if (modals_) {
|
||||
canvas::CanvasConfig modal_config = updated_config;
|
||||
modal_config.on_config_changed =
|
||||
[this](const canvas::CanvasConfig& cfg) { ApplyConfigSnapshot(cfg); };
|
||||
modal_config.on_scale_changed =
|
||||
[this](const canvas::CanvasConfig& cfg) { ApplyScaleSnapshot(cfg); };
|
||||
modals_->ShowScalingControls(canvas_id_, modal_config, bitmap_);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
snapshot);
|
||||
|
||||
if (modals_) {
|
||||
modals_->Render();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static bool show_bitmap_data = false;
|
||||
if (show_bitmap_data && bitmap_ != nullptr) {
|
||||
@@ -461,8 +685,6 @@ 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;
|
||||
|
||||
@@ -486,6 +708,35 @@ void Canvas::ResetView() {
|
||||
scrolling_ = ImVec2(0, 0);
|
||||
}
|
||||
|
||||
void Canvas::ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot) {
|
||||
config_.enable_grid = snapshot.enable_grid;
|
||||
config_.enable_hex_labels = snapshot.enable_hex_labels;
|
||||
config_.enable_custom_labels = snapshot.enable_custom_labels;
|
||||
config_.enable_context_menu = snapshot.enable_context_menu;
|
||||
config_.is_draggable = snapshot.is_draggable;
|
||||
config_.auto_resize = snapshot.auto_resize;
|
||||
config_.grid_step = snapshot.grid_step;
|
||||
config_.global_scale = snapshot.global_scale;
|
||||
config_.canvas_size = snapshot.canvas_size;
|
||||
config_.content_size = snapshot.content_size;
|
||||
config_.custom_canvas_size = snapshot.canvas_size.x > 0 && snapshot.canvas_size.y > 0;
|
||||
|
||||
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;
|
||||
scrolling_ = snapshot.scrolling;
|
||||
}
|
||||
|
||||
void Canvas::ApplyScaleSnapshot(const canvas::CanvasConfig& snapshot) {
|
||||
config_.global_scale = snapshot.global_scale;
|
||||
global_scale_ = config_.global_scale;
|
||||
scrolling_ = snapshot.scrolling;
|
||||
}
|
||||
|
||||
bool Canvas::DrawTilePainter(const Bitmap &bitmap, int size, float scale) {
|
||||
const ImGuiIO &io = GetIO();
|
||||
const bool is_hovered = IsItemHovered();
|
||||
@@ -913,7 +1164,7 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Use optimized rendering for large groups to improve performance
|
||||
bool use_optimized_rendering = group.size() > 16; // Optimize for large selections
|
||||
bool use_optimized_rendering = group.size() > 128; // Optimize for large selections
|
||||
|
||||
// Pre-calculate common values to avoid repeated computation
|
||||
const float tile_scale = tile_size * scale;
|
||||
@@ -954,8 +1205,7 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
|
||||
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;
|
||||
auto tilemap_size = 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;
|
||||
@@ -1108,7 +1358,7 @@ void Canvas::DrawOverlay() {
|
||||
.grid_step = config_.grid_step
|
||||
};
|
||||
|
||||
// Use high-level utility function
|
||||
// Use high-level utility function with local points (synchronized from interaction handler)
|
||||
CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
|
||||
}
|
||||
|
||||
@@ -1266,6 +1516,36 @@ void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
|
||||
}
|
||||
|
||||
void Canvas::ShowAdvancedCanvasProperties() {
|
||||
// Use the new modal system if available
|
||||
if (modals_) {
|
||||
canvas::CanvasConfig modal_config;
|
||||
modal_config.canvas_size = canvas_sz_;
|
||||
modal_config.content_size = config_.content_size;
|
||||
modal_config.global_scale = global_scale_;
|
||||
modal_config.grid_step = custom_step_;
|
||||
modal_config.enable_grid = enable_grid_;
|
||||
modal_config.enable_hex_labels = enable_hex_tile_labels_;
|
||||
modal_config.enable_custom_labels = enable_custom_labels_;
|
||||
modal_config.enable_context_menu = enable_context_menu_;
|
||||
modal_config.is_draggable = draggable_;
|
||||
modal_config.auto_resize = config_.auto_resize;
|
||||
modal_config.scrolling = scrolling_;
|
||||
modal_config.on_config_changed = [this](const canvas::CanvasConfig& updated_config) {
|
||||
// Update legacy variables when config changes
|
||||
enable_grid_ = updated_config.enable_grid;
|
||||
enable_hex_tile_labels_ = updated_config.enable_hex_labels;
|
||||
enable_custom_labels_ = updated_config.enable_custom_labels;
|
||||
};
|
||||
modal_config.on_scale_changed = [this](const canvas::CanvasConfig& updated_config) {
|
||||
global_scale_ = updated_config.global_scale;
|
||||
scrolling_ = updated_config.scrolling;
|
||||
};
|
||||
|
||||
modals_->ShowAdvancedProperties(canvas_id_, modal_config, bitmap_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to legacy modal system
|
||||
if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Advanced Canvas Configuration");
|
||||
ImGui::Separator();
|
||||
@@ -1346,6 +1626,39 @@ void Canvas::ShowAdvancedCanvasProperties() {
|
||||
// Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor
|
||||
|
||||
void Canvas::ShowScalingControls() {
|
||||
// Use the new modal system if available
|
||||
if (modals_) {
|
||||
canvas::CanvasConfig modal_config;
|
||||
modal_config.canvas_size = canvas_sz_;
|
||||
modal_config.content_size = config_.content_size;
|
||||
modal_config.global_scale = global_scale_;
|
||||
modal_config.grid_step = custom_step_;
|
||||
modal_config.enable_grid = enable_grid_;
|
||||
modal_config.enable_hex_labels = enable_hex_tile_labels_;
|
||||
modal_config.enable_custom_labels = enable_custom_labels_;
|
||||
modal_config.enable_context_menu = enable_context_menu_;
|
||||
modal_config.is_draggable = draggable_;
|
||||
modal_config.auto_resize = config_.auto_resize;
|
||||
modal_config.scrolling = scrolling_;
|
||||
modal_config.on_config_changed = [this](const canvas::CanvasConfig& updated_config) {
|
||||
// Update legacy variables when config changes
|
||||
enable_grid_ = updated_config.enable_grid;
|
||||
enable_hex_tile_labels_ = updated_config.enable_hex_labels;
|
||||
enable_custom_labels_ = updated_config.enable_custom_labels;
|
||||
enable_context_menu_ = updated_config.enable_context_menu;
|
||||
};
|
||||
modal_config.on_scale_changed = [this](const canvas::CanvasConfig& updated_config) {
|
||||
draggable_ = updated_config.is_draggable;
|
||||
custom_step_ = updated_config.grid_step;
|
||||
global_scale_ = updated_config.global_scale;
|
||||
scrolling_ = updated_config.scrolling;
|
||||
};
|
||||
|
||||
modals_->ShowScalingControls(canvas_id_, modal_config);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to legacy modal system
|
||||
if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("Canvas Scaling and Display Controls");
|
||||
ImGui::Separator();
|
||||
@@ -1439,8 +1752,6 @@ void Canvas::ShowScalingControls() {
|
||||
}
|
||||
}
|
||||
|
||||
// Old ROM palette management methods removed - now handled by EnhancedPaletteEditor
|
||||
|
||||
// BPP format management methods
|
||||
void Canvas::ShowBppFormatSelector() {
|
||||
if (!bpp_format_ui_) {
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
#include "app/gui/enhanced_palette_editor.h"
|
||||
#include "app/gfx/bpp_format_manager.h"
|
||||
#include "app/gui/bpp_format_ui.h"
|
||||
#include "app/gui/canvas/canvas_modals.h"
|
||||
#include "app/gui/canvas/canvas_context_menu.h"
|
||||
#include "app/gui/canvas/canvas_usage_tracker.h"
|
||||
#include "app/gui/canvas/canvas_performance_integration.h"
|
||||
#include "app/gui/canvas/canvas_interaction_handler.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -103,6 +108,37 @@ class Canvas {
|
||||
void UpdateInfoGrid(ImVec2 bg_size, float grid_size = 64.0f,
|
||||
int label_id = 0);
|
||||
|
||||
// ==================== Modern ImGui-Style Interface ====================
|
||||
|
||||
/**
|
||||
* @brief Begin canvas rendering (ImGui-style)
|
||||
*
|
||||
* Modern alternative to DrawBackground(). Handles:
|
||||
* - Background and border rendering
|
||||
* - Size calculation
|
||||
* - Scroll/drag setup
|
||||
* - Context menu
|
||||
*
|
||||
* Usage:
|
||||
* ```cpp
|
||||
* canvas.Begin();
|
||||
* canvas.DrawBitmap(bitmap);
|
||||
* if (canvas.DrawTilePainter(tile, 16)) { ... }
|
||||
* canvas.End(); // Draws grid and overlay
|
||||
* ```
|
||||
*/
|
||||
void Begin(ImVec2 canvas_size = ImVec2(0, 0));
|
||||
|
||||
/**
|
||||
* @brief End canvas rendering (ImGui-style)
|
||||
*
|
||||
* Modern alternative to manual DrawGrid() + DrawOverlay().
|
||||
* Automatically draws grid and overlay if enabled.
|
||||
*/
|
||||
void End();
|
||||
|
||||
// ==================== Legacy Interface (Backward Compatible) ====================
|
||||
|
||||
// Background for the Canvas represents region without any content drawn to
|
||||
// it, but can be controlled by the user.
|
||||
void DrawBackground(ImVec2 canvas_size = ImVec2(0, 0));
|
||||
@@ -125,6 +161,13 @@ class Canvas {
|
||||
std::unique_ptr<gui::BppConversionDialog> bpp_conversion_dialog_;
|
||||
std::unique_ptr<gui::BppComparisonTool> bpp_comparison_tool_;
|
||||
|
||||
// Enhanced canvas components
|
||||
std::unique_ptr<canvas::CanvasModals> modals_;
|
||||
std::unique_ptr<canvas::CanvasContextMenu> context_menu_;
|
||||
std::shared_ptr<canvas::CanvasUsageTracker> usage_tracker_;
|
||||
std::shared_ptr<canvas::CanvasPerformanceIntegration> performance_integration_;
|
||||
canvas::CanvasInteractionHandler interaction_handler_;
|
||||
|
||||
void AddContextMenuItem(const ContextMenuItem& item);
|
||||
void ClearContextMenuItems();
|
||||
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
|
||||
@@ -134,6 +177,8 @@ class Canvas {
|
||||
void ShowScalingControls();
|
||||
void SetZoomToFit(const gfx::Bitmap& bitmap);
|
||||
void ResetView();
|
||||
void ApplyConfigSnapshot(const canvas::CanvasConfig& snapshot);
|
||||
void ApplyScaleSnapshot(const canvas::CanvasConfig& snapshot);
|
||||
|
||||
// Modular component access
|
||||
CanvasConfig& GetConfig() { return config_; }
|
||||
@@ -154,6 +199,18 @@ class Canvas {
|
||||
bool ConvertBitmapFormat(gfx::BppFormat target_format);
|
||||
gfx::BppFormat GetCurrentBppFormat() const;
|
||||
|
||||
// Enhanced canvas management
|
||||
void InitializeEnhancedComponents();
|
||||
void SetUsageMode(canvas::CanvasUsage usage);
|
||||
canvas::CanvasUsage GetUsageMode() const;
|
||||
void RecordCanvasOperation(const std::string& operation_name, double time_ms);
|
||||
void ShowPerformanceUI();
|
||||
void ShowUsageReport();
|
||||
|
||||
// Interaction handler access
|
||||
canvas::CanvasInteractionHandler& GetInteractionHandler() { return interaction_handler_; }
|
||||
const canvas::CanvasInteractionHandler& GetInteractionHandler() const { return interaction_handler_; }
|
||||
|
||||
// Initialization and cleanup
|
||||
void InitializeDefaults();
|
||||
void Cleanup();
|
||||
@@ -226,8 +283,9 @@ class Canvas {
|
||||
void ZoomIn() { global_scale_ += 0.25f; }
|
||||
void ZoomOut() { global_scale_ -= 0.25f; }
|
||||
|
||||
auto points() const { return points_; }
|
||||
auto mutable_points() { return &points_; }
|
||||
// Points accessors - points_ is maintained separately for custom overlay drawing
|
||||
const ImVector<ImVec2>& points() const { return points_; }
|
||||
ImVector<ImVec2>* mutable_points() { return &points_; }
|
||||
auto push_back(ImVec2 pos) { points_.push_back(pos); }
|
||||
auto draw_list() const { return draw_list_; }
|
||||
auto zero_point() const { return canvas_p0_; }
|
||||
@@ -345,6 +403,7 @@ class Canvas {
|
||||
ImVec2 mouse_pos_in_canvas_;
|
||||
|
||||
// Drawing and labeling
|
||||
// NOTE: points_ synchronized from interaction_handler_ for backward compatibility
|
||||
ImVector<ImVec2> points_;
|
||||
ImVector<ImVector<std::string>> labels_;
|
||||
|
||||
@@ -382,6 +441,97 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
|
||||
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
|
||||
const std::string& label = "", bool auto_resize = true);
|
||||
|
||||
/**
|
||||
* @class ScopedCanvas
|
||||
* @brief RAII wrapper for Canvas (ImGui-style)
|
||||
*
|
||||
* Automatically calls Begin() on construction and End() on destruction,
|
||||
* preventing forgotten End() calls and ensuring proper cleanup.
|
||||
*
|
||||
* Usage:
|
||||
* ```cpp
|
||||
* {
|
||||
* gui::ScopedCanvas canvas("MyCanvas", ImVec2(512, 512));
|
||||
* canvas->DrawBitmap(bitmap);
|
||||
* if (canvas->DrawTilePainter(tile, 16)) {
|
||||
* HandlePaint(canvas->drawn_tile_position());
|
||||
* }
|
||||
* } // Automatic End() and cleanup
|
||||
* ```
|
||||
*
|
||||
* Or wrap existing canvas:
|
||||
* ```cpp
|
||||
* Canvas my_canvas("Editor");
|
||||
* {
|
||||
* ScopedCanvas scoped(my_canvas);
|
||||
* scoped->DrawBitmap(bitmap);
|
||||
* } // Automatic End()
|
||||
* ```
|
||||
*/
|
||||
class ScopedCanvas {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct and begin a new canvas
|
||||
*/
|
||||
explicit ScopedCanvas(const std::string& id, ImVec2 canvas_size = ImVec2(0, 0))
|
||||
: canvas_(new Canvas(id, canvas_size)), owned_(true), active_(true) {
|
||||
canvas_->Begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wrap existing canvas with RAII
|
||||
*/
|
||||
explicit ScopedCanvas(Canvas& canvas)
|
||||
: canvas_(&canvas), owned_(false), active_(true) {
|
||||
canvas_->Begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor automatically calls End()
|
||||
*/
|
||||
~ScopedCanvas() {
|
||||
if (active_ && canvas_) {
|
||||
canvas_->End();
|
||||
}
|
||||
if (owned_) {
|
||||
delete canvas_;
|
||||
}
|
||||
}
|
||||
|
||||
// No copy, move only
|
||||
ScopedCanvas(const ScopedCanvas&) = delete;
|
||||
ScopedCanvas& operator=(const ScopedCanvas&) = delete;
|
||||
|
||||
ScopedCanvas(ScopedCanvas&& other) noexcept
|
||||
: canvas_(other.canvas_), owned_(other.owned_), active_(other.active_) {
|
||||
other.active_ = false;
|
||||
other.canvas_ = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Arrow operator for clean syntax: scoped->DrawBitmap(...)
|
||||
*/
|
||||
Canvas* operator->() { return canvas_; }
|
||||
const Canvas* operator->() const { return canvas_; }
|
||||
|
||||
/**
|
||||
* @brief Dereference operator for direct access: (*scoped).DrawBitmap(...)
|
||||
*/
|
||||
Canvas& operator*() { return *canvas_; }
|
||||
const Canvas& operator*() const { return *canvas_; }
|
||||
|
||||
/**
|
||||
* @brief Get underlying canvas
|
||||
*/
|
||||
Canvas* get() { return canvas_; }
|
||||
const Canvas* get() const { return canvas_; }
|
||||
|
||||
private:
|
||||
Canvas* canvas_;
|
||||
bool owned_;
|
||||
bool active_;
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
|
||||
72
src/app/gui/canvas/canvas.cmake
Normal file
72
src/app/gui/canvas/canvas.cmake
Normal file
@@ -0,0 +1,72 @@
|
||||
# Canvas system CMake configuration
|
||||
# This file configures the canvas system components
|
||||
|
||||
# Canvas core components
|
||||
set(CANVAS_SOURCES
|
||||
canvas_modals.cc
|
||||
canvas_context_menu.cc
|
||||
canvas_usage_tracker.cc
|
||||
canvas_performance_integration.cc
|
||||
canvas_interaction_handler.cc
|
||||
)
|
||||
|
||||
set(CANVAS_HEADERS
|
||||
canvas_modals.h
|
||||
canvas_context_menu.h
|
||||
canvas_usage_tracker.h
|
||||
canvas_performance_integration.h
|
||||
canvas_interaction_handler.h
|
||||
)
|
||||
|
||||
# Create canvas library
|
||||
add_library(yaze_canvas STATIC
|
||||
${CANVAS_SOURCES}
|
||||
${CANVAS_HEADERS}
|
||||
)
|
||||
|
||||
# Set target properties
|
||||
set_target_properties(yaze_canvas PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(yaze_canvas PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../..
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../../incl
|
||||
)
|
||||
|
||||
# Link dependencies
|
||||
target_link_libraries(yaze_canvas PUBLIC
|
||||
yaze_gfx
|
||||
yaze_gui_common
|
||||
imgui
|
||||
SDL2::SDL2
|
||||
)
|
||||
|
||||
# Compiler-specific options
|
||||
if(MSVC)
|
||||
target_compile_options(yaze_canvas PRIVATE /W4)
|
||||
else()
|
||||
target_compile_options(yaze_canvas PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
# Add canvas to parent GUI library
|
||||
target_sources(yaze_gui PRIVATE
|
||||
${CANVAS_SOURCES}
|
||||
${CANVAS_HEADERS}
|
||||
)
|
||||
|
||||
# Install rules
|
||||
install(TARGETS yaze_canvas
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
|
||||
install(FILES ${CANVAS_HEADERS}
|
||||
DESTINATION include/yaze/app/gui/canvas
|
||||
)
|
||||
541
src/app/gui/canvas/canvas_context_menu.cc
Normal file
541
src/app/gui/canvas/canvas_context_menu.cc
Normal file
@@ -0,0 +1,541 @@
|
||||
#include "canvas_context_menu.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/performance_dashboard.h"
|
||||
#include "app/gui/enhanced_palette_editor.h"
|
||||
#include "app/gui/bpp_format_ui.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "gui/canvas/canvas_modals.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
namespace {
|
||||
inline void Dispatch(
|
||||
const std::function<void(CanvasContextMenu::Command, const CanvasConfig&)>& handler,
|
||||
CanvasContextMenu::Command command, CanvasConfig config) {
|
||||
if (handler) {
|
||||
handler(command, config);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CanvasContextMenu::Initialize(const std::string& canvas_id) {
|
||||
canvas_id_ = canvas_id;
|
||||
enabled_ = true;
|
||||
current_usage_ = CanvasUsage::kTilePainting;
|
||||
|
||||
// Initialize canvas state
|
||||
canvas_size_ = ImVec2(0, 0);
|
||||
content_size_ = ImVec2(0, 0);
|
||||
global_scale_ = 1.0F;
|
||||
grid_step_ = 32.0F;
|
||||
enable_grid_ = true;
|
||||
enable_hex_labels_ = false;
|
||||
enable_custom_labels_ = false;
|
||||
enable_context_menu_ = true;
|
||||
is_draggable_ = false;
|
||||
auto_resize_ = false;
|
||||
scrolling_ = ImVec2(0, 0);
|
||||
|
||||
// Create default menu items
|
||||
CreateDefaultMenuItems();
|
||||
}
|
||||
|
||||
void CanvasContextMenu::SetUsageMode(CanvasUsage usage) {
|
||||
current_usage_ = usage;
|
||||
}
|
||||
|
||||
void CanvasContextMenu::AddMenuItem(const ContextMenuItem& item) {
|
||||
global_items_.push_back(item);
|
||||
}
|
||||
|
||||
void CanvasContextMenu::AddMenuItem(const ContextMenuItem& item, CanvasUsage usage) {
|
||||
usage_specific_items_[usage].push_back(item);
|
||||
}
|
||||
|
||||
void CanvasContextMenu::ClearMenuItems() {
|
||||
global_items_.clear();
|
||||
usage_specific_items_.clear();
|
||||
}
|
||||
|
||||
void CanvasContextMenu::Render(
|
||||
const std::string& context_id, const ImVec2& mouse_pos,
|
||||
const gfx::Bitmap* bitmap, const gfx::SnesPalette* palette,
|
||||
const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config) {
|
||||
if (!enabled_) return;
|
||||
|
||||
// Context menu (under default mouse threshold)
|
||||
if (ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
enable_context_menu_ && drag_delta.x == 0.0F && drag_delta.y == 0.0F) {
|
||||
ImGui::OpenPopupOnItemClick(context_id.c_str(), ImGuiPopupFlags_MouseButtonRight);
|
||||
}
|
||||
|
||||
// Contents of the Context Menu
|
||||
if (ImGui::BeginPopup(context_id.c_str())) {
|
||||
// Render usage-specific menu first
|
||||
RenderUsageSpecificMenu();
|
||||
|
||||
// Add separator if there are usage-specific items
|
||||
if (!usage_specific_items_[current_usage_].empty()) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// Render view controls
|
||||
RenderViewControlsMenu(command_handler, current_config);
|
||||
ImGui::Separator();
|
||||
|
||||
// Render canvas properties
|
||||
RenderCanvasPropertiesMenu(command_handler, current_config);
|
||||
ImGui::Separator();
|
||||
|
||||
// Render bitmap operations if bitmap is available
|
||||
if (bitmap) {
|
||||
RenderBitmapOperationsMenu(bitmap);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
// Render palette operations if palette is available
|
||||
if (palette) {
|
||||
RenderPaletteOperationsMenu(palette);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (bitmap) {
|
||||
RenderBppOperationsMenu(bitmap);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
RenderPerformanceMenu();
|
||||
ImGui::Separator();
|
||||
|
||||
RenderGridControlsMenu(command_handler, current_config);
|
||||
ImGui::Separator();
|
||||
|
||||
RenderScalingControlsMenu(command_handler, current_config);
|
||||
|
||||
// Render global menu items
|
||||
if (!global_items_.empty()) {
|
||||
ImGui::Separator();
|
||||
RenderMenuSection("Custom Actions", global_items_);
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
bool CanvasContextMenu::ShouldShowContextMenu() const {
|
||||
return enabled_ && enable_context_menu_;
|
||||
}
|
||||
|
||||
void CanvasContextMenu::SetCanvasState(const ImVec2& canvas_size,
|
||||
const ImVec2& content_size,
|
||||
float global_scale,
|
||||
float grid_step,
|
||||
bool enable_grid,
|
||||
bool enable_hex_labels,
|
||||
bool enable_custom_labels,
|
||||
bool enable_context_menu,
|
||||
bool is_draggable,
|
||||
bool auto_resize,
|
||||
const ImVec2& scrolling) {
|
||||
canvas_size_ = canvas_size;
|
||||
content_size_ = content_size;
|
||||
global_scale_ = global_scale;
|
||||
grid_step_ = grid_step;
|
||||
enable_grid_ = enable_grid;
|
||||
enable_hex_labels_ = enable_hex_labels;
|
||||
enable_custom_labels_ = enable_custom_labels;
|
||||
enable_context_menu_ = enable_context_menu;
|
||||
is_draggable_ = is_draggable;
|
||||
auto_resize_ = auto_resize;
|
||||
scrolling_ = scrolling;
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderMenuItem(const ContextMenuItem& item) {
|
||||
if (!item.visible_condition()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
RenderMenuItem(subitem);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.enabled_condition()) {
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
if (item.separator_after) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderMenuSection(const std::string& title,
|
||||
const std::vector<ContextMenuItem>& items) {
|
||||
if (items.empty()) return;
|
||||
|
||||
ImGui::TextColored(ImVec4(0.7F, 0.7F, 0.7F, 1.0F), "%s", title.c_str());
|
||||
for (const auto& item : items) {
|
||||
RenderMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderUsageSpecificMenu() {
|
||||
auto it = usage_specific_items_.find(current_usage_);
|
||||
if (it == usage_specific_items_.end() || it->second.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string usage_name = GetUsageModeName(current_usage_);
|
||||
ImVec4 usage_color = GetUsageModeColor(current_usage_);
|
||||
|
||||
ImGui::TextColored(usage_color, "%s %s Mode", ICON_MD_COLOR_LENS, usage_name.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
for (const auto& item : it->second) {
|
||||
RenderMenuItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderViewControlsMenu(
|
||||
const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config) {
|
||||
if (ImGui::BeginMenu("View Controls")) {
|
||||
if (ImGui::MenuItem("Reset View", "Ctrl+R")) {
|
||||
Dispatch(command_handler, Command::kResetView, current_config);
|
||||
}
|
||||
if (ImGui::MenuItem("Zoom to Fit", "Ctrl+F")) {
|
||||
Dispatch(command_handler, Command::kZoomToFit, current_config);
|
||||
}
|
||||
if (ImGui::MenuItem("Zoom In", "Ctrl++")) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.global_scale *= 1.25F;
|
||||
Dispatch(command_handler, Command::kSetScale, updated);
|
||||
}
|
||||
if (ImGui::MenuItem("Zoom Out", "Ctrl+-")) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.global_scale *= 0.8F;
|
||||
Dispatch(command_handler, Command::kSetScale, updated);
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Show Grid", nullptr, enable_grid_)) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.enable_grid = !enable_grid_;
|
||||
Dispatch(command_handler, Command::kToggleGrid, updated);
|
||||
}
|
||||
if (ImGui::MenuItem("Show Hex Labels", nullptr, enable_hex_labels_)) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.enable_hex_labels = !enable_hex_labels_;
|
||||
Dispatch(command_handler, Command::kToggleHexLabels, updated);
|
||||
}
|
||||
if (ImGui::MenuItem("Show Custom Labels", nullptr, enable_custom_labels_)) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.enable_custom_labels = !enable_custom_labels_;
|
||||
Dispatch(command_handler, Command::kToggleCustomLabels, updated);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderCanvasPropertiesMenu(
|
||||
const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config) {
|
||||
if (ImGui::BeginMenu(ICON_MD_SETTINGS " Canvas Properties")) {
|
||||
ImGui::Text("Canvas Size: %.0f x %.0f", canvas_size_.x, canvas_size_.y);
|
||||
ImGui::Text("Content Size: %.0f x %.0f", content_size_.x, content_size_.y);
|
||||
ImGui::Text("Global Scale: %.2f", global_scale_);
|
||||
ImGui::Text("Grid Step: %.1f", grid_step_);
|
||||
ImGui::Text("Mouse Position: %.0f x %.0f", 0.0F, 0.0F); // Would need actual mouse pos
|
||||
|
||||
if (ImGui::MenuItem("Advanced Properties...")) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.enable_grid = enable_grid_;
|
||||
updated.enable_hex_labels = enable_hex_labels_;
|
||||
updated.enable_custom_labels = enable_custom_labels_;
|
||||
updated.enable_context_menu = enable_context_menu_;
|
||||
updated.is_draggable = is_draggable_;
|
||||
updated.auto_resize = auto_resize_;
|
||||
updated.grid_step = grid_step_;
|
||||
updated.canvas_size = canvas_size_;
|
||||
updated.content_size = content_size_;
|
||||
updated.scrolling = scrolling_;
|
||||
Dispatch(command_handler, Command::kOpenAdvancedProperties, updated);
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderBitmapOperationsMenu(const gfx::Bitmap* bitmap) {
|
||||
if (ImGui::BeginMenu(ICON_MD_IMAGE " Bitmap Operations")) {
|
||||
ImGui::Text("Size: %d x %d", bitmap->width(), bitmap->height());
|
||||
ImGui::Text("Format: %s", "Unknown"); // Would need format detection
|
||||
|
||||
if (ImGui::MenuItem("Edit Bitmap Data...")) {
|
||||
// Open bitmap data editor
|
||||
}
|
||||
if (ImGui::MenuItem("Export Bitmap...")) {
|
||||
// Export bitmap
|
||||
}
|
||||
if (ImGui::MenuItem("Import Bitmap...")) {
|
||||
// Import bitmap
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderPaletteOperationsMenu(const gfx::SnesPalette* palette) {
|
||||
if (ImGui::BeginMenu(ICON_MD_PALETTE " Palette Operations")) {
|
||||
ImGui::Text("Colors: %zu", palette->size());
|
||||
|
||||
if (ImGui::MenuItem("Edit Palette...")) {
|
||||
// Open palette editor
|
||||
}
|
||||
if (ImGui::MenuItem("Color Analysis...")) {
|
||||
// Open color analysis
|
||||
}
|
||||
if (ImGui::MenuItem("Apply ROM Palette...")) {
|
||||
// Apply ROM palette
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderBppOperationsMenu(const gfx::Bitmap* bitmap) {
|
||||
if (ImGui::BeginMenu(ICON_MD_SWAP_HORIZ " BPP Operations")) {
|
||||
if (ImGui::MenuItem("Format Analysis...")) {
|
||||
// Open BPP analysis
|
||||
}
|
||||
if (ImGui::MenuItem("Convert Format...")) {
|
||||
// Open BPP conversion dialog
|
||||
}
|
||||
if (ImGui::MenuItem("Format Comparison...")) {
|
||||
// Open format comparison tool
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderPerformanceMenu() {
|
||||
if (ImGui::BeginMenu(ICON_MD_TRENDING_UP " Performance")) {
|
||||
auto& profiler = gfx::PerformanceProfiler::Get();
|
||||
auto canvas_stats = profiler.GetStats("canvas_operations");
|
||||
auto draw_stats = profiler.GetStats("canvas_draw");
|
||||
|
||||
ImGui::Text("Canvas Operations: %zu", canvas_stats.sample_count);
|
||||
ImGui::Text("Average Time: %.2f ms", draw_stats.avg_time_us / 1000.0);
|
||||
|
||||
if (ImGui::MenuItem("Performance Dashboard...")) {
|
||||
gfx::PerformanceDashboard::Get().SetVisible(true);
|
||||
}
|
||||
if (ImGui::MenuItem("Usage Report...")) {
|
||||
// Open usage report
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderGridControlsMenu(
|
||||
const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config) {
|
||||
if (ImGui::BeginMenu(ICON_MD_GRID_ON " Grid Controls")) {
|
||||
const struct GridOption {
|
||||
const char* label;
|
||||
float value;
|
||||
} options[] = {{"8x8", 8.0F}, {"16x16", 16.0F},
|
||||
{"32x32", 32.0F}, {"64x64", 64.0F}};
|
||||
|
||||
for (const auto& option : options) {
|
||||
bool selected = grid_step_ == option.value;
|
||||
if (ImGui::MenuItem(option.label, nullptr, selected)) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.grid_step = option.value;
|
||||
Dispatch(command_handler, Command::kSetGridStep, updated);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderScalingControlsMenu(
|
||||
const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config) {
|
||||
if (ImGui::BeginMenu(ICON_MD_ZOOM_IN " Scaling Controls")) {
|
||||
const struct ScaleOption {
|
||||
const char* label;
|
||||
float value;
|
||||
} options[] = {{"0.25x", 0.25F}, {"0.5x", 0.5F}, {"1x", 1.0F},
|
||||
{"2x", 2.0F}, {"4x", 4.0F}, {"8x", 8.0F}};
|
||||
|
||||
for (const auto& option : options) {
|
||||
if (ImGui::MenuItem(option.label)) {
|
||||
CanvasConfig updated = current_config;
|
||||
updated.global_scale = option.value;
|
||||
Dispatch(command_handler, Command::kSetScale, updated);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::RenderMaterialIcon(const std::string& icon_name, const ImVec4& color) {
|
||||
// Simple material icon rendering using Unicode symbols
|
||||
static std::unordered_map<std::string, const char*> icon_map = {
|
||||
{"grid_on", ICON_MD_GRID_ON}, {"label", ICON_MD_LABEL}, {"edit", ICON_MD_EDIT}, {"menu", ICON_MD_MENU},
|
||||
{"drag_indicator", ICON_MD_DRAG_INDICATOR}, {"fit_screen", ICON_MD_FIT_SCREEN}, {"zoom_in", ICON_MD_ZOOM_IN},
|
||||
{"speed", ICON_MD_SPEED}, {"timer", ICON_MD_TIMER}, {"functions", ICON_MD_FUNCTIONS}, {"schedule", ICON_MD_SCHEDULE},
|
||||
{"refresh", ICON_MD_REFRESH}, {"settings", ICON_MD_SETTINGS}, {"info", ICON_MD_INFO},
|
||||
{"view", ICON_MD_VISIBILITY}, {"properties", ICON_MD_SETTINGS}, {"bitmap", ICON_MD_IMAGE}, {"palette", ICON_MD_PALETTE},
|
||||
{"bpp", ICON_MD_SWAP_HORIZ}, {"performance", ICON_MD_TRENDING_UP}, {"grid", ICON_MD_GRID_ON}, {"scaling", ICON_MD_ZOOM_IN}
|
||||
};
|
||||
|
||||
auto it = icon_map.find(icon_name);
|
||||
if (it != icon_map.end()) {
|
||||
ImGui::TextColored(color, "%s", it->second);
|
||||
}
|
||||
}
|
||||
|
||||
std::string CanvasContextMenu::GetUsageModeName(CanvasUsage usage) const {
|
||||
switch (usage) {
|
||||
case CanvasUsage::kTilePainting: return "Tile Painting";
|
||||
case CanvasUsage::kTileSelecting: return "Tile Selecting";
|
||||
case CanvasUsage::kSelectRectangle: return "Rectangle Selection";
|
||||
case CanvasUsage::kColorPainting: return "Color Painting";
|
||||
case CanvasUsage::kBitmapEditing: return "Bitmap Editing";
|
||||
case CanvasUsage::kPaletteEditing: return "Palette Editing";
|
||||
case CanvasUsage::kBppConversion: return "BPP Conversion";
|
||||
case CanvasUsage::kPerformanceMode: return "Performance Mode";
|
||||
case CanvasUsage::kUnknown: return "Unknown";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ImVec4 CanvasContextMenu::GetUsageModeColor(CanvasUsage usage) const {
|
||||
switch (usage) {
|
||||
case CanvasUsage::kTilePainting: return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green
|
||||
case CanvasUsage::kTileSelecting: return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue
|
||||
case CanvasUsage::kSelectRectangle: return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow
|
||||
case CanvasUsage::kColorPainting: return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta
|
||||
case CanvasUsage::kBitmapEditing: return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange
|
||||
case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple
|
||||
case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan
|
||||
case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red
|
||||
case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray
|
||||
default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasContextMenu::CreateDefaultMenuItems() {
|
||||
// Create default menu items for different usage modes
|
||||
|
||||
// Tile Painting mode items
|
||||
ContextMenuItem tile_paint_item("Paint Tile", "paint", []() {
|
||||
// Tile painting action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kTilePainting].push_back(tile_paint_item);
|
||||
|
||||
// Tile Selecting mode items
|
||||
ContextMenuItem tile_select_item("Select Tile", "select", []() {
|
||||
// Tile selection action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kTileSelecting].push_back(tile_select_item);
|
||||
|
||||
// Rectangle Selection mode items
|
||||
ContextMenuItem rect_select_item("Select Rectangle", "rect", []() {
|
||||
// Rectangle selection action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kSelectRectangle].push_back(rect_select_item);
|
||||
|
||||
// Color Painting mode items
|
||||
ContextMenuItem color_paint_item("Paint Color", "color", []() {
|
||||
// Color painting action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kColorPainting].push_back(color_paint_item);
|
||||
|
||||
// Bitmap Editing mode items
|
||||
ContextMenuItem bitmap_edit_item("Edit Bitmap", "edit", []() {
|
||||
// Bitmap editing action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kBitmapEditing].push_back(bitmap_edit_item);
|
||||
|
||||
// Palette Editing mode items
|
||||
ContextMenuItem palette_edit_item("Edit Palette", "palette", []() {
|
||||
// Palette editing action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kPaletteEditing].push_back(palette_edit_item);
|
||||
|
||||
// BPP Conversion mode items
|
||||
ContextMenuItem bpp_convert_item("Convert Format", "convert", []() {
|
||||
// BPP conversion action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kBppConversion].push_back(bpp_convert_item);
|
||||
|
||||
// Performance Mode items
|
||||
ContextMenuItem perf_item("Performance Analysis", "perf", []() {
|
||||
// Performance analysis action
|
||||
});
|
||||
usage_specific_items_[CanvasUsage::kPerformanceMode].push_back(perf_item);
|
||||
}
|
||||
|
||||
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateViewMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback) {
|
||||
return ContextMenuItem(label, icon, callback);
|
||||
}
|
||||
|
||||
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBitmapMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback) {
|
||||
return ContextMenuItem(label, icon, callback);
|
||||
}
|
||||
|
||||
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePaletteMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback) {
|
||||
return ContextMenuItem(label, icon, callback);
|
||||
}
|
||||
|
||||
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreateBppMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback) {
|
||||
return ContextMenuItem(label, icon, callback);
|
||||
}
|
||||
|
||||
CanvasContextMenu::ContextMenuItem CanvasContextMenu::CreatePerformanceMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback) {
|
||||
return ContextMenuItem(label, icon, callback);
|
||||
}
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
153
src/app/gui/canvas/canvas_context_menu.h
Normal file
153
src/app/gui/canvas/canvas_context_menu.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_CONTEXT_MENU_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_CONTEXT_MENU_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "gui/canvas/canvas_modals.h"
|
||||
#include "canvas_usage_tracker.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
class CanvasContextMenu {
|
||||
public:
|
||||
enum class Command {
|
||||
kNone,
|
||||
kResetView,
|
||||
kZoomToFit,
|
||||
kZoomIn,
|
||||
kZoomOut,
|
||||
kToggleGrid,
|
||||
kToggleHexLabels,
|
||||
kToggleCustomLabels,
|
||||
kToggleContextMenu,
|
||||
kToggleAutoResize,
|
||||
kToggleDraggable,
|
||||
kOpenAdvancedProperties,
|
||||
kOpenScalingControls,
|
||||
kSetGridStep,
|
||||
kSetScale,
|
||||
};
|
||||
|
||||
CanvasContextMenu() = default;
|
||||
|
||||
struct ContextMenuItem {
|
||||
std::string label;
|
||||
std::string shortcut;
|
||||
std::string icon;
|
||||
std::function<void()> callback;
|
||||
std::function<bool()> enabled_condition = []() { return true; };
|
||||
std::function<bool()> visible_condition = []() { return true; };
|
||||
std::vector<ContextMenuItem> subitems;
|
||||
ImVec4 color = ImVec4(1, 1, 1, 1);
|
||||
bool separator_after = false;
|
||||
|
||||
ContextMenuItem() = default;
|
||||
ContextMenuItem(const std::string& lbl, const std::string& ico,
|
||||
std::function<void()> cb, const std::string& sc = "")
|
||||
: label(lbl), shortcut(sc), icon(ico), callback(std::move(cb)) {}
|
||||
};
|
||||
|
||||
void Initialize(const std::string& canvas_id);
|
||||
void SetUsageMode(CanvasUsage usage);
|
||||
void AddMenuItem(const ContextMenuItem& item);
|
||||
void AddMenuItem(const ContextMenuItem& item, CanvasUsage usage);
|
||||
void ClearMenuItems();
|
||||
|
||||
void Render(const std::string& context_id,
|
||||
const ImVec2& mouse_pos,
|
||||
const gfx::Bitmap* bitmap,
|
||||
const gfx::SnesPalette* palette,
|
||||
const std::function<void(Command, const canvas::CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config);
|
||||
|
||||
bool ShouldShowContextMenu() const;
|
||||
void SetEnabled(bool enabled) { enabled_ = enabled; }
|
||||
bool IsEnabled() const { return enabled_; }
|
||||
CanvasUsage GetUsageMode() const { return current_usage_; }
|
||||
|
||||
void SetCanvasState(const ImVec2& canvas_size,
|
||||
const ImVec2& content_size,
|
||||
float global_scale,
|
||||
float grid_step,
|
||||
bool enable_grid,
|
||||
bool enable_hex_labels,
|
||||
bool enable_custom_labels,
|
||||
bool enable_context_menu,
|
||||
bool is_draggable,
|
||||
bool auto_resize,
|
||||
const ImVec2& scrolling);
|
||||
|
||||
private:
|
||||
std::string canvas_id_;
|
||||
bool enabled_ = true;
|
||||
CanvasUsage current_usage_ = CanvasUsage::kTilePainting;
|
||||
|
||||
ImVec2 canvas_size_;
|
||||
ImVec2 content_size_;
|
||||
float global_scale_ = 1.0f;
|
||||
float grid_step_ = 32.0f;
|
||||
bool enable_grid_ = true;
|
||||
bool enable_hex_labels_ = false;
|
||||
bool enable_custom_labels_ = false;
|
||||
bool enable_context_menu_ = true;
|
||||
bool is_draggable_ = false;
|
||||
bool auto_resize_ = false;
|
||||
ImVec2 scrolling_;
|
||||
|
||||
std::unordered_map<CanvasUsage, std::vector<ContextMenuItem>> usage_specific_items_;
|
||||
std::vector<ContextMenuItem> global_items_;
|
||||
|
||||
void RenderMenuItem(const ContextMenuItem& item);
|
||||
void RenderMenuSection(const std::string& title,
|
||||
const std::vector<ContextMenuItem>& items);
|
||||
void RenderUsageSpecificMenu();
|
||||
void RenderViewControlsMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config);
|
||||
void RenderCanvasPropertiesMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config);
|
||||
void RenderBitmapOperationsMenu(const gfx::Bitmap* bitmap);
|
||||
void RenderPaletteOperationsMenu(const gfx::SnesPalette* palette);
|
||||
void RenderBppOperationsMenu(const gfx::Bitmap* bitmap);
|
||||
void RenderPerformanceMenu();
|
||||
void RenderGridControlsMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config);
|
||||
void RenderScalingControlsMenu(const std::function<void(Command, const CanvasConfig&)>& command_handler,
|
||||
CanvasConfig current_config);
|
||||
|
||||
void RenderMaterialIcon(const std::string& icon_name,
|
||||
const ImVec4& color = ImVec4(1, 1, 1, 1));
|
||||
std::string GetUsageModeName(CanvasUsage usage) const;
|
||||
ImVec4 GetUsageModeColor(CanvasUsage usage) const;
|
||||
|
||||
void CreateDefaultMenuItems();
|
||||
ContextMenuItem CreateViewMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback);
|
||||
ContextMenuItem CreateBitmapMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback);
|
||||
ContextMenuItem CreatePaletteMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback);
|
||||
ContextMenuItem CreateBppMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback);
|
||||
ContextMenuItem CreatePerformanceMenuItem(const std::string& label,
|
||||
const std::string& icon,
|
||||
std::function<void()> callback);
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_CONTEXT_MENU_H
|
||||
369
src/app/gui/canvas/canvas_interaction_handler.cc
Normal file
369
src/app/gui/canvas/canvas_interaction_handler.cc
Normal file
@@ -0,0 +1,369 @@
|
||||
#include "canvas_interaction_handler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
namespace {
|
||||
|
||||
// Helper function to align position to grid
|
||||
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
|
||||
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
|
||||
std::floor(pos.y / grid_step) * grid_step);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CanvasInteractionHandler::Initialize(const std::string& canvas_id) {
|
||||
canvas_id_ = canvas_id;
|
||||
ClearState();
|
||||
}
|
||||
|
||||
void CanvasInteractionHandler::ClearState() {
|
||||
hover_points_.clear();
|
||||
selected_points_.clear();
|
||||
selected_tiles_.clear();
|
||||
drawn_tile_pos_ = ImVec2(-1, -1);
|
||||
mouse_pos_in_canvas_ = ImVec2(0, 0);
|
||||
selected_tile_pos_ = ImVec2(-1, -1);
|
||||
rect_select_active_ = false;
|
||||
}
|
||||
|
||||
TileInteractionResult CanvasInteractionHandler::Update(
|
||||
ImVec2 canvas_p0, ImVec2 scrolling, float /*global_scale*/, float /*tile_size*/,
|
||||
ImVec2 /*canvas_size*/, bool is_hovered) {
|
||||
|
||||
TileInteractionResult result;
|
||||
|
||||
if (!is_hovered) {
|
||||
hover_points_.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
mouse_pos_in_canvas_ = ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
|
||||
// Update based on current mode - each mode is handled by its specific Draw method
|
||||
// This method exists for future state updates if needed
|
||||
(void)current_mode_; // Suppress unused warning
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::DrawTilePainter(
|
||||
const gfx::Bitmap& bitmap, ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) {
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
const auto scaled_size = tile_size * global_scale;
|
||||
|
||||
// Clear hover when not hovering
|
||||
if (!is_hovered) {
|
||||
hover_points_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset previous hover
|
||||
hover_points_.clear();
|
||||
|
||||
// Calculate grid-aligned paint position
|
||||
ImVec2 paint_pos = AlignToGrid(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);
|
||||
|
||||
hover_points_.push_back(paint_pos);
|
||||
hover_points_.push_back(paint_pos_end);
|
||||
|
||||
// Draw preview of tile at hover position
|
||||
if (bitmap.is_active() && draw_list) {
|
||||
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));
|
||||
}
|
||||
|
||||
// Check for paint action
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
drawn_tile_pos_ = paint_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::DrawTilemapPainter(
|
||||
gfx::Tilemap& tilemap, int current_tile, ImDrawList* draw_list,
|
||||
ImVec2 canvas_p0, ImVec2 scrolling, float global_scale, bool is_hovered) {
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
|
||||
// Safety check
|
||||
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) {
|
||||
hover_points_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
hover_points_.clear();
|
||||
|
||||
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||
mouse_pos_in_canvas_ = paint_pos;
|
||||
|
||||
hover_points_.push_back(paint_pos);
|
||||
hover_points_.push_back(ImVec2(paint_pos.x + scaled_size, paint_pos.y + scaled_size));
|
||||
|
||||
// Draw tile preview from atlas
|
||||
if (tilemap.atlas.is_active() && tilemap.atlas.texture() && draw_list) {
|
||||
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;
|
||||
|
||||
if (tile_x >= 0 && tile_x < tilemap.atlas.width() &&
|
||||
tile_y >= 0 && tile_y < tilemap.atlas.height()) {
|
||||
|
||||
ImVec2 uv0 = ImVec2(static_cast<float>(tile_x) / tilemap.atlas.width(),
|
||||
static_cast<float>(tile_y) / tilemap.atlas.height());
|
||||
ImVec2 uv1 = ImVec2(static_cast<float>(tile_x + tilemap.tile_size.x) / tilemap.atlas.width(),
|
||||
static_cast<float>(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 (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
|
||||
ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||
drawn_tile_pos_ = paint_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::DrawSolidTilePainter(
|
||||
const ImVec4& color, ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, float global_scale, float tile_size, bool is_hovered) {
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
auto scaled_tile_size = tile_size * global_scale;
|
||||
static bool is_dragging = false;
|
||||
static ImVec2 start_drag_pos;
|
||||
|
||||
if (!is_hovered) {
|
||||
hover_points_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
hover_points_.clear();
|
||||
|
||||
ImVec2 paint_pos = AlignToGrid(mouse_pos, scaled_tile_size);
|
||||
mouse_pos_in_canvas_ = paint_pos;
|
||||
|
||||
// Clamp to canvas bounds (assuming canvas_size from Update)
|
||||
// For now, skip clamping as we don't have canvas_size here
|
||||
|
||||
hover_points_.push_back(paint_pos);
|
||||
hover_points_.push_back(ImVec2(paint_pos.x + scaled_tile_size, paint_pos.y + scaled_tile_size));
|
||||
|
||||
if (draw_list) {
|
||||
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 (ImGui::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;
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::DrawTileSelector(
|
||||
ImDrawList* /*draw_list*/, ImVec2 canvas_p0, ImVec2 scrolling, float tile_size,
|
||||
bool is_hovered) {
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
|
||||
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
hover_points_.clear();
|
||||
ImVec2 painter_pos = AlignToGrid(mouse_pos, tile_size);
|
||||
|
||||
hover_points_.push_back(painter_pos);
|
||||
hover_points_.push_back(ImVec2(painter_pos.x + tile_size, painter_pos.y + tile_size));
|
||||
mouse_pos_in_canvas_ = painter_pos;
|
||||
}
|
||||
|
||||
if (is_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::DrawSelectRect(
|
||||
int current_map, ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
float global_scale, float tile_size, bool is_hovered) {
|
||||
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
const ImVec2 mouse_pos(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
const float scaled_size = tile_size * global_scale;
|
||||
static ImVec2 drag_start_pos;
|
||||
static bool dragging = false;
|
||||
constexpr int small_map_size = 0x200;
|
||||
|
||||
if (!is_hovered) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate superX and superY accounting for world offset
|
||||
int super_y = 0;
|
||||
int super_x = 0;
|
||||
if (current_map < 0x40) {
|
||||
super_y = current_map / 8;
|
||||
super_x = current_map % 8;
|
||||
} else if (current_map < 0x80) {
|
||||
super_y = (current_map - 0x40) / 8;
|
||||
super_x = (current_map - 0x40) % 8;
|
||||
} else {
|
||||
super_y = (current_map - 0x80) / 8;
|
||||
super_x = (current_map - 0x80) % 8;
|
||||
}
|
||||
|
||||
// Handle right click for single tile selection
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||
ImVec2 painter_pos = AlignToGrid(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 = super_x * 0x20 + tile16_x;
|
||||
int index_y = super_y * 0x20 + tile16_y;
|
||||
selected_tile_pos_ = ImVec2(index_x, index_y);
|
||||
selected_points_.clear();
|
||||
rect_select_active_ = false;
|
||||
|
||||
drag_start_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||
}
|
||||
|
||||
// Draw rectangle while dragging
|
||||
ImVec2 drag_end_pos = AlignToGrid(mouse_pos, scaled_size);
|
||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && draw_list) {
|
||||
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, IM_COL32(255, 255, 255, 255));
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
// Complete selection on release
|
||||
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||
dragging = false;
|
||||
|
||||
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;
|
||||
|
||||
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));
|
||||
|
||||
constexpr int tiles_per_local_map = small_map_size / 16;
|
||||
|
||||
for (int tile_y = start_y; tile_y <= end_y; tile_y += tile16_size) {
|
||||
for (int tile_x = start_x; tile_x <= end_x; tile_x += tile16_size) {
|
||||
int local_map_x = tile_x / small_map_size;
|
||||
int local_map_y = tile_y / small_map_size;
|
||||
|
||||
int tile16_x = (tile_x % small_map_size) / tile16_size;
|
||||
int tile16_y = (tile_y % small_map_size) / tile16_size;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
selected_points_.clear();
|
||||
selected_points_.push_back(drag_start_pos);
|
||||
selected_points_.push_back(drag_end_pos);
|
||||
rect_select_active_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper methods - these are thin wrappers that could be static but kept as instance
|
||||
// methods for potential future state access
|
||||
ImVec2 CanvasInteractionHandler::AlignPosToGrid(ImVec2 pos, float grid_step) {
|
||||
return AlignToGrid(pos, grid_step);
|
||||
}
|
||||
|
||||
ImVec2 CanvasInteractionHandler::GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling) {
|
||||
const ImGuiIO& imgui_io = ImGui::GetIO();
|
||||
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||
return ImVec2(imgui_io.MousePos.x - origin.x, imgui_io.MousePos.y - origin.y);
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::IsMouseClicked(ImGuiMouseButton button) {
|
||||
return ImGui::IsMouseClicked(button);
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::IsMouseDoubleClicked(ImGuiMouseButton button) {
|
||||
return ImGui::IsMouseDoubleClicked(button);
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::IsMouseDragging(ImGuiMouseButton button) {
|
||||
return ImGui::IsMouseDragging(button);
|
||||
}
|
||||
|
||||
bool CanvasInteractionHandler::IsMouseReleased(ImGuiMouseButton button) {
|
||||
return ImGui::IsMouseReleased(button);
|
||||
}
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
208
src/app/gui/canvas/canvas_interaction_handler.h
Normal file
208
src/app/gui/canvas/canvas_interaction_handler.h
Normal file
@@ -0,0 +1,208 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_HANDLER_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_HANDLER_H
|
||||
|
||||
#include <vector>
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/tilemap.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
/**
|
||||
* @brief Tile interaction mode for canvas
|
||||
*/
|
||||
enum class TileInteractionMode {
|
||||
kNone, // No interaction
|
||||
kPaintSingle, // Paint single tiles
|
||||
kPaintDrag, // Paint while dragging
|
||||
kSelectSingle, // Select single tile
|
||||
kSelectRectangle, // Select rectangular region
|
||||
kColorPaint // Paint with solid color
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Result of a tile interaction operation
|
||||
*/
|
||||
struct TileInteractionResult {
|
||||
bool interaction_occurred = false;
|
||||
ImVec2 tile_position = ImVec2(-1, -1);
|
||||
std::vector<ImVec2> selected_tiles;
|
||||
int tile_id = -1;
|
||||
|
||||
void Reset() {
|
||||
interaction_occurred = false;
|
||||
tile_position = ImVec2(-1, -1);
|
||||
selected_tiles.clear();
|
||||
tile_id = -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Handles all tile-based interactions for Canvas
|
||||
*
|
||||
* Consolidates tile painting, selection, and multi-selection logic
|
||||
* that was previously scattered across Canvas methods. Provides a
|
||||
* unified interface for common tile interaction patterns.
|
||||
*
|
||||
* Key Features:
|
||||
* - Single tile painting with preview
|
||||
* - Drag painting for continuous tile placement
|
||||
* - Single tile selection
|
||||
* - Rectangle selection for multi-tile operations
|
||||
* - Color painting mode
|
||||
* - Grid-aligned positioning
|
||||
* - Hover preview
|
||||
*/
|
||||
class CanvasInteractionHandler {
|
||||
public:
|
||||
CanvasInteractionHandler() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize the interaction handler
|
||||
*/
|
||||
void Initialize(const std::string& canvas_id);
|
||||
|
||||
/**
|
||||
* @brief Set the interaction mode
|
||||
*/
|
||||
void SetMode(TileInteractionMode mode) { current_mode_ = mode; }
|
||||
TileInteractionMode GetMode() const { return current_mode_; }
|
||||
|
||||
/**
|
||||
* @brief Update interaction state (call once per frame)
|
||||
* @param canvas_p0 Canvas top-left screen position
|
||||
* @param scrolling Canvas scroll offset
|
||||
* @param global_scale Canvas zoom scale
|
||||
* @param tile_size Logical tile size
|
||||
* @param canvas_size Canvas dimensions
|
||||
* @param is_hovered Whether mouse is over canvas
|
||||
* @return Interaction result for this frame
|
||||
*/
|
||||
TileInteractionResult Update(ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
float global_scale, float tile_size,
|
||||
ImVec2 canvas_size, bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Draw tile painter (preview + interaction)
|
||||
* @param bitmap Tile bitmap to paint
|
||||
* @param draw_list ImGui draw list
|
||||
* @param canvas_p0 Canvas top-left position
|
||||
* @param scrolling Canvas scroll offset
|
||||
* @param global_scale Canvas zoom scale
|
||||
* @param tile_size Logical tile size
|
||||
* @param is_hovered Whether mouse is over canvas
|
||||
* @return True if tile was painted
|
||||
*/
|
||||
bool DrawTilePainter(const gfx::Bitmap& bitmap, ImDrawList* draw_list,
|
||||
ImVec2 canvas_p0, ImVec2 scrolling, float global_scale,
|
||||
float tile_size, bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Draw tilemap painter (preview + interaction)
|
||||
*/
|
||||
bool DrawTilemapPainter(gfx::Tilemap& tilemap, int current_tile,
|
||||
ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, float global_scale, bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Draw solid color painter
|
||||
*/
|
||||
bool DrawSolidTilePainter(const ImVec4& color, ImDrawList* draw_list,
|
||||
ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
float global_scale, float tile_size, bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Draw tile selector (single tile selection)
|
||||
*/
|
||||
bool DrawTileSelector(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, float tile_size, bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Draw rectangle selector (multi-tile selection)
|
||||
* @param current_map Map ID for coordinate calculation
|
||||
* @param draw_list ImGui draw list
|
||||
* @param canvas_p0 Canvas position
|
||||
* @param scrolling Scroll offset
|
||||
* @param global_scale Zoom scale
|
||||
* @param tile_size Tile size
|
||||
* @param is_hovered Whether mouse is over canvas
|
||||
* @return True if selection was made
|
||||
*/
|
||||
bool DrawSelectRect(int current_map, ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, float global_scale, float tile_size,
|
||||
bool is_hovered);
|
||||
|
||||
/**
|
||||
* @brief Get current hover points (for DrawOverlay)
|
||||
*/
|
||||
const ImVector<ImVec2>& GetHoverPoints() const { return hover_points_; }
|
||||
|
||||
/**
|
||||
* @brief Get selected points (for DrawOverlay)
|
||||
*/
|
||||
const ImVector<ImVec2>& GetSelectedPoints() const { return selected_points_; }
|
||||
|
||||
/**
|
||||
* @brief Get selected tiles from last rectangle selection
|
||||
*/
|
||||
const std::vector<ImVec2>& GetSelectedTiles() const { return selected_tiles_; }
|
||||
|
||||
/**
|
||||
* @brief Get last drawn tile position
|
||||
*/
|
||||
ImVec2 GetDrawnTilePosition() const { return drawn_tile_pos_; }
|
||||
|
||||
/**
|
||||
* @brief Get current mouse position in canvas space
|
||||
*/
|
||||
ImVec2 GetMousePositionInCanvas() const { return mouse_pos_in_canvas_; }
|
||||
|
||||
/**
|
||||
* @brief Clear all interaction state
|
||||
*/
|
||||
void ClearState();
|
||||
|
||||
/**
|
||||
* @brief Check if rectangle selection is active
|
||||
*/
|
||||
bool IsRectSelectActive() const { return rect_select_active_; }
|
||||
|
||||
/**
|
||||
* @brief Get selected tile position (for single selection)
|
||||
*/
|
||||
ImVec2 GetSelectedTilePosition() const { return selected_tile_pos_; }
|
||||
|
||||
/**
|
||||
* @brief Set selected tile position
|
||||
*/
|
||||
void SetSelectedTilePosition(ImVec2 pos) { selected_tile_pos_ = pos; }
|
||||
|
||||
private:
|
||||
std::string canvas_id_;
|
||||
TileInteractionMode current_mode_ = TileInteractionMode::kNone;
|
||||
|
||||
// Interaction state
|
||||
ImVector<ImVec2> hover_points_; // Current hover preview points
|
||||
ImVector<ImVec2> selected_points_; // Selected rectangle points
|
||||
std::vector<ImVec2> selected_tiles_; // Selected tiles from rect
|
||||
ImVec2 drawn_tile_pos_ = ImVec2(-1, -1); // Last drawn tile position
|
||||
ImVec2 mouse_pos_in_canvas_ = ImVec2(0, 0); // Current mouse in canvas space
|
||||
ImVec2 selected_tile_pos_ = ImVec2(-1, -1); // Single tile selection
|
||||
bool rect_select_active_ = false;
|
||||
|
||||
// Helper methods
|
||||
ImVec2 AlignPosToGrid(ImVec2 pos, float grid_step);
|
||||
ImVec2 GetMousePosition(ImVec2 canvas_p0, ImVec2 scrolling);
|
||||
bool IsMouseClicked(ImGuiMouseButton button);
|
||||
bool IsMouseDoubleClicked(ImGuiMouseButton button);
|
||||
bool IsMouseDragging(ImGuiMouseButton button);
|
||||
bool IsMouseReleased(ImGuiMouseButton button);
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_INTERACTION_HANDLER_H
|
||||
591
src/app/gui/canvas/canvas_modals.cc
Normal file
591
src/app/gui/canvas/canvas_modals.cc
Normal file
@@ -0,0 +1,591 @@
|
||||
#include "canvas_modals.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/performance_dashboard.h"
|
||||
#include "app/gui/enhanced_palette_editor.h"
|
||||
#include "app/gui/bpp_format_ui.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
namespace {
|
||||
void DispatchConfigCallback(const std::function<void(const CanvasConfig&)>& callback,
|
||||
const CanvasConfig& config) {
|
||||
if (callback) {
|
||||
callback(config);
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchScaleCallback(const std::function<void(const CanvasConfig&)>& callback,
|
||||
const CanvasConfig& config) {
|
||||
if (callback) {
|
||||
callback(config);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CanvasModals::ShowAdvancedProperties(const std::string& canvas_id,
|
||||
const CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap) {
|
||||
|
||||
std::string modal_id = canvas_id + "_advanced_properties";
|
||||
|
||||
auto render_func = [=]() mutable {
|
||||
CanvasConfig mutable_config = config; // Create mutable copy
|
||||
mutable_config.on_config_changed = config.on_config_changed;
|
||||
mutable_config.on_scale_changed = config.on_scale_changed;
|
||||
RenderAdvancedPropertiesModal(modal_id, mutable_config, bitmap);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::ShowScalingControls(const std::string& canvas_id,
|
||||
const CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap) {
|
||||
|
||||
std::string modal_id = canvas_id + "_scaling_controls";
|
||||
|
||||
auto render_func = [=]() mutable {
|
||||
CanvasConfig mutable_config = config; // Create mutable copy
|
||||
mutable_config.on_config_changed = config.on_config_changed;
|
||||
mutable_config.on_scale_changed = config.on_scale_changed;
|
||||
RenderScalingControlsModal(modal_id, mutable_config, bitmap);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::ShowBppConversionDialog(const std::string& canvas_id,
|
||||
const BppConversionOptions& options) {
|
||||
|
||||
std::string modal_id = canvas_id + "_bpp_conversion";
|
||||
|
||||
auto render_func = [=]() {
|
||||
RenderBppConversionModal(modal_id, options);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::ShowPaletteEditor(const std::string& canvas_id,
|
||||
const PaletteEditorOptions& options) {
|
||||
|
||||
std::string modal_id = canvas_id + "_palette_editor";
|
||||
|
||||
auto render_func = [=]() {
|
||||
RenderPaletteEditorModal(modal_id, options);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::ShowColorAnalysis(const std::string& canvas_id,
|
||||
const ColorAnalysisOptions& options) {
|
||||
|
||||
std::string modal_id = canvas_id + "_color_analysis";
|
||||
|
||||
auto render_func = [=]() {
|
||||
RenderColorAnalysisModal(modal_id, options);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::ShowPerformanceIntegration(const std::string& canvas_id,
|
||||
const PerformanceOptions& options) {
|
||||
|
||||
std::string modal_id = canvas_id + "_performance";
|
||||
|
||||
auto render_func = [=]() {
|
||||
RenderPerformanceModal(modal_id, options);
|
||||
};
|
||||
|
||||
OpenModal(modal_id, render_func);
|
||||
}
|
||||
|
||||
void CanvasModals::Render() {
|
||||
for (auto& modal : active_modals_) {
|
||||
if (modal.is_open) {
|
||||
modal.render_func();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove closed modals
|
||||
active_modals_.erase(
|
||||
std::remove_if(active_modals_.begin(), active_modals_.end(),
|
||||
[](const ModalState& modal) { return !modal.is_open; }),
|
||||
active_modals_.end());
|
||||
}
|
||||
|
||||
bool CanvasModals::IsAnyModalOpen() const {
|
||||
return std::any_of(active_modals_.begin(), active_modals_.end(),
|
||||
[](const ModalState& modal) { return modal.is_open; });
|
||||
}
|
||||
|
||||
void CanvasModals::RenderAdvancedPropertiesModal(const std::string& canvas_id,
|
||||
CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap) {
|
||||
|
||||
std::string modal_title = "Advanced Canvas Properties";
|
||||
ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_SETTINGS, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Canvas Information Section
|
||||
if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Canvas Information", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Columns(2, "CanvasInfo");
|
||||
|
||||
RenderMetricCard("Canvas Size",
|
||||
std::to_string(static_cast<int>(config.canvas_size.x)) + " x " +
|
||||
std::to_string(static_cast<int>(config.canvas_size.y)),
|
||||
ICON_MD_STRAIGHTEN, ImVec4(0.2F, 0.8F, 1.0F, 1.0F));
|
||||
|
||||
RenderMetricCard("Content Size",
|
||||
std::to_string(static_cast<int>(config.content_size.x)) + " x " +
|
||||
std::to_string(static_cast<int>(config.content_size.y)),
|
||||
ICON_MD_IMAGE, ImVec4(0.8F, 0.2F, 1.0F, 1.0F));
|
||||
|
||||
ImGui::NextColumn();
|
||||
|
||||
RenderMetricCard("Global Scale",
|
||||
std::to_string(static_cast<int>(config.global_scale * 100)) + "%",
|
||||
ICON_MD_ZOOM_IN, ImVec4(1.0F, 0.8F, 0.2F, 1.0F));
|
||||
|
||||
RenderMetricCard("Grid Step",
|
||||
std::to_string(static_cast<int>(config.grid_step)) + "px",
|
||||
ICON_MD_GRID_ON, ImVec4(0.2F, 1.0F, 0.2F, 1.0F));
|
||||
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
// View Settings Section
|
||||
if (ImGui::CollapsingHeader("👁️ View Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
ImGui::Checkbox("Show Grid", &config.enable_grid);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("grid_on");
|
||||
|
||||
ImGui::Checkbox("Show Hex Labels", &config.enable_hex_labels);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("label");
|
||||
|
||||
ImGui::Checkbox("Show Custom Labels", &config.enable_custom_labels);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("edit");
|
||||
|
||||
ImGui::Checkbox("Enable Context Menu", &config.enable_context_menu);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("menu");
|
||||
|
||||
ImGui::Checkbox("Draggable Canvas", &config.is_draggable);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("drag_indicator");
|
||||
|
||||
ImGui::Checkbox("Auto Resize for Tables", &config.auto_resize);
|
||||
ImGui::SameLine();
|
||||
RenderMaterialIcon("fit_screen");
|
||||
}
|
||||
|
||||
// Scale Controls Section
|
||||
if (ImGui::CollapsingHeader(ICON_MD_BUILD " Scale Controls", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
RenderSliderWithIcon("Global Scale", "zoom_in", &config.global_scale, 0.1f, 10.0f, "%.2f");
|
||||
RenderSliderWithIcon("Grid Step", "grid_on", &config.grid_step, 1.0f, 128.0f, "%.1f");
|
||||
|
||||
// Preset scale buttons
|
||||
ImGui::Text("Preset Scales:");
|
||||
ImGui::SameLine();
|
||||
|
||||
const char* preset_labels[] = {"0.25x", "0.5x", "1x", "2x", "4x", "8x"};
|
||||
const float preset_values[] = {0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f};
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i > 0) ImGui::SameLine();
|
||||
if (ImGui::Button(preset_labels[i])) {
|
||||
config.global_scale = preset_values[i];
|
||||
DispatchConfigCallback(config.on_config_changed, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scrolling Controls Section
|
||||
if (ImGui::CollapsingHeader("📜 Scrolling Controls")) {
|
||||
ImGui::Text("Current Scroll: %.1f, %.1f", config.scrolling.x, config.scrolling.y);
|
||||
|
||||
if (ImGui::Button("Reset Scroll")) {
|
||||
config.scrolling = ImVec2(0, 0);
|
||||
DispatchConfigCallback(config.on_config_changed, config);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Center View") && bitmap) {
|
||||
config.scrolling = ImVec2(-(bitmap->width() * config.global_scale - config.canvas_size.x) / 2.0f,
|
||||
-(bitmap->height() * config.global_scale - config.canvas_size.y) / 2.0f);
|
||||
DispatchConfigCallback(config.on_config_changed, config);
|
||||
}
|
||||
}
|
||||
|
||||
// Performance Integration Section
|
||||
if (ImGui::CollapsingHeader(ICON_MD_TRENDING_UP " Performance")) {
|
||||
auto& profiler = gfx::PerformanceProfiler::Get();
|
||||
|
||||
// Get stats for canvas operations
|
||||
auto canvas_stats = profiler.GetStats("canvas_operations");
|
||||
auto draw_stats = profiler.GetStats("canvas_draw");
|
||||
|
||||
RenderMetricCard("Canvas Operations",
|
||||
std::to_string(canvas_stats.sample_count) + " ops",
|
||||
"speed", ImVec4(0.2F, 1.0F, 0.2F, 1.0F));
|
||||
|
||||
RenderMetricCard("Average Time",
|
||||
std::to_string(draw_stats.avg_time_us / 1000.0) + " ms",
|
||||
"timer", ImVec4(1.0F, 0.8F, 0.2F, 1.0F));
|
||||
|
||||
if (ImGui::Button("Open Performance Dashboard")) {
|
||||
gfx::PerformanceDashboard::Get().SetVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Apply Changes", ImVec2(120, 0))) {
|
||||
DispatchConfigCallback(config.on_config_changed, config);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Reset to Defaults", ImVec2(150, 0))) {
|
||||
config.global_scale = 1.0f;
|
||||
config.grid_step = 32.0f;
|
||||
config.enable_grid = true;
|
||||
config.enable_hex_labels = false;
|
||||
config.enable_custom_labels = false;
|
||||
config.enable_context_menu = true;
|
||||
config.is_draggable = false;
|
||||
config.auto_resize = false;
|
||||
config.scrolling = ImVec2(0, 0);
|
||||
DispatchConfigCallback(config.on_config_changed, config);
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderScalingControlsModal(const std::string& canvas_id,
|
||||
CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap) {
|
||||
|
||||
std::string modal_title = "Canvas Scaling Controls";
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_ZOOM_IN, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Global Scale Section
|
||||
ImGui::Text("Global Scale: %.3f", config.global_scale);
|
||||
RenderSliderWithIcon("##GlobalScale", "zoom_in", &config.global_scale, 0.1f, 10.0f, "%.2f");
|
||||
|
||||
// Preset scale buttons
|
||||
ImGui::Text("Preset Scales:");
|
||||
const char* preset_labels[] = {"0.25x", "0.5x", "1x", "2x", "4x", "8x"};
|
||||
const float preset_values[] = {0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f};
|
||||
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i > 0) ImGui::SameLine();
|
||||
if (ImGui::Button(preset_labels[i])) {
|
||||
config.global_scale = preset_values[i];
|
||||
DispatchScaleCallback(config.on_scale_changed, config);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Grid Configuration Section
|
||||
ImGui::Text("Grid Step: %.1f", config.grid_step);
|
||||
RenderSliderWithIcon("##GridStep", "grid_on", &config.grid_step, 1.0f, 128.0f, "%.1f");
|
||||
|
||||
// Grid size presets
|
||||
ImGui::Text("Grid Presets:");
|
||||
const char* grid_labels[] = {"8x8", "16x16", "32x32", "64x64"};
|
||||
const float grid_values[] = {8.0f, 16.0f, 32.0f, 64.0f};
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (i > 0) ImGui::SameLine();
|
||||
if (ImGui::Button(grid_labels[i])) {
|
||||
config.grid_step = grid_values[i];
|
||||
DispatchScaleCallback(config.on_scale_changed, config);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
// Canvas Information Section
|
||||
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());
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Apply", ImVec2(120, 0))) {
|
||||
DispatchScaleCallback(config.on_scale_changed, config);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderBppConversionModal(const std::string& canvas_id,
|
||||
const BppConversionOptions& options) {
|
||||
|
||||
std::string modal_title = "BPP Format Conversion";
|
||||
ImGui::SetNextWindowSize(ImVec2(600, 500), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_SWAP_HORIZ, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Use the existing BppFormatUI for the conversion dialog
|
||||
static std::unique_ptr<gui::BppFormatUI> bpp_ui =
|
||||
std::make_unique<gui::BppFormatUI>(canvas_id + "_bpp_ui");
|
||||
|
||||
// Render the format selector
|
||||
if (options.bitmap && options.palette) {
|
||||
bpp_ui->RenderFormatSelector(const_cast<gfx::Bitmap*>(options.bitmap),
|
||||
*options.palette, options.on_convert);
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderPaletteEditorModal(const std::string& canvas_id,
|
||||
const PaletteEditorOptions& options) {
|
||||
|
||||
std::string modal_title = options.title.empty() ? "Palette Editor" : options.title;
|
||||
ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_PALETTE, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Use the existing EnhancedPaletteEditor
|
||||
static std::unique_ptr<gui::EnhancedPaletteEditor> palette_editor =
|
||||
std::make_unique<gui::EnhancedPaletteEditor>();
|
||||
|
||||
if (options.palette) {
|
||||
palette_editor->ShowPaletteEditor(*options.palette, modal_title);
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderColorAnalysisModal(const std::string& canvas_id,
|
||||
const ColorAnalysisOptions& options) {
|
||||
|
||||
std::string modal_title = "Color Analysis";
|
||||
ImGui::SetNextWindowSize(ImVec2(700, 500), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_ZOOM_IN, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Use the existing EnhancedPaletteEditor for color analysis
|
||||
static std::unique_ptr<gui::EnhancedPaletteEditor> palette_editor =
|
||||
std::make_unique<gui::EnhancedPaletteEditor>();
|
||||
|
||||
if (options.bitmap) {
|
||||
palette_editor->ShowColorAnalysis(*options.bitmap, modal_title);
|
||||
}
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderPerformanceModal(const std::string& canvas_id,
|
||||
const PerformanceOptions& options) {
|
||||
|
||||
std::string modal_title = "Canvas Performance";
|
||||
ImGui::SetNextWindowSize(ImVec2(500, 300), ImGuiCond_FirstUseEver);
|
||||
|
||||
if (ImGui::BeginPopupModal(modal_title.c_str(), nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
|
||||
// Header with icon
|
||||
ImGui::Text("%s %s", ICON_MD_TRENDING_UP, modal_title.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
// Performance metrics
|
||||
RenderMetricCard("Operation", options.operation_name, "speed", ImVec4(0.2f, 1.0f, 0.2f, 1.0f));
|
||||
RenderMetricCard("Time", std::to_string(options.operation_time_ms) + " ms", "timer", ImVec4(1.0f, 0.8f, 0.2f, 1.0f));
|
||||
|
||||
// Get overall performance stats
|
||||
auto& profiler = gfx::PerformanceProfiler::Get();
|
||||
auto canvas_stats = profiler.GetStats("canvas_operations");
|
||||
auto draw_stats = profiler.GetStats("canvas_draw");
|
||||
|
||||
RenderMetricCard("Total Operations", std::to_string(canvas_stats.sample_count), "functions", ImVec4(0.2F, 0.8F, 1.0F, 1.0F));
|
||||
RenderMetricCard("Average Time", std::to_string(draw_stats.avg_time_us / 1000.0) + " ms", "schedule", ImVec4(0.8F, 0.2F, 1.0F, 1.0F));
|
||||
|
||||
// Action Buttons
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
if (ImGui::Button("Open Dashboard", ImVec2(150, 0))) {
|
||||
gfx::PerformanceDashboard::Get().SetVisible(true);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button("Close", ImVec2(120, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::OpenModal(const std::string& id, std::function<void()> render_func) {
|
||||
// Check if modal already exists
|
||||
auto it = std::find_if(active_modals_.begin(), active_modals_.end(),
|
||||
[&id](const ModalState& modal) { return modal.id == id; });
|
||||
|
||||
if (it != active_modals_.end()) {
|
||||
it->is_open = true;
|
||||
it->render_func = render_func;
|
||||
} else {
|
||||
active_modals_.push_back({true, id, render_func});
|
||||
}
|
||||
|
||||
// Open the popup
|
||||
ImGui::OpenPopup(id.c_str());
|
||||
}
|
||||
|
||||
void CanvasModals::CloseModal(const std::string& id) {
|
||||
auto it = std::find_if(active_modals_.begin(), active_modals_.end(),
|
||||
[&id](const ModalState& modal) { return modal.id == id; });
|
||||
|
||||
if (it != active_modals_.end()) {
|
||||
it->is_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CanvasModals::IsModalOpen(const std::string& id) const {
|
||||
auto it = std::find_if(active_modals_.begin(), active_modals_.end(),
|
||||
[&id](const ModalState& modal) { return modal.id == id; });
|
||||
|
||||
return it != active_modals_.end() && it->is_open;
|
||||
}
|
||||
|
||||
void CanvasModals::RenderMaterialIcon(const std::string& icon_name, const ImVec4& color) {
|
||||
// Simple material icon rendering using Unicode symbols
|
||||
// In a real implementation, you'd use a proper icon font
|
||||
static std::unordered_map<std::string, const char*> icon_map = {
|
||||
{"grid_on", ICON_MD_GRID_ON}, {"label", ICON_MD_LABEL}, {"edit", ICON_MD_EDIT}, {"menu", ICON_MD_MENU},
|
||||
{"drag_indicator", ICON_MD_DRAG_INDICATOR}, {"fit_screen", ICON_MD_FIT_SCREEN}, {"zoom_in", ICON_MD_ZOOM_IN},
|
||||
{"speed", ICON_MD_SPEED}, {"timer", ICON_MD_TIMER}, {"functions", ICON_MD_FUNCTIONS}, {"schedule", ICON_MD_SCHEDULE},
|
||||
{"refresh", ICON_MD_REFRESH}, {"settings", ICON_MD_SETTINGS}, {"info", ICON_MD_INFO}
|
||||
};
|
||||
|
||||
auto it = icon_map.find(icon_name);
|
||||
if (it != icon_map.end()) {
|
||||
ImGui::TextColored(color, "%s", it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasModals::RenderMetricCard(const std::string& title, const std::string& value,
|
||||
const std::string& icon, const ImVec4& color) {
|
||||
ImGui::BeginGroup();
|
||||
|
||||
// Icon and title
|
||||
ImGui::Text("%s %s", icon.c_str(), title.c_str());
|
||||
|
||||
// Value with color
|
||||
ImGui::TextColored(color, "%s", value.c_str());
|
||||
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
||||
void CanvasModals::RenderSliderWithIcon(const std::string& label, const std::string& icon,
|
||||
float* value, float min_val, float max_val,
|
||||
const char* format) {
|
||||
ImGui::Text("%s %s", icon.c_str(), label.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(200);
|
||||
ImGui::SliderFloat(("##" + label).c_str(), value, min_val, max_val, format);
|
||||
}
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
182
src/app/gui/canvas/canvas_modals.h
Normal file
182
src/app/gui/canvas/canvas_modals.h
Normal file
@@ -0,0 +1,182 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_MODALS_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_MODALS_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/gfx/bpp_format_manager.h"
|
||||
#include "gui/canvas_utils.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
void DispatchConfigCallback(const std::function<void(const CanvasConfig&)>& callback,
|
||||
const CanvasConfig& config);
|
||||
void DispatchScaleCallback(const std::function<void(const CanvasConfig&)>& callback,
|
||||
const CanvasConfig& config);
|
||||
|
||||
/**
|
||||
* @brief Canvas configuration options for modals
|
||||
*/
|
||||
struct CanvasConfig {
|
||||
ImVec2 canvas_size = ImVec2(0, 0);
|
||||
ImVec2 content_size = ImVec2(0, 0);
|
||||
float global_scale = 1.0f;
|
||||
float grid_step = 32.0f;
|
||||
bool enable_grid = true;
|
||||
bool enable_hex_labels = false;
|
||||
bool enable_custom_labels = false;
|
||||
bool enable_context_menu = true;
|
||||
bool is_draggable = false;
|
||||
bool auto_resize = false;
|
||||
ImVec2 scrolling = ImVec2(0, 0);
|
||||
|
||||
// Callbacks provide updated configuration state
|
||||
std::function<void(const CanvasConfig&)> on_config_changed;
|
||||
std::function<void(const CanvasConfig&)> on_scale_changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief BPP conversion options
|
||||
*/
|
||||
struct BppConversionOptions {
|
||||
const gfx::Bitmap* bitmap = nullptr;
|
||||
const gfx::SnesPalette* palette = nullptr;
|
||||
std::function<void(gfx::BppFormat)> on_convert;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Palette editor options
|
||||
*/
|
||||
struct PaletteEditorOptions {
|
||||
gfx::SnesPalette* palette = nullptr;
|
||||
std::string title = "Palette Editor";
|
||||
std::function<void()> on_palette_changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Color analysis options
|
||||
*/
|
||||
struct ColorAnalysisOptions {
|
||||
const gfx::Bitmap* bitmap = nullptr;
|
||||
const gfx::SnesPalette* palette = nullptr;
|
||||
std::string title = "Color Analysis";
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Performance integration options
|
||||
*/
|
||||
struct PerformanceOptions {
|
||||
std::string operation_name;
|
||||
double operation_time_ms = 0.0;
|
||||
std::function<void()> on_dashboard_request;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Modal dialog management for canvas operations
|
||||
*/
|
||||
class CanvasModals {
|
||||
public:
|
||||
CanvasModals() = default;
|
||||
|
||||
/**
|
||||
* @brief Show advanced canvas properties modal
|
||||
*/
|
||||
void ShowAdvancedProperties(const std::string& canvas_id,
|
||||
const CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Show scaling controls modal
|
||||
*/
|
||||
void ShowScalingControls(const std::string& canvas_id,
|
||||
const CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Show BPP format conversion dialog
|
||||
*/
|
||||
void ShowBppConversionDialog(const std::string& canvas_id,
|
||||
const BppConversionOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Show palette editor modal
|
||||
*/
|
||||
void ShowPaletteEditor(const std::string& canvas_id,
|
||||
const PaletteEditorOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Show color analysis modal
|
||||
*/
|
||||
void ShowColorAnalysis(const std::string& canvas_id,
|
||||
const ColorAnalysisOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Show performance dashboard integration
|
||||
*/
|
||||
void ShowPerformanceIntegration(const std::string& canvas_id,
|
||||
const PerformanceOptions& options);
|
||||
|
||||
/**
|
||||
* @brief Render all active modals
|
||||
*/
|
||||
void Render();
|
||||
|
||||
/**
|
||||
* @brief Check if any modal is open
|
||||
*/
|
||||
bool IsAnyModalOpen() const;
|
||||
|
||||
private:
|
||||
struct ModalState {
|
||||
bool is_open = false;
|
||||
std::string id;
|
||||
std::function<void()> render_func;
|
||||
};
|
||||
|
||||
std::vector<ModalState> active_modals_;
|
||||
|
||||
// Modal rendering functions
|
||||
void RenderAdvancedPropertiesModal(const std::string& canvas_id,
|
||||
CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap);
|
||||
|
||||
void RenderScalingControlsModal(const std::string& canvas_id,
|
||||
CanvasConfig& config,
|
||||
const gfx::Bitmap* bitmap);
|
||||
|
||||
void RenderBppConversionModal(const std::string& canvas_id,
|
||||
const BppConversionOptions& options);
|
||||
|
||||
void RenderPaletteEditorModal(const std::string& canvas_id,
|
||||
const PaletteEditorOptions& options);
|
||||
|
||||
void RenderColorAnalysisModal(const std::string& canvas_id,
|
||||
const ColorAnalysisOptions& options);
|
||||
|
||||
void RenderPerformanceModal(const std::string& canvas_id,
|
||||
const PerformanceOptions& options);
|
||||
|
||||
// Helper methods
|
||||
void OpenModal(const std::string& id, std::function<void()> render_func);
|
||||
void CloseModal(const std::string& id);
|
||||
bool IsModalOpen(const std::string& id) const;
|
||||
|
||||
// UI helper methods
|
||||
void RenderMaterialIcon(const std::string& icon_name, const ImVec4& color = ImVec4(1, 1, 1, 1));
|
||||
void RenderMetricCard(const std::string& title, const std::string& value,
|
||||
const std::string& icon, const ImVec4& color = ImVec4(1, 1, 1, 1));
|
||||
void RenderSliderWithIcon(const std::string& label, const std::string& icon,
|
||||
float* value, float min_val, float max_val,
|
||||
const char* format = "%.2f");
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_MODALS_H
|
||||
603
src/app/gui/canvas/canvas_performance_integration.cc
Normal file
603
src/app/gui/canvas/canvas_performance_integration.cc
Normal file
@@ -0,0 +1,603 @@
|
||||
#include "canvas_performance_integration.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/performance_dashboard.h"
|
||||
#include "util/log.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
void CanvasPerformanceIntegration::Initialize(const std::string& canvas_id) {
|
||||
canvas_id_ = canvas_id;
|
||||
monitoring_enabled_ = true;
|
||||
current_metrics_.Reset();
|
||||
|
||||
// Initialize performance profiler integration
|
||||
dashboard_ = &gfx::PerformanceDashboard::Get();
|
||||
|
||||
util::logf("Initialized performance integration for canvas: %s", canvas_id_.c_str());
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::StartMonitoring() {
|
||||
if (!monitoring_enabled_) return;
|
||||
|
||||
// Start frame timer
|
||||
frame_timer_active_ = true;
|
||||
frame_timer_ = std::make_unique<gfx::ScopedTimer>("canvas_frame_" + canvas_id_);
|
||||
|
||||
util::logf("Started performance monitoring for canvas: %s", canvas_id_.c_str());
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::StopMonitoring() {
|
||||
if (frame_timer_active_) {
|
||||
frame_timer_.reset();
|
||||
frame_timer_active_ = false;
|
||||
}
|
||||
if (draw_timer_active_) {
|
||||
draw_timer_.reset();
|
||||
draw_timer_active_ = false;
|
||||
}
|
||||
if (interaction_timer_active_) {
|
||||
interaction_timer_.reset();
|
||||
interaction_timer_active_ = false;
|
||||
}
|
||||
if (modal_timer_active_) {
|
||||
modal_timer_.reset();
|
||||
modal_timer_active_ = false;
|
||||
}
|
||||
|
||||
util::logf("Stopped performance monitoring for canvas: %s", canvas_id_.c_str());
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::UpdateMetrics() {
|
||||
if (!monitoring_enabled_) return;
|
||||
|
||||
// Update frame time
|
||||
UpdateFrameTime();
|
||||
|
||||
// Update draw time
|
||||
UpdateDrawTime();
|
||||
|
||||
// Update interaction time
|
||||
UpdateInteractionTime();
|
||||
|
||||
// Update modal time
|
||||
UpdateModalTime();
|
||||
|
||||
// Calculate cache hit ratio
|
||||
CalculateCacheHitRatio();
|
||||
|
||||
// Save current metrics periodically
|
||||
static auto last_save = std::chrono::steady_clock::now();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - last_save).count() >= 5) {
|
||||
SaveCurrentMetrics();
|
||||
last_save = now;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RecordOperation(const std::string& operation_name,
|
||||
double time_ms,
|
||||
CanvasUsage usage_mode) {
|
||||
if (!monitoring_enabled_) return;
|
||||
|
||||
// Update operation counts based on usage mode
|
||||
switch (usage_mode) {
|
||||
case CanvasUsage::kTilePainting:
|
||||
current_metrics_.tile_paint_operations++;
|
||||
break;
|
||||
case CanvasUsage::kTileSelecting:
|
||||
current_metrics_.tile_select_operations++;
|
||||
break;
|
||||
case CanvasUsage::kSelectRectangle:
|
||||
current_metrics_.rectangle_select_operations++;
|
||||
break;
|
||||
case CanvasUsage::kColorPainting:
|
||||
current_metrics_.color_paint_operations++;
|
||||
break;
|
||||
case CanvasUsage::kBppConversion:
|
||||
current_metrics_.bpp_conversion_operations++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Record operation timing in internal metrics
|
||||
// Note: PerformanceProfiler uses StartTimer/EndTimer pattern, not RecordOperation
|
||||
|
||||
// Update usage tracker if available
|
||||
if (usage_tracker_) {
|
||||
usage_tracker_->RecordOperation(operation_name, time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RecordMemoryUsage(size_t texture_memory,
|
||||
size_t bitmap_memory,
|
||||
size_t palette_memory) {
|
||||
current_metrics_.texture_memory_mb = texture_memory / (1024 * 1024);
|
||||
current_metrics_.bitmap_memory_mb = bitmap_memory / (1024 * 1024);
|
||||
current_metrics_.palette_memory_mb = palette_memory / (1024 * 1024);
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RecordCachePerformance(int hits, int misses) {
|
||||
current_metrics_.cache_hits = hits;
|
||||
current_metrics_.cache_misses = misses;
|
||||
CalculateCacheHitRatio();
|
||||
}
|
||||
|
||||
// These methods are already defined in the header as inline, removing duplicates
|
||||
|
||||
std::string CanvasPerformanceIntegration::GetPerformanceSummary() const {
|
||||
std::ostringstream summary;
|
||||
|
||||
summary << "Canvas Performance Summary (" << canvas_id_ << ")\n";
|
||||
summary << "=====================================\n\n";
|
||||
|
||||
summary << "Timing Metrics:\n";
|
||||
summary << " Frame Time: " << FormatTime(current_metrics_.frame_time_ms) << "\n";
|
||||
summary << " Draw Time: " << FormatTime(current_metrics_.draw_time_ms) << "\n";
|
||||
summary << " Interaction Time: " << FormatTime(current_metrics_.interaction_time_ms) << "\n";
|
||||
summary << " Modal Time: " << FormatTime(current_metrics_.modal_time_ms) << "\n\n";
|
||||
|
||||
summary << "Operation Counts:\n";
|
||||
summary << " Draw Calls: " << current_metrics_.draw_calls << "\n";
|
||||
summary << " Texture Updates: " << current_metrics_.texture_updates << "\n";
|
||||
summary << " Palette Lookups: " << current_metrics_.palette_lookups << "\n";
|
||||
summary << " Bitmap Operations: " << current_metrics_.bitmap_operations << "\n\n";
|
||||
|
||||
summary << "Canvas Operations:\n";
|
||||
summary << " Tile Paint: " << current_metrics_.tile_paint_operations << "\n";
|
||||
summary << " Tile Select: " << current_metrics_.tile_select_operations << "\n";
|
||||
summary << " Rectangle Select: " << current_metrics_.rectangle_select_operations << "\n";
|
||||
summary << " Color Paint: " << current_metrics_.color_paint_operations << "\n";
|
||||
summary << " BPP Conversion: " << current_metrics_.bpp_conversion_operations << "\n\n";
|
||||
|
||||
summary << "Memory Usage:\n";
|
||||
summary << " Texture Memory: " << FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024) << "\n";
|
||||
summary << " Bitmap Memory: " << FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024) << "\n";
|
||||
summary << " Palette Memory: " << FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024) << "\n\n";
|
||||
|
||||
summary << "Cache Performance:\n";
|
||||
summary << " Hit Ratio: " << std::fixed << std::setprecision(1)
|
||||
<< (current_metrics_.cache_hit_ratio * 100.0) << "%\n";
|
||||
summary << " Hits: " << current_metrics_.cache_hits << "\n";
|
||||
summary << " Misses: " << current_metrics_.cache_misses << "\n";
|
||||
|
||||
return summary.str();
|
||||
}
|
||||
|
||||
std::vector<std::string> CanvasPerformanceIntegration::GetPerformanceRecommendations() const {
|
||||
std::vector<std::string> recommendations;
|
||||
|
||||
// Frame time recommendations
|
||||
if (current_metrics_.frame_time_ms > 16.67) { // 60 FPS threshold
|
||||
recommendations.push_back("Frame time is high - consider reducing draw calls or optimizing rendering");
|
||||
}
|
||||
|
||||
// Draw time recommendations
|
||||
if (current_metrics_.draw_time_ms > 10.0) {
|
||||
recommendations.push_back("Draw time is high - consider using texture atlases or reducing texture switches");
|
||||
}
|
||||
|
||||
// Memory recommendations
|
||||
size_t total_memory = current_metrics_.texture_memory_mb +
|
||||
current_metrics_.bitmap_memory_mb +
|
||||
current_metrics_.palette_memory_mb;
|
||||
if (total_memory > 100) { // 100MB threshold
|
||||
recommendations.push_back("Memory usage is high - consider implementing texture streaming or compression");
|
||||
}
|
||||
|
||||
// Cache recommendations
|
||||
if (current_metrics_.cache_hit_ratio < 0.8) {
|
||||
recommendations.push_back("Cache hit ratio is low - consider increasing cache size or improving cache strategy");
|
||||
}
|
||||
|
||||
// Operation count recommendations
|
||||
if (current_metrics_.draw_calls > 1000) {
|
||||
recommendations.push_back("High draw call count - consider batching operations or using instanced rendering");
|
||||
}
|
||||
|
||||
if (current_metrics_.texture_updates > 100) {
|
||||
recommendations.push_back("Frequent texture updates - consider using texture arrays or atlases");
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
std::string CanvasPerformanceIntegration::ExportPerformanceReport() const {
|
||||
std::ostringstream report;
|
||||
|
||||
report << "Canvas Performance Report\n";
|
||||
report << "========================\n\n";
|
||||
|
||||
report << "Canvas ID: " << canvas_id_ << "\n";
|
||||
report << "Monitoring Enabled: " << (monitoring_enabled_ ? "Yes" : "No") << "\n\n";
|
||||
|
||||
report << GetPerformanceSummary() << "\n";
|
||||
|
||||
// Performance history
|
||||
if (!performance_history_.empty()) {
|
||||
report << "Performance History:\n";
|
||||
report << "===================\n\n";
|
||||
|
||||
for (size_t i = 0; i < performance_history_.size(); ++i) {
|
||||
const auto& metrics = performance_history_[i];
|
||||
report << "Sample " << (i + 1) << ":\n";
|
||||
report << " Frame Time: " << FormatTime(metrics.frame_time_ms) << "\n";
|
||||
report << " Draw Calls: " << metrics.draw_calls << "\n";
|
||||
report << " Memory: " << FormatMemory((metrics.texture_memory_mb +
|
||||
metrics.bitmap_memory_mb +
|
||||
metrics.palette_memory_mb) * 1024 * 1024) << "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
auto recommendations = GetPerformanceRecommendations();
|
||||
if (!recommendations.empty()) {
|
||||
report << "Recommendations:\n";
|
||||
report << "===============\n\n";
|
||||
for (const auto& rec : recommendations) {
|
||||
report << "• " << rec << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return report.str();
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderPerformanceUI() {
|
||||
if (!monitoring_enabled_) return;
|
||||
|
||||
if (ImGui::Begin("Canvas Performance", &show_performance_ui_)) {
|
||||
// Performance overview
|
||||
RenderPerformanceOverview();
|
||||
|
||||
if (show_detailed_metrics_) {
|
||||
ImGui::Separator();
|
||||
RenderDetailedMetrics();
|
||||
}
|
||||
|
||||
if (show_recommendations_) {
|
||||
ImGui::Separator();
|
||||
RenderRecommendations();
|
||||
}
|
||||
|
||||
// Control buttons
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Toggle Detailed Metrics")) {
|
||||
show_detailed_metrics_ = !show_detailed_metrics_;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Toggle Recommendations")) {
|
||||
show_recommendations_ = !show_recommendations_;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Export Report")) {
|
||||
std::string report = ExportPerformanceReport();
|
||||
// Could save to file or show in modal
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::SetUsageTracker(std::shared_ptr<CanvasUsageTracker> tracker) {
|
||||
usage_tracker_ = tracker;
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::UpdateFrameTime() {
|
||||
if (frame_timer_) {
|
||||
// Frame time would be calculated by the timer
|
||||
current_metrics_.frame_time_ms = 16.67; // Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::UpdateDrawTime() {
|
||||
if (draw_timer_) {
|
||||
// Draw time would be calculated by the timer
|
||||
current_metrics_.draw_time_ms = 5.0; // Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::UpdateInteractionTime() {
|
||||
if (interaction_timer_) {
|
||||
// Interaction time would be calculated by the timer
|
||||
current_metrics_.interaction_time_ms = 1.0; // Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::UpdateModalTime() {
|
||||
if (modal_timer_) {
|
||||
// Modal time would be calculated by the timer
|
||||
current_metrics_.modal_time_ms = 0.5; // Placeholder
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::CalculateCacheHitRatio() {
|
||||
int total_requests = current_metrics_.cache_hits + current_metrics_.cache_misses;
|
||||
if (total_requests > 0) {
|
||||
current_metrics_.cache_hit_ratio = static_cast<double>(current_metrics_.cache_hits) / total_requests;
|
||||
} else {
|
||||
current_metrics_.cache_hit_ratio = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::SaveCurrentMetrics() {
|
||||
performance_history_.push_back(current_metrics_);
|
||||
|
||||
// Keep only last 100 samples
|
||||
if (performance_history_.size() > 100) {
|
||||
performance_history_.erase(performance_history_.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::AnalyzePerformance() {
|
||||
// Analyze performance trends and patterns
|
||||
if (performance_history_.size() < 2) return;
|
||||
|
||||
// Calculate trends
|
||||
double frame_time_trend = 0.0;
|
||||
double memory_trend = 0.0;
|
||||
|
||||
for (size_t i = 1; i < performance_history_.size(); ++i) {
|
||||
const auto& prev = performance_history_[i - 1];
|
||||
const auto& curr = performance_history_[i];
|
||||
|
||||
frame_time_trend += (curr.frame_time_ms - prev.frame_time_ms);
|
||||
memory_trend += ((curr.texture_memory_mb + curr.bitmap_memory_mb + curr.palette_memory_mb) -
|
||||
(prev.texture_memory_mb + prev.bitmap_memory_mb + prev.palette_memory_mb));
|
||||
}
|
||||
|
||||
frame_time_trend /= (performance_history_.size() - 1);
|
||||
memory_trend /= (performance_history_.size() - 1);
|
||||
|
||||
// Log trends
|
||||
if (std::abs(frame_time_trend) > 1.0) {
|
||||
util::logf("Canvas %s: Frame time trend: %.2f ms/sample",
|
||||
canvas_id_.c_str(), frame_time_trend);
|
||||
}
|
||||
|
||||
if (std::abs(memory_trend) > 1.0) {
|
||||
util::logf("Canvas %s: Memory trend: %.2f MB/sample",
|
||||
canvas_id_.c_str(), memory_trend);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderPerformanceOverview() {
|
||||
ImGui::Text("Performance Overview");
|
||||
ImGui::Separator();
|
||||
|
||||
// Frame time
|
||||
ImVec4 frame_color = GetPerformanceColor(current_metrics_.frame_time_ms, 16.67, 33.33);
|
||||
ImGui::TextColored(frame_color, "Frame Time: %s", FormatTime(current_metrics_.frame_time_ms).c_str());
|
||||
|
||||
// Draw time
|
||||
ImVec4 draw_color = GetPerformanceColor(current_metrics_.draw_time_ms, 10.0, 20.0);
|
||||
ImGui::TextColored(draw_color, "Draw Time: %s", FormatTime(current_metrics_.draw_time_ms).c_str());
|
||||
|
||||
// Memory usage
|
||||
size_t total_memory = current_metrics_.texture_memory_mb +
|
||||
current_metrics_.bitmap_memory_mb +
|
||||
current_metrics_.palette_memory_mb;
|
||||
ImVec4 memory_color = GetPerformanceColor(total_memory, 50.0, 100.0);
|
||||
ImGui::TextColored(memory_color, "Memory: %s", FormatMemory(total_memory * 1024 * 1024).c_str());
|
||||
|
||||
// Cache performance
|
||||
ImVec4 cache_color = GetPerformanceColor(current_metrics_.cache_hit_ratio * 100.0, 80.0, 60.0);
|
||||
ImGui::TextColored(cache_color, "Cache Hit Ratio: %.1f%%", current_metrics_.cache_hit_ratio * 100.0);
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderDetailedMetrics() {
|
||||
ImGui::Text("Detailed Metrics");
|
||||
ImGui::Separator();
|
||||
|
||||
// Operation counts
|
||||
RenderOperationCounts();
|
||||
|
||||
// Memory breakdown
|
||||
RenderMemoryUsage();
|
||||
|
||||
// Cache performance
|
||||
RenderCachePerformance();
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderMemoryUsage() {
|
||||
if (ImGui::CollapsingHeader("Memory Usage")) {
|
||||
ImGui::Text("Texture Memory: %s", FormatMemory(current_metrics_.texture_memory_mb * 1024 * 1024).c_str());
|
||||
ImGui::Text("Bitmap Memory: %s", FormatMemory(current_metrics_.bitmap_memory_mb * 1024 * 1024).c_str());
|
||||
ImGui::Text("Palette Memory: %s", FormatMemory(current_metrics_.palette_memory_mb * 1024 * 1024).c_str());
|
||||
|
||||
size_t total = current_metrics_.texture_memory_mb +
|
||||
current_metrics_.bitmap_memory_mb +
|
||||
current_metrics_.palette_memory_mb;
|
||||
ImGui::Text("Total Memory: %s", FormatMemory(total * 1024 * 1024).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderOperationCounts() {
|
||||
if (ImGui::CollapsingHeader("Operation Counts")) {
|
||||
ImGui::Text("Draw Calls: %d", current_metrics_.draw_calls);
|
||||
ImGui::Text("Texture Updates: %d", current_metrics_.texture_updates);
|
||||
ImGui::Text("Palette Lookups: %d", current_metrics_.palette_lookups);
|
||||
ImGui::Text("Bitmap Operations: %d", current_metrics_.bitmap_operations);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Canvas Operations:");
|
||||
ImGui::Text(" Tile Paint: %d", current_metrics_.tile_paint_operations);
|
||||
ImGui::Text(" Tile Select: %d", current_metrics_.tile_select_operations);
|
||||
ImGui::Text(" Rectangle Select: %d", current_metrics_.rectangle_select_operations);
|
||||
ImGui::Text(" Color Paint: %d", current_metrics_.color_paint_operations);
|
||||
ImGui::Text(" BPP Conversion: %d", current_metrics_.bpp_conversion_operations);
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderCachePerformance() {
|
||||
if (ImGui::CollapsingHeader("Cache Performance")) {
|
||||
ImGui::Text("Cache Hits: %d", current_metrics_.cache_hits);
|
||||
ImGui::Text("Cache Misses: %d", current_metrics_.cache_misses);
|
||||
ImGui::Text("Hit Ratio: %.1f%%", current_metrics_.cache_hit_ratio * 100.0);
|
||||
|
||||
// Cache hit ratio bar
|
||||
ImGui::ProgressBar(current_metrics_.cache_hit_ratio, ImVec2(0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderRecommendations() {
|
||||
ImGui::Text("Performance Recommendations");
|
||||
ImGui::Separator();
|
||||
|
||||
auto recommendations = GetPerformanceRecommendations();
|
||||
if (recommendations.empty()) {
|
||||
ImGui::TextColored(ImVec4(0.2F, 1.0F, 0.2F, 1.0F), "✓ Performance looks good!");
|
||||
} else {
|
||||
for (const auto& rec : recommendations) {
|
||||
ImGui::TextColored(ImVec4(1.0F, 0.8F, 0.2F, 1.0F), "⚠ %s", rec.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasPerformanceIntegration::RenderPerformanceGraph() {
|
||||
if (ImGui::CollapsingHeader("Performance Graph")) {
|
||||
// Simple performance graph using ImGui plot lines
|
||||
static std::vector<float> frame_times;
|
||||
static std::vector<float> draw_times;
|
||||
|
||||
// Add current values
|
||||
frame_times.push_back(static_cast<float>(current_metrics_.frame_time_ms));
|
||||
draw_times.push_back(static_cast<float>(current_metrics_.draw_time_ms));
|
||||
|
||||
// Keep only last 100 samples
|
||||
if (frame_times.size() > 100) {
|
||||
frame_times.erase(frame_times.begin());
|
||||
draw_times.erase(draw_times.begin());
|
||||
}
|
||||
|
||||
if (!frame_times.empty()) {
|
||||
ImGui::PlotLines("Frame Time (ms)", frame_times.data(),
|
||||
static_cast<int>(frame_times.size()), 0, nullptr, 0.0F, 50.0F,
|
||||
ImVec2(0, 100));
|
||||
ImGui::PlotLines("Draw Time (ms)", draw_times.data(),
|
||||
static_cast<int>(draw_times.size()), 0, nullptr, 0.0F, 30.0F,
|
||||
ImVec2(0, 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string CanvasPerformanceIntegration::FormatTime(double time_ms) const {
|
||||
if (time_ms < 1.0) {
|
||||
return std::to_string(static_cast<int>(time_ms * 1000)) + " μs";
|
||||
} else if (time_ms < 1000.0) {
|
||||
return std::to_string(static_cast<int>(time_ms * 10) / 10.0) + " ms";
|
||||
} else {
|
||||
return std::to_string(static_cast<int>(time_ms / 1000)) + " s";
|
||||
}
|
||||
}
|
||||
|
||||
std::string CanvasPerformanceIntegration::FormatMemory(size_t bytes) const {
|
||||
if (bytes < 1024) {
|
||||
return std::to_string(bytes) + " B";
|
||||
} else if (bytes < 1024 * 1024) {
|
||||
return std::to_string(bytes / 1024) + " KB";
|
||||
} else {
|
||||
return std::to_string(bytes / (1024 * 1024)) + " MB";
|
||||
}
|
||||
}
|
||||
|
||||
ImVec4 CanvasPerformanceIntegration::GetPerformanceColor(double value,
|
||||
double threshold_good,
|
||||
double threshold_warning) const {
|
||||
if (value <= threshold_good) {
|
||||
return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green
|
||||
} else if (value <= threshold_warning) {
|
||||
return ImVec4(1.0F, 1.0F, 0.2F, 1.0F); // Yellow
|
||||
} else {
|
||||
return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red
|
||||
}
|
||||
}
|
||||
|
||||
// CanvasPerformanceManager implementation
|
||||
|
||||
CanvasPerformanceManager& CanvasPerformanceManager::Get() {
|
||||
static CanvasPerformanceManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CanvasPerformanceManager::RegisterIntegration(const std::string& canvas_id,
|
||||
std::shared_ptr<CanvasPerformanceIntegration> integration) {
|
||||
integrations_[canvas_id] = integration;
|
||||
util::logf("Registered performance integration for canvas: %s", canvas_id.c_str());
|
||||
}
|
||||
|
||||
std::shared_ptr<CanvasPerformanceIntegration> CanvasPerformanceManager::GetIntegration(const std::string& canvas_id) {
|
||||
auto it = integrations_.find(canvas_id);
|
||||
if (it != integrations_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CanvasPerformanceManager::UpdateAllIntegrations() {
|
||||
for (auto& [id, integration] : integrations_) {
|
||||
integration->UpdateMetrics();
|
||||
}
|
||||
}
|
||||
|
||||
std::string CanvasPerformanceManager::GetGlobalPerformanceSummary() const {
|
||||
std::ostringstream summary;
|
||||
|
||||
summary << "Global Canvas Performance Summary\n";
|
||||
summary << "=================================\n\n";
|
||||
|
||||
summary << "Registered Canvases: " << integrations_.size() << "\n\n";
|
||||
|
||||
for (const auto& [id, integration] : integrations_) {
|
||||
summary << "Canvas: " << id << "\n";
|
||||
summary << "----------------------------------------\n";
|
||||
summary << integration->GetPerformanceSummary() << "\n\n";
|
||||
}
|
||||
|
||||
return summary.str();
|
||||
}
|
||||
|
||||
std::string CanvasPerformanceManager::ExportGlobalPerformanceReport() const {
|
||||
std::ostringstream report;
|
||||
|
||||
report << "Global Canvas Performance Report\n";
|
||||
report << "================================\n\n";
|
||||
|
||||
report << GetGlobalPerformanceSummary();
|
||||
|
||||
// Global recommendations
|
||||
report << "Global Recommendations:\n";
|
||||
report << "=======================\n\n";
|
||||
|
||||
for (const auto& [id, integration] : integrations_) {
|
||||
auto recommendations = integration->GetPerformanceRecommendations();
|
||||
if (!recommendations.empty()) {
|
||||
report << "Canvas " << id << ":\n";
|
||||
for (const auto& rec : recommendations) {
|
||||
report << " • " << rec << "\n";
|
||||
}
|
||||
report << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return report.str();
|
||||
}
|
||||
|
||||
void CanvasPerformanceManager::ClearAllIntegrations() {
|
||||
for (auto& [id, integration] : integrations_) {
|
||||
integration->StopMonitoring();
|
||||
}
|
||||
integrations_.clear();
|
||||
util::logf("Cleared all canvas performance integrations");
|
||||
}
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
269
src/app/gui/canvas/canvas_performance_integration.h
Normal file
269
src/app/gui/canvas/canvas_performance_integration.h
Normal file
@@ -0,0 +1,269 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_PERFORMANCE_INTEGRATION_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_PERFORMANCE_INTEGRATION_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/performance_dashboard.h"
|
||||
#include "canvas_usage_tracker.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
/**
|
||||
* @brief Canvas performance metrics
|
||||
*/
|
||||
struct CanvasPerformanceMetrics {
|
||||
// Timing metrics
|
||||
double frame_time_ms = 0.0;
|
||||
double draw_time_ms = 0.0;
|
||||
double interaction_time_ms = 0.0;
|
||||
double modal_time_ms = 0.0;
|
||||
|
||||
// Operation counts
|
||||
int draw_calls = 0;
|
||||
int texture_updates = 0;
|
||||
int palette_lookups = 0;
|
||||
int bitmap_operations = 0;
|
||||
|
||||
// Memory usage
|
||||
size_t texture_memory_mb = 0;
|
||||
size_t bitmap_memory_mb = 0;
|
||||
size_t palette_memory_mb = 0;
|
||||
|
||||
// Cache performance
|
||||
double cache_hit_ratio = 0.0;
|
||||
int cache_hits = 0;
|
||||
int cache_misses = 0;
|
||||
|
||||
// Canvas-specific metrics
|
||||
int tile_paint_operations = 0;
|
||||
int tile_select_operations = 0;
|
||||
int rectangle_select_operations = 0;
|
||||
int color_paint_operations = 0;
|
||||
int bpp_conversion_operations = 0;
|
||||
|
||||
void Reset() {
|
||||
frame_time_ms = 0.0;
|
||||
draw_time_ms = 0.0;
|
||||
interaction_time_ms = 0.0;
|
||||
modal_time_ms = 0.0;
|
||||
draw_calls = 0;
|
||||
texture_updates = 0;
|
||||
palette_lookups = 0;
|
||||
bitmap_operations = 0;
|
||||
texture_memory_mb = 0;
|
||||
bitmap_memory_mb = 0;
|
||||
palette_memory_mb = 0;
|
||||
cache_hit_ratio = 0.0;
|
||||
cache_hits = 0;
|
||||
cache_misses = 0;
|
||||
tile_paint_operations = 0;
|
||||
tile_select_operations = 0;
|
||||
rectangle_select_operations = 0;
|
||||
color_paint_operations = 0;
|
||||
bpp_conversion_operations = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Canvas performance integration with dashboard
|
||||
*/
|
||||
class CanvasPerformanceIntegration {
|
||||
public:
|
||||
CanvasPerformanceIntegration() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize performance integration
|
||||
*/
|
||||
void Initialize(const std::string& canvas_id);
|
||||
|
||||
/**
|
||||
* @brief Start performance monitoring
|
||||
*/
|
||||
void StartMonitoring();
|
||||
|
||||
/**
|
||||
* @brief Stop performance monitoring
|
||||
*/
|
||||
void StopMonitoring();
|
||||
|
||||
/**
|
||||
* @brief Update performance metrics
|
||||
*/
|
||||
void UpdateMetrics();
|
||||
|
||||
/**
|
||||
* @brief Record canvas operation
|
||||
*/
|
||||
void RecordOperation(const std::string& operation_name,
|
||||
double time_ms,
|
||||
CanvasUsage usage_mode = CanvasUsage::kUnknown);
|
||||
|
||||
/**
|
||||
* @brief Record memory usage
|
||||
*/
|
||||
void RecordMemoryUsage(size_t texture_memory,
|
||||
size_t bitmap_memory,
|
||||
size_t palette_memory);
|
||||
|
||||
/**
|
||||
* @brief Record cache performance
|
||||
*/
|
||||
void RecordCachePerformance(int hits, int misses);
|
||||
|
||||
/**
|
||||
* @brief Get current performance metrics
|
||||
*/
|
||||
const CanvasPerformanceMetrics& GetCurrentMetrics() const { return current_metrics_; }
|
||||
|
||||
/**
|
||||
* @brief Get performance history
|
||||
*/
|
||||
const std::vector<CanvasPerformanceMetrics>& GetPerformanceHistory() const {
|
||||
return performance_history_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get performance summary
|
||||
*/
|
||||
std::string GetPerformanceSummary() const;
|
||||
|
||||
/**
|
||||
* @brief Get performance recommendations
|
||||
*/
|
||||
std::vector<std::string> GetPerformanceRecommendations() const;
|
||||
|
||||
/**
|
||||
* @brief Export performance report
|
||||
*/
|
||||
std::string ExportPerformanceReport() const;
|
||||
|
||||
/**
|
||||
* @brief Render performance UI
|
||||
*/
|
||||
void RenderPerformanceUI();
|
||||
|
||||
/**
|
||||
* @brief Set usage tracker integration
|
||||
*/
|
||||
void SetUsageTracker(std::shared_ptr<CanvasUsageTracker> tracker);
|
||||
|
||||
/**
|
||||
* @brief Enable/disable performance monitoring
|
||||
*/
|
||||
void SetMonitoringEnabled(bool enabled) { monitoring_enabled_ = enabled; }
|
||||
bool IsMonitoringEnabled() const { return monitoring_enabled_; }
|
||||
|
||||
private:
|
||||
std::string canvas_id_;
|
||||
bool monitoring_enabled_ = true;
|
||||
CanvasPerformanceMetrics current_metrics_;
|
||||
std::vector<CanvasPerformanceMetrics> performance_history_;
|
||||
|
||||
// Performance profiler integration
|
||||
std::unique_ptr<gfx::ScopedTimer> frame_timer_;
|
||||
std::unique_ptr<gfx::ScopedTimer> draw_timer_;
|
||||
std::unique_ptr<gfx::ScopedTimer> interaction_timer_;
|
||||
std::unique_ptr<gfx::ScopedTimer> modal_timer_;
|
||||
bool frame_timer_active_ = false;
|
||||
bool draw_timer_active_ = false;
|
||||
bool interaction_timer_active_ = false;
|
||||
bool modal_timer_active_ = false;
|
||||
|
||||
// Usage tracker integration
|
||||
std::shared_ptr<CanvasUsageTracker> usage_tracker_;
|
||||
|
||||
// Performance dashboard integration
|
||||
gfx::PerformanceDashboard* dashboard_ = nullptr;
|
||||
|
||||
// UI state
|
||||
bool show_performance_ui_ = false;
|
||||
bool show_detailed_metrics_ = false;
|
||||
bool show_recommendations_ = false;
|
||||
|
||||
// Helper methods
|
||||
void UpdateFrameTime();
|
||||
void UpdateDrawTime();
|
||||
void UpdateInteractionTime();
|
||||
void UpdateModalTime();
|
||||
void CalculateCacheHitRatio();
|
||||
void SaveCurrentMetrics();
|
||||
void AnalyzePerformance();
|
||||
|
||||
// UI rendering methods
|
||||
void RenderPerformanceOverview();
|
||||
void RenderDetailedMetrics();
|
||||
void RenderMemoryUsage();
|
||||
void RenderOperationCounts();
|
||||
void RenderCachePerformance();
|
||||
void RenderRecommendations();
|
||||
void RenderPerformanceGraph();
|
||||
|
||||
// Helper methods
|
||||
std::string FormatTime(double time_ms) const;
|
||||
std::string FormatMemory(size_t bytes) const;
|
||||
ImVec4 GetPerformanceColor(double value, double threshold_good, double threshold_warning) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global canvas performance manager
|
||||
*/
|
||||
class CanvasPerformanceManager {
|
||||
public:
|
||||
static CanvasPerformanceManager& Get();
|
||||
|
||||
/**
|
||||
* @brief Register a canvas performance integration
|
||||
*/
|
||||
void RegisterIntegration(const std::string& canvas_id,
|
||||
std::shared_ptr<CanvasPerformanceIntegration> integration);
|
||||
|
||||
/**
|
||||
* @brief Get integration for canvas
|
||||
*/
|
||||
std::shared_ptr<CanvasPerformanceIntegration> GetIntegration(const std::string& canvas_id);
|
||||
|
||||
/**
|
||||
* @brief Get all integrations
|
||||
*/
|
||||
const std::unordered_map<std::string, std::shared_ptr<CanvasPerformanceIntegration>>&
|
||||
GetAllIntegrations() const { return integrations_; }
|
||||
|
||||
/**
|
||||
* @brief Update all integrations
|
||||
*/
|
||||
void UpdateAllIntegrations();
|
||||
|
||||
/**
|
||||
* @brief Get global performance summary
|
||||
*/
|
||||
std::string GetGlobalPerformanceSummary() const;
|
||||
|
||||
/**
|
||||
* @brief Export global performance report
|
||||
*/
|
||||
std::string ExportGlobalPerformanceReport() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all integrations
|
||||
*/
|
||||
void ClearAllIntegrations();
|
||||
|
||||
private:
|
||||
CanvasPerformanceManager() = default;
|
||||
~CanvasPerformanceManager() = default;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CanvasPerformanceIntegration>> integrations_;
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_PERFORMANCE_INTEGRATION_H
|
||||
429
src/app/gui/canvas/canvas_usage_tracker.cc
Normal file
429
src/app/gui/canvas/canvas_usage_tracker.cc
Normal file
@@ -0,0 +1,429 @@
|
||||
#include "canvas_usage_tracker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
void CanvasUsageTracker::Initialize(const std::string& canvas_id) {
|
||||
canvas_id_ = canvas_id;
|
||||
current_stats_.Reset();
|
||||
current_stats_.session_start = std::chrono::steady_clock::now();
|
||||
last_activity_ = current_stats_.session_start;
|
||||
session_start_ = current_stats_.session_start;
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::SetUsageMode(CanvasUsage usage) {
|
||||
if (current_stats_.usage_mode != usage) {
|
||||
// Save current stats before changing mode
|
||||
SaveCurrentStats();
|
||||
|
||||
// Update usage mode
|
||||
current_stats_.usage_mode = usage;
|
||||
current_stats_.mode_changes++;
|
||||
|
||||
// Record mode change interaction
|
||||
RecordInteraction(CanvasInteraction::kModeChange, GetUsageModeName(usage));
|
||||
|
||||
util::logf("Canvas %s: Usage mode changed to %s",
|
||||
canvas_id_.c_str(), GetUsageModeName(usage).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::RecordInteraction(CanvasInteraction interaction,
|
||||
const std::string& details) {
|
||||
interaction_history_.push_back({interaction, details});
|
||||
|
||||
// Update activity time
|
||||
last_activity_ = std::chrono::steady_clock::now();
|
||||
|
||||
// Update interaction counts
|
||||
switch (interaction) {
|
||||
case CanvasInteraction::kMouseClick:
|
||||
current_stats_.mouse_clicks++;
|
||||
break;
|
||||
case CanvasInteraction::kMouseDrag:
|
||||
current_stats_.mouse_drags++;
|
||||
break;
|
||||
case CanvasInteraction::kContextMenu:
|
||||
current_stats_.context_menu_opens++;
|
||||
break;
|
||||
case CanvasInteraction::kModalOpen:
|
||||
current_stats_.modal_opens++;
|
||||
break;
|
||||
case CanvasInteraction::kToolChange:
|
||||
current_stats_.tool_changes++;
|
||||
break;
|
||||
case CanvasInteraction::kModeChange:
|
||||
current_stats_.mode_changes++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::RecordOperation(const std::string& operation_name,
|
||||
double time_ms) {
|
||||
operation_times_[operation_name].push_back(time_ms);
|
||||
current_stats_.total_operations++;
|
||||
|
||||
// Update average operation time
|
||||
double total_time = 0.0;
|
||||
int total_ops = 0;
|
||||
for (const auto& [name, times] : operation_times_) {
|
||||
for (double t : times) {
|
||||
total_time += t;
|
||||
total_ops++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total_ops > 0) {
|
||||
current_stats_.average_operation_time_ms = total_time / total_ops;
|
||||
}
|
||||
|
||||
// Update max operation time
|
||||
if (time_ms > current_stats_.max_operation_time_ms) {
|
||||
current_stats_.max_operation_time_ms = time_ms;
|
||||
}
|
||||
|
||||
// Record as interaction
|
||||
RecordInteraction(CanvasInteraction::kKeyboardInput, operation_name);
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::UpdateCanvasState(const ImVec2& canvas_size,
|
||||
const ImVec2& content_size,
|
||||
float global_scale,
|
||||
float grid_step,
|
||||
bool enable_grid,
|
||||
bool enable_hex_labels,
|
||||
bool enable_custom_labels) {
|
||||
current_stats_.canvas_size = canvas_size;
|
||||
current_stats_.content_size = content_size;
|
||||
current_stats_.global_scale = global_scale;
|
||||
current_stats_.grid_step = grid_step;
|
||||
current_stats_.enable_grid = enable_grid;
|
||||
current_stats_.enable_hex_labels = enable_hex_labels;
|
||||
current_stats_.enable_custom_labels = enable_custom_labels;
|
||||
|
||||
// Update activity time
|
||||
last_activity_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
// These methods are already defined in the header as inline, removing duplicates
|
||||
|
||||
std::string CanvasUsageTracker::GetUsageModeName(CanvasUsage usage) const {
|
||||
switch (usage) {
|
||||
case CanvasUsage::kTilePainting: return "Tile Painting";
|
||||
case CanvasUsage::kTileSelecting: return "Tile Selecting";
|
||||
case CanvasUsage::kSelectRectangle: return "Rectangle Selection";
|
||||
case CanvasUsage::kColorPainting: return "Color Painting";
|
||||
case CanvasUsage::kBitmapEditing: return "Bitmap Editing";
|
||||
case CanvasUsage::kPaletteEditing: return "Palette Editing";
|
||||
case CanvasUsage::kBppConversion: return "BPP Conversion";
|
||||
case CanvasUsage::kPerformanceMode: return "Performance Mode";
|
||||
case CanvasUsage::kUnknown: return "Unknown";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ImVec4 CanvasUsageTracker::GetUsageModeColor(CanvasUsage usage) const {
|
||||
switch (usage) {
|
||||
case CanvasUsage::kTilePainting: return ImVec4(0.2F, 1.0F, 0.2F, 1.0F); // Green
|
||||
case CanvasUsage::kTileSelecting: return ImVec4(0.2F, 0.8F, 1.0F, 1.0F); // Blue
|
||||
case CanvasUsage::kSelectRectangle: return ImVec4(1.0F, 0.8F, 0.2F, 1.0F); // Yellow
|
||||
case CanvasUsage::kColorPainting: return ImVec4(1.0F, 0.2F, 1.0F, 1.0F); // Magenta
|
||||
case CanvasUsage::kBitmapEditing: return ImVec4(1.0F, 0.5F, 0.2F, 1.0F); // Orange
|
||||
case CanvasUsage::kPaletteEditing: return ImVec4(0.8F, 0.2F, 1.0F, 1.0F); // Purple
|
||||
case CanvasUsage::kBppConversion: return ImVec4(0.2F, 1.0F, 1.0F, 1.0F); // Cyan
|
||||
case CanvasUsage::kPerformanceMode: return ImVec4(1.0F, 0.2F, 0.2F, 1.0F); // Red
|
||||
case CanvasUsage::kUnknown: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray
|
||||
default: return ImVec4(0.7F, 0.7F, 0.7F, 1.0F); // Gray
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> CanvasUsageTracker::GetUsageRecommendations() const {
|
||||
std::vector<std::string> recommendations;
|
||||
|
||||
// Analyze usage patterns and provide recommendations
|
||||
if (current_stats_.mouse_clicks > 100) {
|
||||
recommendations.push_back("Consider using keyboard shortcuts to reduce mouse usage");
|
||||
}
|
||||
|
||||
if (current_stats_.context_menu_opens > 20) {
|
||||
recommendations.push_back("Frequent context menu usage - consider adding toolbar buttons");
|
||||
}
|
||||
|
||||
if (current_stats_.modal_opens > 10) {
|
||||
recommendations.push_back("Many modal dialogs opened - consider persistent panels");
|
||||
}
|
||||
|
||||
if (current_stats_.average_operation_time_ms > 100.0) {
|
||||
recommendations.push_back("Operations are slow - check performance optimization");
|
||||
}
|
||||
|
||||
if (current_stats_.mode_changes > 5) {
|
||||
recommendations.push_back("Frequent mode switching - consider mode-specific toolbars");
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
std::string CanvasUsageTracker::ExportUsageReport() const {
|
||||
std::ostringstream report;
|
||||
|
||||
report << "Canvas Usage Report for: " << canvas_id_ << "\n";
|
||||
report << "==========================================\n\n";
|
||||
|
||||
// Session information
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto session_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - session_start_);
|
||||
|
||||
report << "Session Information:\n";
|
||||
report << " Duration: " << FormatDuration(session_duration) << "\n";
|
||||
report << " Current Mode: " << GetUsageModeName(current_stats_.usage_mode) << "\n";
|
||||
report << " Mode Changes: " << current_stats_.mode_changes << "\n\n";
|
||||
|
||||
// Interaction statistics
|
||||
report << "Interaction Statistics:\n";
|
||||
report << " Mouse Clicks: " << current_stats_.mouse_clicks << "\n";
|
||||
report << " Mouse Drags: " << current_stats_.mouse_drags << "\n";
|
||||
report << " Context Menu Opens: " << current_stats_.context_menu_opens << "\n";
|
||||
report << " Modal Opens: " << current_stats_.modal_opens << "\n";
|
||||
report << " Tool Changes: " << current_stats_.tool_changes << "\n\n";
|
||||
|
||||
// Performance statistics
|
||||
report << "Performance Statistics:\n";
|
||||
report << " Total Operations: " << current_stats_.total_operations << "\n";
|
||||
report << " Average Operation Time: " << std::fixed << std::setprecision(2)
|
||||
<< current_stats_.average_operation_time_ms << " ms\n";
|
||||
report << " Max Operation Time: " << std::fixed << std::setprecision(2)
|
||||
<< current_stats_.max_operation_time_ms << " ms\n\n";
|
||||
|
||||
// Canvas state
|
||||
report << "Canvas State:\n";
|
||||
report << " Canvas Size: " << static_cast<int>(current_stats_.canvas_size.x)
|
||||
<< " x " << static_cast<int>(current_stats_.canvas_size.y) << "\n";
|
||||
report << " Content Size: " << static_cast<int>(current_stats_.content_size.x)
|
||||
<< " x " << static_cast<int>(current_stats_.content_size.y) << "\n";
|
||||
report << " Global Scale: " << std::fixed << std::setprecision(2)
|
||||
<< current_stats_.global_scale << "\n";
|
||||
report << " Grid Step: " << std::fixed << std::setprecision(1)
|
||||
<< current_stats_.grid_step << "\n";
|
||||
report << " Grid Enabled: " << (current_stats_.enable_grid ? "Yes" : "No") << "\n";
|
||||
report << " Hex Labels: " << (current_stats_.enable_hex_labels ? "Yes" : "No") << "\n";
|
||||
report << " Custom Labels: " << (current_stats_.enable_custom_labels ? "Yes" : "No") << "\n\n";
|
||||
|
||||
// Operation breakdown
|
||||
if (!operation_times_.empty()) {
|
||||
report << "Operation Breakdown:\n";
|
||||
for (const auto& [operation, times] : operation_times_) {
|
||||
double avg_time = CalculateAverageOperationTime(operation);
|
||||
report << " " << operation << ": " << times.size() << " operations, "
|
||||
<< "avg " << std::fixed << std::setprecision(2) << avg_time << " ms\n";
|
||||
}
|
||||
report << "\n";
|
||||
}
|
||||
|
||||
// Recommendations
|
||||
auto recommendations = GetUsageRecommendations();
|
||||
if (!recommendations.empty()) {
|
||||
report << "Recommendations:\n";
|
||||
for (const auto& rec : recommendations) {
|
||||
report << " • " << rec << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return report.str();
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::ClearHistory() {
|
||||
usage_history_.clear();
|
||||
interaction_history_.clear();
|
||||
operation_times_.clear();
|
||||
current_stats_.Reset();
|
||||
current_stats_.session_start = std::chrono::steady_clock::now();
|
||||
last_activity_ = current_stats_.session_start;
|
||||
session_start_ = current_stats_.session_start;
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::StartSession() {
|
||||
session_start_ = std::chrono::steady_clock::now();
|
||||
current_stats_.session_start = session_start_;
|
||||
last_activity_ = session_start_;
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::EndSession() {
|
||||
// Update final statistics
|
||||
UpdateActiveTime();
|
||||
UpdateIdleTime();
|
||||
|
||||
// Save final stats
|
||||
SaveCurrentStats();
|
||||
|
||||
util::logf("Canvas %s: Session ended. Duration: %s, Operations: %d",
|
||||
canvas_id_.c_str(),
|
||||
FormatDuration(std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - session_start_)).c_str(),
|
||||
current_stats_.total_operations);
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::UpdateActiveTime() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto time_since_activity = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_activity_);
|
||||
|
||||
if (time_since_activity.count() < 5000) { // 5 seconds threshold
|
||||
current_stats_.active_time += time_since_activity;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::UpdateIdleTime() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto time_since_activity = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
now - last_activity_);
|
||||
|
||||
if (time_since_activity.count() >= 5000) { // 5 seconds threshold
|
||||
current_stats_.idle_time += time_since_activity;
|
||||
}
|
||||
}
|
||||
|
||||
void CanvasUsageTracker::SaveCurrentStats() {
|
||||
// Update final times
|
||||
UpdateActiveTime();
|
||||
UpdateIdleTime();
|
||||
|
||||
// Calculate total time
|
||||
current_stats_.total_time = current_stats_.active_time + current_stats_.idle_time;
|
||||
|
||||
// Save to history
|
||||
usage_history_.push_back(current_stats_);
|
||||
|
||||
// Reset for next session
|
||||
current_stats_.Reset();
|
||||
current_stats_.session_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
double CanvasUsageTracker::CalculateAverageOperationTime(const std::string& operation_name) const {
|
||||
auto it = operation_times_.find(operation_name);
|
||||
if (it == operation_times_.end() || it->second.empty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double total = 0.0;
|
||||
for (double time : it->second) {
|
||||
total += time;
|
||||
}
|
||||
|
||||
return total / it->second.size();
|
||||
}
|
||||
|
||||
std::string CanvasUsageTracker::FormatDuration(const std::chrono::milliseconds& duration) const {
|
||||
auto total_ms = duration.count();
|
||||
auto hours = total_ms / 3600000;
|
||||
auto minutes = (total_ms % 3600000) / 60000;
|
||||
auto seconds = (total_ms % 60000) / 1000;
|
||||
|
||||
std::ostringstream ss;
|
||||
if (hours > 0) {
|
||||
ss << hours << "h " << minutes << "m " << seconds << "s";
|
||||
} else if (minutes > 0) {
|
||||
ss << minutes << "m " << seconds << "s";
|
||||
} else {
|
||||
ss << seconds << "s";
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// CanvasUsageManager implementation
|
||||
|
||||
CanvasUsageManager& CanvasUsageManager::Get() {
|
||||
static CanvasUsageManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void CanvasUsageManager::RegisterTracker(const std::string& canvas_id,
|
||||
std::shared_ptr<CanvasUsageTracker> tracker) {
|
||||
trackers_[canvas_id] = tracker;
|
||||
util::logf("Registered usage tracker for canvas: %s", canvas_id.c_str());
|
||||
}
|
||||
|
||||
std::shared_ptr<CanvasUsageTracker> CanvasUsageManager::GetTracker(const std::string& canvas_id) {
|
||||
auto it = trackers_.find(canvas_id);
|
||||
if (it != trackers_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CanvasUsageStats CanvasUsageManager::GetGlobalStats() const {
|
||||
CanvasUsageStats global_stats;
|
||||
|
||||
for (const auto& [id, tracker] : trackers_) {
|
||||
const auto& stats = tracker->GetCurrentStats();
|
||||
|
||||
global_stats.mouse_clicks += stats.mouse_clicks;
|
||||
global_stats.mouse_drags += stats.mouse_drags;
|
||||
global_stats.context_menu_opens += stats.context_menu_opens;
|
||||
global_stats.modal_opens += stats.modal_opens;
|
||||
global_stats.tool_changes += stats.tool_changes;
|
||||
global_stats.mode_changes += stats.mode_changes;
|
||||
global_stats.total_operations += stats.total_operations;
|
||||
|
||||
// Update averages
|
||||
if (stats.average_operation_time_ms > global_stats.average_operation_time_ms) {
|
||||
global_stats.average_operation_time_ms = stats.average_operation_time_ms;
|
||||
}
|
||||
if (stats.max_operation_time_ms > global_stats.max_operation_time_ms) {
|
||||
global_stats.max_operation_time_ms = stats.max_operation_time_ms;
|
||||
}
|
||||
}
|
||||
|
||||
return global_stats;
|
||||
}
|
||||
|
||||
std::string CanvasUsageManager::ExportGlobalReport() const {
|
||||
std::ostringstream report;
|
||||
|
||||
report << "Global Canvas Usage Report\n";
|
||||
report << "==========================\n\n";
|
||||
|
||||
report << "Registered Canvases: " << trackers_.size() << "\n\n";
|
||||
|
||||
for (const auto& [id, tracker] : trackers_) {
|
||||
report << "Canvas: " << id << "\n";
|
||||
report << "----------------------------------------\n";
|
||||
report << tracker->ExportUsageReport() << "\n\n";
|
||||
}
|
||||
|
||||
// Global summary
|
||||
auto global_stats = GetGlobalStats();
|
||||
report << "Global Summary:\n";
|
||||
report << " Total Mouse Clicks: " << global_stats.mouse_clicks << "\n";
|
||||
report << " Total Operations: " << global_stats.total_operations << "\n";
|
||||
report << " Average Operation Time: " << std::fixed << std::setprecision(2)
|
||||
<< global_stats.average_operation_time_ms << " ms\n";
|
||||
report << " Max Operation Time: " << std::fixed << std::setprecision(2)
|
||||
<< global_stats.max_operation_time_ms << " ms\n";
|
||||
|
||||
return report.str();
|
||||
}
|
||||
|
||||
void CanvasUsageManager::ClearAllTrackers() {
|
||||
for (auto& [id, tracker] : trackers_) {
|
||||
tracker->ClearHistory();
|
||||
}
|
||||
trackers_.clear();
|
||||
util::logf("Cleared all canvas usage trackers");
|
||||
}
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
249
src/app/gui/canvas/canvas_usage_tracker.h
Normal file
249
src/app/gui/canvas/canvas_usage_tracker.h
Normal file
@@ -0,0 +1,249 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_USAGE_TRACKER_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_USAGE_TRACKER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
/**
|
||||
* @brief Canvas usage patterns and tracking
|
||||
*/
|
||||
enum class CanvasUsage {
|
||||
kTilePainting, // Drawing tiles on canvas
|
||||
kTileSelecting, // Selecting tiles from canvas
|
||||
kSelectRectangle, // Rectangle selection mode
|
||||
kColorPainting, // Color painting mode
|
||||
kBitmapEditing, // Direct bitmap editing
|
||||
kPaletteEditing, // Palette editing mode
|
||||
kBppConversion, // BPP format conversion
|
||||
kPerformanceMode, // Performance monitoring mode
|
||||
kUnknown // Unknown or mixed usage
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Canvas interaction types
|
||||
*/
|
||||
enum class CanvasInteraction {
|
||||
kMouseClick,
|
||||
kMouseDrag,
|
||||
kMouseRelease,
|
||||
kKeyboardInput,
|
||||
kContextMenu,
|
||||
kModalOpen,
|
||||
kModalClose,
|
||||
kToolChange,
|
||||
kModeChange
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Canvas usage statistics
|
||||
*/
|
||||
struct CanvasUsageStats {
|
||||
CanvasUsage usage_mode = CanvasUsage::kUnknown;
|
||||
std::chrono::steady_clock::time_point session_start;
|
||||
std::chrono::milliseconds total_time{0};
|
||||
std::chrono::milliseconds active_time{0};
|
||||
std::chrono::milliseconds idle_time{0};
|
||||
|
||||
// Interaction counts
|
||||
int mouse_clicks = 0;
|
||||
int mouse_drags = 0;
|
||||
int context_menu_opens = 0;
|
||||
int modal_opens = 0;
|
||||
int tool_changes = 0;
|
||||
int mode_changes = 0;
|
||||
|
||||
// Performance metrics
|
||||
double average_operation_time_ms = 0.0;
|
||||
double max_operation_time_ms = 0.0;
|
||||
int total_operations = 0;
|
||||
|
||||
// Canvas state
|
||||
ImVec2 canvas_size = ImVec2(0, 0);
|
||||
ImVec2 content_size = ImVec2(0, 0);
|
||||
float global_scale = 1.0F;
|
||||
float grid_step = 32.0F;
|
||||
bool enable_grid = true;
|
||||
bool enable_hex_labels = false;
|
||||
bool enable_custom_labels = false;
|
||||
|
||||
void Reset() {
|
||||
usage_mode = CanvasUsage::kUnknown;
|
||||
session_start = std::chrono::steady_clock::now();
|
||||
total_time = std::chrono::milliseconds{0};
|
||||
active_time = std::chrono::milliseconds{0};
|
||||
idle_time = std::chrono::milliseconds{0};
|
||||
mouse_clicks = 0;
|
||||
mouse_drags = 0;
|
||||
context_menu_opens = 0;
|
||||
modal_opens = 0;
|
||||
tool_changes = 0;
|
||||
mode_changes = 0;
|
||||
average_operation_time_ms = 0.0;
|
||||
max_operation_time_ms = 0.0;
|
||||
total_operations = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Canvas usage tracking and analysis system
|
||||
*/
|
||||
class CanvasUsageTracker {
|
||||
public:
|
||||
CanvasUsageTracker() = default;
|
||||
|
||||
/**
|
||||
* @brief Initialize the usage tracker
|
||||
*/
|
||||
void Initialize(const std::string& canvas_id);
|
||||
|
||||
/**
|
||||
* @brief Set the current usage mode
|
||||
*/
|
||||
void SetUsageMode(CanvasUsage usage);
|
||||
|
||||
/**
|
||||
* @brief Record an interaction
|
||||
*/
|
||||
void RecordInteraction(CanvasInteraction interaction,
|
||||
const std::string& details = "");
|
||||
|
||||
/**
|
||||
* @brief Record operation timing
|
||||
*/
|
||||
void RecordOperation(const std::string& operation_name,
|
||||
double time_ms);
|
||||
|
||||
/**
|
||||
* @brief Update canvas state
|
||||
*/
|
||||
void UpdateCanvasState(const ImVec2& canvas_size,
|
||||
const ImVec2& content_size,
|
||||
float global_scale,
|
||||
float grid_step,
|
||||
bool enable_grid,
|
||||
bool enable_hex_labels,
|
||||
bool enable_custom_labels);
|
||||
|
||||
/**
|
||||
* @brief Get current usage statistics
|
||||
*/
|
||||
const CanvasUsageStats& GetCurrentStats() const { return current_stats_; }
|
||||
|
||||
/**
|
||||
* @brief Get usage history
|
||||
*/
|
||||
const std::vector<CanvasUsageStats>& GetUsageHistory() const { return usage_history_; }
|
||||
|
||||
/**
|
||||
* @brief Get usage mode name
|
||||
*/
|
||||
std::string GetUsageModeName(CanvasUsage usage) const;
|
||||
|
||||
/**
|
||||
* @brief Get usage mode color for UI
|
||||
*/
|
||||
ImVec4 GetUsageModeColor(CanvasUsage usage) const;
|
||||
|
||||
/**
|
||||
* @brief Get usage recommendations
|
||||
*/
|
||||
std::vector<std::string> GetUsageRecommendations() const;
|
||||
|
||||
/**
|
||||
* @brief Export usage report
|
||||
*/
|
||||
std::string ExportUsageReport() const;
|
||||
|
||||
/**
|
||||
* @brief Clear usage history
|
||||
*/
|
||||
void ClearHistory();
|
||||
|
||||
/**
|
||||
* @brief Start session
|
||||
*/
|
||||
void StartSession();
|
||||
|
||||
/**
|
||||
* @brief End session
|
||||
*/
|
||||
void EndSession();
|
||||
|
||||
private:
|
||||
std::string canvas_id_;
|
||||
CanvasUsageStats current_stats_;
|
||||
std::vector<CanvasUsageStats> usage_history_;
|
||||
std::chrono::steady_clock::time_point last_activity_;
|
||||
std::chrono::steady_clock::time_point session_start_;
|
||||
|
||||
// Interaction history
|
||||
std::vector<std::pair<CanvasInteraction, std::string>> interaction_history_;
|
||||
std::unordered_map<std::string, std::vector<double>> operation_times_;
|
||||
|
||||
// Helper methods
|
||||
void UpdateActiveTime();
|
||||
void UpdateIdleTime();
|
||||
void SaveCurrentStats();
|
||||
double CalculateAverageOperationTime(const std::string& operation_name) const;
|
||||
std::string FormatDuration(const std::chrono::milliseconds& duration) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Global canvas usage tracker manager
|
||||
*/
|
||||
class CanvasUsageManager {
|
||||
public:
|
||||
static CanvasUsageManager& Get();
|
||||
|
||||
/**
|
||||
* @brief Register a canvas tracker
|
||||
*/
|
||||
void RegisterTracker(const std::string& canvas_id,
|
||||
std::shared_ptr<CanvasUsageTracker> tracker);
|
||||
|
||||
/**
|
||||
* @brief Get tracker for canvas
|
||||
*/
|
||||
std::shared_ptr<CanvasUsageTracker> GetTracker(const std::string& canvas_id);
|
||||
|
||||
/**
|
||||
* @brief Get all trackers
|
||||
*/
|
||||
const std::unordered_map<std::string, std::shared_ptr<CanvasUsageTracker>>&
|
||||
GetAllTrackers() const { return trackers_; }
|
||||
|
||||
/**
|
||||
* @brief Get global usage statistics
|
||||
*/
|
||||
CanvasUsageStats GetGlobalStats() const;
|
||||
|
||||
/**
|
||||
* @brief Export global usage report
|
||||
*/
|
||||
std::string ExportGlobalReport() const;
|
||||
|
||||
/**
|
||||
* @brief Clear all trackers
|
||||
*/
|
||||
void ClearAllTrackers();
|
||||
|
||||
private:
|
||||
CanvasUsageManager() = default;
|
||||
~CanvasUsageManager() = default;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<CanvasUsageTracker>> trackers_;
|
||||
};
|
||||
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_USAGE_TRACKER_H
|
||||
398
src/app/gui/canvas/canvas_utils_moved.cc
Normal file
398
src/app/gui/canvas/canvas_utils_moved.cc
Normal file
@@ -0,0 +1,398 @@
|
||||
#include "canvas_utils_moved.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "app/core/window.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "util/log.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
namespace CanvasUtils {
|
||||
|
||||
using core::Renderer;
|
||||
|
||||
ImVec2 AlignToGrid(ImVec2 pos, float grid_step) {
|
||||
return ImVec2(std::floor(pos.x / grid_step) * grid_step,
|
||||
std::floor(pos.y / grid_step) * grid_step);
|
||||
}
|
||||
|
||||
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size,
|
||||
float global_scale) {
|
||||
if (content_size.x <= 0 || content_size.y <= 0)
|
||||
return global_scale;
|
||||
|
||||
float scale_x = (canvas_size.x * global_scale) / content_size.x;
|
||||
float scale_y = (canvas_size.y * global_scale) / content_size.y;
|
||||
return std::min(scale_x, scale_y);
|
||||
}
|
||||
|
||||
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale,
|
||||
int tiles_per_row) {
|
||||
float scaled_tile_size = tile_size * scale;
|
||||
int tile_x = static_cast<int>(mouse_pos.x / scaled_tile_size);
|
||||
int tile_y = static_cast<int>(mouse_pos.y / scaled_tile_size);
|
||||
|
||||
return tile_x + (tile_y * tiles_per_row);
|
||||
}
|
||||
|
||||
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
|
||||
if (!rom || palette_manager.palettes_loaded) {
|
||||
return palette_manager.palettes_loaded;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto& palette_groups = rom->palette_group();
|
||||
palette_manager.rom_palette_groups.clear();
|
||||
palette_manager.palette_group_names.clear();
|
||||
|
||||
// Overworld palettes
|
||||
if (palette_groups.overworld_main.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(
|
||||
palette_groups.overworld_main[0]);
|
||||
palette_manager.palette_group_names.push_back("Overworld Main");
|
||||
}
|
||||
if (palette_groups.overworld_aux.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(
|
||||
palette_groups.overworld_aux[0]);
|
||||
palette_manager.palette_group_names.push_back("Overworld Aux");
|
||||
}
|
||||
if (palette_groups.overworld_animated.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(
|
||||
palette_groups.overworld_animated[0]);
|
||||
palette_manager.palette_group_names.push_back("Overworld Animated");
|
||||
}
|
||||
|
||||
// Dungeon palettes
|
||||
if (palette_groups.dungeon_main.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(
|
||||
palette_groups.dungeon_main[0]);
|
||||
palette_manager.palette_group_names.push_back("Dungeon Main");
|
||||
}
|
||||
|
||||
// Sprite palettes
|
||||
if (palette_groups.global_sprites.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(
|
||||
palette_groups.global_sprites[0]);
|
||||
palette_manager.palette_group_names.push_back("Global Sprites");
|
||||
}
|
||||
if (palette_groups.armors.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(palette_groups.armors[0]);
|
||||
palette_manager.palette_group_names.push_back("Armor");
|
||||
}
|
||||
if (palette_groups.swords.size() > 0) {
|
||||
palette_manager.rom_palette_groups.push_back(palette_groups.swords[0]);
|
||||
palette_manager.palette_group_names.push_back("Swords");
|
||||
}
|
||||
|
||||
palette_manager.palettes_loaded = true;
|
||||
util::logf("Canvas: Loaded %zu ROM palette groups",
|
||||
palette_manager.rom_palette_groups.size());
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
util::logf("Canvas: Failed to load ROM palette groups: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ApplyPaletteGroup(gfx::Bitmap* bitmap,
|
||||
const CanvasPaletteManager& palette_manager,
|
||||
int group_index, int palette_index) {
|
||||
if (!bitmap || group_index < 0 ||
|
||||
group_index >=
|
||||
static_cast<int>(palette_manager.rom_palette_groups.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const auto& selected_palette =
|
||||
palette_manager.rom_palette_groups[group_index];
|
||||
|
||||
// Apply the palette based on the index
|
||||
if (palette_index >= 0 && palette_index < 8) {
|
||||
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
|
||||
} else {
|
||||
bitmap->SetPalette(selected_palette);
|
||||
}
|
||||
|
||||
Renderer::Get().UpdateBitmap(bitmap);
|
||||
util::logf("Canvas: Applied palette group %d, index %d to bitmap",
|
||||
group_index, palette_index);
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
util::logf("Canvas: Failed to apply palette: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Drawing utility functions
|
||||
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
int x, int y, int w, int h, ImVec4 color,
|
||||
float global_scale) {
|
||||
// Apply global scale to position and size
|
||||
float scaled_x = x * global_scale;
|
||||
float scaled_y = y * global_scale;
|
||||
float scaled_w = w * global_scale;
|
||||
float scaled_h = h * global_scale;
|
||||
|
||||
ImVec2 origin(canvas_p0.x + scrolling.x + scaled_x,
|
||||
canvas_p0.y + scrolling.y + scaled_y);
|
||||
ImVec2 size(canvas_p0.x + scrolling.x + scaled_x + scaled_w,
|
||||
canvas_p0.y + scrolling.y + scaled_y + scaled_h);
|
||||
|
||||
uint32_t color_u32 = IM_COL32(color.x, color.y, color.z, color.w);
|
||||
draw_list->AddRectFilled(origin, size, color_u32);
|
||||
|
||||
// Add a black outline
|
||||
ImVec2 outline_origin(origin.x - 1, origin.y - 1);
|
||||
ImVec2 outline_size(size.x + 1, size.y + 1);
|
||||
draw_list->AddRect(outline_origin, outline_size, IM_COL32(0, 0, 0, 255));
|
||||
}
|
||||
|
||||
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
const std::string& text, int x, int y, float global_scale) {
|
||||
// Apply global scale to text position
|
||||
float scaled_x = x * global_scale;
|
||||
float scaled_y = y * global_scale;
|
||||
|
||||
ImVec2 text_pos(canvas_p0.x + scrolling.x + scaled_x,
|
||||
canvas_p0.y + scrolling.y + scaled_y);
|
||||
|
||||
// Draw text with black shadow for better visibility
|
||||
draw_list->AddText(ImVec2(text_pos.x + 1, text_pos.y + 1),
|
||||
IM_COL32(0, 0, 0, 255), text.c_str());
|
||||
draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255), text.c_str());
|
||||
}
|
||||
|
||||
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, int x, int y, int w, int h,
|
||||
uint32_t color) {
|
||||
ImVec2 origin(canvas_p0.x + scrolling.x + x, canvas_p0.y + scrolling.y + y);
|
||||
ImVec2 size(canvas_p0.x + scrolling.x + x + w,
|
||||
canvas_p0.y + scrolling.y + y + h);
|
||||
draw_list->AddRect(origin, size, color, 0, 0, 1.5f);
|
||||
}
|
||||
|
||||
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, int x, int y, int w, int h,
|
||||
ImVec4 color) {
|
||||
uint32_t color_u32 =
|
||||
IM_COL32(color.x * 255, color.y * 255, color.z * 255, color.w * 255);
|
||||
DrawCanvasOutline(draw_list, canvas_p0, scrolling, x, y, w, h, color_u32);
|
||||
}
|
||||
|
||||
// Grid utility functions
|
||||
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 canvas_p1, ImVec2 scrolling, float grid_step,
|
||||
float global_scale) {
|
||||
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
|
||||
const float grid_thickness = 0.5f;
|
||||
|
||||
float scaled_grid_step = grid_step * global_scale;
|
||||
|
||||
for (float x = fmodf(scrolling.x, scaled_grid_step);
|
||||
x < (canvas_p1.x - canvas_p0.x); x += scaled_grid_step) {
|
||||
draw_list->AddLine(ImVec2(canvas_p0.x + x, canvas_p0.y),
|
||||
ImVec2(canvas_p0.x + x, canvas_p1.y), grid_color,
|
||||
grid_thickness);
|
||||
}
|
||||
|
||||
for (float y = fmodf(scrolling.y, scaled_grid_step);
|
||||
y < (canvas_p1.y - canvas_p0.y); y += scaled_grid_step) {
|
||||
draw_list->AddLine(ImVec2(canvas_p0.x, canvas_p0.y + y),
|
||||
ImVec2(canvas_p1.x, canvas_p0.y + y), grid_color,
|
||||
grid_thickness);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, int highlight_tile_id,
|
||||
float grid_step) {
|
||||
if (highlight_tile_id == -1)
|
||||
return;
|
||||
|
||||
int tile_x = highlight_tile_id % 8;
|
||||
int tile_y = highlight_tile_id / 8;
|
||||
ImVec2 tile_pos(canvas_p0.x + scrolling.x + tile_x * grid_step,
|
||||
canvas_p0.y + scrolling.y + tile_y * grid_step);
|
||||
ImVec2 tile_pos_end(tile_pos.x + grid_step, tile_pos.y + grid_step);
|
||||
|
||||
draw_list->AddRectFilled(tile_pos, tile_pos_end, IM_COL32(255, 0, 255, 255));
|
||||
}
|
||||
|
||||
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0,
|
||||
ImVec2 scrolling, ImVec2 canvas_sz, float grid_step,
|
||||
float global_scale) {
|
||||
float scaled_grid_step = grid_step * global_scale;
|
||||
|
||||
for (float x = fmodf(scrolling.x, scaled_grid_step);
|
||||
x < canvas_sz.x * global_scale; x += scaled_grid_step) {
|
||||
for (float y = fmodf(scrolling.y, scaled_grid_step);
|
||||
y < canvas_sz.y * global_scale; y += scaled_grid_step) {
|
||||
int tile_x = (x - scrolling.x) / scaled_grid_step;
|
||||
int tile_y = (y - scrolling.y) / scaled_grid_step;
|
||||
int tile_id = tile_x + (tile_y * 16);
|
||||
|
||||
char hex_id[8];
|
||||
snprintf(hex_id, sizeof(hex_id), "%02X", tile_id);
|
||||
|
||||
draw_list->AddText(ImVec2(canvas_p0.x + x + (scaled_grid_step / 2) - 4,
|
||||
canvas_p0.y + y + (scaled_grid_step / 2) - 4),
|
||||
IM_COL32(255, 255, 255, 255), hex_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Layout and interaction utilities
|
||||
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size,
|
||||
bool use_custom) {
|
||||
return use_custom ? custom_size : content_region;
|
||||
}
|
||||
|
||||
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale) {
|
||||
return ImVec2(canvas_size.x * global_scale, canvas_size.y * global_scale);
|
||||
}
|
||||
|
||||
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1) {
|
||||
return point.x >= canvas_p0.x && point.x <= canvas_p1.x &&
|
||||
point.y >= canvas_p0.y && point.y <= canvas_p1.y;
|
||||
}
|
||||
|
||||
// Size reporting for ImGui table integration
|
||||
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale,
|
||||
float padding) {
|
||||
// Calculate minimum size needed to display content with padding
|
||||
ImVec2 min_size = ImVec2(content_size.x * global_scale + padding * 2,
|
||||
content_size.y * global_scale + padding * 2);
|
||||
|
||||
// Ensure minimum practical size
|
||||
min_size.x = std::max(min_size.x, 64.0f);
|
||||
min_size.y = std::max(min_size.y, 64.0f);
|
||||
|
||||
return min_size;
|
||||
}
|
||||
|
||||
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale,
|
||||
float min_scale) {
|
||||
// Calculate preferred size with minimum scale constraint
|
||||
float effective_scale = std::max(global_scale, min_scale);
|
||||
ImVec2 preferred_size = ImVec2(content_size.x * effective_scale + 8.0f,
|
||||
content_size.y * effective_scale + 8.0f);
|
||||
|
||||
// Cap to reasonable maximum sizes for table integration
|
||||
preferred_size.x = std::min(preferred_size.x, 800.0f);
|
||||
preferred_size.y = std::min(preferred_size.y, 600.0f);
|
||||
|
||||
return preferred_size;
|
||||
}
|
||||
|
||||
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label) {
|
||||
// Reserve space in ImGui layout so tables know the size
|
||||
if (!label.empty()) {
|
||||
ImGui::Text("%s", label.c_str());
|
||||
}
|
||||
ImGui::Dummy(canvas_size);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() -
|
||||
canvas_size.x); // Move back to start
|
||||
}
|
||||
|
||||
void SetNextCanvasSize(ImVec2 size, bool auto_resize) {
|
||||
if (auto_resize) {
|
||||
// Use auto-sizing child window for table integration
|
||||
ImGui::SetNextWindowContentSize(size);
|
||||
} else {
|
||||
// Fixed size
|
||||
ImGui::SetNextWindowSize(size);
|
||||
}
|
||||
}
|
||||
|
||||
// High-level composite operations
|
||||
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id) {
|
||||
if (!ctx.enable_grid)
|
||||
return;
|
||||
|
||||
ctx.draw_list->PushClipRect(ctx.canvas_p0, ctx.canvas_p1, true);
|
||||
|
||||
// Draw grid lines
|
||||
DrawCanvasGridLines(ctx.draw_list, ctx.canvas_p0, ctx.canvas_p1,
|
||||
ctx.scrolling, ctx.grid_step, ctx.global_scale);
|
||||
|
||||
// Draw highlight if specified
|
||||
if (highlight_tile_id != -1) {
|
||||
DrawCustomHighlight(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
|
||||
highlight_tile_id, ctx.grid_step * ctx.global_scale);
|
||||
}
|
||||
|
||||
// Draw hex labels if enabled
|
||||
if (ctx.enable_hex_labels) {
|
||||
DrawHexTileLabels(ctx.draw_list, ctx.canvas_p0, ctx.scrolling,
|
||||
ImVec2(ctx.canvas_p1.x - ctx.canvas_p0.x,
|
||||
ctx.canvas_p1.y - ctx.canvas_p0.y),
|
||||
ctx.grid_step, ctx.global_scale);
|
||||
}
|
||||
|
||||
ctx.draw_list->PopClipRect();
|
||||
}
|
||||
|
||||
void DrawCanvasOverlay(const CanvasRenderContext& ctx,
|
||||
const ImVector<ImVec2>& points,
|
||||
const ImVector<ImVec2>& selected_points) {
|
||||
const ImVec2 origin(ctx.canvas_p0.x + ctx.scrolling.x,
|
||||
ctx.canvas_p0.y + ctx.scrolling.y);
|
||||
|
||||
// Draw hover points
|
||||
for (int n = 0; n < points.Size; n += 2) {
|
||||
ctx.draw_list->AddRect(
|
||||
ImVec2(origin.x + points[n].x, origin.y + points[n].y),
|
||||
ImVec2(origin.x + points[n + 1].x, origin.y + points[n + 1].y),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
}
|
||||
|
||||
// Draw selection rectangles
|
||||
if (!selected_points.empty()) {
|
||||
for (int n = 0; n < selected_points.size(); n += 2) {
|
||||
ctx.draw_list->AddRect(ImVec2(origin.x + selected_points[n].x,
|
||||
origin.y + selected_points[n].y),
|
||||
ImVec2(origin.x + selected_points[n + 1].x + 0x10,
|
||||
origin.y + selected_points[n + 1].y + 0x10),
|
||||
IM_COL32(255, 255, 255, 255), 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawCanvasLabels(const CanvasRenderContext& ctx,
|
||||
const ImVector<ImVector<std::string>>& labels,
|
||||
int current_labels, int tile_id_offset) {
|
||||
if (current_labels >= labels.size())
|
||||
return;
|
||||
|
||||
float scaled_grid_step = ctx.grid_step * ctx.global_scale;
|
||||
|
||||
for (float x = fmodf(ctx.scrolling.x, scaled_grid_step);
|
||||
x < (ctx.canvas_p1.x - ctx.canvas_p0.x); x += scaled_grid_step) {
|
||||
for (float y = fmodf(ctx.scrolling.y, scaled_grid_step);
|
||||
y < (ctx.canvas_p1.y - ctx.canvas_p0.y); y += scaled_grid_step) {
|
||||
int tile_x = (x - ctx.scrolling.x) / scaled_grid_step;
|
||||
int tile_y = (y - ctx.scrolling.y) / scaled_grid_step;
|
||||
int tile_id = tile_x + (tile_y * tile_id_offset);
|
||||
|
||||
if (tile_id >= labels[current_labels].size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const std::string& label = labels[current_labels][tile_id];
|
||||
ctx.draw_list->AddText(
|
||||
ImVec2(ctx.canvas_p0.x + x + (scaled_grid_step / 2) - tile_id_offset,
|
||||
ctx.canvas_p0.y + y + (scaled_grid_step / 2) - tile_id_offset),
|
||||
IM_COL32(255, 255, 255, 255), label.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CanvasUtils
|
||||
} // namespace canvas
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
198
src/app/gui/canvas/canvas_utils_moved.h
Normal file
198
src/app/gui/canvas/canvas_utils_moved.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_UTILS_H
|
||||
#define YAZE_APP_GUI_CANVAS_CANVAS_UTILS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
namespace canvas {
|
||||
|
||||
/**
|
||||
* @brief Configuration for canvas display and interaction
|
||||
*
|
||||
* Modern single-source-of-truth configuration structure.
|
||||
* Replaces the dual state management pattern.
|
||||
*/
|
||||
struct CanvasConfig {
|
||||
bool enable_grid = true;
|
||||
bool enable_hex_labels = false;
|
||||
bool enable_custom_labels = false;
|
||||
bool enable_context_menu = true;
|
||||
bool is_draggable = false;
|
||||
bool auto_resize = false;
|
||||
float grid_step = 32.0f;
|
||||
float global_scale = 1.0f;
|
||||
ImVec2 canvas_size = ImVec2(0, 0);
|
||||
ImVec2 content_size = ImVec2(0, 0); // Size of actual content (bitmap, etc.)
|
||||
bool custom_canvas_size = false;
|
||||
ImVec2 scrolling = ImVec2(0, 0);
|
||||
|
||||
// Modern callbacks for config updates
|
||||
std::function<void(const CanvasConfig&)> on_config_changed;
|
||||
std::function<void(const CanvasConfig&)> on_scale_changed;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Selection state for canvas interactions
|
||||
*/
|
||||
struct CanvasSelection {
|
||||
std::vector<ImVec2> selected_tiles;
|
||||
std::vector<ImVec2> selected_points;
|
||||
ImVec2 selected_tile_pos = ImVec2(-1, -1);
|
||||
bool select_rect_active = false;
|
||||
|
||||
void Clear() {
|
||||
selected_tiles.clear();
|
||||
selected_points.clear();
|
||||
selected_tile_pos = ImVec2(-1, -1);
|
||||
select_rect_active = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Palette management state for canvas
|
||||
*/
|
||||
struct CanvasPaletteManager {
|
||||
std::vector<gfx::SnesPalette> rom_palette_groups;
|
||||
std::vector<std::string> palette_group_names;
|
||||
gfx::SnesPalette original_palette;
|
||||
bool palettes_loaded = false;
|
||||
int current_group_index = 0;
|
||||
int current_palette_index = 0;
|
||||
|
||||
void Clear() {
|
||||
rom_palette_groups.clear();
|
||||
palette_group_names.clear();
|
||||
original_palette.clear();
|
||||
palettes_loaded = false;
|
||||
current_group_index = 0;
|
||||
current_palette_index = 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Context menu item configuration
|
||||
*/
|
||||
struct CanvasContextMenuItem {
|
||||
std::string label;
|
||||
std::string shortcut;
|
||||
std::function<void()> callback;
|
||||
std::function<bool()> enabled_condition = []() { return true; };
|
||||
std::vector<CanvasContextMenuItem> subitems;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Render context for canvas drawing operations
|
||||
*/
|
||||
struct CanvasRenderContext {
|
||||
ImDrawList* draw_list;
|
||||
ImVec2 canvas_p0;
|
||||
ImVec2 canvas_p1;
|
||||
ImVec2 scrolling;
|
||||
float global_scale;
|
||||
bool enable_grid;
|
||||
bool enable_hex_labels;
|
||||
float grid_step;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Utility functions for canvas operations
|
||||
*
|
||||
* Stateless utilities for common canvas operations.
|
||||
* Following ImGui design pattern of pure helper functions.
|
||||
*/
|
||||
namespace CanvasUtils {
|
||||
|
||||
// ==================== Core Utilities (Stateless) ====================
|
||||
|
||||
/**
|
||||
* @brief Align position to grid
|
||||
* Pure function - no side effects
|
||||
*/
|
||||
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
|
||||
|
||||
/**
|
||||
* @brief Calculate effective scale for content
|
||||
*/
|
||||
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale);
|
||||
|
||||
/**
|
||||
* @brief Get tile ID from mouse position
|
||||
*/
|
||||
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row);
|
||||
|
||||
// ==================== Palette Management ====================
|
||||
|
||||
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
|
||||
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
|
||||
int group_index, int palette_index);
|
||||
|
||||
// ==================== Drawing Utilities ====================
|
||||
|
||||
void DrawCanvasRect(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
int x, int y, int w, int h, ImVec4 color, float global_scale);
|
||||
|
||||
void DrawCanvasText(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
const std::string& text, int x, int y, float global_scale);
|
||||
|
||||
void DrawCanvasOutline(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
int x, int y, int w, int h, uint32_t color = IM_COL32(255, 255, 255, 200));
|
||||
|
||||
void DrawCanvasOutlineWithColor(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
int x, int y, int w, int h, ImVec4 color);
|
||||
|
||||
// ==================== Grid Utilities ====================
|
||||
|
||||
void DrawCanvasGridLines(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 canvas_p1,
|
||||
ImVec2 scrolling, float grid_step, float global_scale);
|
||||
|
||||
void DrawCustomHighlight(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
int highlight_tile_id, float grid_step);
|
||||
|
||||
void DrawHexTileLabels(ImDrawList* draw_list, ImVec2 canvas_p0, ImVec2 scrolling,
|
||||
ImVec2 canvas_sz, float grid_step, float global_scale);
|
||||
|
||||
// ==================== Layout Utilities ====================
|
||||
|
||||
ImVec2 CalculateCanvasSize(ImVec2 content_region, ImVec2 custom_size, bool use_custom);
|
||||
ImVec2 CalculateScaledCanvasSize(ImVec2 canvas_size, float global_scale);
|
||||
bool IsPointInCanvas(ImVec2 point, ImVec2 canvas_p0, ImVec2 canvas_p1);
|
||||
|
||||
// ==================== Size Reporting for Tables ====================
|
||||
|
||||
ImVec2 CalculateMinimumCanvasSize(ImVec2 content_size, float global_scale, float padding = 4.0f);
|
||||
ImVec2 CalculatePreferredCanvasSize(ImVec2 content_size, float global_scale, float min_scale = 1.0f);
|
||||
void ReserveCanvasSpace(ImVec2 canvas_size, const std::string& label = "");
|
||||
void SetNextCanvasSize(ImVec2 size, bool auto_resize = false);
|
||||
|
||||
// ==================== Composite Operations ====================
|
||||
|
||||
void DrawCanvasGrid(const CanvasRenderContext& ctx, int highlight_tile_id = -1);
|
||||
void DrawCanvasOverlay(const CanvasRenderContext& ctx, const ImVector<ImVec2>& points,
|
||||
const ImVector<ImVec2>& selected_points);
|
||||
void DrawCanvasLabels(const CanvasRenderContext& ctx, const ImVector<ImVector<std::string>>& labels,
|
||||
int current_labels, int tile_id_offset);
|
||||
|
||||
} // namespace CanvasUtils
|
||||
|
||||
} // namespace canvas
|
||||
|
||||
// ==================== Compatibility Aliases (gui namespace) ====================
|
||||
|
||||
// For backward compatibility, provide aliases in gui namespace
|
||||
using CanvasConfig = canvas::CanvasConfig;
|
||||
using CanvasSelection = canvas::CanvasSelection;
|
||||
using CanvasPaletteManager = canvas::CanvasPaletteManager;
|
||||
using CanvasContextMenuItem = canvas::CanvasContextMenuItem;
|
||||
|
||||
namespace CanvasUtils = canvas::CanvasUtils;
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_CANVAS_CANVAS_UTILS_H
|
||||
@@ -11,4 +11,10 @@ set(
|
||||
app/gui/theme_manager.cc
|
||||
app/gui/background_renderer.cc
|
||||
app/gui/bpp_format_ui.cc
|
||||
# Canvas system components
|
||||
app/gui/canvas/canvas_modals.cc
|
||||
app/gui/canvas/canvas_context_menu.cc
|
||||
app/gui/canvas/canvas_usage_tracker.cc
|
||||
app/gui/canvas/canvas_performance_integration.cc
|
||||
app/gui/canvas/canvas_interaction_handler.cc
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user