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:
@@ -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
|
||||
|
||||
@@ -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
366
src/app/gui/canvas_utils.cc
Normal 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
147
src/app/gui/canvas_utils.h
Normal 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
|
||||
466
src/app/gui/enhanced_palette_editor.cc
Normal file
466
src/app/gui/enhanced_palette_editor.cc
Normal 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", ¤t_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", ¤t_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
|
||||
92
src/app/gui/enhanced_palette_editor.h
Normal file
92
src/app/gui/enhanced_palette_editor.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user