feat: Implement Phase A UI hierarchy foundations (closes #122, #102, #116, #118)

Parent-Child UI System (#122):
- Add parent weak_ptr to UIDrawable for hierarchy tracking
- Add setParent(), getParent(), removeFromParent() methods
- UICollection now tracks owner and sets parent on append/insert
- Auto-remove from old parent when adding to new collection

Global Position Property (#102):
- Add get_global_position() that walks up parent chain
- Expose as read-only 'global_position' property on all UI types
- Add UIDRAWABLE_PARENT_GETSETTERS macro for consistent bindings

Dirty Flag System (#116):
- Modify markDirty() to propagate up the parent chain
- Add isDirty() and clearDirty() methods for render optimization

Scene as Drawable (#118):
- Add position, visible, opacity properties to Scene
- Add setProperty()/getProperty() for animation support
- Apply scene transformations in PyScene::render()
- Fix lifecycle callbacks to clear errors when methods don't exist
- Add GameEngine::getScene() public accessor

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
John McCardle 2025-11-27 16:33:17 -05:00
commit e3d8f54d46
19 changed files with 988 additions and 67 deletions

View file

@ -64,18 +64,23 @@ void PyScene::doAction(std::string name, std::string type)
void PyScene::render()
{
// #118: Skip rendering if scene is not visible
if (!visible) {
return;
}
game->getRenderTarget().clear();
// Only sort if z_index values have changed
if (ui_elements_need_sort) {
std::sort(ui_elements->begin(), ui_elements->end(),
std::sort(ui_elements->begin(), ui_elements->end(),
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
return a->z_index < b->z_index;
});
ui_elements_need_sort = false;
}
// Render in sorted order (no need to copy anymore)
// Render in sorted order with scene-level transformations
for (auto e: *ui_elements)
{
if (e) {
@ -86,9 +91,22 @@ void PyScene::render()
// Count this as a draw call (each visible element = 1+ draw calls)
game->metrics.drawCalls++;
}
e->render();
// #118: Apply scene-level opacity to element
float original_opacity = e->opacity;
if (opacity < 1.0f) {
e->opacity = original_opacity * opacity;
}
// #118: Render with scene position offset
e->render(position, game->getRenderTarget());
// #118: Restore original opacity
if (opacity < 1.0f) {
e->opacity = original_opacity;
}
}
}
// Display is handled by GameEngine
}