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 "PyFOV.h"
|
||||
#include "PyTransition.h"
|
||||
#include "PyEasing.h"
|
||||
#include "PySound.h"
|
||||
#include "PyMusic.h"
|
||||
#include "PyKeyboard.h"
|
||||
|
|
@ -429,6 +430,13 @@ PyObject* PyInit_mcrfpy()
|
|||
// Note: default_transition and default_transition_duration are handled via
|
||||
// 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
|
||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||
if (automation_module != NULL) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "PyAnimation.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Doc.h"
|
||||
#include "PyEasing.h"
|
||||
#include "UIDrawable.h"
|
||||
#include "UIFrame.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) {
|
||||
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", "callback", nullptr};
|
||||
|
||||
|
||||
const char* property_name;
|
||||
PyObject* target_value;
|
||||
float duration;
|
||||
const char* easing_name = "linear";
|
||||
PyObject* easing_arg = Py_None;
|
||||
int delta = 0;
|
||||
PyObject* callback = nullptr;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|spO", const_cast<char**>(keywords),
|
||||
&property_name, &target_value, &duration, &easing_name, &delta, &callback)) {
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|OpO", const_cast<char**>(keywords),
|
||||
&property_name, &target_value, &duration, &easing_arg, &delta, &callback)) {
|
||||
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");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get easing function
|
||||
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
|
||||
|
||||
|
||||
// Get easing function from argument (enum, string, int, or None)
|
||||
EasingFunction easingFunc;
|
||||
if (!PyEasing::from_arg(easing_arg, &easingFunc, nullptr)) {
|
||||
return -1; // Error already set by from_arg
|
||||
}
|
||||
|
||||
// Create the Animation
|
||||
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