diff --git a/src/dusk/animation/CMakeLists.txt b/src/dusk/animation/CMakeLists.txt index 68bb619a..7696f9c4 100644 --- a/src/dusk/animation/CMakeLists.txt +++ b/src/dusk/animation/CMakeLists.txt @@ -7,5 +7,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC easing.c animation.c - animationproperty.c ) diff --git a/src/dusk/animation/animation.c b/src/dusk/animation/animation.c index 0007eacc..1380bd6b 100644 --- a/src/dusk/animation/animation.c +++ b/src/dusk/animation/animation.c @@ -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)); } \ No newline at end of file diff --git a/src/dusk/animation/animation.h b/src/dusk/animation/animation.h index 90b81528..5a76bdaf 100644 --- a/src/dusk/animation/animation.h +++ b/src/dusk/animation/animation.h @@ -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); \ No newline at end of file +float_t animationGetValue(animation_t *anim, const float_t time); \ No newline at end of file diff --git a/src/dusk/animation/animationproperty.c b/src/dusk/animation/animationproperty.c deleted file mode 100644 index ec74a926..00000000 --- a/src/dusk/animation/animationproperty.c +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/dusk/animation/animationproperty.h b/src/dusk/animation/animationproperty.h deleted file mode 100644 index f2be7fbe..00000000 --- a/src/dusk/animation/animationproperty.h +++ /dev/null @@ -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 -); \ No newline at end of file diff --git a/src/dusk/animation/keyframe.h b/src/dusk/animation/keyframe.h index 7543f647..6ea03984 100644 --- a/src/dusk/animation/keyframe.h +++ b/src/dusk/animation/keyframe.h @@ -10,4 +10,4 @@ typedef struct { float_t time; float_t value; easingtype_t easing; -} keyframe_t; +} keyframe_t; \ No newline at end of file diff --git a/src/dusk/event/event.c b/src/dusk/event/event.c index a6b12977..c97d70c4 100644 --- a/src/dusk/event/event.c +++ b/src/dusk/event/event.c @@ -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); } } diff --git a/src/dusk/event/event.h b/src/dusk/event/event.h index 340ad3d1..b94b3f59 100644 --- a/src/dusk/event/event.h +++ b/src/dusk/event/event.h @@ -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); diff --git a/src/dusk/input/input.c b/src/dusk/input/input.c index d0854638..70cf416a 100644 --- a/src/dusk/input/input.c +++ b/src/dusk/input/input.c @@ -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 diff --git a/src/dusk/input/inputaction.h b/src/dusk/input/inputaction.h index 867f3ace..22fd68fe 100644 --- a/src/dusk/input/inputaction.h +++ b/src/dusk/input/inputaction.h @@ -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; diff --git a/src/dusk/ui/uifullbox.c b/src/dusk/ui/uifullbox.c index c4f85268..e54af74c 100644 --- a/src/dusk/ui/uifullbox.c +++ b/src/dusk/ui/uifullbox.c @@ -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) { diff --git a/src/dusk/ui/uifullbox.h b/src/dusk/ui/uifullbox.h index e5e3d476..c1f172cc 100644 --- a/src/dusk/ui/uifullbox.h +++ b/src/dusk/ui/uifullbox.h @@ -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; diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/uiloading.c index 3b6de794..25bcebc3 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/uiloading.c @@ -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); } diff --git a/src/dusk/ui/uiloading.h b/src/dusk/ui/uiloading.h index 49f54548..60bf9414 100644 --- a/src/dusk/ui/uiloading.h +++ b/src/dusk/ui/uiloading.h @@ -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; diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index f9746b30..6c0c0c17 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -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(); } diff --git a/src/dusk/ui/uitextbox.h b/src/dusk/ui/uitextbox.h index 033e8415..51e73d68 100644 --- a/src/dusk/ui/uitextbox.h +++ b/src/dusk/ui/uitextbox.h @@ -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; diff --git a/src/dusk/util/math.c b/src/dusk/util/math.c index 9f0caa3e..40eecf9f 100644 --- a/src/dusk/util/math.c +++ b/src/dusk/util/math.c @@ -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); } \ No newline at end of file diff --git a/src/dusk/util/math.h b/src/dusk/util/math.h index d27981bf..ab8cf45e 100644 --- a/src/dusk/util/math.h +++ b/src/dusk/util/math.h @@ -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); \ No newline at end of file +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); \ No newline at end of file