Easing functions as enum
This commit is contained in:
parent
357c2ac7d7
commit
d878c8684d
4 changed files with 278 additions and 9 deletions
|
|
@ -10,6 +10,7 @@
|
||||||
#include "PySceneObject.h"
|
#include "PySceneObject.h"
|
||||||
#include "PyFOV.h"
|
#include "PyFOV.h"
|
||||||
#include "PyTransition.h"
|
#include "PyTransition.h"
|
||||||
|
#include "PyEasing.h"
|
||||||
#include "PySound.h"
|
#include "PySound.h"
|
||||||
#include "PyMusic.h"
|
#include "PyMusic.h"
|
||||||
#include "PyKeyboard.h"
|
#include "PyKeyboard.h"
|
||||||
|
|
@ -429,6 +430,13 @@ PyObject* PyInit_mcrfpy()
|
||||||
// Note: default_transition and default_transition_duration are handled via
|
// Note: default_transition and default_transition_duration are handled via
|
||||||
// mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration
|
// mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration
|
||||||
|
|
||||||
|
// Add Easing enum class (uses Python's IntEnum)
|
||||||
|
PyObject* easing_class = PyEasing::create_enum_class(m);
|
||||||
|
if (!easing_class) {
|
||||||
|
// If enum creation fails, continue without it (non-fatal)
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Add automation submodule
|
// Add automation submodule
|
||||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||||
if (automation_module != NULL) {
|
if (automation_module != NULL) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "McRFPy_Doc.h"
|
#include "McRFPy_Doc.h"
|
||||||
|
#include "PyEasing.h"
|
||||||
#include "UIDrawable.h"
|
#include "UIDrawable.h"
|
||||||
#include "UIFrame.h"
|
#include "UIFrame.h"
|
||||||
#include "UICaption.h"
|
#include "UICaption.h"
|
||||||
|
|
@ -20,16 +21,16 @@ PyObject* PyAnimation::create(PyTypeObject* type, PyObject* args, PyObject* kwds
|
||||||
|
|
||||||
int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", "callback", nullptr};
|
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", "callback", nullptr};
|
||||||
|
|
||||||
const char* property_name;
|
const char* property_name;
|
||||||
PyObject* target_value;
|
PyObject* target_value;
|
||||||
float duration;
|
float duration;
|
||||||
const char* easing_name = "linear";
|
PyObject* easing_arg = Py_None;
|
||||||
int delta = 0;
|
int delta = 0;
|
||||||
PyObject* callback = nullptr;
|
PyObject* callback = nullptr;
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|spO", const_cast<char**>(keywords),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|OpO", const_cast<char**>(keywords),
|
||||||
&property_name, &target_value, &duration, &easing_name, &delta, &callback)) {
|
&property_name, &target_value, &duration, &easing_arg, &delta, &callback)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,10 +99,13 @@ int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string");
|
PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get easing function
|
// Get easing function from argument (enum, string, int, or None)
|
||||||
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
|
EasingFunction easingFunc;
|
||||||
|
if (!PyEasing::from_arg(easing_arg, &easingFunc, nullptr)) {
|
||||||
|
return -1; // Error already set by from_arg
|
||||||
|
}
|
||||||
|
|
||||||
// Create the Animation
|
// Create the Animation
|
||||||
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0, callback);
|
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0, callback);
|
||||||
|
|
||||||
|
|
|
||||||
228
src/PyEasing.cpp
Normal file
228
src/PyEasing.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#include "PyEasing.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
|
||||||
|
// Static storage for cached enum class reference
|
||||||
|
PyObject* PyEasing::easing_enum_class = nullptr;
|
||||||
|
|
||||||
|
// Easing function table - maps enum value to function and name
|
||||||
|
struct EasingEntry {
|
||||||
|
const char* name;
|
||||||
|
int value;
|
||||||
|
EasingFunction func;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const EasingEntry easing_table[] = {
|
||||||
|
{"LINEAR", 0, EasingFunctions::linear},
|
||||||
|
{"EASE_IN", 1, EasingFunctions::easeIn},
|
||||||
|
{"EASE_OUT", 2, EasingFunctions::easeOut},
|
||||||
|
{"EASE_IN_OUT", 3, EasingFunctions::easeInOut},
|
||||||
|
{"EASE_IN_QUAD", 4, EasingFunctions::easeInQuad},
|
||||||
|
{"EASE_OUT_QUAD", 5, EasingFunctions::easeOutQuad},
|
||||||
|
{"EASE_IN_OUT_QUAD", 6, EasingFunctions::easeInOutQuad},
|
||||||
|
{"EASE_IN_CUBIC", 7, EasingFunctions::easeInCubic},
|
||||||
|
{"EASE_OUT_CUBIC", 8, EasingFunctions::easeOutCubic},
|
||||||
|
{"EASE_IN_OUT_CUBIC", 9, EasingFunctions::easeInOutCubic},
|
||||||
|
{"EASE_IN_QUART", 10, EasingFunctions::easeInQuart},
|
||||||
|
{"EASE_OUT_QUART", 11, EasingFunctions::easeOutQuart},
|
||||||
|
{"EASE_IN_OUT_QUART", 12, EasingFunctions::easeInOutQuart},
|
||||||
|
{"EASE_IN_SINE", 13, EasingFunctions::easeInSine},
|
||||||
|
{"EASE_OUT_SINE", 14, EasingFunctions::easeOutSine},
|
||||||
|
{"EASE_IN_OUT_SINE", 15, EasingFunctions::easeInOutSine},
|
||||||
|
{"EASE_IN_EXPO", 16, EasingFunctions::easeInExpo},
|
||||||
|
{"EASE_OUT_EXPO", 17, EasingFunctions::easeOutExpo},
|
||||||
|
{"EASE_IN_OUT_EXPO", 18, EasingFunctions::easeInOutExpo},
|
||||||
|
{"EASE_IN_CIRC", 19, EasingFunctions::easeInCirc},
|
||||||
|
{"EASE_OUT_CIRC", 20, EasingFunctions::easeOutCirc},
|
||||||
|
{"EASE_IN_OUT_CIRC", 21, EasingFunctions::easeInOutCirc},
|
||||||
|
{"EASE_IN_ELASTIC", 22, EasingFunctions::easeInElastic},
|
||||||
|
{"EASE_OUT_ELASTIC", 23, EasingFunctions::easeOutElastic},
|
||||||
|
{"EASE_IN_OUT_ELASTIC", 24, EasingFunctions::easeInOutElastic},
|
||||||
|
{"EASE_IN_BACK", 25, EasingFunctions::easeInBack},
|
||||||
|
{"EASE_OUT_BACK", 26, EasingFunctions::easeOutBack},
|
||||||
|
{"EASE_IN_OUT_BACK", 27, EasingFunctions::easeInOutBack},
|
||||||
|
{"EASE_IN_BOUNCE", 28, EasingFunctions::easeInBounce},
|
||||||
|
{"EASE_OUT_BOUNCE", 29, EasingFunctions::easeOutBounce},
|
||||||
|
{"EASE_IN_OUT_BOUNCE", 30, EasingFunctions::easeInOutBounce},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Old string names (for backwards compatibility)
|
||||||
|
static const char* legacy_names[] = {
|
||||||
|
"linear", "easeIn", "easeOut", "easeInOut",
|
||||||
|
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||||
|
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||||
|
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||||
|
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||||
|
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||||
|
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||||
|
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||||
|
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||||
|
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NUM_EASING_ENTRIES = sizeof(easing_table) / sizeof(easing_table[0]);
|
||||||
|
|
||||||
|
const char* PyEasing::easing_name(int value) {
|
||||||
|
if (value >= 0 && value < NUM_EASING_ENTRIES) {
|
||||||
|
return easing_table[value].name;
|
||||||
|
}
|
||||||
|
return "LINEAR";
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyEasing::create_enum_class(PyObject* module) {
|
||||||
|
// Import IntEnum from enum module
|
||||||
|
PyObject* enum_module = PyImport_ImportModule("enum");
|
||||||
|
if (!enum_module) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* int_enum = PyObject_GetAttrString(enum_module, "IntEnum");
|
||||||
|
Py_DECREF(enum_module);
|
||||||
|
if (!int_enum) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create dict of enum members
|
||||||
|
PyObject* members = PyDict_New();
|
||||||
|
if (!members) {
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all easing function members
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
PyObject* value = PyLong_FromLong(easing_table[i].value);
|
||||||
|
if (!value) {
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (PyDict_SetItemString(members, easing_table[i].name, value) < 0) {
|
||||||
|
Py_DECREF(value);
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call IntEnum("Easing", members) to create the enum class
|
||||||
|
PyObject* name = PyUnicode_FromString("Easing");
|
||||||
|
if (!name) {
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntEnum(name, members) using functional API
|
||||||
|
PyObject* args = PyTuple_Pack(2, name, members);
|
||||||
|
Py_DECREF(name);
|
||||||
|
Py_DECREF(members);
|
||||||
|
if (!args) {
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* easing_class = PyObject_Call(int_enum, args, NULL);
|
||||||
|
Py_DECREF(args);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
|
||||||
|
if (!easing_class) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the reference for fast type checking
|
||||||
|
easing_enum_class = easing_class;
|
||||||
|
Py_INCREF(easing_enum_class);
|
||||||
|
|
||||||
|
// Add to module
|
||||||
|
if (PyModule_AddObject(module, "Easing", easing_class) < 0) {
|
||||||
|
Py_DECREF(easing_class);
|
||||||
|
easing_enum_class = nullptr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return easing_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyEasing::from_arg(PyObject* arg, EasingFunction* out_func, bool* was_none) {
|
||||||
|
if (was_none) *was_none = false;
|
||||||
|
|
||||||
|
// Accept None -> default to linear
|
||||||
|
if (arg == Py_None) {
|
||||||
|
if (was_none) *was_none = true;
|
||||||
|
*out_func = EasingFunctions::linear;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept Easing enum member (check if it's an instance of our enum)
|
||||||
|
if (easing_enum_class && PyObject_IsInstance(arg, easing_enum_class)) {
|
||||||
|
// IntEnum members have a 'value' attribute
|
||||||
|
PyObject* value = PyObject_GetAttrString(arg, "value");
|
||||||
|
if (!value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
long val = PyLong_AsLong(value);
|
||||||
|
Py_DECREF(value);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= 0 && val < NUM_EASING_ENTRIES) {
|
||||||
|
*out_func = easing_table[val].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid Easing value: %ld. Must be 0-%d.", val, NUM_EASING_ENTRIES - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept int (for backwards compatibility and direct enum value access)
|
||||||
|
if (PyLong_Check(arg)) {
|
||||||
|
long val = PyLong_AsLong(arg);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= 0 && val < NUM_EASING_ENTRIES) {
|
||||||
|
*out_func = easing_table[val].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid easing value: %ld. Must be 0-%d or use mcrfpy.Easing enum.",
|
||||||
|
val, NUM_EASING_ENTRIES - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept string (for backwards compatibility)
|
||||||
|
if (PyUnicode_Check(arg)) {
|
||||||
|
const char* name = PyUnicode_AsUTF8(arg);
|
||||||
|
if (!name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check legacy string names first
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, legacy_names[i]) == 0) {
|
||||||
|
*out_func = easing_table[i].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check enum-style names (EASE_IN_OUT, etc.)
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, easing_table[i].name) == 0) {
|
||||||
|
*out_func = easing_table[i].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build error message with available options
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Unknown easing function: '%s'. Use mcrfpy.Easing enum (e.g., Easing.EASE_IN_OUT) "
|
||||||
|
"or legacy string names: 'linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInQuad', etc.",
|
||||||
|
name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Easing must be mcrfpy.Easing enum member, string, int, or None");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
29
src/PyEasing.h
Normal file
29
src/PyEasing.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
|
||||||
|
// Module-level Easing enum class (created at runtime using Python's IntEnum)
|
||||||
|
// Stored as a module attribute: mcrfpy.Easing
|
||||||
|
|
||||||
|
class PyEasing {
|
||||||
|
public:
|
||||||
|
// Create the Easing enum class and add to module
|
||||||
|
// Returns the enum class (new reference), or NULL on error
|
||||||
|
static PyObject* create_enum_class(PyObject* module);
|
||||||
|
|
||||||
|
// Helper to extract easing function from Python arg
|
||||||
|
// Accepts Easing enum, string (for backwards compatibility), int, or None
|
||||||
|
// Returns 1 on success, 0 on error (with exception set)
|
||||||
|
// If arg is None, sets *out_func to linear and sets *was_none to true
|
||||||
|
static int from_arg(PyObject* arg, EasingFunction* out_func, bool* was_none = nullptr);
|
||||||
|
|
||||||
|
// Convert easing enum value to string name
|
||||||
|
static const char* easing_name(int value);
|
||||||
|
|
||||||
|
// Cached reference to the Easing enum class for fast type checking
|
||||||
|
static PyObject* easing_enum_class;
|
||||||
|
|
||||||
|
// Number of easing functions
|
||||||
|
static const int NUM_EASING_FUNCTIONS = 32;
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue