From 2c204550034180827a240d437a73d293d213ad8f Mon Sep 17 00:00:00 2001 From: John McCardle Date: Tue, 6 Jan 2026 14:04:53 -0500 Subject: [PATCH] support for Scene object as parent, from Python: closes #183 --- src/McRFPy_API.cpp | 8 +++-- src/PySceneObject.cpp | 16 +++++++++- src/PySceneObject.h | 5 +++- src/UICollection.cpp | 16 ++++++---- src/UIDrawable.cpp | 70 ++++++++++++++++++++++++++++++++++++++++--- src/UIDrawable.h | 9 +++++- 6 files changed, 108 insertions(+), 16 deletions(-) diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 8992bdf..db33395 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -879,9 +879,11 @@ PyObject* McRFPy_API::_sceneUI(PyObject* self, PyObject* args) { //Py_INCREF(Py_None); //return Py_None; PyUICollectionObject* o = (PyUICollectionObject*)PyUICollectionType.tp_alloc(&PyUICollectionType, 0); - if (o) - o->data = ui; - return (PyObject*)o; + if (o) { + o->data = ui; + o->scene_name = scene_cstr; // #183: Track scene ownership + } + return (PyObject*)o; } // Internal use - called by PySceneObject::activate() diff --git a/src/PySceneObject.cpp b/src/PySceneObject.cpp index 3ccfc1c..59cb51b 100644 --- a/src/PySceneObject.cpp +++ b/src/PySceneObject.cpp @@ -519,6 +519,20 @@ PyMethodDef PySceneClass::methods[] = { {NULL} }; +// #183: Lookup scene by name (returns new reference or nullptr) +PyObject* PySceneClass::get_scene_by_name(const std::string& name) +{ + if (name.empty()) return nullptr; + + if (python_scenes.count(name) > 0) { + PyObject* scene_obj = (PyObject*)python_scenes[name]; + Py_INCREF(scene_obj); + return scene_obj; + } + + return nullptr; +} + // Helper function to trigger lifecycle events void McRFPy_API::triggerSceneChange(const std::string& from_scene, const std::string& to_scene) { @@ -526,7 +540,7 @@ void McRFPy_API::triggerSceneChange(const std::string& from_scene, const std::st if (!from_scene.empty() && python_scenes.count(from_scene) > 0) { PySceneClass::call_on_exit(python_scenes[from_scene]); } - + // Call on_enter for the new scene if (!to_scene.empty() && python_scenes.count(to_scene) > 0) { PySceneClass::call_on_enter(python_scenes[to_scene]); diff --git a/src/PySceneObject.h b/src/PySceneObject.h index fdba708..9fb7c23 100644 --- a/src/PySceneObject.h +++ b/src/PySceneObject.h @@ -39,7 +39,10 @@ public: static void call_on_keypress(PySceneObject* self, std::string key, std::string action); static void call_update(PySceneObject* self, float dt); static void call_on_resize(PySceneObject* self, int width, int height); - + + // #183: Lookup scene by name (returns new reference or nullptr) + static PyObject* get_scene_by_name(const std::string& name); + static PyGetSetDef getsetters[]; static PyMethodDef methods[]; }; diff --git a/src/UICollection.cpp b/src/UICollection.cpp index ce154c6..6512dfd 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -656,15 +656,19 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) // Helper lambda to add drawable with parent tracking auto addDrawable = [&](std::shared_ptr drawable) { - // #122: Remove from old parent if it has one - if (auto old_parent = drawable->getParent()) { - drawable->removeFromParent(); - } + // #183: Remove from old parent (drawable or scene) + drawable->removeFromParent(); drawable->z_index = new_z_index; - // #122: Set new parent (owner of this collection) - drawable->setParent(owner_ptr); + // #183: Set new parent - either scene or drawable + if (!self->scene_name.empty()) { + // This is a scene's children collection + drawable->setParentScene(self->scene_name); + } else { + // This is a Frame/Grid's children collection + drawable->setParent(owner_ptr); + } self->data->push_back(drawable); }; diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index f9a6dbe..bde2bb6 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -12,6 +12,7 @@ #include "Animation.h" #include "PyAnimation.h" #include "PyEasing.h" +#include "PySceneObject.h" // #183: For scene parent lookup UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; } @@ -764,6 +765,12 @@ int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) { // #122 - Parent-child hierarchy implementation void UIDrawable::setParent(std::shared_ptr new_parent) { parent = new_parent; + parent_scene.clear(); // #183: Clear scene parent when setting drawable parent +} + +void UIDrawable::setParentScene(const std::string& scene_name) { + parent.reset(); // #183: Clear drawable parent when setting scene parent + parent_scene = scene_name; } std::shared_ptr UIDrawable::getParent() const { @@ -771,6 +778,22 @@ std::shared_ptr UIDrawable::getParent() const { } void UIDrawable::removeFromParent() { + // #183: Handle scene parent removal + if (!parent_scene.empty()) { + auto ui = Resources::game->scene_ui(parent_scene); + if (ui) { + for (auto it = ui->begin(); it != ui->end(); ++it) { + if (it->get() == this) { + ui->erase(it); + break; + } + } + } + parent_scene.clear(); + return; + } + + // Handle drawable parent removal auto p = parent.lock(); if (!p) return; @@ -899,6 +922,15 @@ PyObject* UIDrawable::get_parent(PyObject* self, void* closure) { return NULL; } + // #183: Check for scene parent first + if (!drawable->parent_scene.empty()) { + PyObject* scene = PySceneClass::get_scene_by_name(drawable->parent_scene); + if (scene) { + return scene; // Already has new reference from get_scene_by_name + } + // Scene not found in python_scenes (shouldn't happen, but fall through to None) + } + auto parent_ptr = drawable->getParent(); if (!parent_ptr) { Py_RETURN_NONE; @@ -1008,22 +1040,52 @@ int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) { return 0; } - // Value must be a Frame or Grid (things with children collections) - // Check if it's a Frame + // Value must be a Frame, Grid, or Scene (things with children collections) PyTypeObject* frame_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"); PyTypeObject* grid_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"); + PyTypeObject* scene_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Scene"); bool is_frame = frame_type && PyObject_IsInstance(value, (PyObject*)frame_type); bool is_grid = grid_type && PyObject_IsInstance(value, (PyObject*)grid_type); + bool is_scene = scene_type && PyObject_IsInstance(value, (PyObject*)scene_type); Py_XDECREF(frame_type); Py_XDECREF(grid_type); + Py_XDECREF(scene_type); - if (!is_frame && !is_grid) { - PyErr_SetString(PyExc_TypeError, "parent must be a Frame, Grid, or None"); + if (!is_frame && !is_grid && !is_scene) { + PyErr_SetString(PyExc_TypeError, "parent must be a Frame, Grid, Scene, or None"); return -1; } + // Handle Scene parent specially - add to scene's children + if (is_scene) { + PySceneObject* scene_obj = (PySceneObject*)value; + std::string scene_name = scene_obj->name; + + // Remove from old parent first + drawable->removeFromParent(); + + // Get the scene's UI elements and add + auto ui = Resources::game->scene_ui(scene_name); + if (ui) { + // Check if already in this scene (prevent duplicates) + bool already_present = false; + for (const auto& child : *ui) { + if (child.get() == drawable.get()) { + already_present = true; + break; + } + } + + if (!already_present) { + ui->push_back(drawable); + drawable->setParentScene(scene_name); + } + } + return 0; + } + // Remove from old parent first drawable->removeFromParent(); diff --git a/src/UIDrawable.h b/src/UIDrawable.h index 484117f..193e544 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -107,13 +107,19 @@ public: // Parent-child hierarchy (#122) std::weak_ptr parent; + // Scene parent tracking (#183) - name of scene if this is a top-level element + std::string parent_scene; + // Set the parent of this drawable (called by collections when adding) void setParent(std::shared_ptr new_parent); + // Set the parent scene name (called by UICollection when adding to scene) + void setParentScene(const std::string& scene_name); + // Get the parent drawable (returns nullptr if no parent or expired) std::shared_ptr getParent() const; - // Remove this drawable from its current parent's children + // Remove this drawable from its current parent's children (or scene) void removeFromParent(); // Get the global (screen) position by walking up the parent chain (#102) @@ -205,6 +211,7 @@ typedef struct { PyObject_HEAD std::shared_ptr>> data; std::weak_ptr owner; // #122: Parent drawable (for Frame.children, Grid.children) + std::string scene_name; // #183: Scene name (for Scene.children) - empty if not scene-level } PyUICollectionObject; typedef struct {