feat: Add AABB/hit testing foundation (#138)
C++ additions: - get_global_bounds(): returns bounds in screen coordinates - contains_point(x, y): hit test using global bounds Python properties (on all UIDrawable types): - bounds: (x, y, w, h) tuple in local coordinates - global_bounds: (x, y, w, h) tuple in screen coordinates These enable the mouse event system (#140, #141, #142) by providing a way to determine which drawable is under the mouse cursor. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
52a655399e
commit
6d5a5e9e16
4 changed files with 236 additions and 0 deletions
|
|
@ -175,6 +175,14 @@ static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure)
|
|||
MCRF_PROPERTY(global_position, \
|
||||
"Global screen position (read-only). " \
|
||||
"Calculates absolute position by walking up the parent chain." \
|
||||
), (void*)type_enum}, \
|
||||
{"bounds", (getter)UIDrawable::get_bounds_py, NULL, \
|
||||
MCRF_PROPERTY(bounds, \
|
||||
"Bounding rectangle (x, y, width, height) in local coordinates." \
|
||||
), (void*)type_enum}, \
|
||||
{"global_bounds", (getter)UIDrawable::get_global_bounds_py, NULL, \
|
||||
MCRF_PROPERTY(global_bounds, \
|
||||
"Bounding rectangle (x, y, width, height) in screen coordinates." \
|
||||
), (void*)type_enum}
|
||||
|
||||
// UIEntity specializations are defined in UIEntity.cpp after UIEntity class is complete
|
||||
|
|
|
|||
|
|
@ -733,6 +733,21 @@ sf::Vector2f UIDrawable::get_global_position() const {
|
|||
return global_pos;
|
||||
}
|
||||
|
||||
// #138 - Global bounds (bounds in screen coordinates)
|
||||
sf::FloatRect UIDrawable::get_global_bounds() const {
|
||||
sf::FloatRect local_bounds = get_bounds();
|
||||
sf::Vector2f global_pos = get_global_position();
|
||||
|
||||
// Return bounds offset to global position
|
||||
return sf::FloatRect(global_pos.x, global_pos.y, local_bounds.width, local_bounds.height);
|
||||
}
|
||||
|
||||
// #138 - Hit testing
|
||||
bool UIDrawable::contains_point(float x, float y) const {
|
||||
sf::FloatRect global_bounds = get_global_bounds();
|
||||
return global_bounds.contains(x, y);
|
||||
}
|
||||
|
||||
// #116 - Dirty flag propagation up parent chain
|
||||
void UIDrawable::markDirty() {
|
||||
if (render_dirty) return; // Already dirty, no need to propagate
|
||||
|
|
@ -978,3 +993,75 @@ PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
// #138 - Python API for bounds property
|
||||
PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICAPTION:
|
||||
drawable = ((PyUICaptionObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UISPRITE:
|
||||
drawable = ((PyUISpriteObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sf::FloatRect bounds = drawable->get_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
||||
// #138 - Python API for global_bounds property
|
||||
PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||
UIDrawable* drawable = nullptr;
|
||||
|
||||
switch (objtype) {
|
||||
case PyObjectsEnum::UIFRAME:
|
||||
drawable = ((PyUIFrameObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICAPTION:
|
||||
drawable = ((PyUICaptionObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UISPRITE:
|
||||
drawable = ((PyUISpriteObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIGRID:
|
||||
drawable = ((PyUIGridObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UILINE:
|
||||
drawable = ((PyUILineObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UICIRCLE:
|
||||
drawable = ((PyUICircleObject*)self)->data.get();
|
||||
break;
|
||||
case PyObjectsEnum::UIARC:
|
||||
drawable = ((PyUIArcObject*)self)->data.get();
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sf::FloatRect bounds = drawable->get_global_bounds();
|
||||
return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ public:
|
|||
static PyObject* get_parent(PyObject* self, void* closure);
|
||||
static int set_parent(PyObject* self, PyObject* value, void* closure);
|
||||
static PyObject* get_global_pos(PyObject* self, void* closure);
|
||||
|
||||
// Python API for hit testing (#138)
|
||||
static PyObject* get_bounds_py(PyObject* self, void* closure);
|
||||
static PyObject* get_global_bounds_py(PyObject* self, void* closure);
|
||||
|
||||
// New properties for Phase 1
|
||||
bool visible = true; // #87 - visibility flag
|
||||
|
|
@ -106,6 +110,10 @@ public:
|
|||
virtual sf::FloatRect get_bounds() const = 0; // #89 - get bounding box
|
||||
virtual void move(float dx, float dy) = 0; // #98 - move by offset
|
||||
virtual void resize(float w, float h) = 0; // #98 - resize to dimensions
|
||||
|
||||
// Hit testing (#138)
|
||||
sf::FloatRect get_global_bounds() const; // Bounds in screen coordinates
|
||||
bool contains_point(float x, float y) const; // Hit test using global bounds
|
||||
|
||||
// Called when position changes to allow derived classes to sync
|
||||
virtual void onPositionChanged() {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue