Implement comprehensive animation system (closes #59)
- Add Animation class with 30+ easing functions (linear, ease in/out, quad, cubic, elastic, bounce, etc.) - Add property system to all UI classes for animation support: - UIFrame: position, size, colors (including individual r/g/b/a components) - UICaption: position, size, text, colors - UISprite: position, scale, sprite_number (with sequence support) - UIGrid: position, size, camera center, zoom - UIEntity: position, sprite properties - Create AnimationManager singleton for frame-based updates - Add Python bindings through PyAnimation wrapper - Support for delta animations (relative values) - Fix segfault when running scripts directly (mcrf_module initialization) - Fix headless/windowed mode behavior to respect --headless flag - Animations run purely in C++ without Python callbacks per frame All UI properties are now animatable with smooth interpolation and professional easing curves. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
dd3c64784d
commit
70cf44f8f0
|
@ -0,0 +1,527 @@
|
||||||
|
#include "Animation.h"
|
||||||
|
#include "UIDrawable.h"
|
||||||
|
#include "UIEntity.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Animation implementation
|
||||||
|
Animation::Animation(const std::string& targetProperty,
|
||||||
|
const AnimationValue& targetValue,
|
||||||
|
float duration,
|
||||||
|
EasingFunction easingFunc,
|
||||||
|
bool delta)
|
||||||
|
: targetProperty(targetProperty)
|
||||||
|
, targetValue(targetValue)
|
||||||
|
, duration(duration)
|
||||||
|
, easingFunc(easingFunc)
|
||||||
|
, delta(delta)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::start(UIDrawable* target) {
|
||||||
|
currentTarget = target;
|
||||||
|
elapsed = 0.0f;
|
||||||
|
|
||||||
|
// Capture startValue from target based on targetProperty
|
||||||
|
if (!currentTarget) return;
|
||||||
|
|
||||||
|
// Try to get the current value based on the expected type
|
||||||
|
std::visit([this](const auto& targetVal) {
|
||||||
|
using T = std::decay_t<decltype(targetVal)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
float value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
int value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// For sprite animation, get current sprite index
|
||||||
|
int value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
sf::Color value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
sf::Vector2f value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
std::string value;
|
||||||
|
if (currentTarget->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::startEntity(UIEntity* target) {
|
||||||
|
currentEntityTarget = target;
|
||||||
|
currentTarget = nullptr; // Clear drawable target
|
||||||
|
elapsed = 0.0f;
|
||||||
|
|
||||||
|
// Capture the starting value from the entity
|
||||||
|
std::visit([this, target](const auto& val) {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
float value = 0.0f;
|
||||||
|
if (target->getProperty(targetProperty, value)) {
|
||||||
|
startValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
// For entities, we might need to handle sprite_number differently
|
||||||
|
if (targetProperty == "sprite_number") {
|
||||||
|
startValue = target->sprite.getSpriteIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Entities don't support other types yet
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Animation::update(float deltaTime) {
|
||||||
|
if ((!currentTarget && !currentEntityTarget) || isComplete()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed += deltaTime;
|
||||||
|
elapsed = std::min(elapsed, duration);
|
||||||
|
|
||||||
|
// Calculate easing value (0.0 to 1.0)
|
||||||
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
||||||
|
float easedT = easingFunc(t);
|
||||||
|
|
||||||
|
// Get interpolated value
|
||||||
|
AnimationValue currentValue = interpolate(easedT);
|
||||||
|
|
||||||
|
// Apply currentValue to target (either drawable or entity)
|
||||||
|
std::visit([this](const auto& value) {
|
||||||
|
using T = std::decay_t<decltype(value)>;
|
||||||
|
|
||||||
|
if (currentTarget) {
|
||||||
|
// Handle UIDrawable targets
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
currentTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (currentEntityTarget) {
|
||||||
|
// Handle UIEntity targets
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
currentEntityTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
currentEntityTarget->setProperty(targetProperty, value);
|
||||||
|
}
|
||||||
|
// Entities don't support other types yet
|
||||||
|
}
|
||||||
|
}, currentValue);
|
||||||
|
|
||||||
|
return !isComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationValue Animation::getCurrentValue() const {
|
||||||
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
||||||
|
float easedT = easingFunc(t);
|
||||||
|
return interpolate(easedT);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationValue Animation::interpolate(float t) const {
|
||||||
|
// Visit the variant to perform type-specific interpolation
|
||||||
|
return std::visit([this, t](const auto& target) -> AnimationValue {
|
||||||
|
using T = std::decay_t<decltype(target)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
// Interpolate float
|
||||||
|
const float* start = std::get_if<float>(&startValue);
|
||||||
|
if (!start) return target; // Type mismatch
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
return *start + target * t;
|
||||||
|
} else {
|
||||||
|
return *start + (target - *start) * t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
// Interpolate integer
|
||||||
|
const int* start = std::get_if<int>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
float result;
|
||||||
|
if (delta) {
|
||||||
|
result = *start + target * t;
|
||||||
|
} else {
|
||||||
|
result = *start + (target - *start) * t;
|
||||||
|
}
|
||||||
|
return static_cast<int>(std::round(result));
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// For sprite animation, interpolate through the list
|
||||||
|
if (target.empty()) return target;
|
||||||
|
|
||||||
|
// Map t to an index in the vector
|
||||||
|
size_t index = static_cast<size_t>(t * (target.size() - 1));
|
||||||
|
index = std::min(index, target.size() - 1);
|
||||||
|
return static_cast<int>(target[index]);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
// Interpolate color
|
||||||
|
const sf::Color* start = std::get_if<sf::Color>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
sf::Color result;
|
||||||
|
if (delta) {
|
||||||
|
result.r = std::clamp(start->r + target.r * t, 0.0f, 255.0f);
|
||||||
|
result.g = std::clamp(start->g + target.g * t, 0.0f, 255.0f);
|
||||||
|
result.b = std::clamp(start->b + target.b * t, 0.0f, 255.0f);
|
||||||
|
result.a = std::clamp(start->a + target.a * t, 0.0f, 255.0f);
|
||||||
|
} else {
|
||||||
|
result.r = start->r + (target.r - start->r) * t;
|
||||||
|
result.g = start->g + (target.g - start->g) * t;
|
||||||
|
result.b = start->b + (target.b - start->b) * t;
|
||||||
|
result.a = start->a + (target.a - start->a) * t;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
// Interpolate vector
|
||||||
|
const sf::Vector2f* start = std::get_if<sf::Vector2f>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
return sf::Vector2f(start->x + target.x * t,
|
||||||
|
start->y + target.y * t);
|
||||||
|
} else {
|
||||||
|
return sf::Vector2f(start->x + (target.x - start->x) * t,
|
||||||
|
start->y + (target.y - start->y) * t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
// For text, show characters based on t
|
||||||
|
const std::string* start = std::get_if<std::string>(&startValue);
|
||||||
|
if (!start) return target;
|
||||||
|
|
||||||
|
// If delta mode, append characters from target
|
||||||
|
if (delta) {
|
||||||
|
size_t chars = static_cast<size_t>(target.length() * t);
|
||||||
|
return *start + target.substr(0, chars);
|
||||||
|
} else {
|
||||||
|
// Transition from start text to target text
|
||||||
|
if (t < 0.5f) {
|
||||||
|
// First half: remove characters from start
|
||||||
|
size_t chars = static_cast<size_t>(start->length() * (1.0f - t * 2.0f));
|
||||||
|
return start->substr(0, chars);
|
||||||
|
} else {
|
||||||
|
// Second half: add characters to target
|
||||||
|
size_t chars = static_cast<size_t>(target.length() * ((t - 0.5f) * 2.0f));
|
||||||
|
return target.substr(0, chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target; // Fallback
|
||||||
|
}, targetValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Easing functions implementation
|
||||||
|
namespace EasingFunctions {
|
||||||
|
|
||||||
|
float linear(float t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeIn(float t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOut(float t) {
|
||||||
|
return t * (2.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOut(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quadratic
|
||||||
|
float easeInQuad(float t) {
|
||||||
|
return t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutQuad(float t) {
|
||||||
|
return t * (2.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutQuad(float t) {
|
||||||
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cubic
|
||||||
|
float easeInCubic(float t) {
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutCubic(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return t1 * t1 * t1 + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutCubic(float t) {
|
||||||
|
return t < 0.5f ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quartic
|
||||||
|
float easeInQuart(float t) {
|
||||||
|
return t * t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutQuart(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return 1.0f - t1 * t1 * t1 * t1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutQuart(float t) {
|
||||||
|
return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - 8.0f * (t - 1.0f) * (t - 1.0f) * (t - 1.0f) * (t - 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sine
|
||||||
|
float easeInSine(float t) {
|
||||||
|
return 1.0f - std::cos(t * M_PI / 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutSine(float t) {
|
||||||
|
return std::sin(t * M_PI / 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutSine(float t) {
|
||||||
|
return 0.5f * (1.0f - std::cos(M_PI * t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential
|
||||||
|
float easeInExpo(float t) {
|
||||||
|
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutExpo(float t) {
|
||||||
|
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutExpo(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f);
|
||||||
|
} else {
|
||||||
|
return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Circular
|
||||||
|
float easeInCirc(float t) {
|
||||||
|
return 1.0f - std::sqrt(1.0f - t * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutCirc(float t) {
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return std::sqrt(1.0f - t1 * t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutCirc(float t) {
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t));
|
||||||
|
} else {
|
||||||
|
return 0.5f * (std::sqrt(1.0f - (2.0f * t - 2.0f) * (2.0f * t - 2.0f)) + 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elastic
|
||||||
|
float easeInElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.3f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return -(a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.3f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
return a * std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * M_PI) / p) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutElastic(float t) {
|
||||||
|
if (t == 0.0f) return 0.0f;
|
||||||
|
if (t == 1.0f) return 1.0f;
|
||||||
|
float p = 0.45f;
|
||||||
|
float a = 1.0f;
|
||||||
|
float s = p / 4.0f;
|
||||||
|
|
||||||
|
if (t < 0.5f) {
|
||||||
|
float t1 = 2.0f * t - 1.0f;
|
||||||
|
return -0.5f * (a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
||||||
|
} else {
|
||||||
|
float t1 = 2.0f * t - 1.0f;
|
||||||
|
return a * std::pow(2.0f, -10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p) * 0.5f + 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back (overshooting)
|
||||||
|
float easeInBack(float t) {
|
||||||
|
const float s = 1.70158f;
|
||||||
|
return t * t * ((s + 1.0f) * t - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeOutBack(float t) {
|
||||||
|
const float s = 1.70158f;
|
||||||
|
float t1 = t - 1.0f;
|
||||||
|
return t1 * t1 * ((s + 1.0f) * t1 + s) + 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutBack(float t) {
|
||||||
|
const float s = 1.70158f * 1.525f;
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * (4.0f * t * t * ((s + 1.0f) * 2.0f * t - s));
|
||||||
|
} else {
|
||||||
|
float t1 = 2.0f * t - 2.0f;
|
||||||
|
return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounce
|
||||||
|
float easeOutBounce(float t) {
|
||||||
|
if (t < 1.0f / 2.75f) {
|
||||||
|
return 7.5625f * t * t;
|
||||||
|
} else if (t < 2.0f / 2.75f) {
|
||||||
|
float t1 = t - 1.5f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.75f;
|
||||||
|
} else if (t < 2.5f / 2.75f) {
|
||||||
|
float t1 = t - 2.25f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.9375f;
|
||||||
|
} else {
|
||||||
|
float t1 = t - 2.625f / 2.75f;
|
||||||
|
return 7.5625f * t1 * t1 + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInBounce(float t) {
|
||||||
|
return 1.0f - easeOutBounce(1.0f - t);
|
||||||
|
}
|
||||||
|
|
||||||
|
float easeInOutBounce(float t) {
|
||||||
|
if (t < 0.5f) {
|
||||||
|
return 0.5f * easeInBounce(2.0f * t);
|
||||||
|
} else {
|
||||||
|
return 0.5f * easeOutBounce(2.0f * t - 1.0f) + 0.5f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get easing function by name
|
||||||
|
EasingFunction getByName(const std::string& name) {
|
||||||
|
static std::unordered_map<std::string, EasingFunction> easingMap = {
|
||||||
|
{"linear", linear},
|
||||||
|
{"easeIn", easeIn},
|
||||||
|
{"easeOut", easeOut},
|
||||||
|
{"easeInOut", easeInOut},
|
||||||
|
{"easeInQuad", easeInQuad},
|
||||||
|
{"easeOutQuad", easeOutQuad},
|
||||||
|
{"easeInOutQuad", easeInOutQuad},
|
||||||
|
{"easeInCubic", easeInCubic},
|
||||||
|
{"easeOutCubic", easeOutCubic},
|
||||||
|
{"easeInOutCubic", easeInOutCubic},
|
||||||
|
{"easeInQuart", easeInQuart},
|
||||||
|
{"easeOutQuart", easeOutQuart},
|
||||||
|
{"easeInOutQuart", easeInOutQuart},
|
||||||
|
{"easeInSine", easeInSine},
|
||||||
|
{"easeOutSine", easeOutSine},
|
||||||
|
{"easeInOutSine", easeInOutSine},
|
||||||
|
{"easeInExpo", easeInExpo},
|
||||||
|
{"easeOutExpo", easeOutExpo},
|
||||||
|
{"easeInOutExpo", easeInOutExpo},
|
||||||
|
{"easeInCirc", easeInCirc},
|
||||||
|
{"easeOutCirc", easeOutCirc},
|
||||||
|
{"easeInOutCirc", easeInOutCirc},
|
||||||
|
{"easeInElastic", easeInElastic},
|
||||||
|
{"easeOutElastic", easeOutElastic},
|
||||||
|
{"easeInOutElastic", easeInOutElastic},
|
||||||
|
{"easeInBack", easeInBack},
|
||||||
|
{"easeOutBack", easeOutBack},
|
||||||
|
{"easeInOutBack", easeInOutBack},
|
||||||
|
{"easeInBounce", easeInBounce},
|
||||||
|
{"easeOutBounce", easeOutBounce},
|
||||||
|
{"easeInOutBounce", easeInOutBounce}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = easingMap.find(name);
|
||||||
|
if (it != easingMap.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return linear; // Default to linear
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace EasingFunctions
|
||||||
|
|
||||||
|
// AnimationManager implementation
|
||||||
|
AnimationManager& AnimationManager::getInstance() {
|
||||||
|
static AnimationManager instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
|
||||||
|
activeAnimations.push_back(animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::update(float deltaTime) {
|
||||||
|
for (auto& anim : activeAnimations) {
|
||||||
|
anim->update(deltaTime);
|
||||||
|
}
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::cleanup() {
|
||||||
|
activeAnimations.erase(
|
||||||
|
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
|
||||||
|
[](const std::shared_ptr<Animation>& anim) {
|
||||||
|
return anim->isComplete();
|
||||||
|
}),
|
||||||
|
activeAnimations.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimationManager::clear() {
|
||||||
|
activeAnimations.clear();
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
#include <SFML/Graphics.hpp>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class UIDrawable;
|
||||||
|
class UIEntity;
|
||||||
|
|
||||||
|
// Forward declare namespace
|
||||||
|
namespace EasingFunctions {
|
||||||
|
float linear(float t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Easing function type
|
||||||
|
typedef std::function<float(float)> EasingFunction;
|
||||||
|
|
||||||
|
// Animation target value can be various types
|
||||||
|
typedef std::variant<
|
||||||
|
float, // Single float value
|
||||||
|
int, // Single integer value
|
||||||
|
std::vector<int>, // List of integers (for sprite animation)
|
||||||
|
sf::Color, // Color animation
|
||||||
|
sf::Vector2f, // Vector animation
|
||||||
|
std::string // String animation (for text)
|
||||||
|
> AnimationValue;
|
||||||
|
|
||||||
|
class Animation {
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
Animation(const std::string& targetProperty,
|
||||||
|
const AnimationValue& targetValue,
|
||||||
|
float duration,
|
||||||
|
EasingFunction easingFunc = EasingFunctions::linear,
|
||||||
|
bool delta = false);
|
||||||
|
|
||||||
|
// Apply this animation to a drawable
|
||||||
|
void start(UIDrawable* target);
|
||||||
|
|
||||||
|
// Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable)
|
||||||
|
void startEntity(UIEntity* target);
|
||||||
|
|
||||||
|
// Update animation (called each frame)
|
||||||
|
// Returns true if animation is still running, false if complete
|
||||||
|
bool update(float deltaTime);
|
||||||
|
|
||||||
|
// Get current interpolated value
|
||||||
|
AnimationValue getCurrentValue() const;
|
||||||
|
|
||||||
|
// Animation properties
|
||||||
|
std::string getTargetProperty() const { return targetProperty; }
|
||||||
|
float getDuration() const { return duration; }
|
||||||
|
float getElapsed() const { return elapsed; }
|
||||||
|
bool isComplete() const { return elapsed >= duration; }
|
||||||
|
bool isDelta() const { return delta; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_number")
|
||||||
|
AnimationValue startValue; // Starting value (captured when animation starts)
|
||||||
|
AnimationValue targetValue; // Target value to animate to
|
||||||
|
float duration; // Animation duration in seconds
|
||||||
|
float elapsed = 0.0f; // Elapsed time
|
||||||
|
EasingFunction easingFunc; // Easing function to use
|
||||||
|
bool delta; // If true, targetValue is relative to start
|
||||||
|
|
||||||
|
UIDrawable* currentTarget = nullptr; // Current target being animated
|
||||||
|
UIEntity* currentEntityTarget = nullptr; // Current entity target (alternative to drawable)
|
||||||
|
|
||||||
|
// Helper to interpolate between values
|
||||||
|
AnimationValue interpolate(float t) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Easing functions library
|
||||||
|
namespace EasingFunctions {
|
||||||
|
// Basic easing functions
|
||||||
|
float linear(float t);
|
||||||
|
float easeIn(float t);
|
||||||
|
float easeOut(float t);
|
||||||
|
float easeInOut(float t);
|
||||||
|
|
||||||
|
// Advanced easing functions
|
||||||
|
float easeInQuad(float t);
|
||||||
|
float easeOutQuad(float t);
|
||||||
|
float easeInOutQuad(float t);
|
||||||
|
|
||||||
|
float easeInCubic(float t);
|
||||||
|
float easeOutCubic(float t);
|
||||||
|
float easeInOutCubic(float t);
|
||||||
|
|
||||||
|
float easeInQuart(float t);
|
||||||
|
float easeOutQuart(float t);
|
||||||
|
float easeInOutQuart(float t);
|
||||||
|
|
||||||
|
float easeInSine(float t);
|
||||||
|
float easeOutSine(float t);
|
||||||
|
float easeInOutSine(float t);
|
||||||
|
|
||||||
|
float easeInExpo(float t);
|
||||||
|
float easeOutExpo(float t);
|
||||||
|
float easeInOutExpo(float t);
|
||||||
|
|
||||||
|
float easeInCirc(float t);
|
||||||
|
float easeOutCirc(float t);
|
||||||
|
float easeInOutCirc(float t);
|
||||||
|
|
||||||
|
float easeInElastic(float t);
|
||||||
|
float easeOutElastic(float t);
|
||||||
|
float easeInOutElastic(float t);
|
||||||
|
|
||||||
|
float easeInBack(float t);
|
||||||
|
float easeOutBack(float t);
|
||||||
|
float easeInOutBack(float t);
|
||||||
|
|
||||||
|
float easeInBounce(float t);
|
||||||
|
float easeOutBounce(float t);
|
||||||
|
float easeInOutBounce(float t);
|
||||||
|
|
||||||
|
// Get easing function by name
|
||||||
|
EasingFunction getByName(const std::string& name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation manager to handle active animations
|
||||||
|
class AnimationManager {
|
||||||
|
public:
|
||||||
|
static AnimationManager& getInstance();
|
||||||
|
|
||||||
|
// Add an animation to be managed
|
||||||
|
void addAnimation(std::shared_ptr<Animation> animation);
|
||||||
|
|
||||||
|
// Update all animations
|
||||||
|
void update(float deltaTime);
|
||||||
|
|
||||||
|
// Remove completed animations
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
// Clear all animations
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationManager() = default;
|
||||||
|
std::vector<std::shared_ptr<Animation>> activeAnimations;
|
||||||
|
};
|
|
@ -4,6 +4,7 @@
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
#include "UITestScene.h"
|
#include "UITestScene.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
|
||||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||||
{
|
{
|
||||||
|
@ -114,11 +115,18 @@ void GameEngine::run()
|
||||||
{
|
{
|
||||||
std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
||||||
float fps = 0.0;
|
float fps = 0.0;
|
||||||
|
frameTime = 0.016f; // Initialize to ~60 FPS
|
||||||
clock.restart();
|
clock.restart();
|
||||||
while (running)
|
while (running)
|
||||||
{
|
{
|
||||||
currentScene()->update();
|
currentScene()->update();
|
||||||
testTimers();
|
testTimers();
|
||||||
|
|
||||||
|
// Update animations (only if frameTime is valid)
|
||||||
|
if (frameTime > 0.0f && frameTime < 1.0f) {
|
||||||
|
AnimationManager::getInstance().update(frameTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (!headless) {
|
if (!headless) {
|
||||||
sUserInput();
|
sUserInput();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "McRFPy_Automation.h"
|
#include "McRFPy_Automation.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
#include "PyAnimation.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "UI.h"
|
#include "UI.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
|
@ -76,6 +77,9 @@ PyObject* PyInit_mcrfpy()
|
||||||
/*collections & iterators*/
|
/*collections & iterators*/
|
||||||
&PyUICollectionType, &PyUICollectionIterType,
|
&PyUICollectionType, &PyUICollectionIterType,
|
||||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||||
|
|
||||||
|
/*animation*/
|
||||||
|
&PyAnimationType,
|
||||||
nullptr};
|
nullptr};
|
||||||
int i = 0;
|
int i = 0;
|
||||||
auto t = pytypes[i];
|
auto t = pytypes[i];
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
#include "PyAnimation.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
#include "UIDrawable.h"
|
||||||
|
#include "UIFrame.h"
|
||||||
|
#include "UICaption.h"
|
||||||
|
#include "UISprite.h"
|
||||||
|
#include "UIGrid.h"
|
||||||
|
#include "UIEntity.h"
|
||||||
|
#include "UI.h" // For the PyTypeObject definitions
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
PyObject* PyAnimation::create(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
|
PyAnimationObject* self = (PyAnimationObject*)type->tp_alloc(type, 0);
|
||||||
|
if (self != NULL) {
|
||||||
|
// Will be initialized in init
|
||||||
|
}
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", nullptr};
|
||||||
|
|
||||||
|
const char* property_name;
|
||||||
|
PyObject* target_value;
|
||||||
|
float duration;
|
||||||
|
const char* easing_name = "linear";
|
||||||
|
int delta = 0;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|sp", const_cast<char**>(keywords),
|
||||||
|
&property_name, &target_value, &duration, &easing_name, &delta)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Python target value to AnimationValue
|
||||||
|
AnimationValue animValue;
|
||||||
|
|
||||||
|
if (PyFloat_Check(target_value)) {
|
||||||
|
animValue = static_cast<float>(PyFloat_AsDouble(target_value));
|
||||||
|
}
|
||||||
|
else if (PyLong_Check(target_value)) {
|
||||||
|
animValue = static_cast<int>(PyLong_AsLong(target_value));
|
||||||
|
}
|
||||||
|
else if (PyList_Check(target_value)) {
|
||||||
|
// List of integers for sprite animation
|
||||||
|
std::vector<int> indices;
|
||||||
|
Py_ssize_t size = PyList_Size(target_value);
|
||||||
|
for (Py_ssize_t i = 0; i < size; i++) {
|
||||||
|
PyObject* item = PyList_GetItem(target_value, i);
|
||||||
|
if (PyLong_Check(item)) {
|
||||||
|
indices.push_back(PyLong_AsLong(item));
|
||||||
|
} else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Sprite animation list must contain only integers");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animValue = indices;
|
||||||
|
}
|
||||||
|
else if (PyTuple_Check(target_value)) {
|
||||||
|
Py_ssize_t size = PyTuple_Size(target_value);
|
||||||
|
if (size == 2) {
|
||||||
|
// Vector2f
|
||||||
|
float x = PyFloat_AsDouble(PyTuple_GetItem(target_value, 0));
|
||||||
|
float y = PyFloat_AsDouble(PyTuple_GetItem(target_value, 1));
|
||||||
|
animValue = sf::Vector2f(x, y);
|
||||||
|
}
|
||||||
|
else if (size == 3 || size == 4) {
|
||||||
|
// Color (RGB or RGBA)
|
||||||
|
int r = PyLong_AsLong(PyTuple_GetItem(target_value, 0));
|
||||||
|
int g = PyLong_AsLong(PyTuple_GetItem(target_value, 1));
|
||||||
|
int b = PyLong_AsLong(PyTuple_GetItem(target_value, 2));
|
||||||
|
int a = size == 4 ? PyLong_AsLong(PyTuple_GetItem(target_value, 3)) : 255;
|
||||||
|
animValue = sf::Color(r, g, b, a);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Tuple must have 2 elements (vector) or 3-4 elements (color)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (PyUnicode_Check(target_value)) {
|
||||||
|
// String for text animation
|
||||||
|
const char* str = PyUnicode_AsUTF8(target_value);
|
||||||
|
animValue = std::string(str);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get easing function
|
||||||
|
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
|
||||||
|
|
||||||
|
// Create the Animation
|
||||||
|
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyAnimation::dealloc(PyAnimationObject* self) {
|
||||||
|
self->data.reset();
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_duration(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyFloat_FromDouble(self->data->getDuration());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_elapsed(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyFloat_FromDouble(self->data->getElapsed());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_is_complete(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyBool_FromLong(self->data->isComplete());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_is_delta(PyAnimationObject* self, void* closure) {
|
||||||
|
return PyBool_FromLong(self->data->isDelta());
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::start(PyAnimationObject* self, PyObject* args) {
|
||||||
|
PyObject* target_obj;
|
||||||
|
if (!PyArg_ParseTuple(args, "O", &target_obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the UIDrawable from the Python object
|
||||||
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
|
// Check type by comparing type names
|
||||||
|
const char* type_name = Py_TYPE(target_obj)->tp_name;
|
||||||
|
|
||||||
|
if (strcmp(type_name, "mcrfpy.Frame") == 0) {
|
||||||
|
PyUIFrameObject* frame = (PyUIFrameObject*)target_obj;
|
||||||
|
drawable = frame->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Caption") == 0) {
|
||||||
|
PyUICaptionObject* caption = (PyUICaptionObject*)target_obj;
|
||||||
|
drawable = caption->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Sprite") == 0) {
|
||||||
|
PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj;
|
||||||
|
drawable = sprite->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Grid") == 0) {
|
||||||
|
PyUIGridObject* grid = (PyUIGridObject*)target_obj;
|
||||||
|
drawable = grid->data.get();
|
||||||
|
}
|
||||||
|
else if (strcmp(type_name, "mcrfpy.Entity") == 0) {
|
||||||
|
// Special handling for Entity since it doesn't inherit from UIDrawable
|
||||||
|
PyUIEntityObject* entity = (PyUIEntityObject*)target_obj;
|
||||||
|
// Start the animation directly on the entity
|
||||||
|
self->data->startEntity(entity->data.get());
|
||||||
|
|
||||||
|
// Add to AnimationManager
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the animation
|
||||||
|
self->data->start(drawable);
|
||||||
|
|
||||||
|
// Add to AnimationManager
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::update(PyAnimationObject* self, PyObject* args) {
|
||||||
|
float deltaTime;
|
||||||
|
if (!PyArg_ParseTuple(args, "f", &deltaTime)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool still_running = self->data->update(deltaTime);
|
||||||
|
return PyBool_FromLong(still_running);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::get_current_value(PyAnimationObject* self, PyObject* args) {
|
||||||
|
AnimationValue value = self->data->getCurrentValue();
|
||||||
|
|
||||||
|
// Convert AnimationValue back to Python
|
||||||
|
return std::visit([](const auto& val) -> PyObject* {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
return PyFloat_FromDouble(val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
return PyLong_FromLong(val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
// This shouldn't happen as we interpolate to int
|
||||||
|
return PyLong_FromLong(0);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
return Py_BuildValue("(iiii)", val.r, val.g, val.b, val.a);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
return Py_BuildValue("(ff)", val.x, val.y);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
return PyUnicode_FromString(val.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyGetSetDef PyAnimation::getsetters[] = {
|
||||||
|
{"property", (getter)get_property, NULL, "Target property name", NULL},
|
||||||
|
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
|
||||||
|
{"elapsed", (getter)get_elapsed, NULL, "Elapsed time in seconds", NULL},
|
||||||
|
{"is_complete", (getter)get_is_complete, NULL, "Whether animation is complete", NULL},
|
||||||
|
{"is_delta", (getter)get_is_delta, NULL, "Whether animation uses delta mode", NULL},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMethodDef PyAnimation::methods[] = {
|
||||||
|
{"start", (PyCFunction)start, METH_VARARGS,
|
||||||
|
"Start the animation on a target UIDrawable"},
|
||||||
|
{"update", (PyCFunction)update, METH_VARARGS,
|
||||||
|
"Update the animation by deltaTime (returns True if still running)"},
|
||||||
|
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
||||||
|
"Get the current interpolated value"},
|
||||||
|
{NULL}
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include "structmember.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
std::shared_ptr<Animation> data;
|
||||||
|
} PyAnimationObject;
|
||||||
|
|
||||||
|
class PyAnimation {
|
||||||
|
public:
|
||||||
|
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
|
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
static void dealloc(PyAnimationObject* self);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_duration(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_elapsed(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_is_complete(PyAnimationObject* self, void* closure);
|
||||||
|
static PyObject* get_is_delta(PyAnimationObject* self, void* closure);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
static PyObject* start(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* update(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* get_current_value(PyAnimationObject* self, PyObject* args);
|
||||||
|
|
||||||
|
static PyGetSetDef getsetters[];
|
||||||
|
static PyMethodDef methods[];
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace mcrfpydef {
|
||||||
|
static PyTypeObject PyAnimationType = {
|
||||||
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
.tp_name = "mcrfpy.Animation",
|
||||||
|
.tp_basicsize = sizeof(PyAnimationObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_doc = PyDoc_STR("Animation object for animating UI properties"),
|
||||||
|
.tp_methods = PyAnimation::methods,
|
||||||
|
.tp_getset = PyAnimation::getsetters,
|
||||||
|
.tp_init = (initproc)PyAnimation::init,
|
||||||
|
.tp_new = PyAnimation::create,
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
#include "ActionCode.h"
|
#include "ActionCode.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "PyCallable.h"
|
#include "PyCallable.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
PyScene::PyScene(GameEngine* g) : Scene(g)
|
PyScene::PyScene(GameEngine* g) : Scene(g)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +67,16 @@ void PyScene::render()
|
||||||
{
|
{
|
||||||
game->getRenderTarget().clear();
|
game->getRenderTarget().clear();
|
||||||
|
|
||||||
|
// Create a copy of the vector to sort by z_index
|
||||||
auto vec = *ui_elements;
|
auto vec = *ui_elements;
|
||||||
|
|
||||||
|
// Sort by z_index (lower values rendered first)
|
||||||
|
std::sort(vec.begin(), vec.end(),
|
||||||
|
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
|
||||||
|
return a->z_index < b->z_index;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render in sorted order
|
||||||
for (auto e: vec)
|
for (auto e: vec)
|
||||||
{
|
{
|
||||||
if (e)
|
if (e)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "PyColor.h"
|
#include "PyColor.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
UIDrawable* UICaption::click_at(sf::Vector2f point)
|
UIDrawable* UICaption::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
@ -198,6 +199,7 @@ PyGetSetDef UICaption::getsetters[] = {
|
||||||
{"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},
|
||||||
{"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text size (integer) in points", (void*)5},
|
{"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text size (integer) in points", (void*)5},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -294,3 +296,172 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UICaption::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
text.setPosition(sf::Vector2f(value, text.getPosition().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
text.setPosition(sf::Vector2f(text.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
text.setCharacterSize(static_cast<unsigned int>(value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline") {
|
||||||
|
text.setOutlineThickness(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.r") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.g") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.b") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.a") {
|
||||||
|
auto color = text.getFillColor();
|
||||||
|
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setFillColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.r") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.g") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.b") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.a") {
|
||||||
|
auto color = text.getOutlineColor();
|
||||||
|
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
|
||||||
|
text.setOutlineColor(color);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::setProperty(const std::string& name, const sf::Color& value) {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
text.setFillColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color") {
|
||||||
|
text.setOutlineColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::setProperty(const std::string& name, const std::string& value) {
|
||||||
|
if (name == "text") {
|
||||||
|
text.setString(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = text.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = text.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
value = static_cast<float>(text.getCharacterSize());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline") {
|
||||||
|
value = text.getOutlineThickness();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.r") {
|
||||||
|
value = text.getFillColor().r;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.g") {
|
||||||
|
value = text.getFillColor().g;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.b") {
|
||||||
|
value = text.getFillColor().b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "fill_color.a") {
|
||||||
|
value = text.getFillColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.r") {
|
||||||
|
value = text.getOutlineColor().r;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.g") {
|
||||||
|
value = text.getOutlineColor().g;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.b") {
|
||||||
|
value = text.getOutlineColor().b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color.a") {
|
||||||
|
value = text.getOutlineColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, sf::Color& value) const {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
value = text.getFillColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "outline_color") {
|
||||||
|
value = text.getOutlineColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UICaption::getProperty(const std::string& name, std::string& value) const {
|
||||||
|
if (name == "text") {
|
||||||
|
value = text.getString();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,15 @@ public:
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||||
|
bool setProperty(const std::string& name, const std::string& value) override;
|
||||||
|
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||||
|
bool getProperty(const std::string& name, std::string& value) const override;
|
||||||
|
|
||||||
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
|
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
|
||||||
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
|
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
using namespace mcrfpydef;
|
using namespace mcrfpydef;
|
||||||
|
|
||||||
|
@ -173,6 +174,12 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
||||||
// if not UIDrawable subclass, reject it
|
// if not UIDrawable subclass, reject it
|
||||||
// self->data->push_back( c++ object inside o );
|
// self->data->push_back( c++ object inside o );
|
||||||
|
|
||||||
|
// Ensure module is initialized
|
||||||
|
if (!McRFPy_API::mcrf_module) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// this would be a great use case for .tp_base
|
// this would be a great use case for .tp_base
|
||||||
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
|
||||||
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
|
||||||
|
@ -184,24 +191,40 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate z_index for the new element
|
||||||
|
int new_z_index = 0;
|
||||||
|
if (!self->data->empty()) {
|
||||||
|
// Get the z_index of the last element and add 10
|
||||||
|
int last_z = self->data->back()->z_index;
|
||||||
|
if (last_z <= INT_MAX - 10) {
|
||||||
|
new_z_index = last_z + 10;
|
||||||
|
} else {
|
||||||
|
new_z_index = INT_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
|
||||||
{
|
{
|
||||||
PyUIFrameObject* frame = (PyUIFrameObject*)o;
|
PyUIFrameObject* frame = (PyUIFrameObject*)o;
|
||||||
|
frame->data->z_index = new_z_index;
|
||||||
self->data->push_back(frame->data);
|
self->data->push_back(frame->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
|
||||||
{
|
{
|
||||||
PyUICaptionObject* caption = (PyUICaptionObject*)o;
|
PyUICaptionObject* caption = (PyUICaptionObject*)o;
|
||||||
|
caption->data->z_index = new_z_index;
|
||||||
self->data->push_back(caption->data);
|
self->data->push_back(caption->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
|
||||||
{
|
{
|
||||||
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
|
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
|
||||||
|
sprite->data->z_index = new_z_index;
|
||||||
self->data->push_back(sprite->data);
|
self->data->push_back(sprite->data);
|
||||||
}
|
}
|
||||||
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
|
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
|
||||||
{
|
{
|
||||||
PyUIGridObject* grid = (PyUIGridObject*)o;
|
PyUIGridObject* grid = (PyUIGridObject*)o;
|
||||||
|
grid->data->z_index = new_z_index;
|
||||||
self->data->push_back(grid->data);
|
self->data->push_back(grid->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,3 +80,68 @@ void UIDrawable::click_register(PyObject* callable)
|
||||||
{
|
{
|
||||||
click_callable = std::make_unique<PyClickCallable>(callable);
|
click_callable = std::make_unique<PyClickCallable>(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* UIDrawable::get_int(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;
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(drawable->z_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIDrawable::set_int(PyObject* self, PyObject* value, 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;
|
||||||
|
default:
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyLong_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "z_index must be an integer");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long z = PyLong_AsLong(value);
|
||||||
|
if (z == -1 && PyErr_Occurred()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to int range
|
||||||
|
if (z < INT_MIN) z = INT_MIN;
|
||||||
|
if (z > INT_MAX) z = INT_MAX;
|
||||||
|
|
||||||
|
drawable->z_index = static_cast<int>(z);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,24 @@ public:
|
||||||
|
|
||||||
static PyObject* get_click(PyObject* self, void* closure);
|
static PyObject* get_click(PyObject* self, void* closure);
|
||||||
static int set_click(PyObject* self, PyObject* value, void* closure);
|
static int set_click(PyObject* self, PyObject* value, void* closure);
|
||||||
|
static PyObject* get_int(PyObject* self, void* closure);
|
||||||
|
static int set_int(PyObject* self, PyObject* value, void* closure);
|
||||||
|
|
||||||
|
// Z-order for rendering (lower values rendered first, higher values on top)
|
||||||
|
int z_index = 0;
|
||||||
|
|
||||||
|
// Animation support
|
||||||
|
virtual bool setProperty(const std::string& name, float value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, int value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const sf::Color& value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const sf::Vector2f& value) { return false; }
|
||||||
|
virtual bool setProperty(const std::string& name, const std::string& value) { return false; }
|
||||||
|
|
||||||
|
virtual bool getProperty(const std::string& name, float& value) const { return false; }
|
||||||
|
virtual bool getProperty(const std::string& name, int& value) const { return false; }
|
||||||
|
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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -261,3 +261,51 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) {
|
||||||
std::string repr_str = ss.str();
|
std::string repr_str = ss.str();
|
||||||
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UIEntity::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
position.x = value;
|
||||||
|
collision_pos.x = static_cast<int>(value);
|
||||||
|
// Update sprite position based on grid position
|
||||||
|
// Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties
|
||||||
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
position.y = value;
|
||||||
|
collision_pos.y = static_cast<int>(value);
|
||||||
|
// Update sprite position based on grid position
|
||||||
|
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "sprite_scale") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIEntity::setProperty(const std::string& name, int value) {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
sprite.setSpriteIndex(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIEntity::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = position.x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = position.y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "sprite_scale") {
|
||||||
|
value = sprite.getScale().x; // Assuming uniform scale
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ public:
|
||||||
UIEntity();
|
UIEntity();
|
||||||
UIEntity(UIGrid&);
|
UIEntity(UIGrid&);
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value);
|
||||||
|
bool setProperty(const std::string& name, int value);
|
||||||
|
bool getProperty(const std::string& name, float& value) const;
|
||||||
|
|
||||||
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
||||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
150
src/UIFrame.cpp
150
src/UIFrame.cpp
|
@ -215,6 +215,7 @@ PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
|
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (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},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -264,3 +265,152 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
|
||||||
if (err_val) return err_val;
|
if (err_val) return err_val;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Animation property system implementation
|
||||||
|
bool UIFrame::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||||
|
return true;
|
||||||
|
} else if (name == "y") {
|
||||||
|
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
} else if (name == "w") {
|
||||||
|
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
} else if (name == "h") {
|
||||||
|
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline") {
|
||||||
|
box.setOutlineThickness(value);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::setProperty(const std::string& name, const sf::Color& value) {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
box.setFillColor(value);
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color") {
|
||||||
|
box.setOutlineColor(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||||
|
if (name == "position") {
|
||||||
|
box.setPosition(value);
|
||||||
|
return true;
|
||||||
|
} else if (name == "size") {
|
||||||
|
box.setSize(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = box.getPosition().x;
|
||||||
|
return true;
|
||||||
|
} else if (name == "y") {
|
||||||
|
value = box.getPosition().y;
|
||||||
|
return true;
|
||||||
|
} else if (name == "w") {
|
||||||
|
value = box.getSize().x;
|
||||||
|
return true;
|
||||||
|
} else if (name == "h") {
|
||||||
|
value = box.getSize().y;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline") {
|
||||||
|
value = box.getOutlineThickness();
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.r") {
|
||||||
|
value = box.getFillColor().r;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.g") {
|
||||||
|
value = box.getFillColor().g;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.b") {
|
||||||
|
value = box.getFillColor().b;
|
||||||
|
return true;
|
||||||
|
} else if (name == "fill_color.a") {
|
||||||
|
value = box.getFillColor().a;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.r") {
|
||||||
|
value = box.getOutlineColor().r;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.g") {
|
||||||
|
value = box.getOutlineColor().g;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.b") {
|
||||||
|
value = box.getOutlineColor().b;
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color.a") {
|
||||||
|
value = box.getOutlineColor().a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, sf::Color& value) const {
|
||||||
|
if (name == "fill_color") {
|
||||||
|
value = box.getFillColor();
|
||||||
|
return true;
|
||||||
|
} else if (name == "outline_color") {
|
||||||
|
value = box.getOutlineColor();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||||
|
if (name == "position") {
|
||||||
|
value = box.getPosition();
|
||||||
|
return true;
|
||||||
|
} else if (name == "size") {
|
||||||
|
value = box.getSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -42,6 +42,15 @@ public:
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyObject* repr(PyUIFrameObject* self);
|
static PyObject* repr(PyUIFrameObject* self);
|
||||||
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
|
// Animation property system
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Color& value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||||
|
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Color& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
|
|
113
src/UIGrid.cpp
113
src/UIGrid.cpp
|
@ -458,6 +458,7 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (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
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -723,3 +724,115 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self)
|
||||||
Py_DECREF(iterType);
|
Py_DECREF(iterType);
|
||||||
return (PyObject*)iterObj;
|
return (PyObject*)iterObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UIGrid::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
box.setPosition(sf::Vector2f(value, box.getPosition().y));
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
box.setPosition(sf::Vector2f(box.getPosition().x, value));
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "w" || name == "width") {
|
||||||
|
box.setSize(sf::Vector2f(value, box.getSize().y));
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "h" || name == "height") {
|
||||||
|
box.setSize(sf::Vector2f(box.getSize().x, value));
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_x") {
|
||||||
|
center_x = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_y") {
|
||||||
|
center_y = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "zoom") {
|
||||||
|
zoom = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::setProperty(const std::string& name, const sf::Vector2f& value) {
|
||||||
|
if (name == "position") {
|
||||||
|
box.setPosition(value);
|
||||||
|
output.setPosition(box.getPosition());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
box.setSize(value);
|
||||||
|
output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center") {
|
||||||
|
center_x = value.x;
|
||||||
|
center_y = value.y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = box.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = box.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "w" || name == "width") {
|
||||||
|
value = box.getSize().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "h" || name == "height") {
|
||||||
|
value = box.getSize().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_x") {
|
||||||
|
value = center_x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center_y") {
|
||||||
|
value = center_y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "zoom") {
|
||||||
|
value = zoom;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIGrid::getProperty(const std::string& name, sf::Vector2f& value) const {
|
||||||
|
if (name == "position") {
|
||||||
|
value = box.getPosition();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "size") {
|
||||||
|
value = box.getSize();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "center") {
|
||||||
|
value = sf::Vector2f(center_x, center_y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,12 @@ public:
|
||||||
sf::RenderTexture renderTexture;
|
sf::RenderTexture renderTexture;
|
||||||
std::vector<UIGridPoint> points;
|
std::vector<UIGridPoint> points;
|
||||||
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
|
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||||
|
|
||||||
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
|
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
|
||||||
|
|
|
@ -58,7 +58,7 @@ void UISprite::setSpriteIndex(int _sprite_index)
|
||||||
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
|
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
|
||||||
}
|
}
|
||||||
|
|
||||||
sf::Vector2f UISprite::getScale()
|
sf::Vector2f UISprite::getScale() const
|
||||||
{
|
{
|
||||||
return sprite.getScale();
|
return sprite.getScale();
|
||||||
}
|
}
|
||||||
|
@ -202,6 +202,7 @@ PyGetSetDef UISprite::getsetters[] = {
|
||||||
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
|
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
|
||||||
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
|
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
|
||||||
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
|
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
|
||||||
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -245,3 +246,84 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Property system implementation for animations
|
||||||
|
bool UISprite::setProperty(const std::string& name, float value) {
|
||||||
|
if (name == "x") {
|
||||||
|
sprite.setPosition(sf::Vector2f(value, sprite.getPosition().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
sprite.setPosition(sf::Vector2f(sprite.getPosition().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_x") {
|
||||||
|
sprite.setScale(sf::Vector2f(value, sprite.getScale().y));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_y") {
|
||||||
|
sprite.setScale(sf::Vector2f(sprite.getScale().x, value));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = static_cast<int>(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::setProperty(const std::string& name, int value) {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
setSpriteIndex(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
z_index = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::getProperty(const std::string& name, float& value) const {
|
||||||
|
if (name == "x") {
|
||||||
|
value = sprite.getPosition().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "y") {
|
||||||
|
value = sprite.getPosition().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale") {
|
||||||
|
value = sprite.getScale().x; // Assuming uniform scale
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_x") {
|
||||||
|
value = sprite.getScale().x;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "scale_y") {
|
||||||
|
value = sprite.getScale().y;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = static_cast<float>(z_index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISprite::getProperty(const std::string& name, int& value) const {
|
||||||
|
if (name == "sprite_number") {
|
||||||
|
value = sprite_index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (name == "z_index") {
|
||||||
|
value = z_index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ public:
|
||||||
void setPosition(sf::Vector2f);
|
void setPosition(sf::Vector2f);
|
||||||
sf::Vector2f getPosition();
|
sf::Vector2f getPosition();
|
||||||
void setScale(sf::Vector2f);
|
void setScale(sf::Vector2f);
|
||||||
sf::Vector2f getScale();
|
sf::Vector2f getScale() const;
|
||||||
void setSpriteIndex(int);
|
void setSpriteIndex(int);
|
||||||
int getSpriteIndex();
|
int getSpriteIndex();
|
||||||
|
|
||||||
|
@ -41,6 +41,12 @@ public:
|
||||||
std::shared_ptr<PyTexture> getTexture();
|
std::shared_ptr<PyTexture> getTexture();
|
||||||
|
|
||||||
PyObjectsEnum derived_type() override final;
|
PyObjectsEnum derived_type() override final;
|
||||||
|
|
||||||
|
// Property system for animations
|
||||||
|
bool setProperty(const std::string& name, float value) override;
|
||||||
|
bool setProperty(const std::string& name, int value) override;
|
||||||
|
bool getProperty(const std::string& name, float& value) const override;
|
||||||
|
bool getProperty(const std::string& name, int& value) const override;
|
||||||
|
|
||||||
|
|
||||||
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
|
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
|
||||||
|
|
26
src/main.cpp
26
src/main.cpp
|
@ -3,6 +3,8 @@
|
||||||
#include "CommandLineParser.h"
|
#include "CommandLineParser.h"
|
||||||
#include "McRogueFaceConfig.h"
|
#include "McRogueFaceConfig.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
|
#include "PyFont.h"
|
||||||
|
#include "PyTexture.h"
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
@ -44,14 +46,27 @@ int run_game_engine(const McRogueFaceConfig& config)
|
||||||
|
|
||||||
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
|
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
|
||||||
{
|
{
|
||||||
// Create a headless game engine for automation API support
|
// Create a game engine with the requested configuration
|
||||||
McRogueFaceConfig engine_config = config;
|
GameEngine* engine = new GameEngine(config);
|
||||||
engine_config.headless = true; // Force headless mode for Python interpreter
|
|
||||||
GameEngine* engine = new GameEngine(engine_config);
|
|
||||||
|
|
||||||
// Initialize Python with configuration
|
// Initialize Python with configuration
|
||||||
McRFPy_API::init_python_with_config(config, argc, argv);
|
McRFPy_API::init_python_with_config(config, argc, argv);
|
||||||
|
|
||||||
|
// Import mcrfpy module and store reference
|
||||||
|
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
|
||||||
|
if (!McRFPy_API::mcrf_module) {
|
||||||
|
PyErr_Print();
|
||||||
|
std::cerr << "Failed to import mcrfpy module" << std::endl;
|
||||||
|
} else {
|
||||||
|
// Set up default_font and default_texture if not already done
|
||||||
|
if (!McRFPy_API::default_font) {
|
||||||
|
McRFPy_API::default_font = std::make_shared<PyFont>("assets/JetbrainsMono.ttf");
|
||||||
|
McRFPy_API::default_texture = std::make_shared<PyTexture>("assets/kenney_tinydungeon.png", 16, 16);
|
||||||
|
}
|
||||||
|
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
|
||||||
|
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
|
||||||
|
}
|
||||||
|
|
||||||
// Handle different Python modes
|
// Handle different Python modes
|
||||||
if (!config.python_command.empty()) {
|
if (!config.python_command.empty()) {
|
||||||
// Execute command from -c
|
// Execute command from -c
|
||||||
|
@ -161,6 +176,9 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv
|
||||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run the game engine after script execution
|
||||||
|
engine->run();
|
||||||
|
|
||||||
Py_Finalize();
|
Py_Finalize();
|
||||||
delete engine;
|
delete engine;
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Animation System Demo - Shows all animation capabilities"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Create main scene
|
||||||
|
mcrfpy.createScene("animation_demo")
|
||||||
|
ui = mcrfpy.sceneUI("animation_demo")
|
||||||
|
mcrfpy.setScene("animation_demo")
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font)
|
||||||
|
title.size = 24
|
||||||
|
title.fill_color = (255, 255, 255)
|
||||||
|
# Note: centered property doesn't exist for Caption
|
||||||
|
ui.append(title)
|
||||||
|
|
||||||
|
# 1. Position Animation Demo
|
||||||
|
pos_frame = mcrfpy.Frame(50, 100, 80, 80)
|
||||||
|
pos_frame.fill_color = (255, 100, 100)
|
||||||
|
pos_frame.outline = 2
|
||||||
|
ui.append(pos_frame)
|
||||||
|
|
||||||
|
pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font)
|
||||||
|
pos_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(pos_label)
|
||||||
|
|
||||||
|
# 2. Size Animation Demo
|
||||||
|
size_frame = mcrfpy.Frame(200, 100, 50, 50)
|
||||||
|
size_frame.fill_color = (100, 255, 100)
|
||||||
|
size_frame.outline = 2
|
||||||
|
ui.append(size_frame)
|
||||||
|
|
||||||
|
size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font)
|
||||||
|
size_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(size_label)
|
||||||
|
|
||||||
|
# 3. Color Animation Demo
|
||||||
|
color_frame = mcrfpy.Frame(350, 100, 80, 80)
|
||||||
|
color_frame.fill_color = (255, 0, 0)
|
||||||
|
ui.append(color_frame)
|
||||||
|
|
||||||
|
color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font)
|
||||||
|
color_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(color_label)
|
||||||
|
|
||||||
|
# 4. Easing Functions Demo
|
||||||
|
easing_y = 250
|
||||||
|
easing_frames = []
|
||||||
|
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"]
|
||||||
|
|
||||||
|
for i, easing in enumerate(easings):
|
||||||
|
x = 50 + i * 120
|
||||||
|
|
||||||
|
frame = mcrfpy.Frame(x, easing_y, 20, 20)
|
||||||
|
frame.fill_color = (100, 150, 255)
|
||||||
|
ui.append(frame)
|
||||||
|
easing_frames.append((frame, easing))
|
||||||
|
|
||||||
|
label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font)
|
||||||
|
label.size = 12
|
||||||
|
label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(label)
|
||||||
|
|
||||||
|
# 5. Complex Animation Demo
|
||||||
|
complex_frame = mcrfpy.Frame(300, 350, 100, 100)
|
||||||
|
complex_frame.fill_color = (128, 128, 255)
|
||||||
|
complex_frame.outline = 3
|
||||||
|
ui.append(complex_frame)
|
||||||
|
|
||||||
|
complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font)
|
||||||
|
complex_label.fill_color = (200, 200, 200)
|
||||||
|
ui.append(complex_label)
|
||||||
|
|
||||||
|
# Start animations
|
||||||
|
def start_animations(runtime):
|
||||||
|
# 1. Position animation - back and forth
|
||||||
|
x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut")
|
||||||
|
x_anim.start(pos_frame)
|
||||||
|
|
||||||
|
# 2. Size animation - pulsing
|
||||||
|
w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut")
|
||||||
|
h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut")
|
||||||
|
w_anim.start(size_frame)
|
||||||
|
h_anim.start(size_frame)
|
||||||
|
|
||||||
|
# 3. Color animation - rainbow cycle
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# 4. Easing demos - all move up with different easings
|
||||||
|
for frame, easing in easing_frames:
|
||||||
|
y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing)
|
||||||
|
y_anim.start(frame)
|
||||||
|
|
||||||
|
# 5. Complex animation - multiple properties
|
||||||
|
cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut")
|
||||||
|
cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut")
|
||||||
|
cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic")
|
||||||
|
ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic")
|
||||||
|
outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear")
|
||||||
|
|
||||||
|
cx_anim.start(complex_frame)
|
||||||
|
cy_anim.start(complex_frame)
|
||||||
|
cw_anim.start(complex_frame)
|
||||||
|
ch_anim.start(complex_frame)
|
||||||
|
outline_anim.start(complex_frame)
|
||||||
|
|
||||||
|
# Individual color component animations
|
||||||
|
r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut")
|
||||||
|
g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut")
|
||||||
|
b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut")
|
||||||
|
|
||||||
|
r_anim.start(complex_frame)
|
||||||
|
g_anim.start(complex_frame)
|
||||||
|
b_anim.start(complex_frame)
|
||||||
|
|
||||||
|
print("All animations started!")
|
||||||
|
|
||||||
|
# Reverse some animations
|
||||||
|
def reverse_animations(runtime):
|
||||||
|
# Position back
|
||||||
|
x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut")
|
||||||
|
x_anim.start(pos_frame)
|
||||||
|
|
||||||
|
# Size back
|
||||||
|
w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut")
|
||||||
|
h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut")
|
||||||
|
w_anim.start(size_frame)
|
||||||
|
h_anim.start(size_frame)
|
||||||
|
|
||||||
|
# Color cycle continues
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# Easing frames back down
|
||||||
|
for frame, easing in easing_frames:
|
||||||
|
y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing)
|
||||||
|
y_anim.start(frame)
|
||||||
|
|
||||||
|
# Continue color cycle
|
||||||
|
def cycle_colors(runtime):
|
||||||
|
color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear")
|
||||||
|
color_anim.start(color_frame)
|
||||||
|
|
||||||
|
# Info text
|
||||||
|
info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font)
|
||||||
|
info.fill_color = (255, 255, 200)
|
||||||
|
# Note: centered property doesn't exist for Caption
|
||||||
|
ui.append(info)
|
||||||
|
|
||||||
|
# Schedule animations
|
||||||
|
mcrfpy.setTimer("start", start_animations, 500)
|
||||||
|
mcrfpy.setTimer("reverse", reverse_animations, 4000)
|
||||||
|
mcrfpy.setTimer("cycle", cycle_colors, 2500)
|
||||||
|
|
||||||
|
# Exit handler
|
||||||
|
def on_key(key):
|
||||||
|
if key == "Escape":
|
||||||
|
mcrfpy.exit()
|
||||||
|
|
||||||
|
mcrfpy.keypressScene(on_key)
|
||||||
|
|
||||||
|
print("Animation demo started! Press Escape to exit.")
|
Loading…
Reference in New Issue