Files
yaze/src/app/gui/canvas/canvas_modals.cc
scawful 991366113e 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.
2025-09-30 13:10:32 -04:00

592 lines
20 KiB
C++

#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