feat: Grid camera defaults to tile (0,0) at top-left + center_camera() method (#169)
Changes: - Default Grid center now positions tile (0,0) at widget's top-left corner - Added center_camera() method to center grid's middle tile at view center - Added center_camera((tile_x, tile_y)) to position tile at top-left of widget - Uses NaN as sentinel to detect if user provided center values in kwargs - Animation-compatible: center_camera() just sets center property, no special state Behavior: - center_camera() → grid's center tile at view center - center_camera((0, 0)) → tile (0,0) at top-left corner - center_camera((5, 10)) → tile (5,10) at top-left corner Before: Grid(size=(320,240)) showed 3/4 of content off-screen (center=0,0) After: Grid(size=(320,240)) shows tile (0,0) at top-left (center=160,120) Closes #169 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
05f28ef7cd
commit
f62362032e
2 changed files with 106 additions and 3 deletions
103
src/UIGrid.cpp
103
src/UIGrid.cpp
|
|
@ -6,8 +6,9 @@
|
|||
#include "Profiler.h"
|
||||
#include "PyFOV.h"
|
||||
#include <algorithm>
|
||||
#include <cmath> // #142 - for std::floor
|
||||
#include <cmath> // #142 - for std::floor, std::isnan
|
||||
#include <cstring> // #150 - for strcmp
|
||||
#include <limits> // #169 - for std::numeric_limits
|
||||
// UIDrawable methods now in UIBase.h
|
||||
|
||||
UIGrid::UIGrid()
|
||||
|
|
@ -735,7 +736,9 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
PyObject* fill_color = nullptr;
|
||||
PyObject* click_handler = nullptr;
|
||||
PyObject* layers_obj = nullptr; // #150 - layers dict
|
||||
float center_x = 0.0f, center_y = 0.0f;
|
||||
// #169 - Use NaN as sentinel to detect if user provided center values
|
||||
float center_x = std::numeric_limits<float>::quiet_NaN();
|
||||
float center_y = std::numeric_limits<float>::quiet_NaN();
|
||||
float zoom = 1.0f;
|
||||
// perspective is now handled via properties, not init args
|
||||
int visible = 1;
|
||||
|
|
@ -862,9 +865,19 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
sf::Vector2f(x, y), sf::Vector2f(w, h));
|
||||
|
||||
// Set additional properties
|
||||
self->data->zoom = zoom; // Set zoom first, needed for default center calculation
|
||||
|
||||
// #169 - Calculate default center if not provided by user
|
||||
// Default: tile (0,0) at top-left of widget
|
||||
if (std::isnan(center_x)) {
|
||||
// Center = half widget size (in pixels), so tile 0,0 appears at top-left
|
||||
center_x = w / (2.0f * zoom);
|
||||
}
|
||||
if (std::isnan(center_y)) {
|
||||
center_y = h / (2.0f * zoom);
|
||||
}
|
||||
self->data->center_x = center_x;
|
||||
self->data->center_y = center_y;
|
||||
self->data->zoom = zoom;
|
||||
// perspective is now handled by perspective_entity and perspective_enabled
|
||||
// self->data->perspective = perspective;
|
||||
self->data->visible = visible;
|
||||
|
|
@ -1730,6 +1743,72 @@ PyObject* UIGrid::py_entities_in_radius(PyUIGridObject* self, PyObject* args, Py
|
|||
return result;
|
||||
}
|
||||
|
||||
// #169 - center_camera implementations
|
||||
void UIGrid::center_camera() {
|
||||
// Center on grid's middle tile
|
||||
int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
center_x = (grid_x / 2.0f) * cell_width;
|
||||
center_y = (grid_y / 2.0f) * cell_height;
|
||||
markDirty(); // #144 - View change affects content
|
||||
}
|
||||
|
||||
void UIGrid::center_camera(float tile_x, float tile_y) {
|
||||
// Position specified tile at top-left of widget
|
||||
int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
// To put tile (tx, ty) at top-left: center = tile_pos + half_viewport
|
||||
float half_viewport_x = box.getSize().x / zoom / 2.0f;
|
||||
float half_viewport_y = box.getSize().y / zoom / 2.0f;
|
||||
center_x = tile_x * cell_width + half_viewport_x;
|
||||
center_y = tile_y * cell_height + half_viewport_y;
|
||||
markDirty(); // #144 - View change affects content
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_center_camera(PyUIGridObject* self, PyObject* args) {
|
||||
PyObject* pos_arg = nullptr;
|
||||
|
||||
// Parse optional positional argument (tuple of tile coordinates)
|
||||
if (!PyArg_ParseTuple(args, "|O", &pos_arg)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pos_arg == nullptr || pos_arg == Py_None) {
|
||||
// No args: center on grid's middle tile
|
||||
self->data->center_camera();
|
||||
} else if (PyTuple_Check(pos_arg) && PyTuple_Size(pos_arg) == 2) {
|
||||
// Tuple provided: center on (tile_x, tile_y)
|
||||
PyObject* x_obj = PyTuple_GetItem(pos_arg, 0);
|
||||
PyObject* y_obj = PyTuple_GetItem(pos_arg, 1);
|
||||
|
||||
float tile_x, tile_y;
|
||||
if (PyFloat_Check(x_obj)) {
|
||||
tile_x = PyFloat_AsDouble(x_obj);
|
||||
} else if (PyLong_Check(x_obj)) {
|
||||
tile_x = (float)PyLong_AsLong(x_obj);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "tile coordinates must be numeric");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (PyFloat_Check(y_obj)) {
|
||||
tile_y = PyFloat_AsDouble(y_obj);
|
||||
} else if (PyLong_Check(y_obj)) {
|
||||
tile_y = (float)PyLong_AsLong(y_obj);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "tile coordinates must be numeric");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
self->data->center_camera(tile_x, tile_y);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "center_camera() takes an optional tuple (tile_x, tile_y)");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyMethodDef UIGrid::methods[] = {
|
||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
||||
{"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
|
|
@ -1818,6 +1897,15 @@ PyMethodDef UIGrid::methods[] = {
|
|||
" radius: Search radius\n\n"
|
||||
"Returns:\n"
|
||||
" List of Entity objects within the radius."},
|
||||
{"center_camera", (PyCFunction)UIGrid::py_center_camera, METH_VARARGS,
|
||||
"center_camera(pos: tuple = None) -> None\n\n"
|
||||
"Center the camera on a tile coordinate.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Optional (tile_x, tile_y) tuple. If None, centers on grid's middle tile.\n\n"
|
||||
"Example:\n"
|
||||
" grid.center_camera() # Center on middle of grid\n"
|
||||
" grid.center_camera((5, 10)) # Center on tile (5, 10)\n"
|
||||
" grid.center_camera((0, 0)) # Center on tile (0, 0)"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -1929,6 +2017,15 @@ PyMethodDef UIGrid_all_methods[] = {
|
|||
" radius: Search radius\n\n"
|
||||
"Returns:\n"
|
||||
" List of Entity objects within the radius."},
|
||||
{"center_camera", (PyCFunction)UIGrid::py_center_camera, METH_VARARGS,
|
||||
"center_camera(pos: tuple = None) -> None\n\n"
|
||||
"Center the camera on a tile coordinate.\n\n"
|
||||
"Args:\n"
|
||||
" pos: Optional (tile_x, tile_y) tuple. If None, centers on grid's middle tile.\n\n"
|
||||
"Example:\n"
|
||||
" grid.center_camera() # Center on middle of grid\n"
|
||||
" grid.center_camera((5, 10)) # Center on tile (5, 10)\n"
|
||||
" grid.center_camera((0, 0)) # Center on tile (0, 0)"},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -170,6 +170,12 @@ public:
|
|||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args);
|
||||
static PyObject* py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115
|
||||
static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); // #169
|
||||
|
||||
// #169 - Camera positioning
|
||||
void center_camera(); // Center on grid's middle tile
|
||||
void center_camera(float tile_x, float tile_y); // Center on specific tile
|
||||
|
||||
static PyMethodDef methods[];
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* get_entities(PyUIGridObject* self, void* closure);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue