feat(canvas): introduce canvas geometry and rendering helpers

- Added new files `canvas_geometry.cc`, `canvas_geometry.h`, `canvas_rendering.cc`, and `canvas_rendering.h` to encapsulate canvas geometry calculations and rendering logic.
- Refactored `Canvas::DrawBackground` and related methods to utilize the new geometry and rendering helpers, improving code organization and readability.
- Introduced `CanvasState` to consolidate canvas state management, gradually replacing scattered state members.

Benefits:
- Enhances maintainability and clarity of canvas-related code.
- Streamlines rendering operations and geometry calculations, improving overall performance and usability.
This commit is contained in:
scawful
2025-10-16 12:37:13 -04:00
parent 4ba507bde5
commit bfad2f91fb
8 changed files with 735 additions and 65 deletions

View File

@@ -371,60 +371,56 @@ void Canvas::UpdateInfoGrid(ImVec2 bg_size, float grid_size, int label_id) {
void Canvas::DrawBackground(ImVec2 canvas_size) { void Canvas::DrawBackground(ImVec2 canvas_size) {
draw_list_ = GetWindowDrawList(); draw_list_ = GetWindowDrawList();
canvas_p0_ = GetCursorScreenPos();
// Phase 1: Calculate geometry using new helper
// Calculate canvas size using utility function state_.geometry = CalculateCanvasGeometry(
ImVec2 content_region = GetContentRegionAvail(); config_, canvas_size,
canvas_sz_ = CanvasUtils::CalculateCanvasSize( GetCursorScreenPos(),
content_region, config_.canvas_size, config_.custom_canvas_size); GetContentRegionAvail());
// Sync legacy fields for backward compatibility
canvas_p0_ = state_.geometry.canvas_p0;
canvas_p1_ = state_.geometry.canvas_p1;
canvas_sz_ = state_.geometry.canvas_sz;
scrolling_ = state_.geometry.scrolling;
// Update config if explicit size provided
if (canvas_size.x != 0) { if (canvas_size.x != 0) {
canvas_sz_ = canvas_size;
config_.canvas_size = canvas_size; config_.canvas_size = canvas_size;
} }
// Phase 1: Render background using helper
RenderCanvasBackground(draw_list_, state_.geometry);
// Calculate scaled canvas bounds ImGui::InvisibleButton(canvas_id_.c_str(), state_.geometry.scaled_size, kMouseFlags);
ImVec2 scaled_size =
CanvasUtils::CalculateScaledCanvasSize(canvas_sz_, config_.global_scale);
// CRITICAL FIX: Ensure minimum size to prevent ImGui assertions
if (scaled_size.x <= 0.0f)
scaled_size.x = 1.0f;
if (scaled_size.y <= 0.0f)
scaled_size.y = 1.0f;
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(), scaled_size, kMouseFlags);
// CRITICAL FIX: Always update hover mouse position when hovering over canvas // CRITICAL FIX: Always update hover mouse position when hovering over canvas
// This fixes the regression where CheckForCurrentMap() couldn't track hover // This fixes the regression where CheckForCurrentMap() couldn't track hover
// Phase 1: Use geometry helper for mouse calculation
if (IsItemHovered()) { if (IsItemHovered()) {
const ImGuiIO& io = GetIO(); const ImGuiIO& io = GetIO();
const ImVec2 origin(canvas_p0_.x + scrolling_.x, canvas_p0_.y + scrolling_.y); mouse_pos_in_canvas_ = CalculateMouseInCanvas(state_.geometry, io.MousePos);
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y); state_.mouse_pos_in_canvas = mouse_pos_in_canvas_; // Sync to state
mouse_pos_in_canvas_ = mouse_pos; state_.is_hovered = true;
is_hovered_ = true;
} else {
state_.is_hovered = false;
is_hovered_ = false;
} }
// Pan handling (Phase 1: Use geometry helper)
if (config_.is_draggable && IsItemHovered()) { if (config_.is_draggable && IsItemHovered()) {
const ImGuiIO& io = GetIO(); const ImGuiIO& io = GetIO();
const bool is_active = IsItemActive(); // Held const bool is_active = IsItemActive(); // Held
const ImVec2 origin(canvas_p0_.x + scrolling_.x,
canvas_p0_.y + scrolling_.y); // Lock scrolled origin
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
// Pan (we use a zero mouse threshold when there's no context menu) // Pan (we use a zero mouse threshold when there's no context menu)
if (const float mouse_threshold_for_pan = if (const float mouse_threshold_for_pan =
enable_context_menu_ ? -1.0f : 0.0f; enable_context_menu_ ? -1.0f : 0.0f;
is_active && is_active &&
IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) { IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan)) {
scrolling_.x += io.MouseDelta.x; ApplyScrollDelta(state_.geometry, io.MouseDelta);
scrolling_.y += io.MouseDelta.y; scrolling_ = state_.geometry.scrolling; // Sync legacy field
config_.scrolling = scrolling_; // Sync config
} }
} }
} }
@@ -1050,7 +1046,7 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
} }
} }
void Canvas::DrawBitmap(Bitmap& bitmap, int /*border_offset*/, float scale) { void Canvas::DrawBitmap(Bitmap& bitmap, int border_offset, float scale) {
if (!bitmap.is_active()) { if (!bitmap.is_active()) {
return; return;
} }
@@ -1059,11 +1055,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, int /*border_offset*/, float scale) {
// Update content size for table integration // Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height()); config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage((ImTextureID)(intptr_t)bitmap.texture(), // Phase 1: Use rendering helper
ImVec2(canvas_p0_.x, canvas_p0_.y), RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, border_offset, scale);
ImVec2(canvas_p0_.x + (bitmap.width() * scale),
canvas_p0_.y + (bitmap.height() * scale)));
draw_list_->AddRect(canvas_p0_, canvas_p1_, kWhiteColor);
} }
void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale, void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
@@ -1077,23 +1070,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, int x_offset, int y_offset, float scale,
// CRITICAL: Store UNSCALED bitmap size as content - scale is applied during rendering // CRITICAL: Store UNSCALED bitmap size as content - scale is applied during rendering
config_.content_size = ImVec2(bitmap.width(), bitmap.height()); config_.content_size = ImVec2(bitmap.width(), bitmap.height());
// Calculate the actual rendered size including scale and offsets // Phase 1: Use rendering helper
// CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, x_offset, y_offset, scale, alpha);
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
ImVec2 total_size(x_offset + rendered_size.x, y_offset + rendered_size.y);
// CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication
// The scale parameter already contains the correct scale factor
// The scrolling should NOT be scaled - it's already in screen space
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 + 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, void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size,
@@ -1106,14 +1084,8 @@ void Canvas::DrawBitmap(Bitmap& bitmap, ImVec2 dest_pos, ImVec2 dest_size,
// Update content size for table integration // Update content size for table integration
config_.content_size = ImVec2(bitmap.width(), bitmap.height()); config_.content_size = ImVec2(bitmap.width(), bitmap.height());
draw_list_->AddImage( // Phase 1: Use rendering helper
(ImTextureID)(intptr_t)bitmap.texture(), RenderBitmapOnCanvas(draw_list_, state_.geometry, bitmap, dest_pos, dest_size, src_pos, src_size);
ImVec2(canvas_p0_.x + dest_pos.x, canvas_p0_.y + dest_pos.y),
ImVec2(canvas_p0_.x + dest_pos.x + dest_size.x,
canvas_p0_.y + dest_pos.y + dest_size.y),
ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
ImVec2((src_pos.x + src_size.x) / bitmap.width(),
(src_pos.y + src_size.y) / bitmap.height()));
} }
// TODO: Add parameters for sizing and positioning // TODO: Add parameters for sizing and positioning

View File

@@ -12,6 +12,9 @@
#include "app/gfx/core/bitmap.h" #include "app/gfx/core/bitmap.h"
#include "app/rom.h" #include "app/rom.h"
#include "app/gui/canvas/canvas_utils.h" #include "app/gui/canvas/canvas_utils.h"
#include "app/gui/canvas/canvas_state.h"
#include "app/gui/canvas/canvas_geometry.h"
#include "app/gui/canvas/canvas_rendering.h"
#include "app/gui/widgets/palette_editor_widget.h" #include "app/gui/widgets/palette_editor_widget.h"
#include "app/gfx/util/bpp_format_manager.h" #include "app/gfx/util/bpp_format_manager.h"
#include "app/gui/canvas/bpp_format_ui.h" #include "app/gui/canvas/bpp_format_ui.h"
@@ -414,6 +417,9 @@ class Canvas {
CanvasSelection selection_; CanvasSelection selection_;
std::unique_ptr<PaletteEditorWidget> palette_editor_; std::unique_ptr<PaletteEditorWidget> palette_editor_;
// Phase 1: Consolidated state (gradually replacing scattered members)
CanvasState state_;
// Automation API (lazy-initialized on first access) // Automation API (lazy-initialized on first access)
std::unique_ptr<CanvasAutomationAPI> automation_api_; std::unique_ptr<CanvasAutomationAPI> automation_api_;

View File

@@ -0,0 +1,84 @@
#include "canvas_geometry.h"
#include <algorithm>
#include "app/gui/canvas/canvas_utils.h"
namespace yaze {
namespace gui {
CanvasGeometry CalculateCanvasGeometry(
const CanvasConfig& config,
ImVec2 requested_size,
ImVec2 cursor_screen_pos,
ImVec2 content_region_avail) {
CanvasGeometry geometry;
// Set canvas top-left position (screen space)
geometry.canvas_p0 = cursor_screen_pos;
// Calculate canvas size using existing utility function
ImVec2 canvas_sz = CanvasUtils::CalculateCanvasSize(
content_region_avail, config.canvas_size, config.custom_canvas_size);
// Override with explicit size if provided
if (requested_size.x != 0) {
canvas_sz = requested_size;
}
geometry.canvas_sz = canvas_sz;
// Calculate scaled canvas bounds
geometry.scaled_size = CanvasUtils::CalculateScaledCanvasSize(
canvas_sz, config.global_scale);
// CRITICAL: Ensure minimum size to prevent ImGui assertions
if (geometry.scaled_size.x <= 0.0f) {
geometry.scaled_size.x = 1.0f;
}
if (geometry.scaled_size.y <= 0.0f) {
geometry.scaled_size.y = 1.0f;
}
// Calculate bottom-right position
geometry.canvas_p1 = ImVec2(
geometry.canvas_p0.x + geometry.scaled_size.x,
geometry.canvas_p0.y + geometry.scaled_size.y);
// Copy scroll offset from config (will be updated by interaction)
geometry.scrolling = config.scrolling;
return geometry;
}
ImVec2 CalculateMouseInCanvas(
const CanvasGeometry& geometry,
ImVec2 mouse_screen_pos) {
// Calculate origin (locked scrolled origin as used throughout canvas.cc)
ImVec2 origin = GetCanvasOrigin(geometry);
// Convert screen space to canvas space
return ImVec2(
mouse_screen_pos.x - origin.x,
mouse_screen_pos.y - origin.y);
}
bool IsPointInCanvasBounds(
const CanvasGeometry& geometry,
ImVec2 point) {
return point.x >= geometry.canvas_p0.x &&
point.x <= geometry.canvas_p1.x &&
point.y >= geometry.canvas_p0.y &&
point.y <= geometry.canvas_p1.y;
}
void ApplyScrollDelta(CanvasGeometry& geometry, ImVec2 delta) {
geometry.scrolling.x += delta.x;
geometry.scrolling.y += delta.y;
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,87 @@
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_GEOMETRY_H
#define YAZE_APP_GUI_CANVAS_CANVAS_GEOMETRY_H
#include "app/gui/canvas/canvas_state.h"
#include "app/gui/canvas/canvas_utils.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Calculate canvas geometry from configuration and ImGui context
*
* Extracts the geometry calculation logic from Canvas::DrawBackground().
* Computes screen-space positions, sizes, and scroll offsets for a canvas
* based on its configuration and the current ImGui layout state.
*
* @param config Canvas configuration (size, scale, custom size flag)
* @param requested_size Explicitly requested canvas size (0,0 = use config)
* @param cursor_screen_pos Current ImGui cursor position (from GetCursorScreenPos)
* @param content_region_avail Available content region (from GetContentRegionAvail)
* @return Calculated geometry for this frame
*/
CanvasGeometry CalculateCanvasGeometry(
const CanvasConfig& config,
ImVec2 requested_size,
ImVec2 cursor_screen_pos,
ImVec2 content_region_avail);
/**
* @brief Calculate mouse position in canvas space
*
* Converts screen-space mouse coordinates to canvas-space coordinates,
* accounting for canvas position and scroll offset. This is the correct
* coordinate system for tile/entity placement calculations.
*
* @param geometry Canvas geometry (must be current frame)
* @param mouse_screen_pos Mouse position in screen space
* @return Mouse position in canvas space
*/
ImVec2 CalculateMouseInCanvas(
const CanvasGeometry& geometry,
ImVec2 mouse_screen_pos);
/**
* @brief Check if a point is within canvas bounds
*
* Tests whether a screen-space point lies within the canvas rectangle.
* Useful for hit testing and hover detection.
*
* @param geometry Canvas geometry (must be current frame)
* @param point Point in screen space to test
* @return True if point is within canvas bounds
*/
bool IsPointInCanvasBounds(
const CanvasGeometry& geometry,
ImVec2 point);
/**
* @brief Apply scroll delta to geometry
*
* Updates the scroll offset in the geometry. Used for pan operations.
*
* @param geometry Canvas geometry to update
* @param delta Scroll delta (typically ImGui::GetIO().MouseDelta)
*/
void ApplyScrollDelta(CanvasGeometry& geometry, ImVec2 delta);
/**
* @brief Get origin point (canvas top-left + scroll offset)
*
* Computes the "locked scrolled origin" used throughout canvas rendering.
* This is the reference point for all canvas-space to screen-space conversions.
*
* @param geometry Canvas geometry
* @return Origin point in screen space
*/
inline ImVec2 GetCanvasOrigin(const CanvasGeometry& geometry) {
return ImVec2(geometry.canvas_p0.x + geometry.scrolling.x,
geometry.canvas_p0.y + geometry.scrolling.y);
}
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_CANVAS_GEOMETRY_H

View File

@@ -0,0 +1,261 @@
#include "canvas_rendering.h"
#include <algorithm>
#include <cmath>
#include "app/gui/canvas/canvas_utils.h"
namespace yaze {
namespace gui {
namespace {
// Constants extracted from canvas.cc
constexpr uint32_t kRectangleColor = IM_COL32(32, 32, 32, 255);
constexpr uint32_t kWhiteColor = IM_COL32(255, 255, 255, 255);
} // namespace
void RenderCanvasBackground(
ImDrawList* draw_list,
const CanvasGeometry& geometry) {
// Draw border and background color (extracted from Canvas::DrawBackground)
draw_list->AddRectFilled(geometry.canvas_p0, geometry.canvas_p1, kRectangleColor);
draw_list->AddRect(geometry.canvas_p0, geometry.canvas_p1, kWhiteColor);
}
void RenderCanvasGrid(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
int highlight_tile_id) {
if (!config.enable_grid) {
return;
}
// Create render context for utility functions (extracted from Canvas::DrawGrid)
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list,
.canvas_p0 = geometry.canvas_p0,
.canvas_p1 = geometry.canvas_p1,
.scrolling = geometry.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::DrawCanvasGrid(ctx, highlight_tile_id);
}
void RenderCanvasOverlay(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points) {
// Create render context for utility functions (extracted from Canvas::DrawOverlay)
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list,
.canvas_p0 = geometry.canvas_p0,
.canvas_p1 = geometry.canvas_p1,
.scrolling = geometry.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 RenderCanvasLabels(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
const ImVector<ImVector<std::string>>& labels,
int current_labels,
int tile_id_offset) {
if (!config.enable_custom_labels || current_labels >= labels.size()) {
return;
}
// Push clip rect to prevent drawing outside canvas
draw_list->PushClipRect(geometry.canvas_p0, geometry.canvas_p1, true);
// Create render context for utility functions
CanvasUtils::CanvasRenderContext ctx = {
.draw_list = draw_list,
.canvas_p0 = geometry.canvas_p0,
.canvas_p1 = geometry.canvas_p1,
.scrolling = geometry.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 (extracted from Canvas::DrawInfoGrid)
CanvasUtils::DrawCanvasLabels(ctx, labels, current_labels, tile_id_offset);
draw_list->PopClipRect();
}
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
int /*border_offset*/,
float scale) {
if (!bitmap.is_active()) {
return;
}
// Extracted from Canvas::DrawBitmap (border offset variant)
draw_list->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(geometry.canvas_p0.x, geometry.canvas_p0.y),
ImVec2(geometry.canvas_p0.x + (bitmap.width() * scale),
geometry.canvas_p0.y + (bitmap.height() * scale)));
draw_list->AddRect(geometry.canvas_p0, geometry.canvas_p1, kWhiteColor);
}
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
int x_offset,
int y_offset,
float scale,
int alpha) {
if (!bitmap.is_active()) {
return;
}
// Calculate the actual rendered size including scale and offsets
// CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling
// Extracted from Canvas::DrawBitmap (x/y offset variant)
ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
// CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication
// The scale parameter already contains the correct scale factor
// The scrolling should NOT be scaled - it's already in screen space
draw_list->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x,
geometry.canvas_p0.y + y_offset + geometry.scrolling.y),
ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x + rendered_size.x,
geometry.canvas_p0.y + y_offset + geometry.scrolling.y + rendered_size.y),
ImVec2(0, 0), ImVec2(1, 1), IM_COL32(255, 255, 255, alpha));
}
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
ImVec2 dest_pos,
ImVec2 dest_size,
ImVec2 src_pos,
ImVec2 src_size) {
if (!bitmap.is_active()) {
return;
}
// Extracted from Canvas::DrawBitmap (custom source/dest regions variant)
draw_list->AddImage(
(ImTextureID)(intptr_t)bitmap.texture(),
ImVec2(geometry.canvas_p0.x + dest_pos.x, geometry.canvas_p0.y + dest_pos.y),
ImVec2(geometry.canvas_p0.x + dest_pos.x + dest_size.x,
geometry.canvas_p0.y + dest_pos.y + dest_size.y),
ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
ImVec2((src_pos.x + src_size.x) / bitmap.width(),
(src_pos.y + src_size.y) / bitmap.height()));
}
void RenderBitmapGroup(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
std::vector<int>& group,
gfx::Tilemap& tilemap,
int tile_size,
float scale,
int local_map_size,
ImVec2 total_map_size) {
// Extracted from Canvas::DrawBitmapGroup (lines 1148-1264)
// This is used for multi-tile selection preview in overworld editor
if (group.empty()) {
return;
}
// OPTIMIZATION: Use optimized rendering for large groups to improve performance
bool use_optimized_rendering = group.size() > 128;
// Pre-calculate common values to avoid repeated computation
const float tile_scale = tile_size * scale;
const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
// Get selected points (note: this assumes selected_points are available in context)
// For now, we'll just render tiles at their grid positions
// The full implementation would need the selected_points passed in
int i = 0;
for (const auto tile_id : group) {
// Calculate grid position for this tile
int tiles_per_row = 32; // Default for standard maps
int x = i % tiles_per_row;
int y = i / tiles_per_row;
int tile_pos_x = x * tile_size * scale;
int tile_pos_y = y * tile_size * scale;
// Check if tile_id is within the range
auto tilemap_size = tilemap.map_size.x;
if (tile_id >= 0 && tile_id < tilemap_size) {
if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
atlas_tiles_per_row > 0) {
int atlas_tile_x = (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
int atlas_tile_y = (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
// Simple bounds check
if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
// Calculate UV coordinates once for efficiency
const float atlas_width = static_cast<float>(tilemap.atlas.width());
const float atlas_height = static_cast<float>(tilemap.atlas.height());
ImVec2 uv0 = ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
ImVec2 uv1 = ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
(atlas_tile_y + tilemap.tile_size.y) / atlas_height);
// Calculate screen positions
float screen_x = geometry.canvas_p0.x + geometry.scrolling.x + tile_pos_x;
float screen_y = geometry.canvas_p0.y + geometry.scrolling.y + tile_pos_y;
float screen_w = tilemap.tile_size.x * scale;
float screen_h = tilemap.tile_size.y * scale;
// Use higher alpha for large selections to make them more visible
uint32_t alpha_color = use_optimized_rendering
? IM_COL32(255, 255, 255, 200)
: IM_COL32(255, 255, 255, 150);
// Draw from atlas texture with optimized parameters
draw_list->AddImage(
(ImTextureID)(intptr_t)tilemap.atlas.texture(),
ImVec2(screen_x, screen_y),
ImVec2(screen_x + screen_w, screen_y + screen_h),
uv0, uv1, alpha_color);
}
}
}
i++;
}
}
} // namespace gui
} // namespace yaze

View File

@@ -0,0 +1,179 @@
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_RENDERING_H
#define YAZE_APP_GUI_CANVAS_CANVAS_RENDERING_H
#include <string>
#include <vector>
#include "app/gfx/core/bitmap.h"
#include "app/gfx/render/tilemap.h"
#include "app/gui/canvas/canvas_state.h"
#include "app/gui/canvas/canvas_utils.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Render canvas background and border
*
* Draws the canvas background rectangle (dark) and border (white)
* at the calculated geometry positions. Extracted from Canvas::DrawBackground().
*
* @param draw_list ImGui draw list for rendering
* @param geometry Canvas geometry for this frame
*/
void RenderCanvasBackground(
ImDrawList* draw_list,
const CanvasGeometry& geometry);
/**
* @brief Render canvas grid with optional highlighting
*
* Draws grid lines, hex labels, and optional tile highlighting.
* Extracted from Canvas::DrawGrid().
*
* @param draw_list ImGui draw list for rendering
* @param geometry Canvas geometry
* @param config Canvas configuration (grid settings)
* @param highlight_tile_id Tile ID to highlight (-1 = no highlight)
*/
void RenderCanvasGrid(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
int highlight_tile_id = -1);
/**
* @brief Render canvas overlay (hover and selection points)
*
* Draws hover preview points and selection rectangle points.
* Extracted from Canvas::DrawOverlay().
*
* @param draw_list ImGui draw list for rendering
* @param geometry Canvas geometry
* @param config Canvas configuration (scale)
* @param points Hover preview points
* @param selected_points Selection rectangle points
*/
void RenderCanvasOverlay(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
const ImVector<ImVec2>& points,
const ImVector<ImVec2>& selected_points);
/**
* @brief Render canvas labels on grid
*
* Draws custom text labels on canvas tiles.
* Extracted from Canvas::DrawInfoGrid().
*
* @param draw_list ImGui draw list for rendering
* @param geometry Canvas geometry
* @param config Canvas configuration
* @param labels Label arrays (one per label set)
* @param current_labels Active label set index
* @param tile_id_offset Tile ID offset for calculation
*/
void RenderCanvasLabels(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
const CanvasConfig& config,
const ImVector<ImVector<std::string>>& labels,
int current_labels,
int tile_id_offset);
/**
* @brief Render bitmap on canvas (border offset variant)
*
* Draws a bitmap with a border offset from canvas origin.
* Extracted from Canvas::DrawBitmap().
*
* @param draw_list ImGui draw list
* @param geometry Canvas geometry
* @param bitmap Bitmap to render
* @param border_offset Offset from canvas edges
* @param scale Rendering scale
*/
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
int border_offset,
float scale);
/**
* @brief Render bitmap on canvas (x/y offset variant)
*
* Draws a bitmap at specified x/y offset with optional alpha.
* Extracted from Canvas::DrawBitmap().
*
* @param draw_list ImGui draw list
* @param geometry Canvas geometry
* @param bitmap Bitmap to render
* @param x_offset X offset from canvas origin
* @param y_offset Y offset from canvas origin
* @param scale Rendering scale
* @param alpha Alpha transparency (0-255)
*/
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
int x_offset,
int y_offset,
float scale,
int alpha);
/**
* @brief Render bitmap on canvas (custom source/dest regions)
*
* Draws a bitmap with explicit source and destination rectangles.
* Extracted from Canvas::DrawBitmap().
*
* @param draw_list ImGui draw list
* @param geometry Canvas geometry
* @param bitmap Bitmap to render
* @param dest_pos Destination position (canvas-relative)
* @param dest_size Destination size
* @param src_pos Source position in bitmap
* @param src_size Source size in bitmap
*/
void RenderBitmapOnCanvas(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
gfx::Bitmap& bitmap,
ImVec2 dest_pos,
ImVec2 dest_size,
ImVec2 src_pos,
ImVec2 src_size);
/**
* @brief Render group of bitmaps from tilemap
*
* Draws multiple tiles for multi-tile selection preview.
* Extracted from Canvas::DrawBitmapGroup().
*
* @param draw_list ImGui draw list
* @param geometry Canvas geometry
* @param group Vector of tile IDs to draw
* @param tilemap Tilemap containing the tiles
* @param tile_size Size of each tile
* @param scale Rendering scale
* @param local_map_size Size of local map in pixels (default 512)
* @param total_map_size Total map size for boundary clamping
*/
void RenderBitmapGroup(
ImDrawList* draw_list,
const CanvasGeometry& geometry,
std::vector<int>& group,
gfx::Tilemap& tilemap,
int tile_size,
float scale,
int local_map_size,
ImVec2 total_map_size);
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_CANVAS_RENDERING_H

View File

@@ -0,0 +1,79 @@
#ifndef YAZE_APP_GUI_CANVAS_CANVAS_STATE_H
#define YAZE_APP_GUI_CANVAS_CANVAS_STATE_H
#include <string>
#include "app/gui/canvas/canvas_utils.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
/**
* @brief Canvas geometry calculated per-frame
*
* Represents the position, size, and scroll state of a canvas
* in both screen space and scaled space. Used by rendering
* functions to correctly position elements.
*/
struct CanvasGeometry {
ImVec2 canvas_p0; // Top-left screen position
ImVec2 canvas_p1; // Bottom-right screen position
ImVec2 canvas_sz; // Actual canvas size (unscaled)
ImVec2 scaled_size; // Size after applying global_scale
ImVec2 scrolling; // Current scroll offset
CanvasGeometry()
: canvas_p0(0, 0), canvas_p1(0, 0), canvas_sz(0, 0),
scaled_size(0, 0), scrolling(0, 0) {}
};
/**
* @brief Complete canvas state snapshot
*
* Aggregates all canvas state into a single POD for easier
* refactoring and testing. Designed to replace the scattered
* state members in the Canvas class gradually.
*
* Usage Pattern:
* - Geometry recalculated every frame in DrawBackground()
* - Configuration updated via user interaction
* - Selection state modified by interaction handlers
* - Drawing state (points, labels) updated during rendering
*/
struct CanvasState {
// Core identification
std::string canvas_id = "Canvas";
std::string context_id = "CanvasContext";
// Configuration (reference existing CanvasConfig)
CanvasConfig config;
// Selection (reference existing CanvasSelection)
CanvasSelection selection;
// Geometry (calculated per-frame)
CanvasGeometry geometry;
// Interaction state
ImVec2 mouse_pos_in_canvas = ImVec2(0, 0);
ImVec2 drawn_tile_pos = ImVec2(-1, -1);
bool is_hovered = false;
// Drawing state
ImVector<ImVec2> points; // Hover preview points
ImVector<ImVector<std::string>> labels;
int current_labels = 0;
int highlight_tile_id = -1;
CanvasState() = default;
// Convenience constructor with ID
explicit CanvasState(const std::string& id)
: canvas_id(id), context_id(id + "Context") {}
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_CANVAS_CANVAS_STATE_H

View File

@@ -29,9 +29,11 @@ set(CANVAS_SRC
app/gui/canvas/canvas.cc app/gui/canvas/canvas.cc
app/gui/canvas/canvas_automation_api.cc app/gui/canvas/canvas_automation_api.cc
app/gui/canvas/canvas_context_menu.cc app/gui/canvas/canvas_context_menu.cc
app/gui/canvas/canvas_geometry.cc
app/gui/canvas/canvas_interaction_handler.cc app/gui/canvas/canvas_interaction_handler.cc
app/gui/canvas/canvas_modals.cc app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_performance_integration.cc app/gui/canvas/canvas_performance_integration.cc
app/gui/canvas/canvas_rendering.cc
app/gui/canvas/canvas_usage_tracker.cc app/gui/canvas/canvas_usage_tracker.cc
app/gui/canvas/canvas_utils.cc app/gui/canvas/canvas_utils.cc
) )