Refactor EditorManager and OverworldEditor for enhanced functionality and error handling

- Updated EditorManager to improve welcome screen logic, ensuring it only displays in truly empty states and not when a ROM is loaded but current_rom_ is null.
- Enhanced error handling in EditorManager by routing editor errors to the toast manager for better user feedback.
- Improved OverworldEditor with enhanced tile interaction detection and added scratch space functionality for better layout management.
- Introduced a new ScratchSpaceSlot structure in OverworldEditor to manage scratch space for tile16 layouts, allowing for more flexible editing and selection.
- Added utility functions in canvas_utils for grid alignment and effective scaling, improving overall canvas interaction.
- Implemented an enhanced palette editor with ROM integration, providing users with tools for color analysis and palette management.
This commit is contained in:
scawful
2025-09-27 15:24:58 -04:00
parent 261032b1bd
commit a9ead0a45c
17 changed files with 4434 additions and 821 deletions

View File

@@ -7,6 +7,8 @@
#include "app/gfx/bitmap.h"
#include "app/gui/color.h"
#include "app/gui/style.h"
#include "app/gui/canvas_utils.h"
#include "util/log.h"
#include "imgui/imgui.h"
#include "imgui_memory_editor.h"
@@ -45,10 +47,135 @@ ImVec2 AlignPosToGrid(ImVec2 pos, float scale) {
}
} // namespace
// Canvas class implementation begins here
void Canvas::InitializeDefaults() {
// Initialize configuration with sensible defaults
config_.enable_grid = true;
config_.enable_hex_labels = false;
config_.enable_custom_labels = false;
config_.enable_context_menu = true;
config_.is_draggable = false;
config_.grid_step = 32.0f;
config_.global_scale = 1.0f;
config_.canvas_size = ImVec2(0, 0);
config_.custom_canvas_size = false;
// Initialize selection state
selection_.Clear();
// Initialize palette editor
palette_editor_ = std::make_unique<EnhancedPaletteEditor>();
// Initialize legacy compatibility variables to match config
enable_grid_ = config_.enable_grid;
enable_hex_tile_labels_ = config_.enable_hex_labels;
enable_custom_labels_ = config_.enable_custom_labels;
enable_context_menu_ = config_.enable_context_menu;
draggable_ = config_.is_draggable;
custom_step_ = config_.grid_step;
global_scale_ = config_.global_scale;
custom_canvas_size_ = config_.custom_canvas_size;
select_rect_active_ = selection_.select_rect_active;
selected_tile_pos_ = selection_.selected_tile_pos;
}
void Canvas::Cleanup() {
palette_editor_.reset();
selection_.Clear();
}
void Canvas::InitializePaletteEditor(Rom* rom) {
rom_ = rom;
if (palette_editor_) {
palette_editor_->Initialize(rom);
}
}
void Canvas::ShowPaletteEditor() {
if (palette_editor_ && bitmap_) {
auto mutable_palette = bitmap_->mutable_palette();
palette_editor_->ShowPaletteEditor(*mutable_palette, "Canvas Palette Editor");
}
}
void Canvas::ShowColorAnalysis() {
if (palette_editor_ && bitmap_) {
palette_editor_->ShowColorAnalysis(*bitmap_, "Canvas Color Analysis");
}
}
bool Canvas::ApplyROMPalette(int group_index, int palette_index) {
if (palette_editor_ && bitmap_) {
return palette_editor_->ApplyROMPalette(bitmap_, group_index, palette_index);
}
return false;
}
// Size reporting methods for table integration
ImVec2 Canvas::GetMinimumSize() const {
return CanvasUtils::CalculateMinimumCanvasSize(config_.content_size, config_.global_scale);
}
ImVec2 Canvas::GetPreferredSize() const {
return CanvasUtils::CalculatePreferredCanvasSize(config_.content_size, config_.global_scale);
}
void Canvas::ReserveTableSpace(const std::string& label) {
ImVec2 size = config_.auto_resize ? GetPreferredSize() : config_.canvas_size;
CanvasUtils::ReserveCanvasSpace(size, label);
}
bool Canvas::BeginTableCanvas(const std::string& label) {
if (config_.auto_resize) {
ImVec2 preferred_size = GetPreferredSize();
CanvasUtils::SetNextCanvasSize(preferred_size, true);
}
// Begin child window that properly reports size to tables
std::string child_id = canvas_id_ + "_TableChild";
ImVec2 child_size = config_.auto_resize ? ImVec2(0, 0) : config_.canvas_size;
bool result = ImGui::BeginChild(child_id.c_str(), child_size,
true, // Always show border for table integration
ImGuiWindowFlags_AlwaysVerticalScrollbar);
if (!label.empty()) {
ImGui::Text("%s", label.c_str());
}
return result;
}
void Canvas::EndTableCanvas() {
ImGui::EndChild();
}
// Improved interaction detection methods
bool Canvas::HasValidSelection() const {
return !points_.empty() && points_.size() >= 2;
}
bool Canvas::WasClicked(ImGuiMouseButton button) const {
return ImGui::IsItemClicked(button) && HasValidSelection();
}
bool Canvas::WasDoubleClicked(ImGuiMouseButton button) const {
return ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(button) && HasValidSelection();
}
ImVec2 Canvas::GetLastClickPosition() const {
if (HasValidSelection()) {
return points_[0]; // Return the first point of the selection
}
return ImVec2(-1, -1); // Invalid position
}
void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event,
int tile_size, float scale) {
global_scale_ = scale;
config_.global_scale = scale;
global_scale_ = scale; // Legacy compatibility
DrawBackground();
DrawContextMenu();
DrawBitmap(bitmap, 2, scale);
@@ -60,7 +187,8 @@ void Canvas::UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
}
void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
enable_custom_labels_ = true;
config_.enable_custom_labels = true;
enable_custom_labels_ = true; // Legacy compatibility
DrawBackground(bg_size);
DrawInfoGrid(grid_size, 8, label_id);
DrawOverlay();
@@ -69,21 +197,27 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
void Canvas::DrawBackground(ImVec2 canvas_size) {
draw_list_ = GetWindowDrawList();
canvas_p0_ = GetCursorScreenPos();
if (!custom_canvas_size_) canvas_sz_ = GetContentRegionAvail();
if (canvas_size.x != 0) canvas_sz_ = canvas_size;
canvas_p1_ = ImVec2(canvas_p0_.x + (canvas_sz_.x * global_scale_),
canvas_p0_.y + (canvas_sz_.y * global_scale_));
// Calculate canvas size using utility function
ImVec2 content_region = GetContentRegionAvail();
canvas_sz_ = CanvasUtils::CalculateCanvasSize(content_region, config_.canvas_size, config_.custom_canvas_size);
if (canvas_size.x != 0) {
canvas_sz_ = canvas_size;
config_.canvas_size = canvas_size;
}
// Calculate scaled canvas bounds
ImVec2 scaled_size = CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale);
canvas_p1_ = ImVec2(canvas_p0_.x + scaled_size.x, canvas_p0_.y + scaled_size.y);
// Draw border and background color
draw_list_->AddRectFilled(canvas_p0_, canvas_p1_, kRectangleColor);
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
ImGui::InvisibleButton(
canvas_id_.c_str(),
ImVec2(canvas_sz_.x * global_scale_, canvas_sz_.y * global_scale_),
kMouseFlags);
ImGui::InvisibleButton(canvas_id_.c_str(), scaled_size, kMouseFlags);
if (draggable_ && IsItemHovered()) {
if (config_.is_draggable && IsItemHovered()) {
const ImGuiIO &io = GetIO();
const bool is_active = IsItemActive(); // Held
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
@@ -140,14 +274,20 @@ void Canvas::DrawContextMenu() {
if (MenuItem("Zoom to Fit", nullptr, false) && bitmap_) {
SetZoomToFit(*bitmap_);
}
if (MenuItem("Advanced Properties", nullptr, false)) {
ImGui::OpenPopup("Advanced Canvas Properties");
}
ImGui::Separator();
MenuItem("Show Grid", nullptr, &enable_grid_);
Selectable("Show Position Labels", &enable_hex_tile_labels_);
if (MenuItem("Bitmap Properties", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Bitmap Properties");
}
if (MenuItem("Edit Palette", nullptr, false) && bitmap_) {
ImGui::OpenPopup("Palette Editor");
ShowPaletteEditor();
}
if (MenuItem("Color Analysis", nullptr, false) && bitmap_) {
ShowColorAnalysis();
}
if (MenuItem("Scaling Controls", nullptr, false)) {
ImGui::OpenPopup("Scaling Controls");
}
if (BeginMenu("Canvas Properties")) {
Text("Canvas Size: %.0f x %.0f", canvas_sz_.x, canvas_sz_.y);
@@ -178,38 +318,78 @@ void Canvas::DrawContextMenu() {
EndMenu();
}
if (BeginMenu("Change Palette")) {
Text("Work in progress");
// TODO: Get ROM data for change palette
// gui::TextWithSeparators("ROM Palette");
// ImGui::SetNextItemWidth(100.f);
// ImGui::Combo("Palette Group", (int *)&edit_palette_group_name_index_,
// gfx::kPaletteGroupAddressesKeys,
// IM_ARRAYSIZE(gfx::kPaletteGroupAddressesKeys));
// ImGui::SetNextItemWidth(100.f);
// gui::InputHexWord("Palette Group Index", &edit_palette_index_);
// auto palette_group = rom()->mutable_palette_group()->get_group(
// gfx::kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
// auto palette = palette_group->mutable_palette(edit_palette_index_);
// if (ImGui::BeginChild("Palette", ImVec2(0, 300), true)) {
// gui::SelectablePalettePipeline(edit_palette_sub_index_,
// refresh_graphics_, *palette);
// if (refresh_graphics_) {
// bitmap_->SetPaletteWithTransparent(*palette,
// edit_palette_sub_index_);
// Renderer::Get().UpdateBitmap(bitmap_);
// refresh_graphics_ = false;
// }
// ImGui::EndChild();
// }
if (BeginMenu("ROM Palette Selection") && rom_) {
Text("Select ROM Palette Group:");
// Enhanced ROM palette group selection
if (palette_editor_) {
// Use our enhanced palette editor's ROM selection
if (MenuItem("Open Enhanced Palette Manager")) {
palette_editor_->ShowROMPaletteManager();
}
ImGui::Separator();
// Quick palette group selection
const char* palette_groups[] = {
"Overworld Main", "Overworld Aux", "Overworld Animated",
"Dungeon Main", "Global Sprites", "Armor", "Swords"
};
if (ImGui::Combo("Quick Palette Group", (int*)&edit_palette_group_name_index_,
palette_groups, IM_ARRAYSIZE(palette_groups))) {
// Group selection changed
}
ImGui::SetNextItemWidth(100.f);
if (ImGui::SliderInt("Palette Index", (int*)&edit_palette_index_, 0, 7)) {
// Palette index changed
}
// Apply button with enhanced functionality
if (ImGui::Button("Apply to Canvas") && bitmap_) {
if (palette_editor_->ApplyROMPalette(bitmap_,
edit_palette_group_name_index_,
edit_palette_index_)) {
util::logf("Applied ROM palette group %d, index %d via context menu",
edit_palette_group_name_index_, edit_palette_index_);
}
}
// Direct palette editing with SelectablePalettePipeline
if (ImGui::TreeNode("Interactive Palette Editor")) {
if (rom_ && bitmap_) {
ImGui::Text("Interactive ROM Palette Editing");
ImGui::Text("Selected Group: %s", palette_groups[edit_palette_group_name_index_]);
// Get the enhanced palette editor's ROM palette if available
if (const auto* rom_palette = palette_editor_->GetSelectedROMPalette()) {
auto editable_palette = const_cast<gfx::SnesPalette&>(*rom_palette);
if (ImGui::BeginChild("SelectablePalette", ImVec2(0, 200), true)) {
// Use the existing SelectablePalettePipeline for interactive editing
gui::SelectablePalettePipeline(edit_palette_sub_index_,
refresh_graphics_, editable_palette);
if (refresh_graphics_) {
bitmap_->SetPaletteWithTransparent(editable_palette, edit_palette_sub_index_);
Renderer::Get().UpdateBitmap(bitmap_);
refresh_graphics_ = false;
util::logf("Applied interactive palette changes to canvas");
}
ImGui::EndChild();
}
} else {
ImGui::Text("Load ROM palettes first using Enhanced Palette Manager");
}
}
ImGui::TreePop();
}
}
EndMenu();
}
if (BeginMenu("View Palette")) {
DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true,
8);
(void)DisplayEditablePalette(*bitmap_->mutable_palette(), "Palette", true, 8);
EndMenu();
}
EndMenu();
@@ -235,11 +415,9 @@ void Canvas::DrawContextMenu() {
ImGui::EndPopup();
}
// Draw enhanced property dialogs
if (bitmap_) {
ShowBitmapProperties(*bitmap_);
ShowPaletteEditor(*bitmap_->mutable_palette());
}
// Draw enhanced property dialogs
ShowAdvancedCanvasProperties();
ShowScalingControls();
}
void Canvas::DrawContextMenuItem(const ContextMenuItem& item) {
@@ -275,66 +453,7 @@ void Canvas::ClearContextMenuItems() {
context_menu_items_.clear();
}
void Canvas::ShowBitmapProperties(const gfx::Bitmap& bitmap) {
if (ImGui::BeginPopupModal("Bitmap Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Bitmap Information");
ImGui::Separator();
ImGui::Text("Size: %d x %d", bitmap.width(), bitmap.height());
ImGui::Text("Depth: %d bits", bitmap.depth());
ImGui::Text("Data Size: %zu bytes", bitmap.size());
ImGui::Text("Active: %s", bitmap.is_active() ? "Yes" : "No");
ImGui::Text("Modified: %s", bitmap.modified() ? "Yes" : "No");
if (bitmap.surface()) {
ImGui::Separator();
ImGui::Text("SDL Surface");
ImGui::Text("Pitch: %d", bitmap.surface()->pitch);
ImGui::Text("Bits Per Pixel: %d", bitmap.surface()->format->BitsPerPixel);
ImGui::Text("Bytes Per Pixel: %d", bitmap.surface()->format->BytesPerPixel);
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void Canvas::ShowPaletteEditor(gfx::SnesPalette& palette) {
if (ImGui::BeginPopupModal("Palette Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Palette Editor");
ImGui::Separator();
// Display palette colors in a grid
int cols = 8;
for (int i = 0; i < palette.size(); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
if (ImGui::ColorButton("##color", display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
// Color selected - could open detailed editor
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d: 0x%04X\nR:%d G:%d B:%d",
i, color.snes(),
(int)(display_color.x * 255),
(int)(display_color.y * 255),
(int)(display_color.z * 255));
}
ImGui::PopID();
}
ImGui::Separator();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ShowPaletteEditor method removed - now handled by EnhancedPaletteEditor
void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
if (!bitmap.is_active()) return;
@@ -342,17 +461,20 @@ void Canvas::SetZoomToFit(const gfx::Bitmap& bitmap) {
ImVec2 available = ImGui::GetContentRegionAvail();
float scale_x = available.x / bitmap.width();
float scale_y = available.y / bitmap.height();
global_scale_ = std::min(scale_x, scale_y);
config_.global_scale = std::min(scale_x, scale_y);
// Ensure minimum readable scale
if (global_scale_ < 0.25f) global_scale_ = 0.25f;
if (config_.global_scale < 0.25f) config_.global_scale = 0.25f;
global_scale_ = config_.global_scale; // Legacy compatibility
// Center the view
scrolling_ = ImVec2(0, 0);
}
void Canvas::ResetView() {
global_scale_ = 1.0f;
config_.global_scale = 1.0f;
global_scale_ = 1.0f; // Legacy compatibility
scrolling_ = ImVec2(0, 0);
}
@@ -656,6 +778,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int border_offset, float scale) {
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x, canvas_p0_.y),
ImVec2(canvas_p0_.x + (bitmap.width() * scale),
@@ -669,14 +795,24 @@ void Canvas::DrawBitmap(Bitmap &bitmap, int x_offset, int y_offset, float scale,
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
// Calculate the actual rendered size including scale and offsets
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + x_offset + scrolling_.x,
canvas_p0_.y + y_offset + scrolling_.y),
ImVec2(
canvas_p0_.x + x_offset + scrolling_.x + (bitmap.width() * scale),
canvas_p0_.y + y_offset + scrolling_.y + (bitmap.height() * scale)),
canvas_p0_.x + x_offset + scrolling_.x + rendered_size.x,
canvas_p0_.y + y_offset + scrolling_.y + rendered_size.y),
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
// Note: Content size for child windows should be set before BeginChild, not here
}
void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
@@ -685,6 +821,10 @@ void Canvas::DrawBitmap(Bitmap &bitmap, ImVec2 dest_pos, ImVec2 dest_size,
return;
}
bitmap_ = &bitmap;
// Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
@@ -710,28 +850,15 @@ void Canvas::DrawBitmapTable(const BitmapTable &gfx_bin) {
}
void Canvas::DrawOutline(int x, int y, int w, int h) {
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, kOutlineRect, 0, 0, 1.5f);
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, IM_COL32(255, 255, 255, 200));
}
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, ImVec4 color) {
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,
IM_COL32(color.x, color.y, color.z, color.w));
CanvasUtils::DrawCanvasOutlineWithColor(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
}
void Canvas::DrawOutlineWithColor(int x, int y, int w, int h, uint32_t color) {
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);
CanvasUtils::DrawCanvasOutline(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color);
}
void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
@@ -840,50 +967,15 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
}
void Canvas::DrawRect(int x, int y, int w, int h, ImVec4 color) {
// 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);
draw_list_->AddRectFilled(origin, size,
IM_COL32(color.x, color.y, color.z, color.w));
// 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, kBlackColor);
CanvasUtils::DrawCanvasRect(draw_list_, canvas_p0_, scrolling_, x, y, w, h, color, config_.global_scale);
}
void Canvas::DrawText(std::string text, int x, int y) {
// Apply global scale to text position
float scaled_x = x * global_scale_;
float scaled_y = y * global_scale_;
draw_list_->AddText(ImVec2(canvas_p0_.x + scrolling_.x + scaled_x + 1,
canvas_p0_.y + scrolling_.y + scaled_y + 1),
kBlackColor, text.data());
draw_list_->AddText(
ImVec2(canvas_p0_.x + scrolling_.x + scaled_x, canvas_p0_.y + scrolling_.y + scaled_y),
kWhiteColor, text.data());
CanvasUtils::DrawCanvasText(draw_list_, canvas_p0_, scrolling_, text, x, y, config_.global_scale);
}
void Canvas::DrawGridLines(float grid_step) {
const uint32_t grid_color = IM_COL32(200, 200, 200, 50);
const float grid_thickness = 0.5f;
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += 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, grid_step);
y < canvas_sz_.y * global_scale_; y += 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);
CanvasUtils::DrawCanvasGridLines(draw_list_, canvas_p0_, canvas_p1_, scrolling_, grid_step, config_.global_scale);
}
void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
@@ -923,91 +1015,50 @@ void Canvas::DrawInfoGrid(float grid_step, int tile_id_offset, int label_id) {
}
void Canvas::DrawCustomHighlight(float grid_step) {
if (highlight_tile_id != -1) {
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));
}
CanvasUtils::DrawCustomHighlight(draw_list_, canvas_p0_, scrolling_, highlight_tile_id, grid_step);
}
void Canvas::DrawGrid(float grid_step, int tile_id_offset) {
// Draw grid + all lines in the canvas
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
if (enable_grid_) {
if (custom_step_ != 0.f) grid_step = custom_step_;
grid_step *= global_scale_; // Apply global scale to grid step
DrawGridLines(grid_step);
DrawCustomHighlight(grid_step);
if (enable_hex_tile_labels_) {
// Draw the hex ID of the tile in the center of the tile square
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step) {
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) {
int tile_x = (x - scrolling_.x) / grid_step;
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * 16);
std::string hex_id = absl::StrFormat("%02X", tile_id);
draw_list_->AddText(ImVec2(canvas_p0_.x + x + (grid_step / 2) - 4,
canvas_p0_.y + y + (grid_step / 2) - 4),
kWhiteColor, hex_id.data());
}
}
}
if (!enable_custom_labels_) {
return;
}
// Draw the contents of labels on the grid
for (float x = fmodf(scrolling_.x, grid_step);
x < canvas_sz_.x * global_scale_; x += grid_step) {
for (float y = fmodf(scrolling_.y, grid_step);
y < canvas_sz_.y * global_scale_; y += grid_step) {
int tile_x = (x - scrolling_.x) / grid_step;
int tile_y = (y - scrolling_.y) / grid_step;
int tile_id = tile_x + (tile_y * tile_id_offset);
if (tile_id >= labels_[current_labels_].size()) {
break;
}
std::string label = labels_[current_labels_][tile_id];
draw_list_->AddText(
ImVec2(canvas_p0_.x + x + (grid_step / 2) - tile_id_offset,
canvas_p0_.y + y + (grid_step / 2) - tile_id_offset),
kWhiteColor, label.data());
}
}
if (config_.grid_step != 0.f) grid_step = config_.grid_step;
// Create render context for utilities
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list_,
.canvas_p0 = canvas_p0_,
.canvas_p1 = canvas_p1_,
.scrolling = scrolling_,
.global_scale = config_.global_scale,
.enable_grid = config_.enable_grid,
.enable_hex_labels = config_.enable_hex_labels,
.grid_step = grid_step
};
// Use high-level utility function
CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
// Draw custom labels if enabled
if (config_.enable_custom_labels) {
draw_list_->PushClipRect(canvas_p0_, canvas_p1_, true);
CanvasUtils::DrawCanvasLabels(ctx, labels_, current_labels_, tile_id_offset);
draw_list_->PopClipRect();
}
}
void Canvas::DrawOverlay() {
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
for (int n = 0; n < points_.Size; n += 2) {
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),
kWhiteColor, 1.0f);
}
if (!selected_points_.empty()) {
for (int n = 0; n < selected_points_.size(); n += 2) {
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),
kWhiteColor, 1.0f);
}
}
draw_list_->PopClipRect();
// Create render context for utilities
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list_,
.canvas_p0 = canvas_p0_,
.canvas_p1 = canvas_p1_,
.scrolling = scrolling_,
.global_scale = config_.global_scale,
.enable_grid = config_.enable_grid,
.enable_hex_labels = config_.enable_hex_labels,
.grid_step = config_.grid_step
};
// Use high-level utility function
CanvasUtils::DrawCanvasOverlay(ctx, points_, selected_points_);
}
void Canvas::DrawLayeredElements() {
@@ -1053,7 +1104,20 @@ void Canvas::DrawLayeredElements() {
void BeginCanvas(Canvas &canvas, ImVec2 child_size) {
gui::BeginPadding(1);
ImGui::BeginChild(canvas.canvas_id().c_str(), child_size, true);
// Use improved canvas sizing for table integration
ImVec2 effective_size = child_size;
if (child_size.x == 0 && child_size.y == 0) {
// Auto-size based on canvas configuration
if (canvas.IsAutoResize()) {
effective_size = canvas.GetPreferredSize();
} else {
effective_size = canvas.GetCurrentSize();
}
}
ImGui::BeginChild(canvas.canvas_id().c_str(), effective_size, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
canvas.DrawBackground();
gui::EndPadding();
canvas.DrawContextMenu();
@@ -1122,4 +1186,208 @@ 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) {
// Configure canvas for table integration
canvas.SetAutoResize(auto_resize);
if (auto_resize && bitmap.is_active()) {
// Auto-calculate size based on bitmap content
ImVec2 content_size = ImVec2(bitmap.width(), bitmap.height());
ImVec2 preferred_size = CanvasUtils::CalculatePreferredCanvasSize(content_size, canvas.GetGlobalScale());
canvas.SetCanvasSize(preferred_size);
}
// Begin table-aware canvas
if (canvas.BeginTableCanvas(label)) {
// Draw the canvas content
canvas.DrawBackground();
canvas.DrawContextMenu();
if (bitmap.is_active()) {
canvas.DrawBitmap(bitmap, 2, 2, canvas.GetGlobalScale());
}
canvas.DrawGrid();
canvas.DrawOverlay();
}
canvas.EndTableCanvas();
}
void Canvas::ShowAdvancedCanvasProperties() {
if (ImGui::BeginPopupModal("Advanced Canvas Properties", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Advanced Canvas Configuration");
ImGui::Separator();
// Canvas properties (read-only info)
ImGui::Text("Canvas Properties");
ImGui::Text("ID: %s", canvas_id_.c_str());
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Content Size: %.0f x %.0f", config_.content_size.x, config_.content_size.y);
ImGui::Text("Global Scale: %.3f", config_.global_scale);
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (config_.content_size.x > 0 && config_.content_size.y > 0) {
ImVec2 min_size = GetMinimumSize();
ImVec2 preferred_size = GetPreferredSize();
ImGui::Text("Minimum Size: %.0f x %.0f", min_size.x, min_size.y);
ImGui::Text("Preferred Size: %.0f x %.0f", preferred_size.x, preferred_size.y);
}
// Editable properties using new config system
ImGui::Separator();
ImGui::Text("View Settings");
if (ImGui::Checkbox("Enable Grid", &config_.enable_grid)) {
enable_grid_ = config_.enable_grid; // Legacy sync
}
if (ImGui::Checkbox("Enable Hex Labels", &config_.enable_hex_labels)) {
enable_hex_tile_labels_ = config_.enable_hex_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Custom Labels", &config_.enable_custom_labels)) {
enable_custom_labels_ = config_.enable_custom_labels; // Legacy sync
}
if (ImGui::Checkbox("Enable Context Menu", &config_.enable_context_menu)) {
enable_context_menu_ = config_.enable_context_menu; // Legacy sync
}
if (ImGui::Checkbox("Draggable", &config_.is_draggable)) {
draggable_ = config_.is_draggable; // Legacy sync
}
if (ImGui::Checkbox("Auto Resize for Tables", &config_.auto_resize)) {
// Auto resize setting changed
}
// Grid controls
ImGui::Separator();
ImGui::Text("Grid Configuration");
if (ImGui::SliderFloat("Grid Step", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Scale controls
ImGui::Separator();
ImGui::Text("Scale Configuration");
if (ImGui::SliderFloat("Global Scale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Scrolling controls
ImGui::Separator();
ImGui::Text("Scrolling Configuration");
ImGui::Text("Current Scroll: %.1f, %.1f", scrolling_.x, scrolling_.y);
if (ImGui::Button("Reset Scroll")) {
scrolling_ = ImVec2(0, 0);
}
ImGui::SameLine();
if (ImGui::Button("Center View")) {
if (bitmap_) {
scrolling_ = ImVec2(-(bitmap_->width() * config_.global_scale - config_.canvas_size.x) / 2.0f,
-(bitmap_->height() * config_.global_scale - config_.canvas_size.y) / 2.0f);
}
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ShowPaletteManager method removed - now handled by EnhancedPaletteEditor
void Canvas::ShowScalingControls() {
if (ImGui::BeginPopupModal("Scaling Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Canvas Scaling and Display Controls");
ImGui::Separator();
// Global scale with new config system
ImGui::Text("Global Scale: %.3f", config_.global_scale);
if (ImGui::SliderFloat("##GlobalScale", &config_.global_scale, 0.1f, 10.0f, "%.2f")) {
global_scale_ = config_.global_scale; // Legacy sync
}
// Preset scale buttons
ImGui::Text("Preset Scales:");
if (ImGui::Button("0.25x")) {
config_.global_scale = 0.25f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("0.5x")) {
config_.global_scale = 0.5f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("1x")) {
config_.global_scale = 1.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("2x")) {
config_.global_scale = 2.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("4x")) {
config_.global_scale = 4.0f;
global_scale_ = config_.global_scale;
}
ImGui::SameLine();
if (ImGui::Button("8x")) {
config_.global_scale = 8.0f;
global_scale_ = config_.global_scale;
}
// Grid configuration
ImGui::Separator();
ImGui::Text("Grid Configuration");
ImGui::Text("Grid Step: %.1f", config_.grid_step);
if (ImGui::SliderFloat("##GridStep", &config_.grid_step, 1.0f, 128.0f, "%.1f")) {
custom_step_ = config_.grid_step; // Legacy sync
}
// Grid size presets
ImGui::Text("Grid Presets:");
if (ImGui::Button("8x8")) {
config_.grid_step = 8.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("16x16")) {
config_.grid_step = 16.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("32x32")) {
config_.grid_step = 32.0f;
custom_step_ = config_.grid_step;
}
ImGui::SameLine();
if (ImGui::Button("64x64")) {
config_.grid_step = 64.0f;
custom_step_ = config_.grid_step;
}
// Canvas size info
ImGui::Separator();
ImGui::Text("Canvas Information");
ImGui::Text("Canvas Size: %.0f x %.0f", config_.canvas_size.x, config_.canvas_size.y);
ImGui::Text("Scaled Size: %.0f x %.0f",
config_.canvas_size.x * config_.global_scale,
config_.canvas_size.y * config_.global_scale);
if (bitmap_) {
ImGui::Text("Bitmap Size: %d x %d", bitmap_->width(), bitmap_->height());
ImGui::Text("Effective Scale: %.3f x %.3f",
(config_.canvas_size.x * config_.global_scale) / bitmap_->width(),
(config_.canvas_size.y * config_.global_scale) / bitmap_->height());
}
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
// Old ROM palette management methods removed - now handled by EnhancedPaletteEditor
} // namespace yaze::gui

View File

@@ -6,9 +6,13 @@
#include <cstdint>
#include <string>
#include <functional>
#include <memory>
#include "app/gfx/bitmap.h"
#include "app/rom.h"
#include "app/gui/canvas_utils.h"
#include "app/gui/enhanced_palette_editor.h"
#include "imgui/imgui.h"
namespace yaze {
@@ -28,55 +32,68 @@ enum class CanvasGridSize { k8x8, k16x16, k32x32, k64x64 };
/**
* @class Canvas
* @brief Represents a canvas for drawing and manipulating graphics.
* @brief Modern, robust canvas for drawing and manipulating graphics.
*
* The Canvas class provides various functions for updating and drawing graphics
* on a canvas. It supports features such as bitmap drawing, context menu
* handling, tile painting, custom grid, and more.
* Following ImGui design patterns, this Canvas class provides:
* - Modular configuration through CanvasConfig
* - Separate selection state management
* - Enhanced palette management integration
* - Performance-optimized rendering
* - Comprehensive context menu system
*/
class Canvas {
public:
Canvas() = default;
explicit Canvas(const std::string &id) : canvas_id_(id) {
context_id_ = id + "Context";
explicit Canvas(const std::string& id)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
}
explicit Canvas(const std::string &id, ImVec2 canvas_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) {
context_id_ = id + "Context";
explicit Canvas(const std::string& id, ImVec2 canvas_size)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size)
: custom_canvas_size_(true), canvas_sz_(canvas_size), canvas_id_(id) {
context_id_ = id + "Context";
SetCanvasGridSize(grid_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
SetGridSize(grid_size);
}
explicit Canvas(const std::string &id, ImVec2 canvas_size,
CanvasGridSize grid_size, float global_scale)
: custom_canvas_size_(true),
global_scale_(global_scale),
canvas_sz_(canvas_size),
canvas_id_(id) {
context_id_ = id + "Context";
SetCanvasGridSize(grid_size);
explicit Canvas(const std::string& id, ImVec2 canvas_size, CanvasGridSize grid_size, float global_scale)
: canvas_id_(id), context_id_(id + "Context") {
InitializeDefaults();
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
config_.global_scale = global_scale;
SetGridSize(grid_size);
}
void SetCanvasGridSize(CanvasGridSize grid_size) {
void SetGridSize(CanvasGridSize grid_size) {
switch (grid_size) {
case CanvasGridSize::k8x8:
custom_step_ = 8.0f;
config_.grid_step = 8.0f;
break;
case CanvasGridSize::k16x16:
custom_step_ = 16.0f;
config_.grid_step = 16.0f;
break;
case CanvasGridSize::k32x32:
custom_step_ = 32.0f;
config_.grid_step = 32.0f;
break;
case CanvasGridSize::k64x64:
custom_step_ = 64.0f;
config_.grid_step = 64.0f;
break;
}
}
// Legacy compatibility
void SetCanvasGridSize(CanvasGridSize grid_size) { SetGridSize(grid_size); }
void UpdateColorPainter(gfx::Bitmap &bitmap, const ImVec4 &color,
const std::function<void()> &event, int tile_size,
float scale = 1.0f);
@@ -106,11 +123,45 @@ class Canvas {
void SetContextMenuEnabled(bool enabled) { context_menu_enabled_ = enabled; }
// Enhanced view and edit operations
void ShowBitmapProperties(const gfx::Bitmap& bitmap);
void ShowPaletteEditor(gfx::SnesPalette& palette);
void ShowAdvancedCanvasProperties();
void ShowScalingControls();
void SetZoomToFit(const gfx::Bitmap& bitmap);
void ResetView();
// Modular component access
CanvasConfig& GetConfig() { return config_; }
const CanvasConfig& GetConfig() const { return config_; }
CanvasSelection& GetSelection() { return selection_; }
const CanvasSelection& GetSelection() const { return selection_; }
// Enhanced palette management
void InitializePaletteEditor(Rom* rom);
void ShowPaletteEditor();
void ShowColorAnalysis();
bool ApplyROMPalette(int group_index, int palette_index);
// Initialization and cleanup
void InitializeDefaults();
void Cleanup();
// Size reporting for ImGui table integration
ImVec2 GetMinimumSize() const;
ImVec2 GetPreferredSize() const;
ImVec2 GetCurrentSize() const { return config_.canvas_size; }
void SetAutoResize(bool auto_resize) { config_.auto_resize = auto_resize; }
bool IsAutoResize() const { return config_.auto_resize; }
// Table integration helpers
void ReserveTableSpace(const std::string& label = "");
bool BeginTableCanvas(const std::string& label = "");
void EndTableCanvas();
// Improved interaction detection
bool HasValidSelection() const;
bool WasClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
bool WasDoubleClicked(ImGuiMouseButton button = ImGuiMouseButton_Left) const;
ImVec2 GetLastClickPosition() const;
private:
void DrawContextMenuItem(const ContextMenuItem& item);
@@ -173,19 +224,34 @@ class Canvas {
void set_global_scale(float scale) { global_scale_ = scale; }
void set_draggable(bool draggable) { draggable_ = draggable; }
// Public accessors for commonly used private members
auto select_rect_active() const { return select_rect_active_; }
auto selected_tiles() const { return selected_tiles_; }
auto selected_tile_pos() const { return selected_tile_pos_; }
void set_selected_tile_pos(ImVec2 pos) { selected_tile_pos_ = pos; }
// Modern accessors using modular structure
bool IsSelectRectActive() const { return selection_.select_rect_active; }
const std::vector<ImVec2>& GetSelectedTiles() const { return selection_.selected_tiles; }
ImVec2 GetSelectedTilePos() const { return selection_.selected_tile_pos; }
void SetSelectedTilePos(ImVec2 pos) { selection_.selected_tile_pos = pos; }
// Public methods for commonly used private methods
void SetCanvasSize(ImVec2 canvas_size) { canvas_sz_ = canvas_size; custom_canvas_size_ = true; }
auto global_scale() const { return global_scale_; }
auto custom_labels_enabled() { return &enable_custom_labels_; }
auto custom_step() const { return custom_step_; }
auto width() const { return canvas_sz_.x; }
auto height() const { return canvas_sz_.y; }
// Configuration accessors
void SetCanvasSize(ImVec2 canvas_size) {
config_.canvas_size = canvas_size;
config_.custom_canvas_size = true;
}
float GetGlobalScale() const { return config_.global_scale; }
void SetGlobalScale(float scale) { config_.global_scale = scale; }
bool* GetCustomLabelsEnabled() { return &config_.enable_custom_labels; }
float GetGridStep() const { return config_.grid_step; }
float GetCanvasWidth() const { return config_.canvas_size.x; }
float GetCanvasHeight() const { return config_.canvas_size.y; }
// Legacy compatibility accessors
auto select_rect_active() const { return selection_.select_rect_active; }
auto selected_tiles() const { return selection_.selected_tiles; }
auto selected_tile_pos() const { return selection_.selected_tile_pos; }
void set_selected_tile_pos(ImVec2 pos) { selection_.selected_tile_pos = pos; }
auto global_scale() const { return config_.global_scale; }
auto custom_labels_enabled() { return &config_.enable_custom_labels; }
auto custom_step() const { return config_.grid_step; }
auto width() const { return config_.canvas_size.x; }
auto height() const { return config_.canvas_size.y; }
// Public accessors for methods that need to be accessed externally
auto canvas_id() const { return canvas_id_; }
@@ -231,50 +297,60 @@ class Canvas {
Rom *rom() const { return rom_; }
private:
bool draggable_ = false;
// Modular configuration and state
CanvasConfig config_;
CanvasSelection selection_;
std::unique_ptr<EnhancedPaletteEditor> palette_editor_;
// Core canvas state
bool is_hovered_ = false;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool select_rect_active_ = false;
bool refresh_graphics_ = false;
// Context menu system
std::vector<ContextMenuItem> context_menu_items_;
bool context_menu_enabled_ = true;
float custom_step_ = 0.0f;
float global_scale_ = 1.0f;
// Legacy members (to be gradually replaced)
int current_labels_ = 0;
int highlight_tile_id = -1;
uint16_t edit_palette_index_ = 0;
uint64_t edit_palette_group_name_index_ = 0;
uint64_t edit_palette_sub_index_ = 0;
// Core canvas state
Bitmap *bitmap_ = nullptr;
Rom *rom_ = nullptr;
ImDrawList *draw_list_ = nullptr;
// Canvas geometry and interaction state
ImVec2 scrolling_;
ImVec2 canvas_sz_;
ImVec2 canvas_p0_;
ImVec2 canvas_p1_;
ImVec2 drawn_tile_pos_;
ImVec2 mouse_pos_in_canvas_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
// Drawing and labeling
ImVector<ImVec2> points_;
ImVector<ImVec2> selected_points_;
ImVector<ImVector<std::string>> labels_;
// Identification
std::string canvas_id_ = "Canvas";
std::string context_id_ = "CanvasContext";
// Legacy compatibility (gradually being replaced by selection_)
std::vector<ImVec2> selected_tiles_;
ImVector<ImVec2> selected_points_;
ImVec2 selected_tile_pos_ = ImVec2(-1, -1);
bool select_rect_active_ = false;
float custom_step_ = 32.0f;
float global_scale_ = 1.0f;
bool enable_grid_ = true;
bool enable_hex_tile_labels_ = false;
bool enable_custom_labels_ = false;
bool enable_context_menu_ = true;
bool custom_canvas_size_ = false;
bool draggable_ = false;
};
void BeginCanvas(Canvas &canvas, ImVec2 child_size = ImVec2(0, 0));
@@ -288,6 +364,10 @@ void BitmapCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap, int width,
int height, int tile_size, bool is_loaded,
bool scrollbar, int canvas_id);
// Table-optimized canvas pipeline with automatic sizing
void TableCanvasPipeline(gui::Canvas &canvas, gfx::Bitmap &bitmap,
const std::string& label = "", bool auto_resize = true);
} // namespace gui
} // namespace yaze

366
src/app/gui/canvas_utils.cc Normal file
View File

@@ -0,0 +1,366 @@
#include "canvas_utils.h"
#include <cmath>
#include "app/core/window.h"
#include "app/gfx/snes_palette.h"
#include "util/log.h"
namespace yaze {
namespace gui {
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 * 255, color.y * 255, color.z * 255, color.w * 255);
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 gui
} // namespace yaze

147
src/app/gui/canvas_utils.h Normal file
View File

@@ -0,0 +1,147 @@
#ifndef YAZE_APP_GUI_CANVAS_UTILS_H
#define YAZE_APP_GUI_CANVAS_UTILS_H
#include <string>
#include <vector>
#include "app/gfx/snes_palette.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Configuration for canvas display and interaction
*/
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;
};
/**
* @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 Utility functions for canvas operations
*/
namespace CanvasUtils {
// Core utility functions
ImVec2 AlignToGrid(ImVec2 pos, float grid_step);
float CalculateEffectiveScale(ImVec2 canvas_size, ImVec2 content_size, float global_scale);
int GetTileIdFromPosition(ImVec2 mouse_pos, float tile_size, float scale, int tiles_per_row);
// Palette management utilities
bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager);
bool ApplyPaletteGroup(gfx::Bitmap* bitmap, const CanvasPaletteManager& palette_manager,
int group_index, int palette_index);
// Drawing utility functions (moved from Canvas class)
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 utility functions
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 and interaction 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 ImGui table integration
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);
// High-level canvas 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;
};
// Composite drawing 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 gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_UTILS_H

View File

@@ -0,0 +1,466 @@
#include "enhanced_palette_editor.h"
#include <algorithm>
#include <map>
#include "app/core/window.h"
#include "app/gui/color.h"
#include "util/log.h"
namespace yaze {
namespace gui {
using core::Renderer;
void EnhancedPaletteEditor::Initialize(Rom* rom) {
rom_ = rom;
rom_palettes_loaded_ = false;
if (rom_) {
LoadROMPalettes();
}
}
void EnhancedPaletteEditor::ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title) {
if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Enhanced Palette Editor");
ImGui::Separator();
// Palette grid editor
DrawPaletteGrid(palette);
ImGui::Separator();
// Analysis and tools
if (ImGui::CollapsingHeader("Palette Analysis")) {
DrawPaletteAnalysis(palette);
}
if (ImGui::CollapsingHeader("ROM Palette Manager") && rom_) {
DrawROMPaletteSelector();
if (ImGui::Button("Apply ROM Palette") && !rom_palette_groups_.empty()) {
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
palette = rom_palette_groups_[current_group_index_];
}
}
}
ImGui::Separator();
// Action buttons
if (ImGui::Button("Save Backup")) {
SavePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Restore Backup")) {
RestorePaletteBackup(palette);
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void EnhancedPaletteEditor::ShowROMPaletteManager() {
if (!show_rom_manager_) return;
if (ImGui::Begin("ROM Palette Manager", &show_rom_manager_)) {
if (!rom_) {
ImGui::Text("No ROM loaded");
ImGui::End();
return;
}
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
DrawROMPaletteSelector();
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Separator();
ImGui::Text("Preview of %s:", palette_group_names_[current_group_index_].c_str());
const auto& preview_palette = rom_palette_groups_[current_group_index_];
DrawPaletteGrid(const_cast<gfx::SnesPalette&>(preview_palette));
DrawPaletteAnalysis(preview_palette);
}
}
ImGui::End();
}
void EnhancedPaletteEditor::ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title) {
if (!show_color_analysis_) return;
if (ImGui::Begin(title.c_str(), &show_color_analysis_)) {
ImGui::Text("Bitmap Color Analysis");
ImGui::Separator();
if (!bitmap.is_active()) {
ImGui::Text("Bitmap is not active");
ImGui::End();
return;
}
// Analyze pixel distribution
std::map<uint8_t, int> pixel_counts;
const auto& data = bitmap.vector();
for (uint8_t pixel : data) {
uint8_t palette_index = pixel & 0x0F; // 4-bit palette index
pixel_counts[palette_index]++;
}
ImGui::Text("Bitmap Size: %d x %d (%zu pixels)",
bitmap.width(), bitmap.height(), data.size());
ImGui::Separator();
ImGui::Text("Pixel Distribution:");
// Show distribution as bars
int total_pixels = static_cast<int>(data.size());
for (const auto& [index, count] : pixel_counts) {
float percentage = (static_cast<float>(count) / total_pixels) * 100.0f;
ImGui::Text("Index %d: %d pixels (%.1f%%)", index, count, percentage);
// Progress bar visualization
ImGui::SameLine();
ImGui::ProgressBar(percentage / 100.0f, ImVec2(100, 0));
// Color swatch if palette is available
if (index < static_cast<int>(bitmap.palette().size())) {
ImGui::SameLine();
auto color = bitmap.palette()[index];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##color" + std::to_string(index)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("SNES Color: 0x%04X\nRGB: (%d, %d, %d)",
color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
}
}
}
ImGui::End();
}
bool EnhancedPaletteEditor::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index) {
if (!bitmap || !rom_palettes_loaded_ ||
group_index < 0 || group_index >= static_cast<int>(rom_palette_groups_.size())) {
return false;
}
try {
const auto& selected_palette = rom_palette_groups_[group_index];
// Save current palette as backup
SavePaletteBackup(bitmap->palette());
// Apply new palette
if (palette_index >= 0 && palette_index < 8) {
bitmap->SetPaletteWithTransparent(selected_palette, palette_index);
} else {
bitmap->SetPalette(selected_palette);
}
Renderer::Get().UpdateBitmap(bitmap);
current_group_index_ = group_index;
current_palette_index_ = palette_index;
util::logf("Applied ROM palette: %s (index %d)",
palette_group_names_[group_index].c_str(), palette_index);
return true;
} catch (const std::exception& e) {
util::logf("Failed to apply ROM palette: %s", e.what());
return false;
}
}
const gfx::SnesPalette* EnhancedPaletteEditor::GetSelectedROMPalette() const {
if (!rom_palettes_loaded_ || current_group_index_ < 0 ||
current_group_index_ >= static_cast<int>(rom_palette_groups_.size())) {
return nullptr;
}
return &rom_palette_groups_[current_group_index_];
}
void EnhancedPaletteEditor::SavePaletteBackup(const gfx::SnesPalette& palette) {
backup_palette_ = palette;
}
bool EnhancedPaletteEditor::RestorePaletteBackup(gfx::SnesPalette& palette) {
if (backup_palette_.size() == 0) {
return false;
}
palette = backup_palette_;
return true;
}
void EnhancedPaletteEditor::DrawPaletteGrid(gfx::SnesPalette& palette, int cols) {
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
if (i % cols != 0) ImGui::SameLine();
auto color = palette[i];
ImVec4 display_color = color.rgb();
ImGui::PushID(i);
// Color button with editing capability
if (ImGui::ColorButton("##color", display_color,
ImGuiColorEditFlags_NoTooltip, ImVec2(30, 30))) {
editing_color_index_ = i;
temp_color_ = display_color;
}
// Context menu for individual colors
if (ImGui::BeginPopupContextItem()) {
ImGui::Text("Color %d (0x%04X)", i, color.snes());
ImGui::Separator();
if (ImGui::MenuItem("Edit Color")) {
editing_color_index_ = i;
temp_color_ = display_color;
}
if (ImGui::MenuItem("Copy Color")) {
// Could implement color clipboard here
}
if (ImGui::MenuItem("Reset to Black")) {
palette[i] = gfx::SnesColor(0);
}
ImGui::EndPopup();
}
// Tooltip with detailed info
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Color %d\nSNES: 0x%04X\nRGB: (%d, %d, %d)\nClick to edit",
i, color.snes(),
static_cast<int>(display_color.x * 255),
static_cast<int>(display_color.y * 255),
static_cast<int>(display_color.z * 255));
}
ImGui::PopID();
}
// Color editor popup
if (editing_color_index_ >= 0) {
ImGui::OpenPopup("Edit Color");
if (ImGui::BeginPopupModal("Edit Color", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Editing Color %d", editing_color_index_);
if (ImGui::ColorEdit4("Color", &temp_color_.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
// Update the palette color in real-time
auto new_snes_color = gfx::SnesColor(temp_color_);
palette[editing_color_index_] = new_snes_color;
}
if (ImGui::Button("Apply")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
editing_color_index_ = -1;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
}
void EnhancedPaletteEditor::DrawROMPaletteSelector() {
if (!rom_palettes_loaded_) {
LoadROMPalettes();
}
if (rom_palette_groups_.empty()) {
ImGui::Text("No ROM palettes available");
return;
}
// Group selector
ImGui::Text("Palette Group:");
if (ImGui::Combo("##PaletteGroup", &current_group_index_,
[](void* data, int idx, const char** out_text) -> bool {
auto* names = static_cast<std::vector<std::string>*>(data);
if (idx < 0 || idx >= static_cast<int>(names->size())) return false;
*out_text = (*names)[idx].c_str();
return true;
}, &palette_group_names_, static_cast<int>(palette_group_names_.size()))) {
// Group changed - could trigger preview update
}
// Palette index selector
ImGui::Text("Palette Index:");
ImGui::SliderInt("##PaletteIndex", &current_palette_index_, 0, 7, "%d");
// Quick palette preview
if (current_group_index_ < static_cast<int>(rom_palette_groups_.size())) {
ImGui::Text("Preview:");
const auto& preview_palette = rom_palette_groups_[current_group_index_];
// Show just first 8 colors in a row
for (int i = 0; i < 8 && i < static_cast<int>(preview_palette.size()); i++) {
if (i > 0) ImGui::SameLine();
auto color = preview_palette[i];
ImVec4 display_color = color.rgb();
ImGui::ColorButton(("##preview" + std::to_string(i)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20));
}
}
}
void EnhancedPaletteEditor::DrawColorEditControls(gfx::SnesColor& color, int color_index) {
ImVec4 rgba = color.rgb();
ImGui::PushID(color_index);
if (ImGui::ColorEdit4("##color_edit", &rgba.x,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_DisplayRGB)) {
color = gfx::SnesColor(rgba);
}
// SNES-specific controls
ImGui::Text("SNES Color: 0x%04X", color.snes());
// Individual RGB component sliders (0-31 for SNES)
int r = (color.snes() & 0x1F);
int g = (color.snes() >> 5) & 0x1F;
int b = (color.snes() >> 10) & 0x1F;
if (ImGui::SliderInt("Red", &r, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFFE0) | (r & 0x1F);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Green", &g, 0, 31)) {
uint16_t new_color = (color.snes() & 0xFC1F) | ((g & 0x1F) << 5);
color = gfx::SnesColor(new_color);
}
if (ImGui::SliderInt("Blue", &b, 0, 31)) {
uint16_t new_color = (color.snes() & 0x83FF) | ((b & 0x1F) << 10);
color = gfx::SnesColor(new_color);
}
ImGui::PopID();
}
void EnhancedPaletteEditor::DrawPaletteAnalysis(const gfx::SnesPalette& palette) {
ImGui::Text("Palette Information:");
ImGui::Text("Size: %zu colors", palette.size());
// Color distribution analysis
std::map<uint16_t, int> color_frequency;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
color_frequency[palette[i].snes()]++;
}
ImGui::Text("Unique Colors: %zu", color_frequency.size());
if (color_frequency.size() < palette.size()) {
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Warning: Duplicate colors detected!");
if (ImGui::TreeNode("Duplicate Colors")) {
for (const auto& [snes_color, count] : color_frequency) {
if (count > 1) {
ImVec4 display_color = gfx::SnesColor(snes_color).rgb();
ImGui::ColorButton(("##dup" + std::to_string(snes_color)).c_str(),
display_color, ImGuiColorEditFlags_NoTooltip, ImVec2(16, 16));
ImGui::SameLine();
ImGui::Text("0x%04X appears %d times", snes_color, count);
}
}
ImGui::TreePop();
}
}
// Brightness analysis
float total_brightness = 0.0f;
float min_brightness = 1.0f;
float max_brightness = 0.0f;
for (int i = 0; i < static_cast<int>(palette.size()); i++) {
ImVec4 color = palette[i].rgb();
float brightness = (color.x + color.y + color.z) / 3.0f;
total_brightness += brightness;
min_brightness = std::min(min_brightness, brightness);
max_brightness = std::max(max_brightness, brightness);
}
float avg_brightness = total_brightness / palette.size();
ImGui::Separator();
ImGui::Text("Brightness Analysis:");
ImGui::Text("Average: %.2f", avg_brightness);
ImGui::Text("Range: %.2f - %.2f", min_brightness, max_brightness);
// Show brightness as progress bar
ImGui::Text("Brightness Distribution:");
ImGui::ProgressBar(avg_brightness, ImVec2(-1, 0), "Avg");
}
void EnhancedPaletteEditor::LoadROMPalettes() {
if (!rom_ || rom_palettes_loaded_) return;
try {
const auto& palette_groups = rom_->palette_group();
rom_palette_groups_.clear();
palette_group_names_.clear();
// Load all available palette groups
if (palette_groups.overworld_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_main[0]);
palette_group_names_.push_back("Overworld Main");
}
if (palette_groups.overworld_aux.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_aux[0]);
palette_group_names_.push_back("Overworld Aux");
}
if (palette_groups.overworld_animated.size() > 0) {
rom_palette_groups_.push_back(palette_groups.overworld_animated[0]);
palette_group_names_.push_back("Overworld Animated");
}
if (palette_groups.dungeon_main.size() > 0) {
rom_palette_groups_.push_back(palette_groups.dungeon_main[0]);
palette_group_names_.push_back("Dungeon Main");
}
if (palette_groups.global_sprites.size() > 0) {
rom_palette_groups_.push_back(palette_groups.global_sprites[0]);
palette_group_names_.push_back("Global Sprites");
}
if (palette_groups.armors.size() > 0) {
rom_palette_groups_.push_back(palette_groups.armors[0]);
palette_group_names_.push_back("Armor");
}
if (palette_groups.swords.size() > 0) {
rom_palette_groups_.push_back(palette_groups.swords[0]);
palette_group_names_.push_back("Swords");
}
rom_palettes_loaded_ = true;
util::logf("Enhanced Palette Editor: Loaded %zu ROM palette groups", rom_palette_groups_.size());
} catch (const std::exception& e) {
util::logf("Enhanced Palette Editor: Failed to load ROM palettes: %s", e.what());
}
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,92 @@
#ifndef YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#define YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H
#include <vector>
#include <map>
#include "app/gfx/snes_palette.h"
#include "app/gfx/bitmap.h"
#include "app/rom.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Enhanced palette editor with ROM integration and analysis tools
*/
class EnhancedPaletteEditor {
public:
EnhancedPaletteEditor() = default;
/**
* @brief Initialize the palette editor with ROM data
*/
void Initialize(Rom* rom);
/**
* @brief Show the main palette editor window
*/
void ShowPaletteEditor(gfx::SnesPalette& palette, const std::string& title = "Palette Editor");
/**
* @brief Show the ROM palette manager window
*/
void ShowROMPaletteManager();
/**
* @brief Show color analysis window for a bitmap
*/
void ShowColorAnalysis(const gfx::Bitmap& bitmap, const std::string& title = "Color Analysis");
/**
* @brief Apply a ROM palette group to a bitmap
*/
bool ApplyROMPalette(gfx::Bitmap* bitmap, int group_index, int palette_index);
/**
* @brief Get the currently selected ROM palette
*/
const gfx::SnesPalette* GetSelectedROMPalette() const;
/**
* @brief Save current palette as backup
*/
void SavePaletteBackup(const gfx::SnesPalette& palette);
/**
* @brief Restore palette from backup
*/
bool RestorePaletteBackup(gfx::SnesPalette& palette);
// Accessors
bool IsROMLoaded() const { return rom_ != nullptr; }
int GetCurrentGroupIndex() const { return current_group_index_; }
int GetCurrentPaletteIndex() const { return current_palette_index_; }
private:
void DrawPaletteGrid(gfx::SnesPalette& palette, int cols = 8);
void DrawROMPaletteSelector();
void DrawColorEditControls(gfx::SnesColor& color, int color_index);
void DrawPaletteAnalysis(const gfx::SnesPalette& palette);
void LoadROMPalettes();
Rom* rom_ = nullptr;
std::vector<gfx::SnesPalette> rom_palette_groups_;
std::vector<std::string> palette_group_names_;
gfx::SnesPalette backup_palette_;
int current_group_index_ = 0;
int current_palette_index_ = 0;
bool rom_palettes_loaded_ = false;
bool show_color_analysis_ = false;
bool show_rom_manager_ = false;
// Color editing state
int editing_color_index_ = -1;
ImVec4 temp_color_ = ImVec4(0, 0, 0, 1);
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_ENHANCED_PALETTE_EDITOR_H

View File

@@ -3,6 +3,8 @@ set(
app/gui/modules/asset_browser.cc
app/gui/modules/text_editor.cc
app/gui/canvas.cc
app/gui/canvas_utils.cc
app/gui/enhanced_palette_editor.cc
app/gui/input.cc
app/gui/style.cc
app/gui/color.cc

View File

@@ -282,7 +282,27 @@ void BeginNoPadding() {
void EndNoPadding() { ImGui::PopStyleVar(2); }
void BeginChildWithScrollbar(const char *str_id) {
ImGui::BeginChild(str_id, ImGui::GetContentRegionAvail(), true,
// Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size) {
// Set content size before beginning child to enable proper scrolling
if (content_size.x > 0 && content_size.y > 0) {
ImGui::SetNextWindowContentSize(content_size);
}
// Get available region but ensure minimum size for proper scrolling
ImVec2 available = ImGui::GetContentRegionAvail();
if (available.x < 64.0f) available.x = 64.0f;
if (available.y < 64.0f) available.y = 64.0f;
ImGui::BeginChild(str_id, available, true,
ImGuiWindowFlags_AlwaysVerticalScrollbar);
}

View File

@@ -34,6 +34,7 @@ void BeginNoPadding();
void EndNoPadding();
void BeginChildWithScrollbar(const char *str_id);
void BeginChildWithScrollbar(const char *str_id, ImVec2 content_size);
void BeginChildBothScrollbars(int id);