Animation fixes: 0-duration edge case, integer value bug resolution
This commit is contained in:
parent
cec76b63dc
commit
357c2ac7d7
8 changed files with 333 additions and 51 deletions
|
|
@ -57,15 +57,15 @@ Animation::~Animation() {
|
||||||
|
|
||||||
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
targetWeak = target;
|
targetWeak = target;
|
||||||
elapsed = 0.0f;
|
elapsed = 0.0f;
|
||||||
callbackTriggered = false; // Reset callback state
|
callbackTriggered = false; // Reset callback state
|
||||||
|
|
||||||
// Capture start value from target
|
// Capture start value from target
|
||||||
std::visit([this, &target](const auto& targetVal) {
|
std::visit([this, &target](const auto& targetVal) {
|
||||||
using T = std::decay_t<decltype(targetVal)>;
|
using T = std::decay_t<decltype(targetVal)>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
float value;
|
float value;
|
||||||
if (target->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
|
|
@ -73,9 +73,15 @@ void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
int value;
|
// Most UI properties use float, so try float first, then int
|
||||||
if (target->getProperty(targetProperty, value)) {
|
float fvalue;
|
||||||
startValue = value;
|
if (target->getProperty(targetProperty, fvalue)) {
|
||||||
|
startValue = static_cast<int>(fvalue);
|
||||||
|
} else {
|
||||||
|
int ivalue;
|
||||||
|
if (target->getProperty(targetProperty, ivalue)) {
|
||||||
|
startValue = ivalue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
|
@ -104,19 +110,29 @@ void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
|
|
||||||
|
// For zero-duration animations, apply final value immediately
|
||||||
|
if (duration <= 0.0f) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
if (pythonCallback && !callbackTriggered) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
entityTargetWeak = target;
|
entityTargetWeak = target;
|
||||||
elapsed = 0.0f;
|
elapsed = 0.0f;
|
||||||
callbackTriggered = false; // Reset callback state
|
callbackTriggered = false; // Reset callback state
|
||||||
|
|
||||||
// Capture the starting value from the entity
|
// Capture the starting value from the entity
|
||||||
std::visit([this, target](const auto& val) {
|
std::visit([this, target](const auto& val) {
|
||||||
using T = std::decay_t<decltype(val)>;
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
if (target->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
|
|
@ -131,6 +147,16 @@ void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
||||||
}
|
}
|
||||||
// Entities don't support other types yet
|
// Entities don't support other types yet
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
|
|
||||||
|
// For zero-duration animations, apply final value immediately
|
||||||
|
if (duration <= 0.0f) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
if (pythonCallback && !callbackTriggered) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Animation::hasValidTarget() const {
|
bool Animation::hasValidTarget() const {
|
||||||
|
|
@ -169,39 +195,55 @@ bool Animation::update(float deltaTime) {
|
||||||
// Try to lock weak_ptr to get shared_ptr
|
// Try to lock weak_ptr to get shared_ptr
|
||||||
std::shared_ptr<UIDrawable> target = targetWeak.lock();
|
std::shared_ptr<UIDrawable> target = targetWeak.lock();
|
||||||
std::shared_ptr<UIEntity> entity = entityTargetWeak.lock();
|
std::shared_ptr<UIEntity> entity = entityTargetWeak.lock();
|
||||||
|
|
||||||
// If both are null, target was destroyed
|
// If both are null, target was destroyed
|
||||||
if (!target && !entity) {
|
if (!target && !entity) {
|
||||||
return false; // Remove this animation
|
return false; // Remove this animation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle already-complete animations (e.g., duration=0)
|
||||||
|
// Apply final value once before returning
|
||||||
if (isComplete()) {
|
if (isComplete()) {
|
||||||
|
if (!callbackTriggered) {
|
||||||
|
// Apply final value for zero-duration animations
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
if (target) {
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
} else if (entity) {
|
||||||
|
applyValue(entity.get(), finalValue);
|
||||||
|
}
|
||||||
|
// Trigger callback
|
||||||
|
if (pythonCallback) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed += deltaTime;
|
elapsed += deltaTime;
|
||||||
elapsed = std::min(elapsed, duration);
|
elapsed = std::min(elapsed, duration);
|
||||||
|
|
||||||
// Calculate easing value (0.0 to 1.0)
|
// Calculate easing value (0.0 to 1.0)
|
||||||
float t = duration > 0 ? elapsed / duration : 1.0f;
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
||||||
float easedT = easingFunc(t);
|
float easedT = easingFunc(t);
|
||||||
|
|
||||||
// Get interpolated value
|
// Get interpolated value
|
||||||
AnimationValue currentValue = interpolate(easedT);
|
AnimationValue currentValue = interpolate(easedT);
|
||||||
|
|
||||||
// Apply to whichever target is valid
|
// Apply to whichever target is valid
|
||||||
if (target) {
|
if (target) {
|
||||||
applyValue(target.get(), currentValue);
|
applyValue(target.get(), currentValue);
|
||||||
} else if (entity) {
|
} else if (entity) {
|
||||||
applyValue(entity.get(), currentValue);
|
applyValue(entity.get(), currentValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger callback when animation completes
|
// Trigger callback when animation completes
|
||||||
// Check pythonCallback again in case it was cleared during update
|
// Check pythonCallback again in case it was cleared during update
|
||||||
if (isComplete() && !callbackTriggered && pythonCallback) {
|
if (isComplete() && !callbackTriggered && pythonCallback) {
|
||||||
triggerCallback();
|
triggerCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
return !isComplete();
|
return !isComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,15 +352,19 @@ AnimationValue Animation::interpolate(float t) const {
|
||||||
|
|
||||||
void Animation::applyValue(UIDrawable* target, const AnimationValue& value) {
|
void Animation::applyValue(UIDrawable* target, const AnimationValue& value) {
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
std::visit([this, target](const auto& val) {
|
std::visit([this, target](const auto& val) {
|
||||||
using T = std::decay_t<decltype(val)>;
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
target->setProperty(targetProperty, val);
|
target->setProperty(targetProperty, val);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
target->setProperty(targetProperty, val);
|
// Most UI properties use float setProperty, so try float first
|
||||||
|
if (!target->setProperty(targetProperty, static_cast<float>(val))) {
|
||||||
|
// Fall back to int if float didn't work
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, sf::Color>) {
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
target->setProperty(targetProperty, val);
|
target->setProperty(targetProperty, val);
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,48 @@ void PyAnimation::dealloc(PyAnimationObject* self) {
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::repr(PyAnimationObject* self) {
|
||||||
|
if (!self->data) {
|
||||||
|
return PyUnicode_FromString("<Animation (uninitialized)>");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string property = self->data->getTargetProperty();
|
||||||
|
float duration = self->data->getDuration();
|
||||||
|
float elapsed = self->data->getElapsed();
|
||||||
|
bool complete = self->data->isComplete();
|
||||||
|
bool delta = self->data->isDelta();
|
||||||
|
bool hasTarget = self->data->hasValidTarget();
|
||||||
|
|
||||||
|
// Format: <Animation 'property' duration=2.0s elapsed=0.5s running>
|
||||||
|
// or: <Animation 'property' duration=2.0s complete>
|
||||||
|
// or: <Animation 'property' duration=2.0s delta complete>
|
||||||
|
// or: <Animation 'property' duration=2.0s (no target)>
|
||||||
|
|
||||||
|
std::string status;
|
||||||
|
if (!hasTarget) {
|
||||||
|
status = "(no target)";
|
||||||
|
} else if (complete) {
|
||||||
|
status = "complete";
|
||||||
|
} else {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "elapsed=%.2fs", elapsed);
|
||||||
|
status = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char result[256];
|
||||||
|
if (delta) {
|
||||||
|
snprintf(result, sizeof(result),
|
||||||
|
"<Animation '%s' duration=%.2fs delta %s>",
|
||||||
|
property.c_str(), duration, status.c_str());
|
||||||
|
} else {
|
||||||
|
snprintf(result, sizeof(result),
|
||||||
|
"<Animation '%s' duration=%.2fs %s>",
|
||||||
|
property.c_str(), duration, status.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyUnicode_FromString(result);
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
||||||
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public:
|
||||||
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
||||||
static void dealloc(PyAnimationObject* self);
|
static void dealloc(PyAnimationObject* self);
|
||||||
|
static PyObject* repr(PyAnimationObject* self);
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
||||||
|
|
@ -42,8 +43,59 @@ namespace mcrfpydef {
|
||||||
.tp_basicsize = sizeof(PyAnimationObject),
|
.tp_basicsize = sizeof(PyAnimationObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
||||||
|
.tp_repr = (reprfunc)PyAnimation::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Animation object for animating UI properties"),
|
.tp_doc = PyDoc_STR(
|
||||||
|
"Animation(property: str, target: Any, duration: float, easing: str = 'linear', delta: bool = False, callback: Callable = None)\n"
|
||||||
|
"\n"
|
||||||
|
"Create an animation that interpolates a property value over time.\n"
|
||||||
|
"\n"
|
||||||
|
"Args:\n"
|
||||||
|
" property: Property name to animate. Valid properties depend on target type:\n"
|
||||||
|
" - Position/Size: 'x', 'y', 'w', 'h', 'pos', 'size'\n"
|
||||||
|
" - Appearance: 'fill_color', 'outline_color', 'outline', 'opacity'\n"
|
||||||
|
" - Sprite: 'sprite_index', 'sprite_number', 'scale'\n"
|
||||||
|
" - Grid: 'center', 'zoom'\n"
|
||||||
|
" - Caption: 'text'\n"
|
||||||
|
" - Sub-properties: 'fill_color.r', 'fill_color.g', 'fill_color.b', 'fill_color.a'\n"
|
||||||
|
" target: Target value for the animation. Type depends on property:\n"
|
||||||
|
" - float: For numeric properties (x, y, w, h, scale, opacity, zoom)\n"
|
||||||
|
" - int: For integer properties (sprite_index)\n"
|
||||||
|
" - tuple (r, g, b[, a]): For color properties\n"
|
||||||
|
" - tuple (x, y): For vector properties (pos, size, center)\n"
|
||||||
|
" - list[int]: For sprite animation sequences\n"
|
||||||
|
" - str: For text animation\n"
|
||||||
|
" duration: Animation duration in seconds.\n"
|
||||||
|
" easing: Easing function name. Options:\n"
|
||||||
|
" - 'linear' (default)\n"
|
||||||
|
" - 'easeIn', 'easeOut', 'easeInOut'\n"
|
||||||
|
" - 'easeInQuad', 'easeOutQuad', 'easeInOutQuad'\n"
|
||||||
|
" - 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'\n"
|
||||||
|
" - 'easeInQuart', 'easeOutQuart', 'easeInOutQuart'\n"
|
||||||
|
" - 'easeInSine', 'easeOutSine', 'easeInOutSine'\n"
|
||||||
|
" - 'easeInExpo', 'easeOutExpo', 'easeInOutExpo'\n"
|
||||||
|
" - 'easeInCirc', 'easeOutCirc', 'easeInOutCirc'\n"
|
||||||
|
" - 'easeInElastic', 'easeOutElastic', 'easeInOutElastic'\n"
|
||||||
|
" - 'easeInBack', 'easeOutBack', 'easeInOutBack'\n"
|
||||||
|
" - 'easeInBounce', 'easeOutBounce', 'easeInOutBounce'\n"
|
||||||
|
" delta: If True, target is relative to start value (additive). Default False.\n"
|
||||||
|
" callback: Function(animation, target) called when animation completes.\n"
|
||||||
|
"\n"
|
||||||
|
"Example:\n"
|
||||||
|
" # Move a frame from current position to x=500 over 2 seconds\n"
|
||||||
|
" anim = mcrfpy.Animation('x', 500.0, 2.0, 'easeInOut')\n"
|
||||||
|
" anim.start(my_frame)\n"
|
||||||
|
"\n"
|
||||||
|
" # Fade out with callback\n"
|
||||||
|
" def on_done(anim, target):\n"
|
||||||
|
" print('Animation complete!')\n"
|
||||||
|
" fade = mcrfpy.Animation('fill_color.a', 0, 1.0, callback=on_done)\n"
|
||||||
|
" fade.start(my_sprite)\n"
|
||||||
|
"\n"
|
||||||
|
" # Animate through sprite frames\n"
|
||||||
|
" walk_cycle = mcrfpy.Animation('sprite_index', [0,1,2,3,2,1], 0.5, 'linear')\n"
|
||||||
|
" walk_cycle.start(my_entity)\n"
|
||||||
|
),
|
||||||
.tp_methods = PyAnimation::methods,
|
.tp_methods = PyAnimation::methods,
|
||||||
.tp_getset = PyAnimation::getsetters,
|
.tp_getset = PyAnimation::getsetters,
|
||||||
.tp_init = (initproc)PyAnimation::init,
|
.tp_init = (initproc)PyAnimation::init,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,34 @@ namespace mcrfpydef {
|
||||||
.tp_repr = PyColor::repr,
|
.tp_repr = PyColor::repr,
|
||||||
.tp_hash = PyColor::hash,
|
.tp_hash = PyColor::hash,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("SFML Color Object"),
|
.tp_doc = PyDoc_STR(
|
||||||
|
"Color(r: int = 0, g: int = 0, b: int = 0, a: int = 255)\n"
|
||||||
|
"\n"
|
||||||
|
"RGBA color representation.\n"
|
||||||
|
"\n"
|
||||||
|
"Args:\n"
|
||||||
|
" r: Red component (0-255)\n"
|
||||||
|
" g: Green component (0-255)\n"
|
||||||
|
" b: Blue component (0-255)\n"
|
||||||
|
" a: Alpha component (0-255, default 255 = opaque)\n"
|
||||||
|
"\n"
|
||||||
|
"Note:\n"
|
||||||
|
" When accessing colors from UI elements (e.g., frame.fill_color),\n"
|
||||||
|
" you receive a COPY of the color. Modifying it doesn't affect the\n"
|
||||||
|
" original. To change a component:\n"
|
||||||
|
"\n"
|
||||||
|
" # This does NOT work:\n"
|
||||||
|
" frame.fill_color.r = 255 # Modifies a temporary copy\n"
|
||||||
|
"\n"
|
||||||
|
" # Do this instead:\n"
|
||||||
|
" c = frame.fill_color\n"
|
||||||
|
" c.r = 255\n"
|
||||||
|
" frame.fill_color = c\n"
|
||||||
|
"\n"
|
||||||
|
" # Or use Animation for sub-properties:\n"
|
||||||
|
" anim = mcrfpy.Animation('fill_color.r', 255, 0.5, 'linear')\n"
|
||||||
|
" anim.start(frame)\n"
|
||||||
|
),
|
||||||
.tp_methods = PyColor::methods,
|
.tp_methods = PyColor::methods,
|
||||||
.tp_getset = PyColor::getsetters,
|
.tp_getset = PyColor::getsetters,
|
||||||
.tp_init = (initproc)PyColor::init,
|
.tp_init = (initproc)PyColor::init,
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,12 @@ PyGetSetDef UICaption::getsetters[] = {
|
||||||
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
|
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
|
||||||
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
|
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
|
||||||
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
|
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
|
||||||
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Fill color of the text", (void*)0},
|
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member,
|
||||||
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1},
|
"Fill color of the text. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", (void*)0},
|
||||||
|
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member,
|
||||||
|
"Outline color of the text. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'outline_color.r', 'outline_color.g', etc.", (void*)1},
|
||||||
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
||||||
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
||||||
|
|
|
||||||
|
|
@ -434,8 +434,12 @@ PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"w", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "width of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 2)},
|
{"w", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "width of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 2)},
|
||||||
{"h", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "height of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 3)},
|
{"h", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "height of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 3)},
|
||||||
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
|
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
|
||||||
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
|
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member,
|
||||||
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
|
"Fill color of the rectangle. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", (void*)0},
|
||||||
|
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member,
|
||||||
|
"Outline color of the rectangle. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'outline_color.r', 'outline_color.g', etc.", (void*)1},
|
||||||
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
MCRF_PROPERTY(on_click,
|
MCRF_PROPERTY(on_click,
|
||||||
|
|
|
||||||
|
|
@ -2059,7 +2059,9 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
), (void*)PyObjectsEnum::UIGRID},
|
), (void*)PyObjectsEnum::UIGRID},
|
||||||
|
|
||||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||||
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL},
|
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color,
|
||||||
|
"Background fill color of the grid. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", NULL},
|
||||||
{"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective,
|
{"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective,
|
||||||
"Entity whose perspective to use for FOV rendering (None for omniscient view). "
|
"Entity whose perspective to use for FOV rendering (None for omniscient view). "
|
||||||
"Setting an entity automatically enables perspective mode.", NULL},
|
"Setting an entity automatically enables perspective mode.", NULL},
|
||||||
|
|
|
||||||
153
stubs/mcrfpy.pyi
153
stubs/mcrfpy.pyi
|
|
@ -12,26 +12,44 @@ Transition = Union[str, None]
|
||||||
# Classes
|
# Classes
|
||||||
|
|
||||||
class Color:
|
class Color:
|
||||||
"""SFML Color Object for RGBA colors."""
|
"""RGBA color representation.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
When accessing colors from UI elements (e.g., frame.fill_color),
|
||||||
|
you receive a COPY of the color. Modifying it doesn't affect the
|
||||||
|
original. To change a component:
|
||||||
|
|
||||||
|
# This does NOT work:
|
||||||
|
frame.fill_color.r = 255 # Modifies a temporary copy
|
||||||
|
|
||||||
|
# Do this instead:
|
||||||
|
c = frame.fill_color
|
||||||
|
c.r = 255
|
||||||
|
frame.fill_color = c
|
||||||
|
|
||||||
|
# Or use Animation for sub-properties:
|
||||||
|
anim = mcrfpy.Animation('fill_color.r', 255, 0.5, 'linear')
|
||||||
|
anim.start(frame)
|
||||||
|
"""
|
||||||
|
|
||||||
r: int
|
r: int
|
||||||
g: int
|
g: int
|
||||||
b: int
|
b: int
|
||||||
a: int
|
a: int
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def __init__(self) -> None: ...
|
def __init__(self) -> None: ...
|
||||||
@overload
|
@overload
|
||||||
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
|
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
|
||||||
|
|
||||||
def from_hex(self, hex_string: str) -> 'Color':
|
def from_hex(self, hex_string: str) -> 'Color':
|
||||||
"""Create color from hex string (e.g., '#FF0000' or 'FF0000')."""
|
"""Create color from hex string (e.g., '#FF0000' or 'FF0000')."""
|
||||||
...
|
...
|
||||||
|
|
||||||
def to_hex(self) -> str:
|
def to_hex(self) -> str:
|
||||||
"""Convert color to hex string format."""
|
"""Convert color to hex string format."""
|
||||||
...
|
...
|
||||||
|
|
||||||
def lerp(self, other: 'Color', t: float) -> 'Color':
|
def lerp(self, other: 'Color', t: float) -> 'Color':
|
||||||
"""Linear interpolation between two colors."""
|
"""Linear interpolation between two colors."""
|
||||||
...
|
...
|
||||||
|
|
@ -534,31 +552,118 @@ class Window:
|
||||||
...
|
...
|
||||||
|
|
||||||
class Animation:
|
class Animation:
|
||||||
"""Animation object for animating UI properties."""
|
"""Animation for interpolating UI properties over time.
|
||||||
|
|
||||||
target: Any
|
Create an animation targeting a specific property, then call start() on a
|
||||||
property: str
|
UI element to begin the animation. The AnimationManager handles updates
|
||||||
duration: float
|
automatically.
|
||||||
easing: str
|
|
||||||
loop: bool
|
Example:
|
||||||
on_complete: Optional[Callable]
|
# Move a frame to x=500 over 2 seconds with easing
|
||||||
|
anim = mcrfpy.Animation('x', 500.0, 2.0, 'easeInOut')
|
||||||
def __init__(self, target: Any, property: str, start_value: Any, end_value: Any,
|
anim.start(my_frame)
|
||||||
duration: float, easing: str = 'linear', loop: bool = False,
|
|
||||||
on_complete: Optional[Callable] = None) -> None: ...
|
# Animate color with completion callback
|
||||||
|
def on_done(anim, target):
|
||||||
def start(self) -> None:
|
print('Fade complete!')
|
||||||
"""Start the animation."""
|
fade = mcrfpy.Animation('fill_color.a', 0, 1.0, callback=on_done)
|
||||||
|
fade.start(my_sprite)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def property(self) -> str:
|
||||||
|
"""Target property name being animated (read-only)."""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self) -> float:
|
||||||
|
"""Animation duration in seconds (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elapsed(self) -> float:
|
||||||
|
"""Time elapsed since animation started in seconds (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_complete(self) -> bool:
|
||||||
|
"""Whether the animation has finished (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_delta(self) -> bool:
|
||||||
|
"""Whether animation uses delta/additive mode (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
property: str,
|
||||||
|
target: Union[float, int, Tuple[float, float], Tuple[int, int, int], Tuple[int, int, int, int], List[int], str],
|
||||||
|
duration: float,
|
||||||
|
easing: str = 'linear',
|
||||||
|
delta: bool = False,
|
||||||
|
callback: Optional[Callable[['Animation', Any], None]] = None) -> None:
|
||||||
|
"""Create an animation for a UI property.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
property: Property name to animate. Common properties:
|
||||||
|
- Position/Size: 'x', 'y', 'w', 'h', 'pos', 'size'
|
||||||
|
- Appearance: 'fill_color', 'outline_color', 'opacity'
|
||||||
|
- Sprite: 'sprite_index', 'scale'
|
||||||
|
- Grid: 'center', 'zoom'
|
||||||
|
- Sub-properties: 'fill_color.r', 'fill_color.g', etc.
|
||||||
|
target: Target value. Type depends on property:
|
||||||
|
- float: For x, y, w, h, scale, opacity, zoom
|
||||||
|
- int: For sprite_index
|
||||||
|
- (r, g, b) or (r, g, b, a): For colors
|
||||||
|
- (x, y): For pos, size, center
|
||||||
|
- [int, ...]: For sprite animation sequences
|
||||||
|
- str: For text animation
|
||||||
|
duration: Animation duration in seconds.
|
||||||
|
easing: Easing function. Options: 'linear', 'easeIn', 'easeOut',
|
||||||
|
'easeInOut', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
|
||||||
|
'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
|
||||||
|
'easeInElastic', 'easeOutElastic', 'easeInOutElastic',
|
||||||
|
'easeInBounce', 'easeOutBounce', 'easeInOutBounce', and more.
|
||||||
|
delta: If True, target value is added to start value.
|
||||||
|
callback: Function(animation, target) called on completion.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def start(self, target: UIElement, conflict_mode: str = 'replace') -> None:
|
||||||
|
"""Start the animation on a UI element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)
|
||||||
|
conflict_mode: How to handle if property is already animating:
|
||||||
|
- 'replace': Stop existing animation, start new one (default)
|
||||||
|
- 'queue': Wait for existing animation to complete
|
||||||
|
- 'error': Raise RuntimeError if property is busy
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
def update(self, dt: float) -> bool:
|
def update(self, dt: float) -> bool:
|
||||||
"""Update animation, returns True if still running."""
|
"""Update animation by time delta. Returns True if still running.
|
||||||
|
|
||||||
|
Note: Normally called automatically by AnimationManager.
|
||||||
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_current_value(self) -> Any:
|
def get_current_value(self) -> Any:
|
||||||
"""Get the current interpolated value."""
|
"""Get the current interpolated value."""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def complete(self) -> None:
|
||||||
|
"""Complete the animation immediately, jumping to final value."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def hasValidTarget(self) -> bool:
|
||||||
|
"""Check if the animation target still exists."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return string representation showing property, duration, and status."""
|
||||||
|
...
|
||||||
|
|
||||||
# Module-level attributes
|
# Module-level attributes
|
||||||
|
|
||||||
__version__: str
|
__version__: str
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue