Updating event handler

This commit is contained in:
2026-05-28 14:22:13 -05:00
parent 03eb328d81
commit 957980b3c5
18 changed files with 136 additions and 288 deletions
-1
View File
@@ -7,5 +7,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC PUBLIC
easing.c easing.c
animation.c animation.c
animationproperty.c
) )
+34 -75
View File
@@ -8,86 +8,45 @@
#include "util/memory.h" #include "util/memory.h"
#include "util/math.h" #include "util/math.h"
void animationInit(animation_t *anim) { void animationInit(
memoryZero(anim, sizeof(animation_t)); animation_t *anim,
eventInit(&anim->onStart); keyframe_t *keyframes,
eventInit(&anim->onComplete); uint16_t keyframeCount
) {
assertNotNull(anim, "Animation pointer cannot be null.");
assertNotNull(keyframes, "Keyframes pointer cannot be null.");
assertTrue(keyframeCount > 0, "Keyframe count must be more than 0.");
anim->keyframes = keyframes;
anim->keyframeCount = keyframeCount;
} }
animationproperty_t *animationAddProperty(animation_t *anim, float_t *target) { float_t animationGetValue(animation_t *anim, const float_t time) {
assertTrue( assertNotNull(anim, "Animation pointer cannot be null.");
anim->propertyCount < ANIMATION_PROPERTY_COUNT_MAX, assertNotNull(anim->keyframes, "Keyframes pointer cannot be null.");
"Property count exceeds ANIMATION_PROPERTY_COUNT_MAX" assertTrue(anim->keyframeCount > 0, "Keyframe count invalid.");
); assertTrue(time >= 0, "Time must be non-negative.");
animationproperty_t *prop = &anim->properties[anim->propertyCount++];
prop->keyframeCount = 0; keyframe_t *start;
prop->target = target; keyframe_t *end;
return prop; keyframe_t *last = anim->keyframes + anim->keyframeCount - 1;
} keyframe_t *current = anim->keyframes;
start = current;
void animationUpdate(animation_t *anim, float_t delta) { do {
assertNotNull(anim, "Animation cannot be null"); if(current->time > time) {
end = current;
if( break;
(anim->flags & ANIMATION_FLAG_FINISHED) &&
!(anim->flags & ANIMATION_FLAG_LOOP_ENABLED)
) return;
bool_t wasStarted = (anim->flags & ANIMATION_FLAG_STARTED) != 0;
anim->flags |= ANIMATION_FLAG_STARTED;
anim->time += delta;
float_t duration = animationGetDuration(anim);
bool_t justFinished = false;
if(anim->time >= duration) {
if(anim->flags & ANIMATION_FLAG_LOOP_ENABLED) {
anim->time = mathModFloat(anim->time, duration);
} else {
anim->time = duration;
if(!(anim->flags & ANIMATION_FLAG_FINISHED)) {
anim->flags |= ANIMATION_FLAG_FINISHED;
justFinished = true;
}
} }
} start = current;
current++;
for(uint8_t i = 0; i < anim->propertyCount; i++) { if(current > last) {
animationproperty_t *prop = &anim->properties[i]; end = start;
if(prop->target != NULL) { break;
*prop->target = animationPropertyGetValue(prop, anim->time);
} }
} } while(true);
if(!wasStarted) eventInvoke(&anim->onStart, anim); float_t t = (time - start->time) / (end->time - start->time);
if(justFinished) eventInvoke(&anim->onComplete, anim); return mathLerp(start->value, end->value, easingApply(start->easing, t));
}
void animationReset(animation_t *anim) {
anim->time = 0.0f;
anim->flags &= ~(ANIMATION_FLAG_FINISHED | ANIMATION_FLAG_STARTED);
}
bool_t animationIsFinished(const animation_t *anim) {
return (anim->flags & ANIMATION_FLAG_FINISHED) != 0;
}
bool_t animationIsStarted(const animation_t *anim) {
return (anim->flags & ANIMATION_FLAG_STARTED) != 0;
}
bool_t animationIsLooping(const animation_t *anim) {
return (anim->flags & ANIMATION_FLAG_LOOP_ENABLED) != 0;
}
float_t animationGetDuration(const animation_t *anim) {
float_t duration = 0.0f;
for(uint8_t i = 0; i < anim->propertyCount; i++) {
animationproperty_t *prop = &anim->properties[i];
if(prop->keyframeCount == 0) continue;
float_t lastKeyframeTime =
prop->keyframes[prop->keyframeCount - 1].time;
if(lastKeyframeTime > duration) duration = lastKeyframeTime;
}
return duration;
} }
+15 -71
View File
@@ -4,87 +4,31 @@
// https://opensource.org/licenses/MIT // https://opensource.org/licenses/MIT
#pragma once #pragma once
#include "animationproperty.h" #include "keyframe.h"
#include "event/event.h"
#define ANIMATION_PROPERTY_COUNT_MAX 8
#define ANIMATION_FLAG_FINISHED (1 << 0)
#define ANIMATION_FLAG_STARTED (1 << 1)
#define ANIMATION_FLAG_LOOP_ENABLED (1 << 2)
typedef struct { typedef struct {
animationproperty_t properties[ANIMATION_PROPERTY_COUNT_MAX]; keyframe_t *keyframes;
uint8_t propertyCount; uint16_t keyframeCount;
float_t time;
uint8_t flags;
event_t onStart;
event_t onComplete;
} animation_t; } animation_t;
/** /**
* Initializes an animation. * Initializes an animation.
* *
* @param anim The animation to initialize. * @param anim The animation to initialize.
* @param keyframes The keyframes to use for the animation.
* @param keyframeCount The number of keyframes in the animation.
*/ */
void animationInit(animation_t *anim); void animationInit(
animation_t *anim,
keyframe_t *keyframes,
uint16_t keyframeCount
);
/** /**
* Adds a new animated property. The caller owns target and must keep it valid * Gets the value of the animation at a given time.
* for the lifetime of the animation.
* *
* @param anim The animation to add the property to. * @param anim The animation to get the value from.
* @param target Pointer to the float to write interpolated values into. * @param time The time at which to get the value, in seconds.
* @return A pointer to the new property. * @return The value of the animation at the given time.
*/ */
animationproperty_t *animationAddProperty(animation_t *anim, float_t *target); float_t animationGetValue(animation_t *anim, const float_t time);
/**
* Advances the animation by delta seconds and writes interpolated values to
* each property's target pointer.
*
* @param anim The animation to update.
* @param delta The time to advance the animation by, in seconds.
*/
void animationUpdate(animation_t *anim, float_t delta);
/**
* Resets the animation to the beginning, clearing Started and Finished flags.
*
* @param anim The animation to reset.
*/
void animationReset(animation_t *anim);
/**
* Returns true if the animation has finished (i.e. reached the end of its
* duration and is not looping).
*
* @param anim The animation to check.
* @return true if the animation has finished, false otherwise.
*/
bool_t animationIsFinished(const animation_t *anim);
/**
* Returns true if the animation has been started (i.e. animationUpdate has
* been called at least once).
*
* @param anim The animation to check.
* @return true if the animation has been started, false otherwise.
*/
bool_t animationIsStarted(const animation_t *anim);
/**
* Returns true if the animation is set to loop.
*
* @param anim The animation to check.
* @return true if the animation is set to loop, false otherwise.
*/
bool_t animationIsLooping(const animation_t *anim);
/**
* Gets the total duration of the animation (based on the keyframes).
*
* @param anim The animation to get the duration of.
* @return The total duration of the animation, in seconds.
*/
float_t animationGetDuration(const animation_t *anim);
-55
View File
@@ -1,55 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "animationproperty.h"
#include "assert/assert.h"
void animationPropertyAddKeyframe(
animationproperty_t *prop,
const float_t time,
const float_t value,
const easingtype_t easing
) {
assertNotNull(prop, "Property cannot be null");
assertTrue(
prop->keyframeCount < ANIMATION_PROPERTY_KEYFRAME_COUNT_MAX,
"Too many keyframes added to property"
);
assertTrue(time >= 0.0f, "Keyframe time cannot be negative");
keyframe_t *frame = &prop->keyframes[prop->keyframeCount++];
frame->time = time;
frame->value = value;
frame->easing = easing;
}
float_t animationPropertyGetValue(
const animationproperty_t *prop,
const float_t time
) {
assertNotNull(prop, "Property cannot be null");
assertTrue(time >= 0.0f, "Time cannot be negative");
if(prop->keyframeCount == 0) return 0.0f;
uint8_t last = prop->keyframeCount - 1;
if(prop->keyframeCount == 1) return prop->keyframes[0].value;
if(time <= prop->keyframes[0].time) return prop->keyframes[0].value;
if(time >= prop->keyframes[last].time) return prop->keyframes[last].value;
for(uint8_t i = 0; i < last; i++) {
const keyframe_t *a = &prop->keyframes[i];
const keyframe_t *b = &prop->keyframes[i + 1];
if(time < a->time || time >= b->time) continue;
float_t t = (time - a->time) / (b->time - a->time);
t = easingApply(a->easing, t);
return a->value + (b->value - a->value) * t;
}
return prop->keyframes[last].value;
}
-45
View File
@@ -1,45 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "keyframe.h"
#define ANIMATION_PROPERTY_KEYFRAME_COUNT_MAX 16
typedef struct {
keyframe_t keyframes[ANIMATION_PROPERTY_KEYFRAME_COUNT_MAX];
uint8_t keyframeCount;
float_t *target;
} animationproperty_t;
/**
* Appends a keyframe to a property. Keyframes must be added in ascending time
* order. Updates the animation's duration.
*
* @param property The property to add the keyframe to.
* @param time The time of the keyframe, in seconds.
* @param value The value of the keyframe.
* @param easing The easing type to use when interpolating to the next keyframe.
*/
void animationPropertyAddKeyframe(
animationproperty_t *property,
const float_t time,
const float_t value,
const easingtype_t easing
);
/**
* Gets the property's value at a given time.
*
* @param prop The property to get the value from.
* @param time The time at which to get the value, in seconds.
* @return The value of the property at the given time.
*/
float_t animationPropertyGetValue(
const animationproperty_t *prop,
const float_t time
);
+1 -1
View File
@@ -10,4 +10,4 @@ typedef struct {
float_t time; float_t time;
float_t value; float_t value;
easingtype_t easing; easingtype_t easing;
} keyframe_t; } keyframe_t;
+31 -19
View File
@@ -9,43 +9,53 @@
#include "assert/assert.h" #include "assert/assert.h"
#include "util/memory.h" #include "util/memory.h"
void eventInit(event_t *event) { void eventInit(event_t *event, eventcallback_t *callbacks, void **users, size_t size) {
assertNotNull(event, "event must not be NULL"); assertNotNull(event, "event must not be NULL");
memoryZero(event, sizeof(event_t)); assertNotNull((void *)callbacks, "callbacks must not be NULL");
assertTrue(size > 0, "size must be greater than 0");
event->callbacks = callbacks;
event->users = users;
event->size = size;
event->count = 0;
memoryZero(callbacks, sizeof(eventcallback_t) * size);
if(users) memoryZero(users, sizeof(void *) * size);
} }
void eventSubscribe(event_t *event, eventcallback_t callback, void *user) { void eventSubscribe(event_t *event, eventcallback_t callback, void *user) {
assertNotNull(event, "event must not be NULL"); assertNotNull(event, "event must not be NULL");
assertNotNull((void *)callback, "callback must not be NULL"); assertNotNull(callback, "callback must not be NULL");
for(uint8_t i = 0; i < event->count; i++) { // Ensure callback isn't already susbcribed
if(event->callbacks[i] == callback && event->users[i] == user) return; for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback) continue;
assertUnreachable("Callback already registered, cannot subscribe twice.");
} }
assertTrue( assertTrue(event->count < event->size, "event subscriber capacity exceeded");
event->count < EVENT_SUBSCRIBER_COUNT_MAX,
"EVENT_SUBSCRIBER_COUNT_MAX exceeded"
);
event->callbacks[event->count] = callback; event->callbacks[event->count] = callback;
event->users[event->count] = user; if(user) {
assertNotNull(event->users, "Cannot add user pointer.");
event->users[event->count] = user;
}
event->count++; event->count++;
} }
void eventUnsubscribe(event_t *event, eventcallback_t callback, void *user) { void eventUnsubscribe(event_t *event, eventcallback_t callback) {
assertNotNull(event, "event must not be NULL"); assertNotNull(event, "event must not be NULL");
assertNotNull((void *)callback, "callback must not be NULL"); assertNotNull(callback, "callback must not be NULL");
for(uint8_t i = 0; i < event->count; i++) { for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback || event->users[i] != user) continue; if(event->callbacks[i] != callback) continue;
uint8_t last = event->count - 1; uint32_t last = event->count - 1;
if(i != last) { if(i != last) {
event->callbacks[i] = event->callbacks[last]; event->callbacks[i] = event->callbacks[last];
event->users[i] = event->users[last]; if(event->users) event->users[i] = event->users[last];
} }
event->callbacks[last] = NULL; event->callbacks[last] = NULL;
event->users[last] = NULL; if(event->users) event->users[last] = NULL;
event->count--; event->count--;
return; return;
} }
@@ -53,7 +63,9 @@ void eventUnsubscribe(event_t *event, eventcallback_t callback, void *user) {
void eventInvoke(const event_t *event, void *params) { void eventInvoke(const event_t *event, void *params) {
assertNotNull(event, "event must not be NULL"); assertNotNull(event, "event must not be NULL");
for(uint8_t i = 0; i < event->count; i++) {
event->callbacks[i](params, event->users[i]); for(uint32_t i = 0; i < event->count; i++) {
void *u = event->users ? event->users[i] : NULL;
event->callbacks[i](params, u);
} }
} }
+21 -13
View File
@@ -8,31 +8,40 @@
#pragma once #pragma once
#include "dusk.h" #include "dusk.h"
#define EVENT_SUBSCRIBER_COUNT_MAX 8
typedef void (*eventcallback_t)(void *params, void *user); typedef void (*eventcallback_t)(void *params, void *user);
typedef struct { typedef struct {
eventcallback_t callbacks[EVENT_SUBSCRIBER_COUNT_MAX]; eventcallback_t *callbacks;
void *users[EVENT_SUBSCRIBER_COUNT_MAX]; void **users;
uint8_t count; size_t size;
uint32_t count;
} event_t; } event_t;
/** /**
* Initializes an event, clearing all subscribers. * Initializes an event, binding it to the provided backing arrays and clearing
* all subscribers. May also be called to reset an event (re-clears subscribers
* without changing the backing arrays or size).
* *
* @param event The event to initialize. * @param event The event to initialize.
* @param callbacks Caller-owned array of at least `size` callback slots.
* @param users Array of user pointers, matching each callback, or NULL.
* @param size Capacity of both arrays, must match.
*/ */
void eventInit(event_t *event); void eventInit(
event_t *event,
eventcallback_t *callbacks,
void **users,
size_t size
);
/** /**
* Subscribes a callback to an event. The callback is invoked with params and * Subscribes a callback to an event. The callback is invoked with params and
* the provided user pointer each time the event fires. The same (callback, * the provided user pointer each time the event fires. The same (callback,
* user) pair may only be subscribed once. * user) pair may only be subscribed once.
* *
* @param event The event to subscribe to. * @param event The event to subscribe to.
* @param callback The function to call when the event fires. * @param callback The function to call when the event fires.
* @param user Arbitrary pointer forwarded to the callback unchanged. * @param user Arbitrary pointer forwarded to the callback unchanged.
*/ */
void eventSubscribe(event_t *event, eventcallback_t callback, void *user); void eventSubscribe(event_t *event, eventcallback_t callback, void *user);
@@ -40,17 +49,16 @@ void eventSubscribe(event_t *event, eventcallback_t callback, void *user);
* Removes a previously subscribed (callback, user) pair. Does nothing if the * Removes a previously subscribed (callback, user) pair. Does nothing if the
* pair is not currently subscribed. * pair is not currently subscribed.
* *
* @param event The event to unsubscribe from. * @param event The event to unsubscribe from.
* @param callback The callback that was passed to eventSubscribe. * @param callback The callback that was passed to eventSubscribe.
* @param user The user pointer that was passed to eventSubscribe.
*/ */
void eventUnsubscribe(event_t *event, eventcallback_t callback, void *user); void eventUnsubscribe(event_t *event, eventcallback_t callback);
/** /**
* Invokes all subscribed callbacks, passing params and each subscriber's user * Invokes all subscribed callbacks, passing params and each subscriber's user
* pointer. * pointer.
* *
* @param event The event to invoke. * @param event The event to invoke.
* @param params Arbitrary pointer forwarded to every callback unchanged. * @param params Arbitrary pointer forwarded to every callback unchanged.
*/ */
void eventInvoke(const event_t *event, void *params); void eventInvoke(const event_t *event, void *params);
+2 -2
View File
@@ -22,8 +22,8 @@ errorret_t inputInit(void) {
INPUT.actions[i].action = (inputaction_t)i; INPUT.actions[i].action = (inputaction_t)i;
INPUT.actions[i].lastValue = 0.0f; INPUT.actions[i].lastValue = 0.0f;
INPUT.actions[i].currentValue = 0.0f; INPUT.actions[i].currentValue = 0.0f;
eventInit(&INPUT.actions[i].onPressed); eventInit(&INPUT.actions[i].onPressed, INPUT.actions[i].onPressedCallbacks, INPUT.actions[i].onPressedUsers, 4);
eventInit(&INPUT.actions[i].onReleased); eventInit(&INPUT.actions[i].onReleased, INPUT.actions[i].onReleasedCallbacks, INPUT.actions[i].onReleasedUsers, 4);
} }
#ifdef inputInitPlatform #ifdef inputInitPlatform
+4
View File
@@ -20,7 +20,11 @@ typedef struct {
float_t currentDynamicValue; float_t currentDynamicValue;
#endif #endif
eventcallback_t onPressedCallbacks[4];
void *onPressedUsers[4];
event_t onPressed; event_t onPressed;
eventcallback_t onReleasedCallbacks[4];
void *onReleasedUsers[4];
event_t onReleased; event_t onReleased;
} inputactiondata_t; } inputactiondata_t;
+1 -1
View File
@@ -19,7 +19,7 @@ uifullbox_t UI_FULLBOX_OVER;
void uiFullboxInit(uifullbox_t *fullbox) { void uiFullboxInit(uifullbox_t *fullbox) {
assertNotNull(fullbox, "fullbox must not be NULL"); assertNotNull(fullbox, "fullbox must not be NULL");
memoryZero(fullbox, sizeof(uifullbox_t)); memoryZero(fullbox, sizeof(uifullbox_t));
eventInit(&fullbox->onTransitionEnd); eventInit(&fullbox->onTransitionEnd, fullbox->onTransitionEndCallbacks, fullbox->onTransitionEndUsers, 4);
} }
void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) { void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) {
+2
View File
@@ -17,6 +17,8 @@ typedef struct {
float_t duration; float_t duration;
float_t time; float_t time;
easingtype_t easing; easingtype_t easing;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd; event_t onTransitionEnd;
} uifullbox_t; } uifullbox_t;
+2 -2
View File
@@ -19,7 +19,7 @@ uiloading_t UI_LOADING;
void uiLoadingInit(void) { void uiLoadingInit(void) {
memoryZero(&UI_LOADING, sizeof(uiloading_t)); memoryZero(&UI_LOADING, sizeof(uiloading_t));
eventInit(&UI_LOADING.onTransitionEnd); eventInit(&UI_LOADING.onTransitionEnd, UI_LOADING.onTransitionEndCallbacks, UI_LOADING.onTransitionEndUsers, 4);
} }
void uiLoadingUpdate(float_t delta) { void uiLoadingUpdate(float_t delta) {
@@ -65,7 +65,7 @@ static void uiLoadingTransition(
UI_LOADING.toAlpha = to; UI_LOADING.toAlpha = to;
UI_LOADING.duration = UI_LOADING_FADE_DURATION; UI_LOADING.duration = UI_LOADING_FADE_DURATION;
UI_LOADING.time = 0.0f; UI_LOADING.time = 0.0f;
eventInit(&UI_LOADING.onTransitionEnd); eventInit(&UI_LOADING.onTransitionEnd, UI_LOADING.onTransitionEndCallbacks, UI_LOADING.onTransitionEndUsers, 4);
if(callback) eventSubscribe(&UI_LOADING.onTransitionEnd, callback, user); if(callback) eventSubscribe(&UI_LOADING.onTransitionEnd, callback, user);
} }
+2
View File
@@ -17,6 +17,8 @@ typedef struct {
float_t toAlpha; float_t toAlpha;
float_t duration; float_t duration;
float_t time; float_t time;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd; event_t onTransitionEnd;
} uiloading_t; } uiloading_t;
+2 -2
View File
@@ -46,8 +46,8 @@ errorret_t uiTextboxInit(void) {
UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f;
UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; UI_TEXTBOX.frame.texture = &TEXTURE_WHITE;
eventInit(&UI_TEXTBOX.onPageComplete); eventInit(&UI_TEXTBOX.onPageComplete, UI_TEXTBOX.onPageCompleteCallbacks, UI_TEXTBOX.onPageCompleteUsers, 4);
eventInit(&UI_TEXTBOX.onLastPage); eventInit(&UI_TEXTBOX.onLastPage, UI_TEXTBOX.onLastPageCallbacks, UI_TEXTBOX.onLastPageUsers, 4);
errorOk(); errorOk();
} }
+4
View File
@@ -42,7 +42,11 @@ typedef struct {
int32_t scroll; int32_t scroll;
inputaction_t advanceAction; inputaction_t advanceAction;
eventcallback_t onPageCompleteCallbacks[4];
void *onPageCompleteUsers[4];
event_t onPageComplete; event_t onPageComplete;
eventcallback_t onLastPageCallbacks[4];
void *onLastPageUsers[4];
event_t onLastPage; event_t onLastPage;
} uitextbox_t; } uitextbox_t;
+4
View File
@@ -24,4 +24,8 @@ float_t mathModFloat(float_t x, float_t y) {
float_t result = fmodf(x, y); float_t result = fmodf(x, y);
if(result < 0) result += y; if(result < 0) result += y;
return result; return result;
}
float_t mathLerp(float_t a, float_t b, float_t t) {
return a + t * (b - a);
} }
+11 -1
View File
@@ -60,4 +60,14 @@ uint32_t mathNextPowTwo(uint32_t value);
* @param y The divisor. * @param y The divisor.
* @return The result of the modulus operation. * @return The result of the modulus operation.
*/ */
float_t mathModFloat(float_t x, float_t y); float_t mathModFloat(float_t x, float_t y);
/**
* Linearly interpolates between two values.
*
* @param a The start value.
* @param b The end value.
* @param t The interpolation factor, between 0 and 1.
* @return The interpolated value.
*/
float_t mathLerp(float_t a, float_t b, float_t t);