feat(rendering): implement RenderTexture base infrastructure and UIFrame clipping (#6)
- Added RenderTexture support to UIDrawable base class - std::unique_ptr<sf::RenderTexture> for opt-in rendering - Dirty flag system for optimization - enableRenderTexture() and markDirty() methods - Implemented clip_children property for UIFrame - Python-accessible boolean property - Automatic RenderTexture creation when enabled - Proper coordinate transformation for nested frames - Updated UIFrame::render() for clipping support - Renders to RenderTexture when clip_children=true - Handles nested clipping correctly - Only re-renders when dirty flag is set - Added comprehensive dirty flag propagation - All property setters mark frame as dirty - Size changes recreate RenderTexture - Animation system integration - Created tests for clipping functionality - Basic clipping test with visual verification - Advanced nested clipping test - Dynamic resize handling test This is Phase 1 of the RenderTexture overhaul, providing the foundation for advanced rendering effects like blur, glow, and viewport rendering.
This commit is contained in:
parent
5e4224a4f8
commit
967ebcf478
7 changed files with 503 additions and 19 deletions
|
|
@ -241,3 +241,36 @@ int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) {
|
|||
drawable->name = name_str;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void UIDrawable::enableRenderTexture(unsigned int width, unsigned int height) {
|
||||
// Create or recreate RenderTexture if size changed
|
||||
if (!render_texture || render_texture->getSize().x != width || render_texture->getSize().y != height) {
|
||||
render_texture = std::make_unique<sf::RenderTexture>();
|
||||
if (!render_texture->create(width, height)) {
|
||||
render_texture.reset();
|
||||
use_render_texture = false;
|
||||
return;
|
||||
}
|
||||
render_sprite.setTexture(render_texture->getTexture());
|
||||
}
|
||||
|
||||
use_render_texture = true;
|
||||
render_dirty = true;
|
||||
}
|
||||
|
||||
void UIDrawable::updateRenderTexture() {
|
||||
if (!use_render_texture || !render_texture) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the RenderTexture
|
||||
render_texture->clear(sf::Color::Transparent);
|
||||
|
||||
// Render content to RenderTexture
|
||||
// This will be overridden by derived classes
|
||||
// For now, just display the texture
|
||||
render_texture->display();
|
||||
|
||||
// Update the sprite
|
||||
render_sprite.setTexture(render_texture->getTexture());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,21 @@ public:
|
|||
virtual bool getProperty(const std::string& name, sf::Color& value) const { return false; }
|
||||
virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; }
|
||||
virtual bool getProperty(const std::string& name, std::string& value) const { return false; }
|
||||
|
||||
protected:
|
||||
// RenderTexture support (opt-in)
|
||||
std::unique_ptr<sf::RenderTexture> render_texture;
|
||||
sf::Sprite render_sprite;
|
||||
bool use_render_texture = false;
|
||||
bool render_dirty = true;
|
||||
|
||||
// Enable RenderTexture for this drawable
|
||||
void enableRenderTexture(unsigned int width, unsigned int height);
|
||||
void updateRenderTexture();
|
||||
|
||||
public:
|
||||
// Mark this drawable as needing redraw
|
||||
void markDirty() { render_dirty = true; }
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
163
src/UIFrame.cpp
163
src/UIFrame.cpp
|
|
@ -89,22 +89,70 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
|
||||
// TODO: Apply opacity when SFML supports it on shapes
|
||||
|
||||
box.move(offset);
|
||||
//Resources::game->getWindow().draw(box);
|
||||
target.draw(box);
|
||||
box.move(-offset);
|
||||
// Check if we need to use RenderTexture for clipping
|
||||
if (clip_children && !children->empty()) {
|
||||
// Enable RenderTexture if not already enabled
|
||||
if (!use_render_texture) {
|
||||
auto size = box.getSize();
|
||||
enableRenderTexture(static_cast<unsigned int>(size.x),
|
||||
static_cast<unsigned int>(size.y));
|
||||
}
|
||||
|
||||
// Update RenderTexture if dirty
|
||||
if (use_render_texture && render_dirty) {
|
||||
// Clear the RenderTexture
|
||||
render_texture->clear(sf::Color::Transparent);
|
||||
|
||||
// Draw the frame box to RenderTexture
|
||||
box.setPosition(0, 0); // Render at origin in texture
|
||||
render_texture->draw(box);
|
||||
|
||||
// Sort children by z_index if needed
|
||||
if (children_need_sort && !children->empty()) {
|
||||
std::sort(children->begin(), children->end(),
|
||||
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||
return a->z_index < b->z_index;
|
||||
});
|
||||
children_need_sort = false;
|
||||
}
|
||||
|
||||
// Render children to RenderTexture at local coordinates
|
||||
for (auto drawable : *children) {
|
||||
drawable->render(sf::Vector2f(0, 0), *render_texture);
|
||||
}
|
||||
|
||||
// Finalize the RenderTexture
|
||||
render_texture->display();
|
||||
|
||||
// Update sprite
|
||||
render_sprite.setTexture(render_texture->getTexture());
|
||||
|
||||
render_dirty = false;
|
||||
}
|
||||
|
||||
// Draw the RenderTexture sprite
|
||||
if (use_render_texture) {
|
||||
render_sprite.setPosition(offset + box.getPosition());
|
||||
target.draw(render_sprite);
|
||||
}
|
||||
} else {
|
||||
// Standard rendering without clipping
|
||||
box.move(offset);
|
||||
target.draw(box);
|
||||
box.move(-offset);
|
||||
|
||||
// Sort children by z_index if needed
|
||||
if (children_need_sort && !children->empty()) {
|
||||
std::sort(children->begin(), children->end(),
|
||||
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||
return a->z_index < b->z_index;
|
||||
});
|
||||
children_need_sort = false;
|
||||
}
|
||||
// Sort children by z_index if needed
|
||||
if (children_need_sort && !children->empty()) {
|
||||
std::sort(children->begin(), children->end(),
|
||||
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||
return a->z_index < b->z_index;
|
||||
});
|
||||
children_need_sort = false;
|
||||
}
|
||||
|
||||
for (auto drawable : *children) {
|
||||
drawable->render(offset + box.getPosition(), target);
|
||||
for (auto drawable : *children) {
|
||||
drawable->render(offset + box.getPosition(), target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,16 +205,36 @@ int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* clos
|
|||
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
|
||||
return -1;
|
||||
}
|
||||
if (member_ptr == 0) //x
|
||||
if (member_ptr == 0) { //x
|
||||
self->data->box.setPosition(val, self->data->box.getPosition().y);
|
||||
else if (member_ptr == 1) //y
|
||||
self->data->markDirty();
|
||||
}
|
||||
else if (member_ptr == 1) { //y
|
||||
self->data->box.setPosition(self->data->box.getPosition().x, val);
|
||||
else if (member_ptr == 2) //w
|
||||
self->data->markDirty();
|
||||
}
|
||||
else if (member_ptr == 2) { //w
|
||||
self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y));
|
||||
else if (member_ptr == 3) //h
|
||||
if (self->data->use_render_texture) {
|
||||
// Need to recreate RenderTexture with new size
|
||||
self->data->enableRenderTexture(static_cast<unsigned int>(self->data->box.getSize().x),
|
||||
static_cast<unsigned int>(self->data->box.getSize().y));
|
||||
}
|
||||
self->data->markDirty();
|
||||
}
|
||||
else if (member_ptr == 3) { //h
|
||||
self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val));
|
||||
else if (member_ptr == 4) //outline
|
||||
if (self->data->use_render_texture) {
|
||||
// Need to recreate RenderTexture with new size
|
||||
self->data->enableRenderTexture(static_cast<unsigned int>(self->data->box.getSize().x),
|
||||
static_cast<unsigned int>(self->data->box.getSize().y));
|
||||
}
|
||||
self->data->markDirty();
|
||||
}
|
||||
else if (member_ptr == 4) { //outline
|
||||
self->data->box.setOutlineThickness(val);
|
||||
self->data->markDirty();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -243,10 +311,12 @@ int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* clos
|
|||
if (member_ptr == 0)
|
||||
{
|
||||
self->data->box.setFillColor(sf::Color(r, g, b, a));
|
||||
self->data->markDirty();
|
||||
}
|
||||
else if (member_ptr == 1)
|
||||
{
|
||||
self->data->box.setOutlineColor(sf::Color(r, g, b, a));
|
||||
self->data->markDirty();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -276,6 +346,28 @@ int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure)
|
|||
return -1;
|
||||
}
|
||||
self->data->box.setPosition(vec->data);
|
||||
self->data->markDirty();
|
||||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UIFrame::get_clip_children(PyUIFrameObject* self, void* closure)
|
||||
{
|
||||
return PyBool_FromLong(self->data->clip_children);
|
||||
}
|
||||
|
||||
int UIFrame::set_clip_children(PyUIFrameObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
if (!PyBool_Check(value)) {
|
||||
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;
|
||||
self->data->markDirty(); // Mark as needing redraw
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -301,6 +393,7 @@ PyGetSetDef UIFrame::getsetters[] = {
|
|||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIFRAME},
|
||||
{"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL},
|
||||
{"clip_children", (getter)UIFrame::get_clip_children, (setter)UIFrame::set_clip_children, "Whether to clip children to frame bounds", NULL},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
{NULL}
|
||||
};
|
||||
|
|
@ -461,58 +554,81 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
|
|||
bool UIFrame::setProperty(const std::string& name, float value) {
|
||||
if (name == "x") {
|
||||
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "y") {
|
||||
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "w") {
|
||||
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||
if (use_render_texture) {
|
||||
// Need to recreate RenderTexture with new size
|
||||
enableRenderTexture(static_cast<unsigned int>(box.getSize().x),
|
||||
static_cast<unsigned int>(box.getSize().y));
|
||||
}
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "h") {
|
||||
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||
if (use_render_texture) {
|
||||
// Need to recreate RenderTexture with new size
|
||||
enableRenderTexture(static_cast<unsigned int>(box.getSize().x),
|
||||
static_cast<unsigned int>(box.getSize().y));
|
||||
}
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline") {
|
||||
box.setOutlineThickness(value);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "fill_color.r") {
|
||||
auto color = box.getFillColor();
|
||||
color.r = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setFillColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "fill_color.g") {
|
||||
auto color = box.getFillColor();
|
||||
color.g = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setFillColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "fill_color.b") {
|
||||
auto color = box.getFillColor();
|
||||
color.b = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setFillColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "fill_color.a") {
|
||||
auto color = box.getFillColor();
|
||||
color.a = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setFillColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline_color.r") {
|
||||
auto color = box.getOutlineColor();
|
||||
color.r = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setOutlineColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline_color.g") {
|
||||
auto color = box.getOutlineColor();
|
||||
color.g = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setOutlineColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline_color.b") {
|
||||
auto color = box.getOutlineColor();
|
||||
color.b = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setOutlineColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline_color.a") {
|
||||
auto color = box.getOutlineColor();
|
||||
color.a = std::clamp(static_cast<int>(value), 0, 255);
|
||||
box.setOutlineColor(color);
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -521,9 +637,11 @@ bool UIFrame::setProperty(const std::string& name, float value) {
|
|||
bool UIFrame::setProperty(const std::string& name, const sf::Color& value) {
|
||||
if (name == "fill_color") {
|
||||
box.setFillColor(value);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "outline_color") {
|
||||
box.setOutlineColor(value);
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -532,9 +650,16 @@ bool UIFrame::setProperty(const std::string& name, const sf::Color& value) {
|
|||
bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||
if (name == "position") {
|
||||
box.setPosition(value);
|
||||
markDirty();
|
||||
return true;
|
||||
} else if (name == "size") {
|
||||
box.setSize(value);
|
||||
if (use_render_texture) {
|
||||
// Need to recreate RenderTexture with new size
|
||||
enableRenderTexture(static_cast<unsigned int>(value.x),
|
||||
static_cast<unsigned int>(value.y));
|
||||
}
|
||||
markDirty();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public:
|
|||
float outline;
|
||||
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
|
||||
bool children_need_sort = true; // Dirty flag for z_index sorting optimization
|
||||
bool clip_children = false; // Whether to clip children to frame bounds
|
||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||
void move(sf::Vector2f);
|
||||
PyObjectsEnum derived_type() override final;
|
||||
|
|
@ -47,6 +48,8 @@ public:
|
|||
static int set_color_member(PyUIFrameObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_pos(PyUIFrameObject* self, void* closure);
|
||||
static int set_pos(PyUIFrameObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_clip_children(PyUIFrameObject* self, void* closure);
|
||||
static int set_clip_children(PyUIFrameObject* self, PyObject* value, void* closure);
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* repr(PyUIFrameObject* self);
|
||||
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue