Squashed commit of the following: [interpreter_mode]
closes #63 closes #69 closes #59 closes #47 closes #2 closes #3 closes #33 closes #27 closes #73 closes #74 closes #78 I'd like to thank Claude Code for ~200-250M total tokens and 5-7M output tokens 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> commit9bd1561bfcAuthor: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 11:20:07 2025 -0400 Alpha 0.1 release - Move RenderTexture (#6) out of alpha requirements, I don't need it that badly - alpha blockers resolved: * Animation system (#59) * Z-order rendering (#63) * Python Sequence Protocol (#69) * New README (#47) * Removed deprecated methods (#2, #3) 🍾 McRogueFace 0.1.0 commit43321487ebAuthor: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 10:36:09 2025 -0400 Issue #63 (z-order rendering) complete - Archive z-order test files commit90c318104bAuthor: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 10:34:06 2025 -0400 Fix Issue #63: Implement z-order rendering with dirty flag optimization - Add dirty flags to PyScene and UIFrame to track when sorting is needed - Implement lazy sorting - only sort when z_index changes or elements are added/removed - Make Frame children respect z_index (previously rendered in insertion order only) - Update UIDrawable::set_int to notify when z_index changes - Mark collections dirty on append, remove, setitem, and slice operations - Remove per-frame vector copy in PyScene::render for better performance commite4482e7189Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 01:58:03 2025 -0400 Implement complete Python Sequence Protocol for collections (closes #69) Major implementation of the full sequence protocol for both UICollection and UIEntityCollection, making them behave like proper Python sequences. Core Features Implemented: - __setitem__ (collection[i] = value) with type validation - __delitem__ (del collection[i]) with proper cleanup - __contains__ (item in collection) by C++ pointer comparison - __add__ (collection + other) returns Python list - __iadd__ (collection += other) with full validation before modification - Negative indexing support throughout - Complete slice support (getting, setting, deletion) - Extended slices with step \!= 1 - index() and count() methods - Type safety enforced for all operations UICollection specifics: - Accepts Frame, Caption, Sprite, and Grid objects only - Preserves z_index when replacing items - Auto-assigns z_index on append (existing behavior maintained) UIEntityCollection specifics: - Accepts Entity objects only - Manages grid references on add/remove/replace - Uses std::list iteration with std::advance() Also includes: - Default value support for constructors: - Caption accepts None for font (uses default_font) - Grid accepts None for texture (uses default_texture) - Sprite accepts None for texture (uses default_texture) - Entity accepts None for texture (uses default_texture) This completes Issue #69, removing it as an Alpha Blocker. commit70cf44f8f0Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 00:56:42 2025 -0400 Implement comprehensive animation system (closes #59) - Add Animation class with 30+ easing functions (linear, ease in/out, quad, cubic, elastic, bounce, etc.) - Add property system to all UI classes for animation support: - UIFrame: position, size, colors (including individual r/g/b/a components) - UICaption: position, size, text, colors - UISprite: position, scale, sprite_number (with sequence support) - UIGrid: position, size, camera center, zoom - UIEntity: position, sprite properties - Create AnimationManager singleton for frame-based updates - Add Python bindings through PyAnimation wrapper - Support for delta animations (relative values) - Fix segfault when running scripts directly (mcrf_module initialization) - Fix headless/windowed mode behavior to respect --headless flag - Animations run purely in C++ without Python callbacks per frame All UI properties are now animatable with smooth interpolation and professional easing curves. commit05bddae511Author: John McCardle <mccardle.john@gmail.com> Date: Fri Jul 4 06:59:02 2025 -0400 Update comprehensive documentation for Alpha release (Issue #47) - Completely rewrote README.md to reflect current features - Updated GitHub Pages documentation site with: - Modern landing page highlighting Crypt of Sokoban - Comprehensive API reference (2700+ lines) with exhaustive examples - Updated getting-started guide with installation and first game tutorial - 8 detailed tutorials covering all major game systems - Quick reference cheat sheet for common operations - Generated documentation screenshots showing UI elements - Fixed deprecated API references and added new features - Added automation API documentation - Included Python 3.12 requirement and platform-specific instructions Note: Text rendering in headless mode has limitations for screenshots commitaf6a5e090bAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:43:58 2025 -0400 Update ROADMAP.md to reflect completion of Issues #2 and #3 - Marked both issues as completed with the removal of deprecated action system - Updated open issue count from ~50 to ~48 - These were both Alpha blockers, bringing us closer to release commit281800cd23Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:43:22 2025 -0400 Remove deprecated registerPyAction/registerInputAction system (closes #2, closes #3) This is our largest net-negative commit yet\! Removed the entire deprecated action registration system that provided unnecessary two-step indirection: keyboard → action string → Python callback Removed components: - McRFPy_API::_registerPyAction() and _registerInputAction() methods - McRFPy_API::callbacks map for storing Python callables - McRFPy_API::doAction() method for executing callbacks - ACTIONPY macro from Scene.h for detecting "_py" suffixed actions - Scene::registerActionInjected() and unregisterActionInjected() methods - tests/api_registerPyAction_issue2_test.py (tested deprecated functionality) The game now exclusively uses keypressScene() for keyboard input handling, which is simpler and more direct. Also commented out the unused _camFollow function that referenced non-existent do_camfollow variable. commitcc8a7d20e8Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:13:59 2025 -0400 Clean up temporary test files commitff83fd8bb1Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:13:46 2025 -0400 Update ROADMAP.md to reflect massive progress today - Fixed 12+ critical bugs in a single session - Implemented 3 missing features (Entity.index, EntityCollection.extend, sprite validation) - Updated Phase 1 progress showing 11 of 12 items complete - Added detailed summary of today's achievements with issue numbers - Emphasized test-driven development approach used throughout commitdae400031fAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:12:29 2025 -0400 Remove deprecated player_input and turn-based functions for Issue #3 Removed the commented-out player_input(), computerTurn(), and playerTurn() functions that were part of the old turn-based system. These are no longer needed as input is now handled through Scene callbacks. Partial fix for #3 commitcb0130b46eAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:09:06 2025 -0400 Implement sprite index validation for Issue #33 Added validation to prevent setting sprite indices outside the valid range for a texture. The implementation: - Adds getSpriteCount() method to PyTexture to expose total sprites - Validates sprite_number setter to ensure index is within bounds - Provides clear error messages showing valid range - Works for both Sprite and Entity objects closes #33 commit1e7f5e9e7eAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:05:47 2025 -0400 Implement EntityCollection.extend() method for Issue #27 Added extend() method to EntityCollection that accepts any iterable of Entity objects and adds them all to the collection. The method: - Accepts lists, tuples, generators, or any iterable - Validates all items are Entity objects - Sets the grid association for each added entity - Properly handles errors and empty iterables closes #27 commit923350137dAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 21:02:14 2025 -0400 Implement Entity.index() method for Issue #73 Added index() method to Entity class that returns the entity's position in its parent grid's entity collection. This enables proper entity removal patterns using entity.index(). commit6134869371Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 20:41:03 2025 -0400 Add validation to keypressScene() for non-callable arguments Added PyCallable_Check validation to ensure keypressScene() only accepts callable objects. Now properly raises TypeError with a clear error message when passed non-callable arguments like strings, numbers, None, or dicts. commit4715356b5eAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 20:31:36 2025 -0400 Fix Sprite texture setter 'error return without exception set' Implemented the missing UISprite::set_texture method to properly: - Validate the input is a Texture instance - Update the sprite's texture using setTexture() - Return appropriate error messages for invalid inputs The setter now works correctly and no longer returns -1 without setting an exception. commit6dd1cec600Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 20:27:32 2025 -0400 Fix Entity property setters and PyVector implementation Fixed the 'new style getargs format' error in Entity property setters by: - Implementing PyObject_to_sfVector2f/2i using PyVector::from_arg - Adding proper error checking in Entity::set_position - Implementing PyVector get_member/set_member for x/y properties - Fixing PyVector::from_arg to handle non-tuple arguments correctly Now Entity.pos and Entity.sprite_number setters work correctly with proper type validation. commitf82b861bcdAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 19:48:33 2025 -0400 Fix Issue #74: Add missing Grid.grid_y property Added individual grid_x and grid_y getter properties to the Grid class to complement the existing grid_size property. This allows direct access to grid dimensions and fixes error messages that referenced these properties before they existed. closes #74 commit59e6f8d53dAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 19:42:32 2025 -0400 Fix Issue #78: Middle mouse click no longer sends 'C' keyboard event The bug was caused by accessing event.key.code on a mouse event without checking the event type first. Since SFML uses a union for events, this read garbage data. The middle mouse button value (2) coincidentally matched the keyboard 'C' value (2), causing the spurious keyboard event. Fixed by adding event type check before accessing key-specific fields. Only keyboard events (KeyPressed/KeyReleased) now trigger key callbacks. Test added to verify middle clicks no longer generate keyboard events. Closes #78 commit1c71d8d4f7Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 19:36:15 2025 -0400 Fix Grid to support None/null texture and fix error message bug - Allow Grid to be created with None as texture parameter - Use default cell dimensions (16x16) when no texture provided - Skip sprite rendering when texture is null, but still render colors - Fix issue #77: Corrected copy/paste error in Grid.at() error messages - Grid now functional for color-only rendering and entity positioning Test created to verify Grid works without texture, showing colored cells. Closes #77 commit18cfe93a44Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 19:25:49 2025 -0400 Fix --exec interactive prompt bug and create comprehensive test suite Major fixes: - Fixed --exec entering Python REPL instead of game loop - Resolved screenshot transparency issue (requires timer callbacks) - Added debug output to trace Python initialization Test suite created: - 13 comprehensive tests covering all Python-exposed methods - Tests use timer callback pattern for proper game loop interaction - Discovered multiple critical bugs and missing features Critical bugs found: - Grid class segfaults on instantiation (blocks all Grid functionality) - Issue #78 confirmed: Middle mouse click sends 'C' keyboard event - Entity property setters have argument parsing errors - Sprite texture setter returns improper error - keypressScene() segfaults on non-callable arguments Documentation updates: - Updated CLAUDE.md with testing guidelines and TDD practices - Created test reports documenting all findings - Updated ROADMAP.md with test results and new priorities The Grid segfault is now the highest priority as it blocks all Grid-based functionality. commit9ad0b6850dAuthor: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 15:55:24 2025 -0400 Update ROADMAP.md to reflect Python interpreter and automation API progress - Mark #32 (Python interpreter behavior) as 90% complete - All major Python flags implemented: -h, -V, -c, -m, -i - Script execution with proper sys.argv handling works - Only stdin (-) support missing - Note that new automation API enables: - Automated UI testing capabilities - Demo recording and playback - Accessibility testing support - Flag issues #53 and #45 as potentially aided by automation API commit7ec4698653Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 14:57:59 2025 -0400 Update ROADMAP.md to remove closed issues - Remove #72 (iterator improvements - closed) - Remove #51 (UIEntity derive from UIDrawable - closed) - Update issue counts: 64 open issues from original 78 - Update dependencies and references to reflect closed issues - Clarify that core iterators are complete, only grid points remain commit68c1a016b0Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 14:27:01 2025 -0400 Implement --exec flag and PyAutoGUI-compatible automation API - Add --exec flag to execute multiple scripts before main program - Scripts are executed in order and share Python interpreter state - Implement full PyAutoGUI-compatible automation API in McRFPy_Automation - Add screenshot, mouse control, keyboard input capabilities - Fix Python initialization issues when multiple scripts are loaded - Update CommandLineParser to handle --exec with proper sys.argv management - Add comprehensive examples and documentation This enables automation testing by allowing test scripts to run alongside games using the same Python environment. The automation API provides event injection into the SFML render loop for UI testing. Closes #32 partially (Python interpreter emulation) References automation testing requirements commit763fa201f0Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 10:43:17 2025 -0400 Python command emulation commita44b8c93e9Author: John McCardle <mccardle.john@gmail.com> Date: Thu Jul 3 09:42:46 2025 -0400 Prep: Cleanup for interpreter mode
This commit is contained in:
parent
167636ce8c
commit
d03182d347
81 changed files with 8608 additions and 400 deletions
787
src/UIGrid.cpp
787
src/UIGrid.cpp
|
|
@ -1,14 +1,21 @@
|
|||
#include "UIGrid.h"
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include <algorithm>
|
||||
|
||||
UIGrid::UIGrid() {}
|
||||
|
||||
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
|
||||
: grid_x(gx), grid_y(gy),
|
||||
zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height),
|
||||
zoom(1.0f),
|
||||
ptex(_ptex), points(gx * gy)
|
||||
{
|
||||
// Use texture dimensions if available, otherwise use defaults
|
||||
int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = _ptex ? _ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
|
||||
center_x = (gx/2) * cell_width;
|
||||
center_y = (gy/2) * cell_height;
|
||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||
|
||||
box.setSize(_wh);
|
||||
|
|
@ -18,7 +25,10 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
|||
// 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
|
||||
|
||||
sprite = ptex->sprite(0);
|
||||
// Only initialize sprite if texture is available
|
||||
if (ptex) {
|
||||
sprite = ptex->sprite(0);
|
||||
}
|
||||
|
||||
output.setTextureRect(
|
||||
sf::IntRect(0, 0,
|
||||
|
|
@ -40,12 +50,17 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
sf::IntRect(0, 0,
|
||||
box.getSize().x, box.getSize().y));
|
||||
renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field
|
||||
|
||||
// Get cell dimensions - use texture if available, otherwise defaults
|
||||
int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT;
|
||||
|
||||
// sprites that are visible according to zoom, center_x, center_y, and box width
|
||||
float center_x_sq = center_x / ptex->sprite_width;
|
||||
float center_y_sq = center_y / ptex->sprite_height;
|
||||
float center_x_sq = center_x / cell_width;
|
||||
float center_y_sq = center_y / cell_height;
|
||||
|
||||
float width_sq = box.getSize().x / (ptex->sprite_width * zoom);
|
||||
float height_sq = box.getSize().y / (ptex->sprite_height * zoom);
|
||||
float width_sq = box.getSize().x / (cell_width * zoom);
|
||||
float height_sq = box.getSize().y / (cell_height * zoom);
|
||||
float left_edge = center_x_sq - (width_sq / 2.0);
|
||||
float top_edge = center_y_sq - (height_sq / 2.0);
|
||||
|
||||
|
|
@ -54,7 +69,7 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
|
||||
//sprite.setScale(sf::Vector2f(zoom, zoom));
|
||||
sf::RectangleShape r; // for colors and overlays
|
||||
r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom));
|
||||
r.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom));
|
||||
r.setOutlineThickness(0);
|
||||
|
||||
int x_limit = left_edge + width_sq + 2;
|
||||
|
|
@ -74,8 +89,8 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
y+=1)
|
||||
{
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x*ptex->sprite_width - left_spritepixels) * zoom,
|
||||
(y*ptex->sprite_height - top_spritepixels) * zoom );
|
||||
(x*cell_width - left_spritepixels) * zoom,
|
||||
(y*cell_height - top_spritepixels) * zoom );
|
||||
|
||||
auto gridpoint = at(std::floor(x), std::floor(y));
|
||||
|
||||
|
|
@ -85,10 +100,10 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
r.setFillColor(gridpoint.color);
|
||||
renderTexture.draw(r);
|
||||
|
||||
// tilesprite
|
||||
// tilesprite - only draw if texture is available
|
||||
// if discovered but not visible, set opacity to 90%
|
||||
// if not discovered... just don't draw it?
|
||||
if (gridpoint.tilesprite != -1) {
|
||||
if (ptex && gridpoint.tilesprite != -1) {
|
||||
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
|
||||
renderTexture.draw(sprite);
|
||||
}
|
||||
|
|
@ -104,8 +119,8 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
//drawent.setScale(zoom, zoom);
|
||||
drawent.setScale(sf::Vector2f(zoom, zoom));
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(e->position.x*ptex->sprite_width - left_spritepixels) * zoom,
|
||||
(e->position.y*ptex->sprite_height - top_spritepixels) * zoom );
|
||||
(e->position.x*cell_width - left_spritepixels) * zoom,
|
||||
(e->position.y*cell_height - top_spritepixels) * zoom );
|
||||
//drawent.setPosition(pixel_pos);
|
||||
//renderTexture.draw(drawent);
|
||||
drawent.render(pixel_pos, renderTexture);
|
||||
|
|
@ -204,46 +219,92 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
|||
|
||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
int grid_x, grid_y;
|
||||
PyObject* textureObj;
|
||||
PyObject* textureObj = Py_None;
|
||||
//float box_x, box_y, box_w, box_h;
|
||||
PyObject* pos, *size;
|
||||
PyObject* pos = NULL;
|
||||
PyObject* size = NULL;
|
||||
|
||||
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
|
||||
if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
||||
if (!PyArg_ParseTuple(args, "ii|OOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
|
||||
return -1; // If parsing fails, return an error
|
||||
}
|
||||
|
||||
PyVectorObject* pos_result = PyVector::from_arg(pos);
|
||||
if (!pos_result)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
return -1;
|
||||
// Default position and size if not provided
|
||||
PyVectorObject* pos_result = NULL;
|
||||
PyVectorObject* size_result = NULL;
|
||||
|
||||
if (pos) {
|
||||
pos_result = PyVector::from_arg(pos);
|
||||
if (!pos_result)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Default position (0, 0)
|
||||
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (vector_class) {
|
||||
PyObject* pos_obj = PyObject_CallFunction(vector_class, "ff", 0.0f, 0.0f);
|
||||
Py_DECREF(vector_class);
|
||||
if (pos_obj) {
|
||||
pos_result = (PyVectorObject*)pos_obj;
|
||||
}
|
||||
}
|
||||
if (!pos_result) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to create default position vector");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PyVectorObject* size_result = PyVector::from_arg(size);
|
||||
if (!size_result)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
return -1;
|
||||
if (size) {
|
||||
size_result = PyVector::from_arg(size);
|
||||
if (!size_result)
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "size must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Default size based on grid dimensions
|
||||
float default_w = grid_x * 16.0f; // Assuming 16 pixel tiles
|
||||
float default_h = grid_y * 16.0f;
|
||||
PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||
if (vector_class) {
|
||||
PyObject* size_obj = PyObject_CallFunction(vector_class, "ff", default_w, default_h);
|
||||
Py_DECREF(vector_class);
|
||||
if (size_obj) {
|
||||
size_result = (PyVectorObject*)size_obj;
|
||||
}
|
||||
}
|
||||
if (!size_result) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to create default size vector");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert PyObject texture to IndexTexture*
|
||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
||||
|
||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
|
||||
return -1;
|
||||
}
|
||||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||
// TODO (7DRL day 2, item 4.) use shared_ptr / PyTextureObject on UIGrid
|
||||
//IndexTexture* texture = pyTexture->data.get();
|
||||
|
||||
// Initialize UIGrid
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
|
||||
// Allow None for texture - use default texture in that case
|
||||
if (textureObj != Py_None) {
|
||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||
return -1;
|
||||
}
|
||||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||
texture_ptr = pyTexture->data;
|
||||
} else {
|
||||
// Use default texture when None is provided
|
||||
texture_ptr = McRFPy_API::default_texture;
|
||||
}
|
||||
|
||||
// Initialize UIGrid - texture_ptr will be nullptr if texture was None
|
||||
//self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||
//self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
|
||||
// sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||
self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data, pos_result->data, size_result->data);
|
||||
self->data = std::make_shared<UIGrid>(grid_x, grid_y, texture_ptr, pos_result->data, size_result->data);
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
|
@ -251,6 +312,14 @@ PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) {
|
|||
return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_grid_x(PyUIGridObject* self, void* closure) {
|
||||
return PyLong_FromLong(self->data->grid_x);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_grid_y(PyUIGridObject* self, void* closure) {
|
||||
return PyLong_FromLong(self->data->grid_y);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) {
|
||||
auto& box = self->data->box;
|
||||
return Py_BuildValue("(ff)", box.getPosition().x, box.getPosition().y);
|
||||
|
|
@ -365,9 +434,16 @@ PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) {
|
|||
//return self->data->getTexture()->pyObject();
|
||||
// PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState")
|
||||
//PyTextureObject* obj = (PyTextureObject*)((&PyTextureType)->tp_alloc(&PyTextureType, 0));
|
||||
|
||||
// Return None if no texture
|
||||
auto texture = self->data->getTexture();
|
||||
if (!texture) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
|
||||
auto obj = (PyTextureObject*)type->tp_alloc(type, 0);
|
||||
obj->data = self->data->getTexture();
|
||||
obj->data = texture;
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
|
|
@ -379,7 +455,7 @@ PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* o)
|
|||
return NULL;
|
||||
}
|
||||
if (x < 0 || x >= self->data->grid_x) {
|
||||
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_y)");
|
||||
PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_x)");
|
||||
return NULL;
|
||||
}
|
||||
if (y < 0 || y >= self->data->grid_y) {
|
||||
|
|
@ -406,6 +482,8 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
|
||||
// TODO - refactor into get_vector_member with field identifier values `(void*)n`
|
||||
{"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL},
|
||||
{"grid_x", (getter)UIGrid::get_grid_x, NULL, "Grid x dimension", NULL},
|
||||
{"grid_y", (getter)UIGrid::get_grid_y, NULL, "Grid y dimension", NULL},
|
||||
{"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL},
|
||||
{"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid (width, height)", NULL},
|
||||
{"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL},
|
||||
|
|
@ -423,6 +501,7 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID},
|
||||
|
||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
@ -546,15 +625,196 @@ return NULL;
|
|||
|
||||
}
|
||||
|
||||
int UIEntityCollection::setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += list->size();
|
||||
|
||||
// Bounds check
|
||||
if (index >= list->size()) {
|
||||
PyErr_SetString(PyExc_IndexError, "EntityCollection assignment index out of range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get iterator to the target position
|
||||
auto it = list->begin();
|
||||
std::advance(it, index);
|
||||
|
||||
// Handle deletion
|
||||
if (value == NULL) {
|
||||
// Clear grid reference from the entity being removed
|
||||
(*it)->grid = nullptr;
|
||||
list->erase(it);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection can only contain Entity objects");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clear grid reference from the old entity
|
||||
(*it)->grid = nullptr;
|
||||
|
||||
// Replace the element and set grid reference
|
||||
*it = entity->data;
|
||||
entity->data->grid = self->grid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UIEntityCollection::contains(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
// Not an Entity, so it can't be in the collection
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Search for the object by comparing C++ pointers
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
return 1; // Found
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Not found
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||
// Create a new Python list containing elements from both collections
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_ssize_t self_len = self->data->size();
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(self_len + other_len);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add all elements from self
|
||||
Py_ssize_t idx = 0;
|
||||
for (const auto& entity : *self->data) {
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||
if (obj) {
|
||||
obj->data = entity;
|
||||
PyList_SET_ITEM(result_list, idx, (PyObject*)obj); // Steals reference
|
||||
} else {
|
||||
Py_DECREF(result_list);
|
||||
Py_DECREF(type);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(type);
|
||||
idx++;
|
||||
}
|
||||
|
||||
// Add all elements from other
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
Py_DECREF(result_list);
|
||||
return NULL;
|
||||
}
|
||||
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
|
||||
}
|
||||
|
||||
return result_list;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::inplace_concat(PyUIEntityCollectionObject* self, PyObject* other) {
|
||||
if (!PySequence_Check(other)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// First, validate ALL items in the sequence before modifying anything
|
||||
Py_ssize_t other_len = PySequence_Length(other);
|
||||
if (other_len == -1) {
|
||||
return NULL; // Error already set
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"EntityCollection can only contain Entity objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
// All items validated, now we can safely add them
|
||||
for (Py_ssize_t i = 0; i < other_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(other, i);
|
||||
if (!item) {
|
||||
return NULL; // Shouldn't happen, but be safe
|
||||
}
|
||||
|
||||
// Use the existing append method which handles grid references
|
||||
PyObject* result = append(self, item);
|
||||
Py_DECREF(item);
|
||||
|
||||
if (!result) {
|
||||
return NULL; // append() failed
|
||||
}
|
||||
Py_DECREF(result); // append returns Py_None
|
||||
}
|
||||
|
||||
Py_INCREF(self);
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
PySequenceMethods UIEntityCollection::sqmethods = {
|
||||
.sq_length = (lenfunc)UIEntityCollection::len,
|
||||
.sq_concat = (binaryfunc)UIEntityCollection::concat,
|
||||
.sq_repeat = NULL,
|
||||
.sq_item = (ssizeargfunc)UIEntityCollection::getitem,
|
||||
//.sq_item_by_index = UIEntityCollection::getitem
|
||||
//.sq_slice - return a subset of the iterable
|
||||
//.sq_ass_item - called when `o[x] = y` is executed (x is any object type)
|
||||
//.sq_ass_slice - cool; no thanks, for now
|
||||
//.sq_contains - called when `x in o` is executed
|
||||
//.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer)
|
||||
.was_sq_slice = NULL,
|
||||
.sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem,
|
||||
.was_sq_ass_slice = NULL,
|
||||
.sq_contains = (objobjproc)UIEntityCollection::contains,
|
||||
.sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat,
|
||||
.sq_inplace_repeat = NULL
|
||||
};
|
||||
|
||||
PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o)
|
||||
|
|
@ -581,31 +841,340 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject*
|
|||
{
|
||||
if (!PyLong_Check(o))
|
||||
{
|
||||
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an integer index to remove");
|
||||
return NULL;
|
||||
}
|
||||
long index = PyLong_AsLong(o);
|
||||
|
||||
// Handle negative indexing
|
||||
while (index < 0) index += self->data->size();
|
||||
|
||||
if (index >= self->data->size())
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "Index out of range");
|
||||
return NULL;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get iterator to the entity to remove
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, index);
|
||||
|
||||
// Clear grid reference before removing
|
||||
(*it)->grid = nullptr;
|
||||
|
||||
// release the shared pointer at correct part of the list
|
||||
self->data->erase(std::next(self->data->begin(), index));
|
||||
self->data->erase(it);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject* o)
|
||||
{
|
||||
// Accept any iterable of Entity objects
|
||||
PyObject* iterator = PyObject_GetIter(o);
|
||||
if (iterator == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError, "UIEntityCollection.extend requires an iterable");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* item;
|
||||
while ((item = PyIter_Next(iterator)) != NULL) {
|
||||
// Check if item is an Entity
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
Py_DECREF(item);
|
||||
Py_DECREF(iterator);
|
||||
PyErr_SetString(PyExc_TypeError, "All items in iterable must be Entity objects");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add the entity to the collection
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)item;
|
||||
self->data->push_back(entity->data);
|
||||
entity->data->grid = self->grid;
|
||||
|
||||
Py_DECREF(item);
|
||||
}
|
||||
|
||||
Py_DECREF(iterator);
|
||||
|
||||
// Check if iteration ended due to an error
|
||||
if (PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "EntityCollection.index requires an Entity object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search for the object
|
||||
Py_ssize_t idx = 0;
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
return PyLong_FromSsize_t(idx);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_ValueError, "Entity not in EntityCollection");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::count(PyUIEntityCollectionObject* self, PyObject* value) {
|
||||
auto list = self->data.get();
|
||||
if (!list) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Type checking - must be an Entity
|
||||
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
// Not an Entity, so count is 0
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Get the C++ object from the Python object
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)value;
|
||||
if (!entity->data) {
|
||||
return PyLong_FromLong(0);
|
||||
}
|
||||
|
||||
// Count occurrences
|
||||
Py_ssize_t count = 0;
|
||||
for (const auto& ent : *list) {
|
||||
if (ent.get() == entity->data.get()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return PyLong_FromSsize_t(count);
|
||||
}
|
||||
|
||||
PyObject* UIEntityCollection::subscript(PyUIEntityCollectionObject* self, PyObject* key) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return NULL;
|
||||
}
|
||||
return getitem(self, index);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject* result_list = PyList_New(slicelength);
|
||||
if (!result_list) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Iterate through the list with slice parameters
|
||||
auto it = self->data->begin();
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
auto cur_it = it;
|
||||
std::advance(cur_it, cur);
|
||||
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||
if (obj) {
|
||||
obj->data = *cur_it;
|
||||
PyList_SET_ITEM(result_list, i, (PyObject*)obj); // Steals reference
|
||||
} else {
|
||||
Py_DECREF(result_list);
|
||||
Py_DECREF(type);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(type);
|
||||
}
|
||||
|
||||
return result_list;
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int UIEntityCollection::ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value) {
|
||||
if (PyLong_Check(key)) {
|
||||
// Single index - delegate to sq_ass_item
|
||||
Py_ssize_t index = PyLong_AsSsize_t(key);
|
||||
if (index == -1 && PyErr_Occurred()) {
|
||||
return -1;
|
||||
}
|
||||
return setitem(self, index, value);
|
||||
} else if (PySlice_Check(key)) {
|
||||
// Handle slice assignment/deletion
|
||||
Py_ssize_t start, stop, step, slicelength;
|
||||
|
||||
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (value == NULL) {
|
||||
// Deletion
|
||||
if (step != 1) {
|
||||
// For non-contiguous slices, delete from highest to lowest to maintain indices
|
||||
std::vector<Py_ssize_t> indices;
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
indices.push_back(cur);
|
||||
}
|
||||
// Sort in descending order
|
||||
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
|
||||
|
||||
// Delete each index
|
||||
for (Py_ssize_t idx : indices) {
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, idx);
|
||||
(*it)->grid = nullptr; // Clear grid reference
|
||||
self->data->erase(it);
|
||||
}
|
||||
} else {
|
||||
// Contiguous slice - delete range
|
||||
auto it_start = self->data->begin();
|
||||
auto it_stop = self->data->begin();
|
||||
std::advance(it_start, start);
|
||||
std::advance(it_stop, stop);
|
||||
|
||||
// Clear grid references
|
||||
for (auto it = it_start; it != it_stop; ++it) {
|
||||
(*it)->grid = nullptr;
|
||||
}
|
||||
|
||||
self->data->erase(it_start, it_stop);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
// Assignment
|
||||
if (!PySequence_Check(value)) {
|
||||
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_ssize_t value_len = PySequence_Length(value);
|
||||
if (value_len == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate all items first
|
||||
std::vector<std::shared_ptr<UIEntity>> new_items;
|
||||
for (Py_ssize_t i = 0; i < value_len; i++) {
|
||||
PyObject* item = PySequence_GetItem(value, i);
|
||||
if (!item) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Type check
|
||||
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) {
|
||||
Py_DECREF(item);
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
"EntityCollection can only contain Entity objects; "
|
||||
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyUIEntityObject* entity = (PyUIEntityObject*)item;
|
||||
Py_DECREF(item);
|
||||
new_items.push_back(entity->data);
|
||||
}
|
||||
|
||||
// Now perform the assignment
|
||||
if (step == 1) {
|
||||
// Contiguous slice
|
||||
if (slicelength != value_len) {
|
||||
// Need to resize - remove old items and insert new ones
|
||||
auto it_start = self->data->begin();
|
||||
auto it_stop = self->data->begin();
|
||||
std::advance(it_start, start);
|
||||
std::advance(it_stop, stop);
|
||||
|
||||
// Clear grid references from old items
|
||||
for (auto it = it_start; it != it_stop; ++it) {
|
||||
(*it)->grid = nullptr;
|
||||
}
|
||||
|
||||
// Erase old range
|
||||
it_start = self->data->erase(it_start, it_stop);
|
||||
|
||||
// Insert new items
|
||||
for (const auto& entity : new_items) {
|
||||
entity->grid = self->grid;
|
||||
it_start = self->data->insert(it_start, entity);
|
||||
++it_start;
|
||||
}
|
||||
} else {
|
||||
// Same size, just replace
|
||||
auto it = self->data->begin();
|
||||
std::advance(it, start);
|
||||
for (const auto& entity : new_items) {
|
||||
(*it)->grid = nullptr; // Clear old grid ref
|
||||
*it = entity;
|
||||
entity->grid = self->grid; // Set new grid ref
|
||||
++it;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Extended slice
|
||||
if (slicelength != value_len) {
|
||||
PyErr_Format(PyExc_ValueError,
|
||||
"attempt to assign sequence of size %zd to extended slice of size %zd",
|
||||
value_len, slicelength);
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto list_it = self->data->begin();
|
||||
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
|
||||
auto cur_it = list_it;
|
||||
std::advance(cur_it, cur);
|
||||
(*cur_it)->grid = nullptr; // Clear old grid ref
|
||||
*cur_it = new_items[i];
|
||||
new_items[i]->grid = self->grid; // Set new grid ref
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s",
|
||||
Py_TYPE(key)->tp_name);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
PyMappingMethods UIEntityCollection::mpmethods = {
|
||||
.mp_length = (lenfunc)UIEntityCollection::len,
|
||||
.mp_subscript = (binaryfunc)UIEntityCollection::subscript,
|
||||
.mp_ass_subscript = (objobjargproc)UIEntityCollection::ass_subscript
|
||||
};
|
||||
|
||||
PyMethodDef UIEntityCollection::methods[] = {
|
||||
{"append", (PyCFunction)UIEntityCollection::append, METH_O},
|
||||
//{"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, // TODO
|
||||
{"extend", (PyCFunction)UIEntityCollection::extend, METH_O},
|
||||
{"remove", (PyCFunction)UIEntityCollection::remove, METH_O},
|
||||
{"index", (PyCFunction)UIEntityCollection::index_method, METH_O},
|
||||
{"count", (PyCFunction)UIEntityCollection::count, METH_O},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -650,3 +1219,115 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
|||
Py_DECREF(iterType);
|
||||
return (PyObject*)iterObj;
|
||||
}
|
||||
|
||||
// Property system implementation for animations
|
||||
bool UIGrid::setProperty(const std::string& name, float value) {
|
||||
if (name == "x") {
|
||||
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||
output.setPosition(box.getPosition());
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||
output.setPosition(box.getPosition());
|
||||
return true;
|
||||
}
|
||||
else if (name == "w" || name == "width") {
|
||||
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||
return true;
|
||||
}
|
||||
else if (name == "h" || name == "height") {
|
||||
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||
return true;
|
||||
}
|
||||
else if (name == "center_x") {
|
||||
center_x = value;
|
||||
return true;
|
||||
}
|
||||
else if (name == "center_y") {
|
||||
center_y = value;
|
||||
return true;
|
||||
}
|
||||
else if (name == "zoom") {
|
||||
zoom = value;
|
||||
return true;
|
||||
}
|
||||
else if (name == "z_index") {
|
||||
z_index = static_cast<int>(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UIGrid::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||
if (name == "position") {
|
||||
box.setPosition(value);
|
||||
output.setPosition(box.getPosition());
|
||||
return true;
|
||||
}
|
||||
else if (name == "size") {
|
||||
box.setSize(value);
|
||||
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||
return true;
|
||||
}
|
||||
else if (name == "center") {
|
||||
center_x = value.x;
|
||||
center_y = value.y;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UIGrid::getProperty(const std::string& name, float& value) const {
|
||||
if (name == "x") {
|
||||
value = box.getPosition().x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
value = box.getPosition().y;
|
||||
return true;
|
||||
}
|
||||
else if (name == "w" || name == "width") {
|
||||
value = box.getSize().x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "h" || name == "height") {
|
||||
value = box.getSize().y;
|
||||
return true;
|
||||
}
|
||||
else if (name == "center_x") {
|
||||
value = center_x;
|
||||
return true;
|
||||
}
|
||||
else if (name == "center_y") {
|
||||
value = center_y;
|
||||
return true;
|
||||
}
|
||||
else if (name == "zoom") {
|
||||
value = zoom;
|
||||
return true;
|
||||
}
|
||||
else if (name == "z_index") {
|
||||
value = static_cast<float>(z_index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UIGrid::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||
if (name == "position") {
|
||||
value = box.getPosition();
|
||||
return true;
|
||||
}
|
||||
else if (name == "size") {
|
||||
value = box.getSize();
|
||||
return true;
|
||||
}
|
||||
else if (name == "center") {
|
||||
value = sf::Vector2f(center_x, center_y);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue