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:
scawful
2025-09-30 13:10:32 -04:00
parent 9e0f614ce8
commit 991366113e
18 changed files with 4792 additions and 59 deletions

View File

@@ -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_) {

View File

@@ -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

View 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
)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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
)