From 214037892efede405f96e8d60d14ad8c97258c0e Mon Sep 17 00:00:00 2001 From: John McCardle Date: Wed, 28 Jan 2026 17:35:34 -0500 Subject: [PATCH] Fix UIGrid RenderTexture sizing - use game resolution instead of hard-coded 1080p - Add ensureRenderTextureSize() helper that creates/resizes renderTexture to match game resolution - Add renderTextureSize tracking member to detect when resize is needed - Call helper in constructor and at start of render() to handle resolution changes - Clamp maximum size to 4096x4096 (SFML texture limits) - Only recreate texture when size actually changes (performance optimization) closes #228 Co-Authored-By: Claude Opus 4.5 --- src/UIGrid.cpp | 112 +++++++++++++++++++++++++++++++------------------ src/UIGrid.h | 19 ++++++--- 2 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 4612e85..9af5354 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -40,8 +40,9 @@ UIGrid::UIGrid() box.setPosition(position); // Sync box position box.setFillColor(sf::Color(0, 0, 0, 0)); - // Initialize render texture (small default size) + // #228 - Initialize render texture to game resolution (small default until game init) renderTexture.create(1, 1); + renderTextureSize = {1, 1}; // Initialize output sprite output.setTextureRect(sf::IntRect(0, 0, 0, 0)); @@ -76,8 +77,8 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _x box.setPosition(position); // Sync box position box.setFillColor(sf::Color(0,0,0,0)); - // create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered - renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors + // #228 - create renderTexture sized to game resolution (dynamically resized as needed) + ensureRenderTextureSize(); // Only initialize sprite if texture is available if (ptex) { @@ -145,6 +146,9 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) // Check visibility if (!visible) return; + // #228 - Ensure renderTexture matches current game resolution + ensureRenderTextureSize(); + // TODO: Apply opacity to output sprite // Get cell dimensions - use texture if available, otherwise defaults @@ -464,6 +468,26 @@ UIGrid::~UIGrid() } } +void UIGrid::ensureRenderTextureSize() +{ + // Get game resolution (or use sensible defaults during early init) + sf::Vector2u resolution{1920, 1080}; + if (Resources::game) { + resolution = Resources::game->getGameResolution(); + } + + // Clamp to reasonable maximum (SFML texture size limits) + unsigned int required_w = std::min(resolution.x, 4096u); + unsigned int required_h = std::min(resolution.y, 4096u); + + // Only recreate if size changed + if (renderTextureSize.x != required_w || renderTextureSize.y != required_h) { + renderTexture.create(required_w, required_h); + renderTextureSize = {required_w, required_h}; + output.setTexture(renderTexture.getTexture()); + } +} + PyObjectsEnum UIGrid::derived_type() { return PyObjectsEnum::UIGRID; @@ -2339,11 +2363,12 @@ PyObject* UIGrid::get_on_cell_enter(PyUIGridObject* self, void* closure) { Py_RETURN_NONE; } +// #230 - Cell hover callbacks now use PyCellHoverCallable (position-only) int UIGrid::set_on_cell_enter(PyUIGridObject* self, PyObject* value, void* closure) { if (value == Py_None) { self->data->on_cell_enter_callable.reset(); } else { - self->data->on_cell_enter_callable = std::make_unique(value); + self->data->on_cell_enter_callable = std::make_unique(value); } return 0; } @@ -2357,11 +2382,12 @@ PyObject* UIGrid::get_on_cell_exit(PyUIGridObject* self, void* closure) { Py_RETURN_NONE; } +// #230 - Cell hover callbacks now use PyCellHoverCallable (position-only) int UIGrid::set_on_cell_exit(PyUIGridObject* self, PyObject* value, void* closure) { if (value == Py_None) { self->data->on_cell_exit_callable.reset(); } else { - self->data->on_cell_exit_callable = std::make_unique(value); + self->data->on_cell_exit_callable = std::make_unique(value); } return 0; } @@ -2553,6 +2579,26 @@ static PyObject* createCellCallbackArgs(sf::Vector2i cell, const std::string& bu return args; } +// #230 - Helper to create cell hover callback arguments: (Vector) only +static PyObject* createCellHoverArgs(sf::Vector2i cell) { + // Create Vector object for cell position + PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); + if (!vector_type) { + PyErr_Print(); + return nullptr; + } + PyObject* cell_pos = PyObject_CallFunction(vector_type, "ii", cell.x, cell.y); + Py_DECREF(vector_type); + if (!cell_pos) { + PyErr_Print(); + return nullptr; + } + + PyObject* args = Py_BuildValue("(O)", cell_pos); + Py_DECREF(cell_pos); + return args; +} + // Fire cell click callback with full signature (cell_pos, button, action) bool UIGrid::fireCellClick(sf::Vector2i cell, const std::string& button, const std::string& action) { // Try property-assigned callback first @@ -2604,23 +2650,12 @@ bool UIGrid::fireCellClick(sf::Vector2i cell, const std::string& button, const s return false; } -// Fire cell enter callback with full signature (cell_pos, button, action) -bool UIGrid::fireCellEnter(sf::Vector2i cell, const std::string& button, const std::string& action) { - // Try property-assigned callback first +// #230 - Fire cell enter callback with position-only signature (cell_pos) +bool UIGrid::fireCellEnter(sf::Vector2i cell) { + // Try property-assigned callback first (now PyCellHoverCallable) if (on_cell_enter_callable && !on_cell_enter_callable->isNone()) { - PyObject* args = createCellCallbackArgs(cell, button, action); - if (args) { - PyObject* result = PyObject_CallObject(on_cell_enter_callable->borrow(), args); - Py_DECREF(args); - if (!result) { - std::cerr << "Cell enter callback raised an exception:" << std::endl; - PyErr_Print(); - PyErr_Clear(); - } else { - Py_DECREF(result); - } - return true; - } + on_cell_enter_callable->call(cell); + return true; } // Try Python subclass method @@ -2631,7 +2666,8 @@ bool UIGrid::fireCellEnter(sf::Vector2i cell, const std::string& button, const s if (cell_callback_cache.has_on_cell_enter) { PyObject* method = PyObject_GetAttrString(pyObj, "on_cell_enter"); if (method && PyCallable_Check(method)) { - PyObject* args = createCellCallbackArgs(cell, button, action); + // #230: Cell hover takes only (cell_pos) + PyObject* args = createCellHoverArgs(cell); if (args) { PyObject* result = PyObject_CallObject(method, args); Py_DECREF(args); @@ -2655,23 +2691,12 @@ bool UIGrid::fireCellEnter(sf::Vector2i cell, const std::string& button, const s return false; } -// Fire cell exit callback with full signature (cell_pos, button, action) -bool UIGrid::fireCellExit(sf::Vector2i cell, const std::string& button, const std::string& action) { - // Try property-assigned callback first +// #230 - Fire cell exit callback with position-only signature (cell_pos) +bool UIGrid::fireCellExit(sf::Vector2i cell) { + // Try property-assigned callback first (now PyCellHoverCallable) if (on_cell_exit_callable && !on_cell_exit_callable->isNone()) { - PyObject* args = createCellCallbackArgs(cell, button, action); - if (args) { - PyObject* result = PyObject_CallObject(on_cell_exit_callable->borrow(), args); - Py_DECREF(args); - if (!result) { - std::cerr << "Cell exit callback raised an exception:" << std::endl; - PyErr_Print(); - PyErr_Clear(); - } else { - Py_DECREF(result); - } - return true; - } + on_cell_exit_callable->call(cell); + return true; } // Try Python subclass method @@ -2682,7 +2707,8 @@ bool UIGrid::fireCellExit(sf::Vector2i cell, const std::string& button, const st if (cell_callback_cache.has_on_cell_exit) { PyObject* method = PyObject_GetAttrString(pyObj, "on_cell_exit"); if (method && PyCallable_Check(method)) { - PyObject* args = createCellCallbackArgs(cell, button, action); + // #230: Cell hover takes only (cell_pos) + PyObject* args = createCellHoverArgs(cell); if (args) { PyObject* result = PyObject_CallObject(method, args); Py_DECREF(args); @@ -2707,19 +2733,23 @@ bool UIGrid::fireCellExit(sf::Vector2i cell, const std::string& button, const st } // #142 - Update cell hover state and fire callbacks +// #230 - Cell hover callbacks now take only (cell_pos), no button/action void UIGrid::updateCellHover(sf::Vector2f mousepos, const std::string& button, const std::string& action) { + (void)button; // #230 - No longer used for hover callbacks + (void)action; // #230 - No longer used for hover callbacks + auto new_cell = screenToCell(mousepos); // Check if cell changed if (new_cell != hovered_cell) { // Fire exit callback for old cell if (hovered_cell.has_value()) { - fireCellExit(hovered_cell.value(), button, action); + fireCellExit(hovered_cell.value()); } // Fire enter callback for new cell if (new_cell.has_value()) { - fireCellEnter(new_cell.value(), button, action); + fireCellEnter(new_cell.value()); } hovered_cell = new_cell; diff --git a/src/UIGrid.h b/src/UIGrid.h index 68efb96..7b8191c 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -84,6 +84,10 @@ public: std::shared_ptr getTexture(); sf::Sprite sprite, output; sf::RenderTexture renderTexture; + sf::Vector2u renderTextureSize{0, 0}; // Track current allocation for resize detection + + // Helper to ensure renderTexture matches game resolution + void ensureRenderTextureSize(); // Intermediate texture for camera_rotation (larger than viewport to hold rotated content) sf::RenderTexture rotationTexture; @@ -131,9 +135,10 @@ public: TCOD_fov_algorithm_t fov_algorithm; // Default FOV algorithm (from mcrfpy.default_fov) int fov_radius; // Default FOV radius - // #142 - Grid cell mouse events - std::unique_ptr on_cell_enter_callable; - std::unique_ptr on_cell_exit_callable; + // #142, #230 - Grid cell mouse events + // Cell hover callbacks take only (cell_pos); cell click still takes (cell_pos, button, action) + std::unique_ptr on_cell_enter_callable; + std::unique_ptr on_cell_exit_callable; std::unique_ptr on_cell_click_callable; std::optional hovered_cell; // Currently hovered cell or nullopt std::optional last_clicked_cell; // Cell clicked during click_at @@ -158,11 +163,13 @@ public: // Now takes button/action for consistent callback signatures void updateCellHover(sf::Vector2f mousepos, const std::string& button, const std::string& action); - // Fire cell callbacks with full signature (cell_pos, button, action) + // Fire cell callbacks + // #230: Cell hover callbacks (enter/exit) now take only (cell_pos) + // Cell click still takes (cell_pos, button, action) // Returns true if a callback was fired bool fireCellClick(sf::Vector2i cell, const std::string& button, const std::string& action); - bool fireCellEnter(sf::Vector2i cell, const std::string& button, const std::string& action); - bool fireCellExit(sf::Vector2i cell, const std::string& button, const std::string& action); + bool fireCellEnter(sf::Vector2i cell); + bool fireCellExit(sf::Vector2i cell); // Refresh cell callback cache for subclass method support void refreshCellCallbackCache(PyObject* pyObj);