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:
@@ -763,30 +763,15 @@ absl::Status EditorManager::Update() {
|
||||
|
||||
// Check if current ROM is valid
|
||||
if (!current_rom_) {
|
||||
// Only auto-show welcome screen if it hasn't been manually closed
|
||||
if (!welcome_screen_manually_closed_) {
|
||||
// Only show welcome screen for truly empty state, not when ROM is loaded but current_rom_ is null
|
||||
if (sessions_.empty() && !welcome_screen_manually_closed_) {
|
||||
show_welcome_screen_ = true;
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Check if any editors are active across ALL sessions
|
||||
bool any_editor_active = false;
|
||||
for (const auto& session : sessions_) {
|
||||
if (!session.rom.is_loaded()) continue;
|
||||
for (auto editor : session.editors.active_editors_) {
|
||||
if (*editor->active()) {
|
||||
any_editor_active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (any_editor_active) break;
|
||||
}
|
||||
|
||||
// Only auto-show welcome screen if no editors are active AND it hasn't been manually closed
|
||||
if (!any_editor_active && !welcome_screen_manually_closed_) {
|
||||
show_welcome_screen_ = true;
|
||||
}
|
||||
// ROM is loaded and valid - don't auto-show welcome screen
|
||||
// Welcome screen should only be shown manually at this point
|
||||
|
||||
// Iterate through ALL sessions to support multi-session docking
|
||||
for (size_t session_idx = 0; session_idx < sessions_.size(); ++session_idx) {
|
||||
@@ -819,6 +804,21 @@ absl::Status EditorManager::Update() {
|
||||
|
||||
status_ = editor->Update();
|
||||
|
||||
// Route editor errors to toast manager
|
||||
if (!status_.ok()) {
|
||||
std::string editor_name = "Editor"; // Get actual editor name if available
|
||||
if (editor == &session.editors.overworld_editor_) editor_name = "Overworld Editor";
|
||||
else if (editor == &session.editors.dungeon_editor_) editor_name = "Dungeon Editor";
|
||||
else if (editor == &session.editors.sprite_editor_) editor_name = "Sprite Editor";
|
||||
else if (editor == &session.editors.graphics_editor_) editor_name = "Graphics Editor";
|
||||
else if (editor == &session.editors.music_editor_) editor_name = "Music Editor";
|
||||
else if (editor == &session.editors.palette_editor_) editor_name = "Palette Editor";
|
||||
else if (editor == &session.editors.screen_editor_) editor_name = "Screen Editor";
|
||||
|
||||
toast_manager_.Show(absl::StrFormat("%s Error: %s", editor_name, status_.message()),
|
||||
editor::ToastType::kError, 8.0f);
|
||||
}
|
||||
|
||||
// Restore context
|
||||
current_rom_ = prev_rom;
|
||||
current_editor_set_ = prev_editor_set;
|
||||
@@ -1285,6 +1285,13 @@ void EditorManager::DrawMenuBar() {
|
||||
if (show_palette_editor_ && current_editor_set_) {
|
||||
Begin("Palette Editor", &show_palette_editor_);
|
||||
status_ = current_editor_set_->palette_editor_.Update();
|
||||
|
||||
// Route palette editor errors to toast manager
|
||||
if (!status_.ok()) {
|
||||
toast_manager_.Show(absl::StrFormat("Palette Editor Error: %s", status_.message()),
|
||||
editor::ToastType::kError, 8.0f);
|
||||
}
|
||||
|
||||
End();
|
||||
}
|
||||
|
||||
@@ -1514,8 +1521,8 @@ absl::Status EditorManager::LoadRom() {
|
||||
manager.Save();
|
||||
RETURN_IF_ERROR(LoadAssets());
|
||||
|
||||
// Reset welcome screen state when ROM is loaded
|
||||
welcome_screen_manually_closed_ = false;
|
||||
// Hide welcome screen when ROM is successfully loaded - don't reset manual close state
|
||||
show_welcome_screen_ = false;
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace yaze::editor {
|
||||
using core::Renderer;
|
||||
using namespace ImGui;
|
||||
|
||||
constexpr int kTile16Size = 0x10;
|
||||
constexpr float kInputFieldSize = 30.f;
|
||||
|
||||
void OverworldEditor::Initialize() {
|
||||
@@ -161,6 +160,7 @@ void OverworldEditor::Initialize() {
|
||||
}
|
||||
HOVER_HINT("Copy Map to Clipboard");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::Load() {
|
||||
@@ -956,6 +956,9 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
||||
const int large_map_size = 1024;
|
||||
const auto canvas_zero_point = ow_map_canvas_.zero_point();
|
||||
|
||||
// Check if the mouse is over the canvas
|
||||
|
||||
|
||||
// Calculate which small map the mouse is currently over
|
||||
int map_x = (mouse_position.x - canvas_zero_point.x) / kOverworldMapSize;
|
||||
int map_y = (mouse_position.y - canvas_zero_point.y) / kOverworldMapSize;
|
||||
@@ -1094,7 +1097,8 @@ void OverworldEditor::DrawOverworldCanvas() {
|
||||
current_map_, current_world_, show_overlay_preview_);
|
||||
}
|
||||
|
||||
if (current_mode == EditingMode::DRAW_TILE) {
|
||||
if (current_mode == EditingMode::DRAW_TILE && ow_map_canvas_.IsMouseHovering()) {
|
||||
// Only allow overworld edits when mouse is actually over the main overworld canvas
|
||||
CheckForOverworldEdits();
|
||||
}
|
||||
if (IsItemHovered()) status_ = CheckForCurrentMap();
|
||||
@@ -1117,31 +1121,38 @@ absl::Status OverworldEditor::DrawTile16Selector() {
|
||||
ImGui::BeginGroup();
|
||||
gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
|
||||
blockset_canvas_.DrawBackground();
|
||||
gui::EndNoPadding();
|
||||
{
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
|
||||
map_blockset_loaded_, /*scale=*/2);
|
||||
gui::EndPadding(); // Fixed: was EndNoPadding()
|
||||
|
||||
blockset_canvas_.DrawContextMenu();
|
||||
blockset_canvas_.DrawBitmap(tile16_blockset_.atlas, /*border_offset=*/2,
|
||||
map_blockset_loaded_, /*scale=*/2);
|
||||
|
||||
if (blockset_canvas_.DrawTileSelector(32.0f)) {
|
||||
// Open the tile16 editor to the tile
|
||||
auto tile_pos = blockset_canvas_.points().front();
|
||||
int grid_x = static_cast<int>(tile_pos.x / 32);
|
||||
int grid_y = static_cast<int>(tile_pos.y / 32);
|
||||
int id = grid_x + grid_y * 8;
|
||||
// Improved tile interaction detection - use proper canvas interaction
|
||||
bool tile_selected = false;
|
||||
if (blockset_canvas_.DrawTileSelector(32.0f)) {
|
||||
tile_selected = true;
|
||||
} else if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && blockset_canvas_.IsMouseHovering()) {
|
||||
// Secondary detection for direct clicks
|
||||
tile_selected = true;
|
||||
}
|
||||
|
||||
if (tile_selected && blockset_canvas_.HasValidSelection()) {
|
||||
auto tile_pos = blockset_canvas_.GetLastClickPosition();
|
||||
int grid_x = static_cast<int>(tile_pos.x / 32);
|
||||
int grid_y = static_cast<int>(tile_pos.y / 32);
|
||||
int id = grid_x + grid_y * 8;
|
||||
|
||||
if (id != current_tile16_ && id >= 0 && id < 512) {
|
||||
current_tile16_ = id;
|
||||
RETURN_IF_ERROR(tile16_editor_.SetCurrentTile(id));
|
||||
show_tile16_editor_ = true;
|
||||
util::logf("Selected Tile16: %d (grid: %d,%d)", id, grid_x, grid_y);
|
||||
}
|
||||
|
||||
if (ImGui::IsItemClicked() && !blockset_canvas_.points().empty()) {
|
||||
int x = blockset_canvas_.points().front().x / 32;
|
||||
int y = blockset_canvas_.points().front().y / 32;
|
||||
current_tile16_ = x + (y * 8);
|
||||
}
|
||||
|
||||
blockset_canvas_.DrawGrid();
|
||||
blockset_canvas_.DrawOverlay();
|
||||
}
|
||||
|
||||
blockset_canvas_.DrawGrid();
|
||||
blockset_canvas_.DrawOverlay();
|
||||
|
||||
EndChild();
|
||||
ImGui::EndGroup();
|
||||
return absl::OkStatus();
|
||||
@@ -1223,6 +1234,10 @@ absl::Status OverworldEditor::DrawTileSelector() {
|
||||
status_ = DrawAreaGraphics();
|
||||
EndTabItem();
|
||||
}
|
||||
if (BeginTabItem("Scratch Space")) {
|
||||
status_ = DrawScratchSpace();
|
||||
EndTabItem();
|
||||
}
|
||||
EndTabBar();
|
||||
}
|
||||
return absl::OkStatus();
|
||||
@@ -2816,4 +2831,426 @@ absl::Status OverworldEditor::UpdateROMVersionMarkers(int target_version) {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace yaze::editor
|
||||
// Scratch space canvas methods
|
||||
absl::Status OverworldEditor::DrawScratchSpace() {
|
||||
// Slot selector
|
||||
Text("Scratch Space Slot:");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (i > 0) SameLine();
|
||||
bool is_current = (current_scratch_slot_ == i);
|
||||
if (is_current) PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f));
|
||||
if (Button(std::to_string(i + 1).c_str(), ImVec2(25, 25))) {
|
||||
current_scratch_slot_ = i;
|
||||
}
|
||||
if (is_current) PopStyleColor();
|
||||
}
|
||||
|
||||
SameLine();
|
||||
if (Button("Save Selection")) {
|
||||
RETURN_IF_ERROR(SaveCurrentSelectionToScratch(current_scratch_slot_));
|
||||
}
|
||||
SameLine();
|
||||
if (Button("Load")) {
|
||||
RETURN_IF_ERROR(LoadScratchToSelection(current_scratch_slot_));
|
||||
}
|
||||
SameLine();
|
||||
if (Button("Clear")) {
|
||||
RETURN_IF_ERROR(ClearScratchSpace(current_scratch_slot_));
|
||||
}
|
||||
|
||||
Text("Slot %d: %s (%dx%d)", current_scratch_slot_ + 1,
|
||||
scratch_spaces_[current_scratch_slot_].name.c_str(),
|
||||
scratch_spaces_[current_scratch_slot_].width,
|
||||
scratch_spaces_[current_scratch_slot_].height);
|
||||
Text("Select tiles from Tile16 tab or make selections in overworld, then draw here!");
|
||||
|
||||
// Initialize scratch bitmap with proper size based on scratch space dimensions
|
||||
auto& current_slot = scratch_spaces_[current_scratch_slot_];
|
||||
if (!current_slot.scratch_bitmap.is_active()) {
|
||||
// Create bitmap based on scratch space dimensions (each tile is 16x16)
|
||||
int bitmap_width = current_slot.width * 16;
|
||||
int bitmap_height = current_slot.height * 16;
|
||||
std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
|
||||
current_slot.scratch_bitmap.Create(bitmap_width, bitmap_height, 8, empty_data);
|
||||
if (all_gfx_loaded_) {
|
||||
palette_ = overworld_.current_area_palette();
|
||||
current_slot.scratch_bitmap.SetPalette(palette_);
|
||||
core::Renderer::Get().RenderBitmap(¤t_slot.scratch_bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the scratch space canvas with dynamic sizing
|
||||
gui::BeginPadding(3);
|
||||
ImGui::BeginGroup();
|
||||
|
||||
// Set proper content size for scrolling based on scratch space dimensions
|
||||
ImVec2 scratch_content_size(current_slot.width * 16 + 4, current_slot.height * 16 + 4);
|
||||
gui::BeginChildWithScrollbar("##ScratchSpaceScrollRegion", scratch_content_size);
|
||||
scratch_canvas_.DrawBackground();
|
||||
gui::EndPadding();
|
||||
|
||||
// Disable context menu for scratch space to allow right-click selection
|
||||
scratch_canvas_.SetContextMenuEnabled(false);
|
||||
|
||||
// Draw the scratch bitmap with proper scaling
|
||||
if (current_slot.scratch_bitmap.is_active()) {
|
||||
scratch_canvas_.DrawBitmap(current_slot.scratch_bitmap, 2, 2, 1.0f);
|
||||
}
|
||||
|
||||
// Handle independent scratch space selection system
|
||||
DrawScratchSpaceSelection();
|
||||
|
||||
// Handle drawing in scratch space - independent of main overworld
|
||||
// Use the currently selected tile from blockset for painting
|
||||
if (!blockset_canvas_.points().empty() && map_blockset_loaded_) {
|
||||
// DrawTilemapPainter provides hover preview and painting functionality
|
||||
if (scratch_canvas_.DrawTilemapPainter(tile16_blockset_, current_tile16_)) {
|
||||
DrawScratchSpaceEdits();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle rectangle selection patterns from main overworld as preview/stamp tool
|
||||
if (ow_map_canvas_.select_rect_active() && !ow_map_canvas_.selected_tiles().empty()) {
|
||||
// Create tile IDs from the selection
|
||||
std::vector<int> selected_tile_ids;
|
||||
overworld_.set_current_world(current_world_);
|
||||
overworld_.set_current_map(current_map_);
|
||||
|
||||
for (const auto& pos : ow_map_canvas_.selected_tiles()) {
|
||||
selected_tile_ids.push_back(overworld_.GetTileFromPosition(pos));
|
||||
}
|
||||
|
||||
// Show the pattern as a preview that can be stamped
|
||||
std::vector<int> tile_ids_copy = selected_tile_ids; // Make a copy for DrawBitmapGroup
|
||||
scratch_canvas_.DrawBitmapGroup(tile_ids_copy, tile16_blockset_, 16, 1.0f);
|
||||
|
||||
// Allow stamping the pattern with left click
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && scratch_canvas_.IsMouseHovering()) {
|
||||
DrawScratchSpacePattern();
|
||||
}
|
||||
}
|
||||
|
||||
scratch_canvas_.DrawGrid();
|
||||
scratch_canvas_.DrawOverlay();
|
||||
|
||||
EndChild();
|
||||
ImGui::EndGroup();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void OverworldEditor::DrawScratchSpaceEdits() {
|
||||
// Handle painting like the main overworld - continuous drawing
|
||||
auto mouse_position = scratch_canvas_.drawn_tile_position();
|
||||
|
||||
// Calculate tile position accounting for padding and 1:1 scale (16px per tile)
|
||||
int tile_x = static_cast<int>(mouse_position.x - 2) / 16; // 16px per tile at 1x scale
|
||||
int tile_y = static_cast<int>(mouse_position.y - 2) / 16;
|
||||
|
||||
// Bounds check for 20x30 scratch space (320x480 pixels / 16 = 20x30 tiles)
|
||||
if (tile_x >= 0 && tile_x < 20 && tile_y >= 0 && tile_y < 30) {
|
||||
// Update the scratch space with the currently selected tile
|
||||
if (tile_x < 32 && tile_y < 32) { // Bounds check for our tile_data array
|
||||
scratch_spaces_[current_scratch_slot_].tile_data[tile_x][tile_y] = current_tile16_;
|
||||
}
|
||||
|
||||
// Update the bitmap immediately for visual feedback
|
||||
UpdateScratchBitmapTile(tile_x, tile_y, current_tile16_);
|
||||
|
||||
// Mark this scratch space as in use
|
||||
if (!scratch_spaces_[current_scratch_slot_].in_use) {
|
||||
scratch_spaces_[current_scratch_slot_].in_use = true;
|
||||
scratch_spaces_[current_scratch_slot_].name = absl::StrFormat("Layout %d", current_scratch_slot_ + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverworldEditor::DrawScratchSpacePattern() {
|
||||
// Handle drawing patterns from overworld selections
|
||||
auto mouse_position = scratch_canvas_.drawn_tile_position();
|
||||
int start_tile_x = static_cast<int>(mouse_position.x - 2) / 16;
|
||||
int start_tile_y = static_cast<int>(mouse_position.y - 2) / 16;
|
||||
|
||||
// Get the selected tiles from overworld
|
||||
overworld_.set_current_world(current_world_);
|
||||
overworld_.set_current_map(current_map_);
|
||||
|
||||
// Calculate pattern dimensions
|
||||
const auto& selected_tiles = ow_map_canvas_.selected_tiles();
|
||||
if (selected_tiles.empty()) return;
|
||||
|
||||
// Determine pattern size (assume rectangular selection)
|
||||
int pattern_width = 1;
|
||||
int pattern_height = static_cast<int>(selected_tiles.size());
|
||||
|
||||
// For rectangular selections, try to determine width/height
|
||||
if (selected_tiles.size() > 1) {
|
||||
// Simple heuristic - assume square or common rectangular patterns
|
||||
pattern_width = static_cast<int>(std::sqrt(selected_tiles.size()));
|
||||
pattern_height = static_cast<int>(selected_tiles.size()) / pattern_width;
|
||||
}
|
||||
|
||||
// Draw the pattern to scratch space (bounds updated for 20x30)
|
||||
int idx = 0;
|
||||
for (int py = 0; py < pattern_height && (start_tile_y + py) < 30; ++py) {
|
||||
for (int px = 0; px < pattern_width && (start_tile_x + px) < 20; ++px) {
|
||||
if (idx < static_cast<int>(selected_tiles.size())) {
|
||||
int tile_id = overworld_.GetTileFromPosition(selected_tiles[idx]);
|
||||
int scratch_x = start_tile_x + px;
|
||||
int scratch_y = start_tile_y + py;
|
||||
|
||||
// Bounds check for tile_data array
|
||||
if (scratch_x < 32 && scratch_y < 32) {
|
||||
scratch_spaces_[current_scratch_slot_].tile_data[scratch_x][scratch_y] = tile_id;
|
||||
}
|
||||
UpdateScratchBitmapTile(scratch_x, scratch_y, tile_id);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverworldEditor::UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot) {
|
||||
// Use current slot if not specified
|
||||
if (slot == -1) slot = current_scratch_slot_;
|
||||
|
||||
// Get the tile data from the tile16 blockset
|
||||
auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
|
||||
if (tile_data.empty()) return;
|
||||
|
||||
auto& scratch_slot = scratch_spaces_[slot];
|
||||
|
||||
// Update the scratch bitmap with the new tile (dimensions based on scratch space size)
|
||||
int scratch_bitmap_width = scratch_slot.width * 16;
|
||||
|
||||
for (int y = 0; y < 16; ++y) {
|
||||
for (int x = 0; x < 16; ++x) {
|
||||
int src_index = y * 16 + x;
|
||||
int dst_x = tile_x * 16 + x;
|
||||
int dst_y = tile_y * 16 + y;
|
||||
int dst_index = dst_y * scratch_bitmap_width + dst_x;
|
||||
|
||||
if (src_index < static_cast<int>(tile_data.size()) &&
|
||||
dst_index < static_cast<int>(scratch_slot.scratch_bitmap.size()) &&
|
||||
dst_x < scratch_bitmap_width) {
|
||||
scratch_slot.scratch_bitmap.WriteToPixel(dst_index, tile_data[src_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scratch_slot.scratch_bitmap.set_modified(true);
|
||||
core::Renderer::Get().UpdateBitmap(&scratch_slot.scratch_bitmap);
|
||||
scratch_slot.in_use = true;
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::SaveCurrentSelectionToScratch(int slot) {
|
||||
if (slot < 0 || slot >= 4) {
|
||||
return absl::InvalidArgumentError("Invalid scratch slot");
|
||||
}
|
||||
|
||||
if (ow_map_canvas_.select_rect_active() && !ow_map_canvas_.selected_tiles().empty()) {
|
||||
// Calculate actual selection dimensions from overworld rectangle
|
||||
const auto& selected_points = ow_map_canvas_.selected_points();
|
||||
if (selected_points.size() >= 2) {
|
||||
const auto start = selected_points[0];
|
||||
const auto end = selected_points[1];
|
||||
|
||||
// Calculate width and height in tiles
|
||||
int selection_width = std::abs(static_cast<int>((end.x - start.x) / 16)) + 1;
|
||||
int selection_height = std::abs(static_cast<int>((end.y - start.y) / 16)) + 1;
|
||||
|
||||
// Update scratch space dimensions to match selection
|
||||
scratch_spaces_[slot].width = std::max(1, std::min(selection_width, 32));
|
||||
scratch_spaces_[slot].height = std::max(1, std::min(selection_height, 32));
|
||||
scratch_spaces_[slot].in_use = true;
|
||||
scratch_spaces_[slot].name = absl::StrFormat("Selection %dx%d",
|
||||
scratch_spaces_[slot].width,
|
||||
scratch_spaces_[slot].height);
|
||||
|
||||
// Recreate bitmap with new dimensions
|
||||
int bitmap_width = scratch_spaces_[slot].width * 16;
|
||||
int bitmap_height = scratch_spaces_[slot].height * 16;
|
||||
std::vector<uint8_t> empty_data(bitmap_width * bitmap_height, 0);
|
||||
scratch_spaces_[slot].scratch_bitmap.Create(bitmap_width, bitmap_height, 8, empty_data);
|
||||
if (all_gfx_loaded_) {
|
||||
palette_ = overworld_.current_area_palette();
|
||||
scratch_spaces_[slot].scratch_bitmap.SetPalette(palette_);
|
||||
core::Renderer::Get().RenderBitmap(&scratch_spaces_[slot].scratch_bitmap);
|
||||
}
|
||||
|
||||
// Save selected tiles to scratch data with proper layout
|
||||
overworld_.set_current_world(current_world_);
|
||||
overworld_.set_current_map(current_map_);
|
||||
|
||||
int idx = 0;
|
||||
for (int y = 0; y < scratch_spaces_[slot].height && idx < static_cast<int>(ow_map_canvas_.selected_tiles().size()); ++y) {
|
||||
for (int x = 0; x < scratch_spaces_[slot].width && idx < static_cast<int>(ow_map_canvas_.selected_tiles().size()); ++x) {
|
||||
if (idx < static_cast<int>(ow_map_canvas_.selected_tiles().size())) {
|
||||
int tile_id = overworld_.GetTileFromPosition(ow_map_canvas_.selected_tiles()[idx]);
|
||||
if (x < 32 && y < 32) {
|
||||
scratch_spaces_[slot].tile_data[x][y] = tile_id;
|
||||
}
|
||||
// Update the bitmap immediately
|
||||
UpdateScratchBitmapTile(x, y, tile_id, slot);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Default single-tile scratch space
|
||||
scratch_spaces_[slot].width = 16; // Default size
|
||||
scratch_spaces_[slot].height = 16;
|
||||
scratch_spaces_[slot].name = absl::StrFormat("Map %d Area", current_map_);
|
||||
scratch_spaces_[slot].in_use = true;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::LoadScratchToSelection(int slot) {
|
||||
if (slot < 0 || slot >= 4) {
|
||||
return absl::InvalidArgumentError("Invalid scratch slot");
|
||||
}
|
||||
|
||||
if (!scratch_spaces_[slot].in_use) {
|
||||
return absl::FailedPreconditionError("Scratch slot is empty");
|
||||
}
|
||||
|
||||
// Placeholder - could restore tiles to current map position
|
||||
util::logf("Loading scratch slot %d: %s", slot, scratch_spaces_[slot].name.c_str());
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status OverworldEditor::ClearScratchSpace(int slot) {
|
||||
if (slot < 0 || slot >= 4) {
|
||||
return absl::InvalidArgumentError("Invalid scratch slot");
|
||||
}
|
||||
|
||||
scratch_spaces_[slot].in_use = false;
|
||||
scratch_spaces_[slot].name = "Empty";
|
||||
|
||||
// Clear the bitmap
|
||||
if (scratch_spaces_[slot].scratch_bitmap.is_active()) {
|
||||
auto& data = scratch_spaces_[slot].scratch_bitmap.mutable_data();
|
||||
std::fill(data.begin(), data.end(), 0);
|
||||
scratch_spaces_[slot].scratch_bitmap.set_modified(true);
|
||||
core::Renderer::Get().UpdateBitmap(&scratch_spaces_[slot].scratch_bitmap);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void OverworldEditor::DrawScratchSpaceSelection() {
|
||||
const ImGuiIO &io = ImGui::GetIO();
|
||||
const ImVec2 origin(scratch_canvas_.zero_point().x + scratch_canvas_.scrolling().x,
|
||||
scratch_canvas_.zero_point().y + scratch_canvas_.scrolling().y);
|
||||
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||
static ImVec2 drag_start_pos;
|
||||
static bool dragging = false;
|
||||
const float tile_size = 16.0f; // 16px per tile at 1x scale
|
||||
|
||||
auto& current_slot = scratch_spaces_[current_scratch_slot_];
|
||||
|
||||
// Handle right click for scratch space selection (completely independent)
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && scratch_canvas_.IsMouseHovering()) {
|
||||
// Clear any existing selection
|
||||
current_slot.selected_tiles.clear();
|
||||
current_slot.selected_points.clear();
|
||||
current_slot.select_rect_active = false;
|
||||
|
||||
// Start new selection
|
||||
ImVec2 aligned_pos;
|
||||
aligned_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size; // Account for padding
|
||||
aligned_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
|
||||
drag_start_pos = aligned_pos;
|
||||
}
|
||||
|
||||
// Handle drag for rectangle selection
|
||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Right) && scratch_canvas_.IsMouseHovering()) {
|
||||
ImVec2 drag_end_pos;
|
||||
drag_end_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size;
|
||||
drag_end_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
|
||||
|
||||
// Draw selection rectangle
|
||||
auto start = ImVec2(scratch_canvas_.zero_point().x + drag_start_pos.x + 2.0f,
|
||||
scratch_canvas_.zero_point().y + drag_start_pos.y + 2.0f);
|
||||
auto end = ImVec2(scratch_canvas_.zero_point().x + drag_end_pos.x + tile_size + 2.0f,
|
||||
scratch_canvas_.zero_point().y + drag_end_pos.y + tile_size + 2.0f);
|
||||
ImGui::GetWindowDrawList()->AddRect(start, end, IM_COL32(255, 255, 255, 255));
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
// Handle release to finalize selection
|
||||
if (dragging && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||
dragging = false;
|
||||
|
||||
ImVec2 drag_end_pos;
|
||||
drag_end_pos.x = std::floor((mouse_pos.x - 2.0f) / tile_size) * tile_size;
|
||||
drag_end_pos.y = std::floor((mouse_pos.y - 2.0f) / tile_size) * tile_size;
|
||||
|
||||
// Ensure proper ordering
|
||||
ImVec2 selection_start = drag_start_pos;
|
||||
ImVec2 selection_end = drag_end_pos;
|
||||
if (selection_start.x > selection_end.x) std::swap(selection_start.x, selection_end.x);
|
||||
if (selection_start.y > selection_end.y) std::swap(selection_start.y, selection_end.y);
|
||||
|
||||
// Convert to tile coordinates
|
||||
int start_tile_x = static_cast<int>(selection_start.x) / static_cast<int>(tile_size);
|
||||
int start_tile_y = static_cast<int>(selection_start.y) / static_cast<int>(tile_size);
|
||||
int end_tile_x = static_cast<int>(selection_end.x) / static_cast<int>(tile_size);
|
||||
int end_tile_y = static_cast<int>(selection_end.y) / static_cast<int>(tile_size);
|
||||
|
||||
// Store selection data in scratch space
|
||||
current_slot.selected_tiles.clear();
|
||||
current_slot.selected_points.clear();
|
||||
current_slot.selected_points.push_back(selection_start);
|
||||
current_slot.selected_points.push_back(selection_end);
|
||||
|
||||
// Collect tile positions
|
||||
for (int y = start_tile_y; y <= end_tile_y; ++y) {
|
||||
for (int x = start_tile_x; x <= end_tile_x; ++x) {
|
||||
if (x >= 0 && x < 20 && y >= 0 && y < 30) { // Bounds check for 20x30 scratch space
|
||||
current_slot.selected_tiles.push_back(ImVec2(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current_slot.select_rect_active = !current_slot.selected_tiles.empty();
|
||||
|
||||
// Copy selected tiles to shared clipboard for use in main overworld
|
||||
if (current_slot.select_rect_active && context_) {
|
||||
std::vector<int> scratch_tile_ids;
|
||||
for (const auto& tile_pos : current_slot.selected_tiles) {
|
||||
int x = static_cast<int>(tile_pos.x);
|
||||
int y = static_cast<int>(tile_pos.y);
|
||||
if (x < 32 && y < 32) { // Bounds check for tile_data array
|
||||
int tile_id = current_slot.tile_data[x][y];
|
||||
scratch_tile_ids.push_back(tile_id);
|
||||
}
|
||||
}
|
||||
|
||||
int width = end_tile_x - start_tile_x + 1;
|
||||
int height = end_tile_y - start_tile_y + 1;
|
||||
|
||||
context_->shared_clipboard.overworld_tile16_ids = std::move(scratch_tile_ids);
|
||||
context_->shared_clipboard.overworld_width = width;
|
||||
context_->shared_clipboard.overworld_height = height;
|
||||
context_->shared_clipboard.has_overworld_tile16 = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw scratch space selection overlay (independent)
|
||||
if (current_slot.select_rect_active && current_slot.selected_points.size() >= 2) {
|
||||
ImVec2 start = current_slot.selected_points[0];
|
||||
ImVec2 end = current_slot.selected_points[1];
|
||||
|
||||
// Draw selection rectangle on scratch canvas
|
||||
auto rect_start = ImVec2(origin.x + start.x + 2.0f, origin.y + start.y + 2.0f);
|
||||
auto rect_end = ImVec2(origin.x + end.x + tile_size + 2.0f, origin.y + end.y + tile_size + 2.0f);
|
||||
ImGui::GetWindowDrawList()->AddRect(rect_start, rect_end, IM_COL32(255, 255, 255, 200), 0.0f, 0, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace yaze::editor
|
||||
@@ -188,6 +188,16 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
||||
void DrawMapPropertiesPanel();
|
||||
void HandleMapInteraction();
|
||||
void SetupOverworldCanvasContextMenu();
|
||||
|
||||
// Scratch space canvas methods
|
||||
absl::Status DrawScratchSpace();
|
||||
absl::Status SaveCurrentSelectionToScratch(int slot);
|
||||
absl::Status LoadScratchToSelection(int slot);
|
||||
absl::Status ClearScratchSpace(int slot);
|
||||
void DrawScratchSpaceEdits();
|
||||
void DrawScratchSpacePattern();
|
||||
void DrawScratchSpaceSelection();
|
||||
void UpdateScratchBitmapTile(int tile_x, int tile_y, int tile_id, int slot = -1);
|
||||
|
||||
absl::Status UpdateUsageStats();
|
||||
void DrawUsageGrid();
|
||||
@@ -253,6 +263,23 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
||||
|
||||
// Map properties system for UI organization
|
||||
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
|
||||
|
||||
// Scratch space for large layouts
|
||||
// Scratch space canvas for tile16 drawing (like a mini overworld)
|
||||
struct ScratchSpaceSlot {
|
||||
gfx::Bitmap scratch_bitmap;
|
||||
std::array<std::array<int, 32>, 32> tile_data; // 32x32 grid of tile16 IDs
|
||||
bool in_use = false;
|
||||
std::string name = "Empty";
|
||||
int width = 16; // Default 16x16 tiles
|
||||
int height = 16;
|
||||
// Independent selection system for scratch space
|
||||
std::vector<ImVec2> selected_tiles;
|
||||
std::vector<ImVec2> selected_points;
|
||||
bool select_rect_active = false;
|
||||
};
|
||||
std::array<ScratchSpaceSlot, 4> scratch_spaces_;
|
||||
int current_scratch_slot_ = 0;
|
||||
|
||||
gfx::Tilemap tile16_blockset_;
|
||||
|
||||
@@ -295,6 +322,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
||||
gui::Canvas graphics_bin_canvas_{"GraphicsBin", kGraphicsBinCanvasSize,
|
||||
gui::CanvasGridSize::k16x16};
|
||||
gui::Canvas properties_canvas_;
|
||||
gui::Canvas scratch_canvas_{"ScratchSpace", ImVec2(320, 480), gui::CanvasGridSize::k32x32};
|
||||
|
||||
gui::Table toolset_table_{"##ToolsetTable0", 12, kToolsetTableFlags};
|
||||
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
||||
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
@@ -19,29 +21,43 @@
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
// Constants for tile editing
|
||||
constexpr int kTile16Size = 16;
|
||||
constexpr int kTile8Size = 8;
|
||||
constexpr int kTilesheetEditorWidth = 0x100;
|
||||
constexpr int kTilesheetEditorHeight = 0x4000;
|
||||
constexpr int kTile16CanvasSize = 0x20;
|
||||
constexpr int kTile8CanvasHeight = 0x175;
|
||||
constexpr int kNumScratchSlots = 4;
|
||||
constexpr int kNumPalettes = 8;
|
||||
constexpr int kTile8PixelCount = 64;
|
||||
constexpr int kTile16PixelCount = 256;
|
||||
|
||||
/**
|
||||
* @brief Popup window to edit Tile16 data
|
||||
*/
|
||||
class Tile16Editor : public gfx::GfxContext {
|
||||
public:
|
||||
Tile16Editor(Rom *rom, gfx::Tilemap *tile16_blockset)
|
||||
Tile16Editor(Rom* rom, gfx::Tilemap* tile16_blockset)
|
||||
: rom_(rom), tile16_blockset_(tile16_blockset) {}
|
||||
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp,
|
||||
const gfx::Bitmap ¤t_gfx_bmp,
|
||||
std::array<uint8_t, 0x200> &all_tiles_types);
|
||||
absl::Status Initialize(const gfx::Bitmap& tile16_blockset_bmp,
|
||||
const gfx::Bitmap& current_gfx_bmp,
|
||||
std::array<uint8_t, 0x200>& all_tiles_types);
|
||||
|
||||
absl::Status Update();
|
||||
|
||||
void DrawTile16Editor();
|
||||
absl::Status UpdateTile16Transfer();
|
||||
absl::Status UpdateBlockset();
|
||||
|
||||
// Scratch space for tile16 layouts
|
||||
void DrawScratchSpace();
|
||||
absl::Status SaveLayoutToScratch(int slot);
|
||||
absl::Status LoadLayoutFromScratch(int slot);
|
||||
|
||||
absl::Status DrawToCurrentTile16(ImVec2 pos);
|
||||
|
||||
absl::Status UpdateTile16Edit();
|
||||
|
||||
absl::Status UpdateTransferTileCanvas();
|
||||
|
||||
absl::Status LoadTile8();
|
||||
|
||||
absl::Status SetCurrentTile(int id);
|
||||
@@ -53,14 +69,61 @@ class Tile16Editor : public gfx::GfxContext {
|
||||
absl::Status LoadTile16FromScratchSpace(int slot);
|
||||
absl::Status ClearScratchSpace(int slot);
|
||||
|
||||
void set_rom(Rom *rom) { rom_ = rom; }
|
||||
Rom *rom() const { return rom_; }
|
||||
// Advanced editing features
|
||||
absl::Status FlipTile16Horizontal();
|
||||
absl::Status FlipTile16Vertical();
|
||||
absl::Status RotateTile16();
|
||||
absl::Status FillTile16WithTile8(int tile8_id);
|
||||
absl::Status AutoTileTile16();
|
||||
absl::Status ClearTile16();
|
||||
|
||||
// Palette management
|
||||
absl::Status CyclePalette(bool forward = true);
|
||||
absl::Status ApplyPaletteToAll(uint8_t palette_id);
|
||||
absl::Status PreviewPaletteChange(uint8_t palette_id);
|
||||
|
||||
// Batch operations
|
||||
absl::Status ApplyToSelection(const std::function<void(int)>& operation);
|
||||
absl::Status BatchEdit(const std::vector<int>& tile_ids,
|
||||
const std::function<void(int)>& operation);
|
||||
|
||||
// History and undo system
|
||||
absl::Status Undo();
|
||||
absl::Status Redo();
|
||||
void SaveUndoState();
|
||||
|
||||
// Live preview system
|
||||
void EnableLivePreview(bool enable) { live_preview_enabled_ = enable; }
|
||||
absl::Status UpdateLivePreview();
|
||||
|
||||
// Validation and integrity checks
|
||||
absl::Status ValidateTile16Data();
|
||||
bool IsTile16Valid(int tile_id) const;
|
||||
|
||||
// Integration with overworld system
|
||||
absl::Status SaveTile16ToROM();
|
||||
absl::Status UpdateOverworldTilemap();
|
||||
absl::Status CommitChangesToBlockset();
|
||||
|
||||
// Helper methods for palette management
|
||||
absl::Status UpdateTile8Palette(int tile8_id);
|
||||
absl::Status RefreshAllPalettes();
|
||||
void DrawPaletteSettings();
|
||||
|
||||
// ROM data access and modification
|
||||
absl::Status UpdateROMTile16Data();
|
||||
absl::Status RefreshTile16Blockset();
|
||||
gfx::Tile16* GetCurrentTile16Data();
|
||||
|
||||
// Manual tile8 input controls
|
||||
void DrawManualTile8Inputs();
|
||||
|
||||
void set_rom(Rom* rom) { rom_ = rom; }
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
private:
|
||||
Rom *rom_ = nullptr;
|
||||
Rom* rom_ = nullptr;
|
||||
bool map_blockset_loaded_ = false;
|
||||
bool transfer_started_ = false;
|
||||
bool transfer_blockset_loaded_ = false;
|
||||
bool x_flip = false;
|
||||
bool y_flip = false;
|
||||
bool priority_tile = false;
|
||||
@@ -78,19 +141,67 @@ class Tile16Editor : public gfx::GfxContext {
|
||||
std::array<gfx::Bitmap, 4> scratch_space_;
|
||||
std::array<bool, 4> scratch_space_used_ = {false, false, false, false};
|
||||
|
||||
// Layout scratch space for tile16 arrangements (4 slots of 8x8 grids)
|
||||
struct LayoutScratch {
|
||||
std::array<std::array<int, 8>, 8> tile_layout; // 8x8 grid of tile16 IDs
|
||||
bool in_use = false;
|
||||
std::string name = "Empty";
|
||||
};
|
||||
std::array<LayoutScratch, 4> layout_scratch_;
|
||||
|
||||
// Undo/Redo system
|
||||
struct UndoState {
|
||||
int tile_id;
|
||||
gfx::Bitmap tile_bitmap;
|
||||
gfx::Tile16 tile_data;
|
||||
uint8_t palette;
|
||||
bool x_flip, y_flip, priority;
|
||||
};
|
||||
std::vector<UndoState> undo_stack_;
|
||||
std::vector<UndoState> redo_stack_;
|
||||
static constexpr size_t kMaxUndoStates_ = 50;
|
||||
|
||||
// Live preview system
|
||||
bool live_preview_enabled_ = true;
|
||||
gfx::Bitmap preview_tile16_;
|
||||
bool preview_dirty_ = false;
|
||||
|
||||
// Selection system
|
||||
std::vector<int> selected_tiles_;
|
||||
int selection_start_tile_ = -1;
|
||||
bool multi_select_mode_ = false;
|
||||
|
||||
// Advanced editing state
|
||||
bool auto_tile_mode_ = false;
|
||||
bool grid_snap_enabled_ = true;
|
||||
bool show_tile_info_ = true;
|
||||
bool show_palette_preview_ = true;
|
||||
|
||||
// Palette management settings
|
||||
bool show_palette_settings_ = false;
|
||||
int current_palette_group_ = 0; // 0=overworld_main, 1=aux1, 2=aux2, etc.
|
||||
uint8_t palette_normalization_mask_ = 0x0F; // Default 4-bit mask
|
||||
bool auto_normalize_pixels_ = true;
|
||||
|
||||
// Performance tracking
|
||||
std::chrono::steady_clock::time_point last_edit_time_;
|
||||
bool batch_mode_ = false;
|
||||
|
||||
util::NotifyValue<uint32_t> notify_tile16;
|
||||
util::NotifyValue<uint8_t> notify_palette;
|
||||
|
||||
std::array<uint8_t, 0x200> all_tiles_types_;
|
||||
|
||||
// Tile16 blockset for selecting the tile to edit
|
||||
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
|
||||
gui::CanvasGridSize::k32x32,};
|
||||
gui::Canvas blockset_canvas_{
|
||||
"blocksetCanvas", ImVec2(kTilesheetEditorWidth, kTilesheetEditorHeight),
|
||||
gui::CanvasGridSize::k32x32};
|
||||
gfx::Bitmap tile16_blockset_bmp_;
|
||||
|
||||
// Canvas for editing the selected tile
|
||||
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
|
||||
gui::CanvasGridSize::k64x64};
|
||||
// Canvas for editing the selected tile - smaller size for table fit
|
||||
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas",
|
||||
ImVec2(128, 128), // Reduced from kTile16CanvasSize to fit tables
|
||||
gui::CanvasGridSize::k64x64, 2.0f}; // Reduced scale to fit tables
|
||||
gfx::Bitmap current_tile16_bmp_;
|
||||
|
||||
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
||||
@@ -100,23 +211,15 @@ class Tile16Editor : public gfx::GfxContext {
|
||||
gui::CanvasGridSize::k32x32};
|
||||
gfx::Bitmap current_gfx_bmp_;
|
||||
|
||||
gui::Canvas transfer_canvas_;
|
||||
gfx::Bitmap transfer_blockset_bmp_;
|
||||
|
||||
gui::Table tile_edit_table_{"##TileEditTable", 3, ImGuiTableFlags_Borders};
|
||||
|
||||
gfx::Tilemap *tile16_blockset_ = nullptr;
|
||||
gfx::Tilemap* tile16_blockset_ = nullptr;
|
||||
std::vector<gfx::Bitmap> current_gfx_individual_;
|
||||
|
||||
PaletteEditor palette_editor_;
|
||||
gfx::SnesPalette palette_;
|
||||
|
||||
absl::Status status_;
|
||||
|
||||
Rom *transfer_rom_ = nullptr;
|
||||
zelda3::Overworld transfer_overworld_{transfer_rom_};
|
||||
std::array<gfx::Bitmap, kNumGfxSheets> transfer_gfx_;
|
||||
absl::Status transfer_status_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <future>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/core/features.h"
|
||||
@@ -19,7 +19,7 @@
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
absl::Status Overworld::Load(Rom *rom) {
|
||||
absl::Status Overworld::Load(Rom* rom) {
|
||||
if (rom->size() == 0) {
|
||||
return absl::InvalidArgumentError("ROM file not loaded");
|
||||
}
|
||||
@@ -103,7 +103,7 @@ void Overworld::FetchLargeMaps() {
|
||||
}
|
||||
|
||||
absl::StatusOr<uint16_t> Overworld::GetTile16ForTile32(
|
||||
int index, int quadrant, int dimension, const uint32_t *map32address) {
|
||||
int index, int quadrant, int dimension, const uint32_t* map32address) {
|
||||
ASSIGN_OR_RETURN(
|
||||
auto arg1, rom()->ReadByte(map32address[dimension] + quadrant + (index)));
|
||||
ASSIGN_OR_RETURN(auto arg2,
|
||||
@@ -120,11 +120,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
|
||||
rom()->version_constants().kMap32TileTR,
|
||||
rom()->version_constants().kMap32TileBL,
|
||||
rom()->version_constants().kMap32TileBR};
|
||||
|
||||
|
||||
// Use ASM version to determine expanded tile32 support, with flag as override
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
bool use_custom_overworld =
|
||||
(asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
if (rom()->data()[kMap32ExpandedFlagPos] != 0x04 && use_custom_overworld) {
|
||||
map32address[0] = rom()->version_constants().kMap32TileTL;
|
||||
map32address[1] = kMap32TileTRExpanded;
|
||||
@@ -173,11 +174,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
|
||||
absl::Status Overworld::AssembleMap16Tiles() {
|
||||
int tpos = kMap16Tiles;
|
||||
int num_tile16 = kNumTile16Individual;
|
||||
|
||||
|
||||
// Use ASM version to determine expanded tile16 support, with flag as override
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
bool use_custom_overworld =
|
||||
(asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
if (rom()->data()[kMap16ExpandedFlagPos] != 0x0F && use_custom_overworld) {
|
||||
tpos = kMap16TilesExpanded;
|
||||
num_tile16 = NumberOfMap16Ex;
|
||||
@@ -203,7 +205,7 @@ absl::Status Overworld::AssembleMap16Tiles() {
|
||||
}
|
||||
|
||||
void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
|
||||
OverworldBlockset &world) {
|
||||
OverworldBlockset& world) {
|
||||
int position_x1 = (x * 2) + (sx * 32);
|
||||
int position_y1 = (y * 2) + (sy * 32);
|
||||
int position_x2 = (x * 2) + 1 + (sx * 32);
|
||||
@@ -214,9 +216,9 @@ void Overworld::AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
|
||||
world[position_x2][position_y2] = tiles32_unique_[tpos].tile3_;
|
||||
}
|
||||
|
||||
void Overworld::OrganizeMapTiles(std::vector<uint8_t> &bytes,
|
||||
std::vector<uint8_t> &bytes2, int i, int sx,
|
||||
int sy, int &ttpos) {
|
||||
void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
|
||||
std::vector<uint8_t>& bytes2, int i, int sx,
|
||||
int sy, int& ttpos) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
auto tidD = (uint16_t)((bytes2[ttpos] << 8) + bytes[ttpos]);
|
||||
@@ -258,11 +260,15 @@ void Overworld::DecompressAllMapTiles() {
|
||||
|
||||
int ttpos = 0;
|
||||
|
||||
if (p1 >= highest) highest = p1;
|
||||
if (p2 >= highest) highest = p2;
|
||||
if (p1 >= highest)
|
||||
highest = p1;
|
||||
if (p2 >= highest)
|
||||
highest = p2;
|
||||
|
||||
if (p1 <= lowest && p1 > kBaseHighest) lowest = p1;
|
||||
if (p2 <= lowest && p2 > kBaseHighest) lowest = p2;
|
||||
if (p1 <= lowest && p1 > kBaseHighest)
|
||||
lowest = p1;
|
||||
if (p2 <= lowest && p2 > kBaseHighest)
|
||||
lowest = p2;
|
||||
|
||||
int size1, size2;
|
||||
auto bytes = gfx::HyruleMagicDecompress(rom()->data() + p2, &size1, 1);
|
||||
@@ -302,7 +308,7 @@ absl::Status Overworld::LoadOverworldMaps() {
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete and check their results
|
||||
for (auto &future : futures) {
|
||||
for (auto& future : futures) {
|
||||
future.wait();
|
||||
RETURN_IF_ERROR(future.get());
|
||||
}
|
||||
@@ -321,12 +327,14 @@ absl::Status Overworld::LoadEntrances() {
|
||||
int ow_entrance_pos_ptr = kOverworldEntrancePos;
|
||||
int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
|
||||
int num_entrances = 129;
|
||||
|
||||
|
||||
// Use ASM version to determine expanded entrance support, with flag as override
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 && use_custom_overworld) {
|
||||
bool use_custom_overworld =
|
||||
(asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 &&
|
||||
use_custom_overworld) {
|
||||
ow_entrance_map_ptr = kOverworldEntranceMapExpanded;
|
||||
ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
|
||||
ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
|
||||
@@ -439,24 +447,29 @@ absl::Status Overworld::LoadItems() {
|
||||
|
||||
// Version 0x03 of the OW ASM added item support for the SW.
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
|
||||
|
||||
// Determine max number of overworld maps based on ASM version
|
||||
int max_ow = (asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80;
|
||||
|
||||
ASSIGN_OR_RETURN(uint32_t pointer_snes,
|
||||
int max_ow =
|
||||
(asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80;
|
||||
|
||||
ASSIGN_OR_RETURN(uint32_t pointer_snes,
|
||||
rom()->ReadLong(zelda3::overworldItemsAddress));
|
||||
uint32_t item_pointer_address = SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
|
||||
|
||||
uint32_t item_pointer_address =
|
||||
SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
|
||||
|
||||
for (int i = 0; i < max_ow; i++) {
|
||||
ASSIGN_OR_RETURN(uint8_t bank_byte, rom()->ReadByte(zelda3::overworldItemsAddressBank));
|
||||
ASSIGN_OR_RETURN(uint8_t bank_byte,
|
||||
rom()->ReadByte(zelda3::overworldItemsAddressBank));
|
||||
int bank = bank_byte & 0x7F;
|
||||
|
||||
ASSIGN_OR_RETURN(uint8_t addr_low, rom()->ReadByte(item_pointer_address + (i * 2)));
|
||||
ASSIGN_OR_RETURN(uint8_t addr_high, rom()->ReadByte(item_pointer_address + (i * 2) + 1));
|
||||
|
||||
uint32_t addr = (bank << 16) + // 1B
|
||||
(addr_high << 8) + // F9
|
||||
addr_low; // 3C
|
||||
|
||||
ASSIGN_OR_RETURN(uint8_t addr_low,
|
||||
rom()->ReadByte(item_pointer_address + (i * 2)));
|
||||
ASSIGN_OR_RETURN(uint8_t addr_high,
|
||||
rom()->ReadByte(item_pointer_address + (i * 2) + 1));
|
||||
|
||||
uint32_t addr = (bank << 16) + // 1B
|
||||
(addr_high << 8) + // F9
|
||||
addr_low; // 3C
|
||||
addr = SnesToPc(addr);
|
||||
|
||||
// Check if this is a large map and skip if not the parent
|
||||
@@ -499,12 +512,13 @@ absl::Status Overworld::LoadItems() {
|
||||
|
||||
absl::Status Overworld::LoadSprites() {
|
||||
std::vector<std::future<absl::Status>> futures;
|
||||
|
||||
|
||||
// Use ASM version to determine sprite table locations, with flag as override
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
|
||||
bool use_custom_overworld =
|
||||
(asm_version != 0xFF) ||
|
||||
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||
|
||||
if (use_custom_overworld && asm_version >= 3 && asm_version != 0xFF) {
|
||||
// v3: Use expanded sprite tables
|
||||
futures.emplace_back(std::async(std::launch::async, [this]() {
|
||||
@@ -529,7 +543,7 @@ absl::Status Overworld::LoadSprites() {
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto &future : futures) {
|
||||
for (auto& future : futures) {
|
||||
future.wait();
|
||||
RETURN_IF_ERROR(future.get());
|
||||
}
|
||||
@@ -540,7 +554,8 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
|
||||
int num_maps_per_gamestate,
|
||||
int game_state) {
|
||||
for (int i = 0; i < num_maps_per_gamestate; i++) {
|
||||
if (map_parent_[i] != i) continue;
|
||||
if (map_parent_[i] != i)
|
||||
continue;
|
||||
|
||||
int current_spr_ptr = sprites_per_gamestate_ptr + (i * 2);
|
||||
ASSIGN_OR_RETURN(auto word_addr, rom()->ReadWord(current_spr_ptr));
|
||||
@@ -549,7 +564,8 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
|
||||
ASSIGN_OR_RETURN(uint8_t b1, rom()->ReadByte(sprite_address));
|
||||
ASSIGN_OR_RETURN(uint8_t b2, rom()->ReadByte(sprite_address + 1));
|
||||
ASSIGN_OR_RETURN(uint8_t b3, rom()->ReadByte(sprite_address + 2));
|
||||
if (b1 == 0xFF) break;
|
||||
if (b1 == 0xFF)
|
||||
break;
|
||||
|
||||
int editor_map_index = i;
|
||||
if (game_state != 0) {
|
||||
@@ -575,12 +591,18 @@ absl::Status Overworld::LoadSpritesFromMap(int sprites_per_gamestate_ptr,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Overworld::Save(Rom *rom) {
|
||||
absl::Status Overworld::Save(Rom* rom) {
|
||||
rom_ = rom;
|
||||
if (expanded_tile16_) RETURN_IF_ERROR(SaveMap16Expanded())
|
||||
RETURN_IF_ERROR(SaveMap16Tiles())
|
||||
if (expanded_tile32_) RETURN_IF_ERROR(SaveMap32Expanded())
|
||||
RETURN_IF_ERROR(SaveMap32Tiles())
|
||||
if (expanded_tile16_) {
|
||||
RETURN_IF_ERROR(SaveMap16Expanded())
|
||||
} else {
|
||||
RETURN_IF_ERROR(SaveMap16Tiles())
|
||||
}
|
||||
if (expanded_tile32_) {
|
||||
RETURN_IF_ERROR(SaveMap32Expanded())
|
||||
} else {
|
||||
RETURN_IF_ERROR(SaveMap32Tiles())
|
||||
}
|
||||
RETURN_IF_ERROR(SaveOverworldMaps())
|
||||
RETURN_IF_ERROR(SaveEntrances())
|
||||
RETURN_IF_ERROR(SaveExits())
|
||||
@@ -635,8 +657,8 @@ absl::Status Overworld::SaveOverworldMaps() {
|
||||
pos = kOverworldMapDataOverflow; // 0x0F8780;
|
||||
}
|
||||
|
||||
const auto compare_array = [](const std::vector<uint8_t> &array1,
|
||||
const std::vector<uint8_t> &array2) -> bool {
|
||||
const auto compare_array = [](const std::vector<uint8_t>& array1,
|
||||
const std::vector<uint8_t>& array2) -> bool {
|
||||
if (array1.size() != array2.size()) {
|
||||
return false;
|
||||
}
|
||||
@@ -750,7 +772,7 @@ absl::Status Overworld::SaveLargeMaps() {
|
||||
// 0x200 otherwise pos * 0x200
|
||||
if (overworld_maps_[i].is_large_map()) {
|
||||
const uint8_t large_map_offsets[] = {0, 1, 8, 9};
|
||||
for (const auto &offset : large_map_offsets) {
|
||||
for (const auto& offset : large_map_offsets) {
|
||||
// Check 1
|
||||
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapSize + i + offset, 0x20));
|
||||
// Check 2
|
||||
@@ -1048,7 +1070,7 @@ absl::Status Overworld::SaveLargeMaps() {
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<uint64_t> GetAllTile16(OverworldMapTiles &map_tiles_) {
|
||||
std::vector<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
|
||||
std::vector<uint64_t> all_tile_16; // Ensure it's 64 bits
|
||||
|
||||
int sx = 0;
|
||||
@@ -1199,6 +1221,127 @@ absl::Status Overworld::SaveMap32Expanded() {
|
||||
rom()->WriteLong(0x017788, PcToSnes(kMap32TileBRExpanded + 4)));
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteLong(0x01779A, PcToSnes(kMap32TileBRExpanded + 5)));
|
||||
|
||||
constexpr int kTilesPer32x32Tile = 6;
|
||||
int unique_tile_index = 0;
|
||||
int num_unique_tiles = tiles32_unique_.size();
|
||||
|
||||
for (int i = 0; i < num_unique_tiles; i += kTilesPer32x32Tile) {
|
||||
if (unique_tile_index >= limit) {
|
||||
return absl::AbortedError("Too many unique tile32 definitions.");
|
||||
}
|
||||
|
||||
// Top Left.
|
||||
auto top_left = rom()->version_constants().kMap32TileTL;
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + i,
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index].tile0_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + (i + 1),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile0_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + (i + 2),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile0_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + (i + 3),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile0_ & 0xFF)));
|
||||
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + (i + 4),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index].tile0_ >> 4) & 0xF0) +
|
||||
((tiles32_unique_[unique_tile_index + 1].tile0_ >> 8) &
|
||||
0x0F))));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_left + (i + 5),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile0_ >> 4) &
|
||||
0xF0) +
|
||||
((tiles32_unique_[unique_tile_index + 3].tile0_ >> 8) &
|
||||
0x0F))));
|
||||
|
||||
// Top Right.
|
||||
auto top_right = topRight;
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + i,
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index].tile1_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + (i + 1),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile1_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + (i + 2),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile1_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + (i + 3),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile1_ & 0xFF)));
|
||||
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + (i + 4),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index].tile1_ >> 4) & 0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 1].tile1_ >> 8) &
|
||||
0x0F))));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
top_right + (i + 5),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile1_ >> 4) &
|
||||
0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 3].tile1_ >> 8) &
|
||||
0x0F))));
|
||||
|
||||
// Bottom Left.
|
||||
auto bottom_left = bottomLeft;
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + i,
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index].tile2_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + (i + 1),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile2_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + (i + 2),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile2_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + (i + 3),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile2_ & 0xFF)));
|
||||
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + (i + 4),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index].tile2_ >> 4) & 0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 1].tile2_ >> 8) &
|
||||
0x0F))));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_left + (i + 5),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile2_ >> 4) &
|
||||
0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 3].tile2_ >> 8) &
|
||||
0x0F))));
|
||||
|
||||
// Bottom Right.
|
||||
auto bottom_right = bottomRight;
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + i,
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index].tile3_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + (i + 1),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 1].tile3_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + (i + 2),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 2].tile3_ & 0xFF)));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + (i + 3),
|
||||
(uint8_t)(tiles32_unique_[unique_tile_index + 3].tile3_ & 0xFF)));
|
||||
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + (i + 4),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index].tile3_ >> 4) & 0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 1].tile3_ >> 8) &
|
||||
0x0F))));
|
||||
RETURN_IF_ERROR(rom()->WriteByte(
|
||||
bottom_right + (i + 5),
|
||||
(uint8_t)(((tiles32_unique_[unique_tile_index + 2].tile3_ >> 4) &
|
||||
0xF0) |
|
||||
((tiles32_unique_[unique_tile_index + 3].tile3_ >> 8) &
|
||||
0x0F))));
|
||||
|
||||
unique_tile_index += 4;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1406,6 +1549,23 @@ absl::Status Overworld::SaveMap16Expanded() {
|
||||
SnesToPc(0x02FD39),
|
||||
static_cast<uint8_t>(PcToSnes(kMap16TilesExpanded) >> 16)));
|
||||
|
||||
int tpos = kMap16TilesExpanded;
|
||||
for (int i = 0; i < NumberOfMap16Ex; i += 1) // 4096
|
||||
{
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile0_)));
|
||||
tpos += 2;
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile1_)));
|
||||
tpos += 2;
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile2_)));
|
||||
tpos += 2;
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteShort(tpos, TileInfoToShort(tiles16_[i].tile3_)));
|
||||
tpos += 2;
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -1532,7 +1692,7 @@ absl::Status Overworld::SaveItems() {
|
||||
|
||||
for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
|
||||
room_items[i] = std::vector<OverworldItem>();
|
||||
for (const OverworldItem &item : all_items_) {
|
||||
for (const OverworldItem& item : all_items_) {
|
||||
if (item.room_map_id_ == i) {
|
||||
room_items[i].emplace_back(item);
|
||||
if (item.id_ == 0x86) {
|
||||
@@ -1570,7 +1730,7 @@ absl::Status Overworld::SaveItems() {
|
||||
for (int i = 0; i < kNumOverworldMapItemPointers; i++) {
|
||||
if (item_pointers_reuse[i] == -1) {
|
||||
item_pointers[i] = data_pos;
|
||||
for (const OverworldItem &item : room_items[i]) {
|
||||
for (const OverworldItem& item : room_items[i]) {
|
||||
short map_pos =
|
||||
static_cast<short>(((item.game_y_ << 6) + item.game_x_) << 1);
|
||||
|
||||
@@ -1657,7 +1817,7 @@ absl::Status Overworld::SaveMapProperties() {
|
||||
|
||||
absl::Status Overworld::SaveMusic() {
|
||||
util::logf("Saving Music Data");
|
||||
|
||||
|
||||
// Save music data for Light World maps
|
||||
for (int i = 0; i < kDarkWorldMapIdStart; i++) {
|
||||
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicBeginning + i,
|
||||
@@ -1672,8 +1832,9 @@ absl::Status Overworld::SaveMusic() {
|
||||
|
||||
// Save music data for Dark World maps
|
||||
for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) {
|
||||
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart),
|
||||
overworld_maps_[i].area_music(0)));
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart),
|
||||
overworld_maps_[i].area_music(0)));
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
@@ -1681,7 +1842,7 @@ absl::Status Overworld::SaveMusic() {
|
||||
|
||||
absl::Status Overworld::SaveAreaSizes() {
|
||||
util::logf("Saving V3 Area Sizes");
|
||||
|
||||
|
||||
// Check if this is a v3 ROM
|
||||
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||
if (asm_version < 3 || asm_version == 0xFF) {
|
||||
@@ -1690,14 +1851,16 @@ absl::Status Overworld::SaveAreaSizes() {
|
||||
|
||||
// Save area sizes to the expanded table
|
||||
for (int i = 0; i < kNumOverworldMaps; i++) {
|
||||
uint8_t area_size_byte = static_cast<uint8_t>(overworld_maps_[i].area_size());
|
||||
uint8_t area_size_byte =
|
||||
static_cast<uint8_t>(overworld_maps_[i].area_size());
|
||||
RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
|
||||
}
|
||||
|
||||
// Save message IDs to expanded table
|
||||
for (int i = 0; i < kNumOverworldMaps; i++) {
|
||||
uint16_t message_id = overworld_maps_[i].message_id();
|
||||
RETURN_IF_ERROR(rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
|
||||
RETURN_IF_ERROR(
|
||||
rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
|
||||
Reference in New Issue
Block a user