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
easing.c
animation.c
animationproperty.c
)
+34 -75
View File
@@ -8,86 +8,45 @@
#include "util/memory.h"
#include "util/math.h"
void animationInit(animation_t *anim) {
memoryZero(anim, sizeof(animation_t));
eventInit(&anim->onStart);
eventInit(&anim->onComplete);
void animationInit(
animation_t *anim,
keyframe_t *keyframes,
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) {
assertTrue(
anim->propertyCount < ANIMATION_PROPERTY_COUNT_MAX,
"Property count exceeds ANIMATION_PROPERTY_COUNT_MAX"
);
animationproperty_t *prop = &anim->properties[anim->propertyCount++];
prop->keyframeCount = 0;
prop->target = target;
return prop;
}
float_t animationGetValue(animation_t *anim, const float_t time) {
assertNotNull(anim, "Animation pointer cannot be null.");
assertNotNull(anim->keyframes, "Keyframes pointer cannot be null.");
assertTrue(anim->keyframeCount > 0, "Keyframe count invalid.");
assertTrue(time >= 0, "Time must be non-negative.");
keyframe_t *start;
keyframe_t *end;
keyframe_t *last = anim->keyframes + anim->keyframeCount - 1;
keyframe_t *current = anim->keyframes;
start = current;
void animationUpdate(animation_t *anim, float_t delta) {
assertNotNull(anim, "Animation cannot be null");
if(
(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;
}
do {
if(current->time > time) {
end = current;
break;
}
}
start = current;
current++;
for(uint8_t i = 0; i < anim->propertyCount; i++) {
animationproperty_t *prop = &anim->properties[i];
if(prop->target != NULL) {
*prop->target = animationPropertyGetValue(prop, anim->time);
if(current > last) {
end = start;
break;
}
}
} while(true);
if(!wasStarted) eventInvoke(&anim->onStart, anim);
if(justFinished) eventInvoke(&anim->onComplete, anim);
}
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;
float_t t = (time - start->time) / (end->time - start->time);
return mathLerp(start->value, end->value, easingApply(start->easing, t));
}
+15 -71
View File
@@ -4,87 +4,31 @@
// https://opensource.org/licenses/MIT
#pragma once
#include "animationproperty.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)
#include "keyframe.h"
typedef struct {
animationproperty_t properties[ANIMATION_PROPERTY_COUNT_MAX];
uint8_t propertyCount;
float_t time;
uint8_t flags;
event_t onStart;
event_t onComplete;
keyframe_t *keyframes;
uint16_t keyframeCount;
} animation_t;
/**
* Initializes an animation.
*
* @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
* for the lifetime of the animation.
* Gets the value of the animation at a given time.
*
* @param anim The animation to add the property to.
* @param target Pointer to the float to write interpolated values into.
* @return A pointer to the new property.
* @param anim The animation to get the value from.
* @param time The time at which to get the value, in seconds.
* @return The value of the animation at the given time.
*/
animationproperty_t *animationAddProperty(animation_t *anim, float_t *target);
/**
* 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);
float_t animationGetValue(animation_t *anim, const float_t time);
-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 value;
easingtype_t easing;
} keyframe_t;
} keyframe_t;
+31 -19
View File
@@ -9,43 +9,53 @@
#include "assert/assert.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");
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) {
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++) {
if(event->callbacks[i] == callback && event->users[i] == user) return;
// Ensure callback isn't already susbcribed
for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback) continue;
assertUnreachable("Callback already registered, cannot subscribe twice.");
}
assertTrue(
event->count < EVENT_SUBSCRIBER_COUNT_MAX,
"EVENT_SUBSCRIBER_COUNT_MAX exceeded"
);
assertTrue(event->count < event->size, "event subscriber capacity exceeded");
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++;
}
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((void *)callback, "callback must not be NULL");
assertNotNull(callback, "callback must not be NULL");
for(uint8_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback || event->users[i] != user) continue;
for(uint32_t i = 0; i < event->count; i++) {
if(event->callbacks[i] != callback) continue;
uint8_t last = event->count - 1;
uint32_t last = event->count - 1;
if(i != 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->users[last] = NULL;
if(event->users) event->users[last] = NULL;
event->count--;
return;
}
@@ -53,7 +63,9 @@ void eventUnsubscribe(event_t *event, eventcallback_t callback, void *user) {
void eventInvoke(const event_t *event, void *params) {
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
#include "dusk.h"
#define EVENT_SUBSCRIBER_COUNT_MAX 8
typedef void (*eventcallback_t)(void *params, void *user);
typedef struct {
eventcallback_t callbacks[EVENT_SUBSCRIBER_COUNT_MAX];
void *users[EVENT_SUBSCRIBER_COUNT_MAX];
uint8_t count;
eventcallback_t *callbacks;
void **users;
size_t size;
uint32_t count;
} 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 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
* the provided user pointer each time the event fires. The same (callback,
* 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 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);
@@ -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
* 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 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
* pointer.
*
* @param event The event to invoke.
* @param event The event to invoke.
* @param params Arbitrary pointer forwarded to every callback unchanged.
*/
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].lastValue = 0.0f;
INPUT.actions[i].currentValue = 0.0f;
eventInit(&INPUT.actions[i].onPressed);
eventInit(&INPUT.actions[i].onReleased);
eventInit(&INPUT.actions[i].onPressed, INPUT.actions[i].onPressedCallbacks, INPUT.actions[i].onPressedUsers, 4);
eventInit(&INPUT.actions[i].onReleased, INPUT.actions[i].onReleasedCallbacks, INPUT.actions[i].onReleasedUsers, 4);
}
#ifdef inputInitPlatform
+4
View File
@@ -20,7 +20,11 @@ typedef struct {
float_t currentDynamicValue;
#endif
eventcallback_t onPressedCallbacks[4];
void *onPressedUsers[4];
event_t onPressed;
eventcallback_t onReleasedCallbacks[4];
void *onReleasedUsers[4];
event_t onReleased;
} inputactiondata_t;
+1 -1
View File
@@ -19,7 +19,7 @@ uifullbox_t UI_FULLBOX_OVER;
void uiFullboxInit(uifullbox_t *fullbox) {
assertNotNull(fullbox, "fullbox must not be NULL");
memoryZero(fullbox, sizeof(uifullbox_t));
eventInit(&fullbox->onTransitionEnd);
eventInit(&fullbox->onTransitionEnd, fullbox->onTransitionEndCallbacks, fullbox->onTransitionEndUsers, 4);
}
void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) {
+2
View File
@@ -17,6 +17,8 @@ typedef struct {
float_t duration;
float_t time;
easingtype_t easing;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd;
} uifullbox_t;
+2 -2
View File
@@ -19,7 +19,7 @@ uiloading_t UI_LOADING;
void uiLoadingInit(void) {
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) {
@@ -65,7 +65,7 @@ static void uiLoadingTransition(
UI_LOADING.toAlpha = to;
UI_LOADING.duration = UI_LOADING_FADE_DURATION;
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);
}
+2
View File
@@ -17,6 +17,8 @@ typedef struct {
float_t toAlpha;
float_t duration;
float_t time;
eventcallback_t onTransitionEndCallbacks[4];
void *onTransitionEndUsers[4];
event_t onTransitionEnd;
} 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.texture = &TEXTURE_WHITE;
eventInit(&UI_TEXTBOX.onPageComplete);
eventInit(&UI_TEXTBOX.onLastPage);
eventInit(&UI_TEXTBOX.onPageComplete, UI_TEXTBOX.onPageCompleteCallbacks, UI_TEXTBOX.onPageCompleteUsers, 4);
eventInit(&UI_TEXTBOX.onLastPage, UI_TEXTBOX.onLastPageCallbacks, UI_TEXTBOX.onLastPageUsers, 4);
errorOk();
}
+4
View File
@@ -42,7 +42,11 @@ typedef struct {
int32_t scroll;
inputaction_t advanceAction;
eventcallback_t onPageCompleteCallbacks[4];
void *onPageCompleteUsers[4];
event_t onPageComplete;
eventcallback_t onLastPageCallbacks[4];
void *onLastPageUsers[4];
event_t onLastPage;
} uitextbox_t;
+4
View File
@@ -24,4 +24,8 @@ float_t mathModFloat(float_t x, float_t y) {
float_t result = fmodf(x, y);
if(result < 0) result += y;
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.
* @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);