Fix UIFrame RenderTexture positioning and toggling issues

- Fix #223: Use `position` instead of `box.getPosition()` for render_sprite
  positioning. The box was being set to (0,0) for texture rendering and
  never restored, causing frames to render at wrong positions.

- Fix #224: Add disableRenderTexture() method and call it when toggling
  clip_children or cache_subtree off. This properly cleans up the texture
  and prevents stale rendering.

- Fix #225: Improve dirty propagation in markContentDirty() to propagate
  to parent even when already dirty, if the parent was cleared (rendered)
  since last propagation. Prevents child changes from being invisible.

- Fix #226: Add fallback to standard rendering when RenderTexture can't
  be created (e.g., zero-size frame). Prevents inconsistent state.

Closes #223, closes #224, closes #225, closes #226

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-22 22:54:50 -05:00
commit 3fea6418ff
4 changed files with 135 additions and 7 deletions

View file

@ -419,11 +419,20 @@ void UIDrawable::enableRenderTexture(unsigned int width, unsigned int height) {
}
render_sprite.setTexture(render_texture->getTexture());
}
use_render_texture = true;
render_dirty = true;
}
void UIDrawable::disableRenderTexture() {
if (!use_render_texture) return;
render_texture.reset();
render_sprite = sf::Sprite(); // Clear stale texture reference
use_render_texture = false;
render_dirty = true;
}
void UIDrawable::updateRenderTexture() {
if (!use_render_texture || !render_texture) {
return;
@ -907,14 +916,14 @@ bool UIDrawable::contains_point(float x, float y) const {
// #144: Content dirty - texture needs rebuild
void UIDrawable::markContentDirty() {
if (render_dirty) return; // Already dirty, no need to propagate
bool was_dirty = render_dirty;
render_dirty = true;
composite_dirty = true; // If content changed, composite also needs update
// Propagate to parent - parent's composite is dirty (child content changed)
// Propagate if: we weren't already dirty, OR parent was cleared (rendered) since last propagation
auto p = parent.lock();
if (p) {
if (p && (!was_dirty || !p->render_dirty)) {
p->markContentDirty(); // Parent also needs to rebuild to include our changes
}
}

View file

@ -248,6 +248,12 @@ protected:
// Enable RenderTexture for this drawable
void enableRenderTexture(unsigned int width, unsigned int height);
void updateRenderTexture();
// Disable RenderTexture for this drawable (public for property setters)
public:
void disableRenderTexture();
protected:
public:
// #144: Dirty flag system - content vs composite

View file

@ -124,6 +124,13 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
}
}
// Fall back to standard rendering if texture not available (e.g., zero size)
if (!use_render_texture) {
use_texture = false; // Force standard path for this render call
}
}
if (use_texture) {
// Update RenderTexture if dirty
if (use_render_texture && render_dirty) {
// Clear the RenderTexture
@ -158,7 +165,8 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
// Draw the RenderTexture sprite (single blit!)
if (use_render_texture) {
render_sprite.setPosition(offset + box.getPosition());
// Use `position` instead of box.getPosition() - box was set to (0,0) for texture rendering
render_sprite.setPosition(offset + position);
target.draw(render_sprite);
}
} else {
@ -389,13 +397,19 @@ int UIFrame::set_clip_children(PyUIFrameObject* self, PyObject* value, void* clo
PyErr_SetString(PyExc_TypeError, "clip_children must be a boolean");
return -1;
}
bool new_clip = PyObject_IsTrue(value);
if (new_clip != self->data->clip_children) {
self->data->clip_children = new_clip;
// Disable texture if no longer needed (and cache_subtree isn't using it)
if (!new_clip && !self->data->cache_subtree) {
self->data->disableRenderTexture();
}
self->data->markDirty(); // Mark as needing redraw
}
return 0;
}
@ -423,7 +437,11 @@ int UIFrame::set_cache_subtree(PyUIFrameObject* self, PyObject* value, void* clo
self->data->enableRenderTexture(static_cast<unsigned int>(size.x),
static_cast<unsigned int>(size.y));
}
} else if (!self->data->clip_children) {
// Disable texture if clip_children also doesn't need it
self->data->disableRenderTexture();
}
self->data->markDirty(); // Mark as needing redraw
}