window_get_mouse_position returns a struct, claude wrote me a state machine for main.cpp, compositor queues animations on type instead of one queue, change from uint32_t to *_handle_t, improve compositor performance, generic raii wrapper in main.cpp over multiple structs.
This commit is contained in:
1 parent
a12c9a3e8f
commit
02b5bc8b52
7 files changed
+664
-406
No files matched your search
+9
-8
@@ -12,6 +12,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct audio_t audio_t;
|
typedef struct audio_t audio_t;
|
||||||
|
typedef uint32_t audio_stream_id_t;
|
||||||
|
|
||||||
// If the return type is bool, false means failure.
|
// If the return type is bool, false means failure.
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ void audio_free_data(uint8_t* data);
|
|||||||
// data must remain valid until the stream ends or is stopped
|
// data must remain valid until the stream ends or is stopped
|
||||||
// loop: if true, the stream restarts on EOF
|
// loop: if true, the stream restarts on EOF
|
||||||
// 0 means failure, valid IDs start from 1
|
// 0 means failure, valid IDs start from 1
|
||||||
uint32_t audio_play(audio_t* audio,
|
audio_stream_id_t audio_play(audio_t* audio,
|
||||||
const uint8_t* data,
|
const uint8_t* data,
|
||||||
int size,
|
int size,
|
||||||
int channels,
|
int channels,
|
||||||
@@ -42,22 +43,22 @@ uint32_t audio_play(audio_t* audio,
|
|||||||
bool loop);
|
bool loop);
|
||||||
|
|
||||||
// Stop and remove stream by ID
|
// Stop and remove stream by ID
|
||||||
bool audio_stop(audio_t* audio, uint32_t id);
|
bool audio_stop(audio_t* audio, audio_stream_id_t id);
|
||||||
|
|
||||||
bool audio_pause(audio_t* audio, uint32_t id);
|
bool audio_pause(audio_t* audio, audio_stream_id_t id);
|
||||||
bool audio_resume(audio_t* audio, uint32_t id);
|
bool audio_resume(audio_t* audio, audio_stream_id_t id);
|
||||||
|
|
||||||
// Returns true if the stream is active (playing or paused)
|
// Returns true if the stream is active (playing or paused)
|
||||||
bool audio_is_active(audio_t* audio, uint32_t id);
|
bool audio_is_active(audio_t* audio, audio_stream_id_t id);
|
||||||
|
|
||||||
// Returns true if the stream is currently playing (not paused, not stopped)
|
// Returns true if the stream is currently playing (not paused, not stopped)
|
||||||
bool audio_is_playing(audio_t* audio, uint32_t id);
|
bool audio_is_playing(audio_t* audio, audio_stream_id_t id);
|
||||||
|
|
||||||
// Per-stream volume, immediate effect, range [0.0, 1.0]
|
// Per-stream volume, immediate effect, range [0.0, 1.0]
|
||||||
bool audio_set_volume(audio_t* audio, uint32_t id, float volume);
|
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume);
|
||||||
|
|
||||||
// Animate per-stream volume linearly over time, driven by audio_tick()
|
// Animate per-stream volume linearly over time, driven by audio_tick()
|
||||||
bool audio_animate_volume(audio_t* audio, uint32_t id,
|
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
|
||||||
float to_volume, float duration_sec);
|
float to_volume, float duration_sec);
|
||||||
|
|
||||||
// Global volume, immediate effect, range [0.0, 1.0]
|
// Global volume, immediate effect, range [0.0, 1.0]
|
||||||
|
|||||||
+18
-23
@@ -41,6 +41,7 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct compositor_t compositor_t;
|
typedef struct compositor_t compositor_t;
|
||||||
|
typedef uint32_t element_handle_t;
|
||||||
|
|
||||||
// If the return type is bool, false means failure.
|
// If the return type is bool, false means failure.
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ void compositor_destroy(compositor_t* comp);
|
|||||||
// FB format is ARGB8888 and it always will be
|
// FB format is ARGB8888 and it always will be
|
||||||
// The compositor takes a reference, caller must keep alive until removed
|
// The compositor takes a reference, caller must keep alive until removed
|
||||||
// 0 means failure, valid IDs start from 1
|
// 0 means failure, valid IDs start from 1
|
||||||
uint32_t compositor_insert(compositor_t* comp,
|
element_handle_t compositor_insert(compositor_t* comp,
|
||||||
const uint32_t* fb,
|
const uint32_t* fb,
|
||||||
uint32_t fb_width,
|
uint32_t fb_width,
|
||||||
uint32_t fb_height,
|
uint32_t fb_height,
|
||||||
@@ -64,24 +65,18 @@ uint32_t compositor_insert(compositor_t* comp,
|
|||||||
anchor_point_t anchor);
|
anchor_point_t anchor);
|
||||||
|
|
||||||
// Remove element by ID
|
// Remove element by ID
|
||||||
bool compositor_remove(compositor_t* comp, uint32_t id);
|
bool compositor_remove(compositor_t* comp, element_handle_t id);
|
||||||
|
|
||||||
// AIO Update element properties, immediate effect
|
// Setters, immediate effect
|
||||||
bool compositor_update(compositor_t* comp, uint32_t id,
|
bool compositor_set_position(compositor_t* comp, element_handle_t id,
|
||||||
int32_t x, int32_t y,
|
|
||||||
int32_t z_order, float scale,
|
|
||||||
bool visible, float alpha);
|
|
||||||
|
|
||||||
// Individual setters, immediate effect
|
|
||||||
bool compositor_set_position(compositor_t* comp, uint32_t id,
|
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
bool compositor_set_zorder(compositor_t* comp, uint32_t id,
|
bool compositor_set_zorder(compositor_t* comp, element_handle_t id,
|
||||||
int32_t z_order);
|
int32_t z_order);
|
||||||
bool compositor_set_scale(compositor_t* comp, uint32_t id,
|
bool compositor_set_scale(compositor_t* comp, element_handle_t id,
|
||||||
float scale);
|
float scale);
|
||||||
bool compositor_set_visible(compositor_t* comp, uint32_t id,
|
bool compositor_set_visible(compositor_t* comp, element_handle_t id,
|
||||||
bool visible);
|
bool visible);
|
||||||
bool compositor_set_alpha(compositor_t* comp, uint32_t id,
|
bool compositor_set_alpha(compositor_t* comp, element_handle_t id,
|
||||||
float alpha);
|
float alpha);
|
||||||
|
|
||||||
// Advances all animations by a given time delta.
|
// Advances all animations by a given time delta.
|
||||||
@@ -91,31 +86,31 @@ void compositor_tick(compositor_t* comp, float delta_time_sec);
|
|||||||
// Queue means the animation will start after the previous animation completed
|
// Queue means the animation will start after the previous animation completed
|
||||||
// Not queueing will play the animation alongside any current animation
|
// Not queueing will play the animation alongside any current animation
|
||||||
// You cannot stack the same type of animation without queueing, the second call will cancel the first call.
|
// You cannot stack the same type of animation without queueing, the second call will cancel the first call.
|
||||||
bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
bool compositor_animate_move(compositor_t* comp, element_handle_t id,
|
||||||
int32_t to_x, int32_t to_y,
|
int32_t to_x, int32_t to_y,
|
||||||
float duration_sec, easing_t easing, bool queue);
|
float duration_sec, easing_t easing, bool queue, bool parallel);
|
||||||
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
|
||||||
float to_alpha,
|
float to_alpha,
|
||||||
float duration_sec, easing_t easing, bool queue);
|
float duration_sec, easing_t easing, bool queue, bool parallel);
|
||||||
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
|
||||||
float to_scale,
|
float to_scale,
|
||||||
float duration_sec, easing_t easing, bool queue);
|
float duration_sec, easing_t easing, bool queue, bool parallel);
|
||||||
|
|
||||||
// Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object.
|
// Immediately finish all animations on a specific object (optionally only N queued, 0 = all), or every object.
|
||||||
bool compositor_animation_finish_element(compositor_t* comp, uint32_t id, int num_queued /*0=all*/);
|
bool compositor_animation_finish_element(compositor_t* comp, element_handle_t id, int num_queued /*0=all*/);
|
||||||
void compositor_animation_finish_all(compositor_t* comp);
|
void compositor_animation_finish_all(compositor_t* comp);
|
||||||
|
|
||||||
// Returns true if any element is animating or has queued animations.
|
// Returns true if any element is animating or has queued animations.
|
||||||
bool compositor_is_animating(compositor_t* comp);
|
bool compositor_is_animating(compositor_t* comp);
|
||||||
|
|
||||||
// Returns true if the specified element is animating or has queued animations.
|
// Returns true if the specified element is animating or has queued animations.
|
||||||
bool compositor_is_element_animating(compositor_t* comp, uint32_t id);
|
bool compositor_is_element_animating(compositor_t* comp, element_handle_t id);
|
||||||
|
|
||||||
// Compose all visible elements and return framebuffer pointer
|
// Compose all visible elements and return framebuffer pointer
|
||||||
const uint32_t* compositor_compose(compositor_t* comp);
|
const uint32_t* compositor_compose(compositor_t* comp);
|
||||||
|
|
||||||
// Mark an element's content as dirty for redrawing
|
// Mark an element's content as dirty for redrawing
|
||||||
bool compositor_mark_content_dirty(compositor_t* comp, uint32_t id);
|
bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id);
|
||||||
void compositor_mark_all_dirty(compositor_t* comp);
|
void compositor_mark_all_dirty(compositor_t* comp);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
+3
-1
@@ -53,6 +53,8 @@ extern "C" {
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef struct { int x, y; } mouse_pos_t;
|
||||||
|
|
||||||
// creates a window with given width, height and title, title can be utf8
|
// creates a window with given width, height and title, title can be utf8
|
||||||
window_t* window_create(int width, int height, const char* title);
|
window_t* window_create(int width, int height, const char* title);
|
||||||
// destroys the window
|
// destroys the window
|
||||||
@@ -66,7 +68,7 @@ bool window_is_key_down(window_t* s, int key_scancode);
|
|||||||
// returns true if the given mouse button is currently held down
|
// returns true if the given mouse button is currently held down
|
||||||
bool window_is_mouse_button_down(window_t* s, int button_index);
|
bool window_is_mouse_button_down(window_t* s, int button_index);
|
||||||
// gets the current mouse position in window coordinates
|
// gets the current mouse position in window coordinates
|
||||||
void window_get_mouse_position(window_t* s, int* x, int* y);
|
mouse_pos_t window_get_mouse_position(window_t* s);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -186,7 +186,7 @@ void audio_free_data(uint8_t* data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t audio_play(audio_t* audio, const uint8_t* data, int size,
|
audio_stream_id_t audio_play(audio_t* audio, const uint8_t* data, int size,
|
||||||
int channels, int rate, bool loop) {
|
int channels, int rate, bool loop) {
|
||||||
if (!audio || !data) return 0;
|
if (!audio || !data) return 0;
|
||||||
|
|
||||||
@@ -218,12 +218,12 @@ uint32_t audio_play(audio_t* audio, const uint8_t* data, int size,
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_stop(audio_t* audio, uint32_t id) {
|
bool audio_stop(audio_t* audio, audio_stream_id_t id) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
return audio->streams.erase(id) > 0; // destructor closes the device
|
return audio->streams.erase(id) > 0; // destructor closes the device
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_pause(audio_t* audio, uint32_t id) {
|
bool audio_pause(audio_t* audio, audio_stream_id_t id) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
auto it = audio->streams.find(id);
|
auto it = audio->streams.find(id);
|
||||||
if (it == audio->streams.end()) return false;
|
if (it == audio->streams.end()) return false;
|
||||||
@@ -235,7 +235,7 @@ bool audio_pause(audio_t* audio, uint32_t id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_resume(audio_t* audio, uint32_t id) {
|
bool audio_resume(audio_t* audio, audio_stream_id_t id) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
auto it = audio->streams.find(id);
|
auto it = audio->streams.find(id);
|
||||||
if (it == audio->streams.end()) return false;
|
if (it == audio->streams.end()) return false;
|
||||||
@@ -247,19 +247,19 @@ bool audio_resume(audio_t* audio, uint32_t id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_is_active(audio_t* audio, uint32_t id) {
|
bool audio_is_active(audio_t* audio, audio_stream_id_t id) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
return audio->streams.count(id) > 0;
|
return audio->streams.count(id) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_is_playing(audio_t* audio, uint32_t id) {
|
bool audio_is_playing(audio_t* audio, audio_stream_id_t id) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
auto it = audio->streams.find(id);
|
auto it = audio->streams.find(id);
|
||||||
if (it == audio->streams.end()) return false;
|
if (it == audio->streams.end()) return false;
|
||||||
return !it->second.paused;
|
return !it->second.paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_set_volume(audio_t* audio, uint32_t id, float volume) {
|
bool audio_set_volume(audio_t* audio, audio_stream_id_t id, float volume) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
auto it = audio->streams.find(id);
|
auto it = audio->streams.find(id);
|
||||||
if (it == audio->streams.end()) return false;
|
if (it == audio->streams.end()) return false;
|
||||||
@@ -269,7 +269,7 @@ bool audio_set_volume(audio_t* audio, uint32_t id, float volume) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool audio_animate_volume(audio_t* audio, uint32_t id,
|
bool audio_animate_volume(audio_t* audio, audio_stream_id_t id,
|
||||||
float to_volume, float duration_sec) {
|
float to_volume, float duration_sec) {
|
||||||
if (!audio) return false;
|
if (!audio) return false;
|
||||||
auto it = audio->streams.find(id);
|
auto it = audio->streams.find(id);
|
||||||
|
|||||||
+95
-100
@@ -65,6 +65,7 @@ struct queued_animation_t {
|
|||||||
bool affects_position;
|
bool affects_position;
|
||||||
bool affects_scale;
|
bool affects_scale;
|
||||||
bool affects_alpha;
|
bool affects_alpha;
|
||||||
|
bool parallel = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct element_t {
|
struct element_t {
|
||||||
@@ -250,7 +251,18 @@ static void update_spatial_hash(compositor_t* comp, element_t* elem) {
|
|||||||
for (int32_t ty = tile_y0; ty < tile_y1; ty++) {
|
for (int32_t ty = tile_y0; ty < tile_y1; ty++) {
|
||||||
for (int32_t tx = tile_x0; tx < tile_x1; tx++) {
|
for (int32_t tx = tile_x0; tx < tile_x1; tx++) {
|
||||||
uint32_t tile_idx = ty * comp->tiles_x + tx;
|
uint32_t tile_idx = ty * comp->tiles_x + tx;
|
||||||
comp->tile_map[tile_idx].push_back(elem->id);
|
auto& tile_list = comp->tile_map[tile_idx];
|
||||||
|
|
||||||
|
auto it_insert = std::lower_bound(
|
||||||
|
tile_list.begin(),
|
||||||
|
tile_list.end(),
|
||||||
|
elem->z_order,
|
||||||
|
[comp](uint32_t current_id, int32_t target_z) {
|
||||||
|
return comp->elements_map[current_id].z_order < target_z;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
tile_list.insert(it_insert, elem->id);
|
||||||
elem->occupied_tiles.push_back(tile_idx);
|
elem->occupied_tiles.push_back(tile_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +299,7 @@ static void update_draw_order(compositor_t* comp, uint32_t id) {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
// Advances all animations by a given time delta.
|
// Advances all animations by a given time delta.
|
||||||
void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
||||||
if (!comp) return;
|
if (!comp) return;
|
||||||
|
|
||||||
for (auto& pair : comp->elements_map) {
|
for (auto& pair : comp->elements_map) {
|
||||||
@@ -295,16 +307,18 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
if (!elem->valid) continue;
|
if (!elem->valid) continue;
|
||||||
animation_t* anim = &elem->anim;
|
animation_t* anim = &elem->anim;
|
||||||
|
|
||||||
// Check if we need to start a queued animation
|
// Are we currently busy doing ANYTHING?
|
||||||
if (anim->type == ANIM_NONE && !elem->anim_queue.empty()) {
|
bool is_busy = anim->animating_position || anim->animating_alpha || anim->animating_scale;
|
||||||
|
|
||||||
|
// If idle, pop the next block of queued animations!
|
||||||
|
if (!is_busy && !elem->anim_queue.empty()) {
|
||||||
|
|
||||||
|
while (!elem->anim_queue.empty()) {
|
||||||
queued_animation_t& next = elem->anim_queue.front();
|
queued_animation_t& next = elem->anim_queue.front();
|
||||||
|
|
||||||
anim->type = next.type;
|
// Route the queued data to the active state
|
||||||
anim->animating_position = next.affects_position;
|
|
||||||
anim->animating_scale = next.affects_scale;
|
|
||||||
anim->animating_alpha = next.affects_alpha;
|
|
||||||
|
|
||||||
if (next.affects_position) {
|
if (next.affects_position) {
|
||||||
|
anim->animating_position = true;
|
||||||
anim->start_x = elem->x;
|
anim->start_x = elem->x;
|
||||||
anim->start_y = elem->y;
|
anim->start_y = elem->y;
|
||||||
anim->end_x = next.to_x;
|
anim->end_x = next.to_x;
|
||||||
@@ -314,13 +328,19 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
anim->position_anim.easing = next.easing;
|
anim->position_anim.easing = next.easing;
|
||||||
}
|
}
|
||||||
if (next.affects_alpha) {
|
if (next.affects_alpha) {
|
||||||
|
anim->animating_alpha = true;
|
||||||
anim->start_alpha = elem->alpha;
|
anim->start_alpha = elem->alpha;
|
||||||
anim->end_alpha = next.to_alpha;
|
anim->end_alpha = next.to_alpha;
|
||||||
anim->alpha_anim.progress_sec = 0.0f;
|
anim->alpha_anim.progress_sec = 0.0f;
|
||||||
anim->alpha_anim.duration_sec = next.duration_sec;
|
anim->alpha_anim.duration_sec = next.duration_sec;
|
||||||
anim->alpha_anim.easing = next.easing;
|
anim->alpha_anim.easing = next.easing;
|
||||||
|
// Auto-show if fading in from 0
|
||||||
|
if (elem->alpha == 0.0f && next.to_alpha > 0.0f) {
|
||||||
|
elem->visible = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (next.affects_scale) {
|
if (next.affects_scale) {
|
||||||
|
anim->animating_scale = true;
|
||||||
anim->start_scale = elem->scale;
|
anim->start_scale = elem->scale;
|
||||||
anim->end_scale = next.to_scale;
|
anim->end_scale = next.to_scale;
|
||||||
anim->scale_anim.progress_sec = 0.0f;
|
anim->scale_anim.progress_sec = 0.0f;
|
||||||
@@ -328,17 +348,30 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
anim->scale_anim.easing = next.easing;
|
anim->scale_anim.easing = next.easing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove it from the queue
|
||||||
elem->anim_queue.erase(elem->anim_queue.begin());
|
elem->anim_queue.erase(elem->anim_queue.begin());
|
||||||
|
|
||||||
|
// THE MAGIC TRICK:
|
||||||
|
// Look at the NEXT item. If it exists and wants to be parallel,
|
||||||
|
// loop around and pop it immediately! Otherwise, break and wait.
|
||||||
|
if (elem->anim_queue.empty() || !elem->anim_queue.front().parallel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if nothing is animating
|
// Re-evaluate busy status since we just popped new animations
|
||||||
if (anim->type == ANIM_NONE) continue;
|
is_busy = anim->animating_position || anim->animating_alpha || anim->animating_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip ticking if nothing is animating at all
|
||||||
|
if (!is_busy) continue;
|
||||||
|
|
||||||
mark_element_dirty(comp, elem);
|
mark_element_dirty(comp, elem);
|
||||||
bool position_changed = false;
|
bool position_changed = false;
|
||||||
bool all_done = true;
|
|
||||||
|
|
||||||
// Update position animation with its own timing
|
// --- UPDATE ACTIVE ANIMATIONS ---
|
||||||
|
|
||||||
|
// Update Position
|
||||||
if (anim->animating_position) {
|
if (anim->animating_position) {
|
||||||
anim->position_anim.progress_sec += delta_time_sec;
|
anim->position_anim.progress_sec += delta_time_sec;
|
||||||
float t_raw = anim->position_anim.progress_sec / anim->position_anim.duration_sec;
|
float t_raw = anim->position_anim.progress_sec / anim->position_anim.duration_sec;
|
||||||
@@ -347,34 +380,33 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
elem->x = anim->end_x;
|
elem->x = anim->end_x;
|
||||||
elem->y = anim->end_y;
|
elem->y = anim->end_y;
|
||||||
anim->animating_position = false;
|
anim->animating_position = false;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
float t_eased = apply_easing(anim->position_anim.easing, t_raw);
|
float t_eased = apply_easing(anim->position_anim.easing, t_raw);
|
||||||
elem->x = (int32_t)std::round(lerp(static_cast<float>(anim->start_x),
|
// Note: using whatever lerp function you have in your engine
|
||||||
static_cast<float>(anim->end_x), t_eased));
|
elem->x = anim->start_x + (anim->end_x - anim->start_x) * t_eased;
|
||||||
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
|
elem->y = anim->start_y + (anim->end_y - anim->start_y) * t_eased;
|
||||||
static_cast<float>(anim->end_y), t_eased));
|
|
||||||
all_done = false;
|
|
||||||
}
|
}
|
||||||
position_changed = true;
|
position_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update alpha animation with its own timing
|
// Update Alpha
|
||||||
if (anim->animating_alpha) {
|
if (anim->animating_alpha) {
|
||||||
anim->alpha_anim.progress_sec += delta_time_sec;
|
anim->alpha_anim.progress_sec += delta_time_sec;
|
||||||
float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec;
|
float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec;
|
||||||
|
|
||||||
if (t_raw >= 1.0f) {
|
if (t_raw >= 1.0f) {
|
||||||
elem->alpha = anim->end_alpha;
|
elem->alpha = anim->end_alpha;
|
||||||
if (elem->alpha == 0.0f) elem->visible = false;
|
if (elem->alpha <= 0.0f) elem->visible = false;
|
||||||
anim->animating_alpha = false;
|
anim->animating_alpha = false;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
float t_eased = apply_easing(anim->alpha_anim.easing, t_raw);
|
float t_eased = apply_easing(anim->alpha_anim.easing, t_raw);
|
||||||
elem->alpha = lerp(anim->start_alpha, anim->end_alpha, t_eased);
|
elem->alpha = anim->start_alpha + (anim->end_alpha - anim->start_alpha) * t_eased;
|
||||||
all_done = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update scale animation with its own timing
|
// Update Scale
|
||||||
if (anim->animating_scale) {
|
if (anim->animating_scale) {
|
||||||
anim->scale_anim.progress_sec += delta_time_sec;
|
anim->scale_anim.progress_sec += delta_time_sec;
|
||||||
float t_raw = anim->scale_anim.progress_sec / anim->scale_anim.duration_sec;
|
float t_raw = anim->scale_anim.progress_sec / anim->scale_anim.duration_sec;
|
||||||
@@ -382,16 +414,16 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
if (t_raw >= 1.0f) {
|
if (t_raw >= 1.0f) {
|
||||||
elem->scale = anim->end_scale;
|
elem->scale = anim->end_scale;
|
||||||
anim->animating_scale = false;
|
anim->animating_scale = false;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
float t_eased = apply_easing(anim->scale_anim.easing, t_raw);
|
float t_eased = apply_easing(anim->scale_anim.easing, t_raw);
|
||||||
elem->scale = lerp(anim->start_scale, anim->end_scale, t_eased);
|
elem->scale = anim->start_scale + (anim->end_scale - anim->start_scale) * t_eased;
|
||||||
all_done = false;
|
|
||||||
}
|
}
|
||||||
position_changed = true;
|
position_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all animations are done, mark as complete
|
// Cleanup empty state marker
|
||||||
if (all_done) {
|
if (!anim->animating_position && !anim->animating_alpha && !anim->animating_scale) {
|
||||||
anim->type = ANIM_NONE;
|
anim->type = ANIM_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,11 +431,13 @@ void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
|||||||
if (position_changed) {
|
if (position_changed) {
|
||||||
clear_spatial_hash(comp, elem->id);
|
clear_spatial_hash(comp, elem->id);
|
||||||
update_spatial_hash(comp, elem);
|
update_spatial_hash(comp, elem);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
|
// Just redrawing pixels, didn't move
|
||||||
mark_element_dirty(comp, elem);
|
mark_element_dirty(comp, elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocates and initializes a new compositor instance.
|
// Allocates and initializes a new compositor instance.
|
||||||
compositor_t* compositor_init(uint32_t width, uint32_t height) {
|
compositor_t* compositor_init(uint32_t width, uint32_t height) {
|
||||||
@@ -443,7 +477,7 @@ void compositor_destroy(compositor_t* comp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inserts a new element into the compositor and returns a unique ID.
|
// Inserts a new element into the compositor and returns a unique ID.
|
||||||
uint32_t compositor_insert(compositor_t* comp,
|
element_handle_t compositor_insert(compositor_t* comp,
|
||||||
const uint32_t* fb,
|
const uint32_t* fb,
|
||||||
uint32_t fb_width,
|
uint32_t fb_width,
|
||||||
uint32_t fb_height,
|
uint32_t fb_height,
|
||||||
@@ -490,7 +524,7 @@ uint32_t compositor_insert(compositor_t* comp,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Removes an element from the compositor.
|
// Removes an element from the compositor.
|
||||||
bool compositor_remove(compositor_t* comp, uint32_t id) {
|
bool compositor_remove(compositor_t* comp, element_handle_t id) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -521,52 +555,8 @@ bool compositor_remove(compositor_t* comp, uint32_t id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates all properties of an element in a single "heavy" call.
|
|
||||||
bool compositor_update(compositor_t* comp, uint32_t id,
|
|
||||||
int32_t x, int32_t y,
|
|
||||||
int32_t z_order, float scale,
|
|
||||||
bool visible, float alpha) {
|
|
||||||
if (!comp) return false;
|
|
||||||
|
|
||||||
element_t* elem = find_element(comp, id);
|
|
||||||
if (!elem) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark old area dirty (based on current position/size)
|
|
||||||
mark_element_dirty(comp, elem);
|
|
||||||
clear_spatial_hash(comp, id); // Clear hash entries for old position/size
|
|
||||||
|
|
||||||
// Update properties
|
|
||||||
elem->x = x;
|
|
||||||
elem->y = y;
|
|
||||||
|
|
||||||
// Check for z_order change
|
|
||||||
bool z_order_changed = (elem->z_order != z_order);
|
|
||||||
elem->z_order = z_order;
|
|
||||||
|
|
||||||
elem->scale = scale;
|
|
||||||
elem->visible = visible;
|
|
||||||
|
|
||||||
// Update the spatial hash and mark the new area dirty
|
|
||||||
update_spatial_hash(comp, elem);
|
|
||||||
|
|
||||||
// Update draw order if z_order changed
|
|
||||||
if (z_order_changed) {
|
|
||||||
update_draw_order(comp, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
elem->alpha = std::max(0.0f, std::min(1.0f, alpha));
|
|
||||||
|
|
||||||
if (elem->alpha == 0.0f) {
|
|
||||||
mark_element_dirty(comp, elem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setter for an element's (x, y) position.
|
// setter for an element's (x, y) position.
|
||||||
bool compositor_set_position(compositor_t* comp, uint32_t id, int32_t x, int32_t y) {
|
bool compositor_set_position(compositor_t* comp, element_handle_t id, int32_t x, int32_t y) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -588,22 +578,29 @@ bool compositor_set_position(compositor_t* comp, uint32_t id, int32_t x, int32_t
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setter for an element's z-order.
|
// setter for an element's z-order.
|
||||||
bool compositor_set_zorder(compositor_t* comp, uint32_t id, int32_t z_order) {
|
bool compositor_set_zorder(compositor_t* comp, element_handle_t id, int32_t z_order) {
|
||||||
if (!comp) return false;
|
if (!comp) return false;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (elem->z_order != z_order) {
|
if (elem->z_order != z_order) {
|
||||||
|
// Must clear from spatial hash BEFORE changing z_order to safely re-insert
|
||||||
|
mark_element_dirty(comp, elem);
|
||||||
|
clear_spatial_hash(comp, id);
|
||||||
|
|
||||||
elem->z_order = z_order;
|
elem->z_order = z_order;
|
||||||
update_draw_order(comp, id);
|
update_draw_order(comp, id);
|
||||||
|
|
||||||
|
// Re-add to spatial hash, which will now insert it using the new z_order
|
||||||
|
update_spatial_hash(comp, elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setter for an element's visibility.
|
// setter for an element's visibility.
|
||||||
bool compositor_set_visible(compositor_t* comp, uint32_t id, bool visible) {
|
bool compositor_set_visible(compositor_t* comp, element_handle_t id, bool visible) {
|
||||||
if (!comp) return false;
|
if (!comp) return false;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -624,7 +621,7 @@ bool compositor_set_visible(compositor_t* comp, uint32_t id, bool visible) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setter for an element's scale.
|
// setter for an element's scale.
|
||||||
bool compositor_set_scale(compositor_t* comp, uint32_t id, float scale) {
|
bool compositor_set_scale(compositor_t* comp, element_handle_t id, float scale) {
|
||||||
if (!comp) return false;
|
if (!comp) return false;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -645,7 +642,7 @@ bool compositor_set_scale(compositor_t* comp, uint32_t id, float scale) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setter for an element's alpha.
|
// setter for an element's alpha.
|
||||||
bool compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) {
|
bool compositor_set_alpha(compositor_t* comp, element_handle_t id, float alpha) {
|
||||||
if (!comp) return false;
|
if (!comp) return false;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -671,7 +668,7 @@ bool compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setter for an element's anchor point.
|
// setter for an element's anchor point.
|
||||||
bool compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t anchor) {
|
bool compositor_set_anchor(compositor_t* comp, element_handle_t id, anchor_point_t anchor) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -695,9 +692,9 @@ bool compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t ancho
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
bool compositor_animate_move(compositor_t* comp, element_handle_t id,
|
||||||
int32_t to_x, int32_t to_y,
|
int32_t to_x, int32_t to_y,
|
||||||
float duration_sec, easing_t easing, bool queue) {
|
float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -716,6 +713,7 @@ bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
|||||||
queued.to_x = to_x;
|
queued.to_x = to_x;
|
||||||
queued.to_y = to_y;
|
queued.to_y = to_y;
|
||||||
queued.is_move_fade = false;
|
queued.is_move_fade = false;
|
||||||
|
queued.parallel = parallel;
|
||||||
queued.affects_position = true;
|
queued.affects_position = true;
|
||||||
queued.affects_scale = false;
|
queued.affects_scale = false;
|
||||||
queued.affects_alpha = false;
|
queued.affects_alpha = false;
|
||||||
@@ -743,8 +741,8 @@ bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
|
||||||
float to_scale, float duration_sec, easing_t easing, bool queue) {
|
float to_scale, float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -762,6 +760,7 @@ bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
|||||||
queued.duration_sec = duration_sec;
|
queued.duration_sec = duration_sec;
|
||||||
queued.to_scale = to_scale;
|
queued.to_scale = to_scale;
|
||||||
queued.is_move_fade = false;
|
queued.is_move_fade = false;
|
||||||
|
queued.parallel = parallel;
|
||||||
queued.affects_position = false;
|
queued.affects_position = false;
|
||||||
queued.affects_scale = true;
|
queued.affects_scale = true;
|
||||||
queued.affects_alpha = false;
|
queued.affects_alpha = false;
|
||||||
@@ -787,8 +786,8 @@ bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
|
||||||
float to_alpha, float duration_sec, easing_t easing, bool queue) {
|
float to_alpha, float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -808,6 +807,7 @@ bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
|||||||
queued.duration_sec = duration_sec;
|
queued.duration_sec = duration_sec;
|
||||||
queued.to_alpha = to_alpha;
|
queued.to_alpha = to_alpha;
|
||||||
queued.is_move_fade = false;
|
queued.is_move_fade = false;
|
||||||
|
queued.parallel = parallel;
|
||||||
queued.affects_position = false;
|
queued.affects_position = false;
|
||||||
queued.affects_scale = false;
|
queued.affects_scale = false;
|
||||||
queued.affects_alpha = true;
|
queued.affects_alpha = true;
|
||||||
@@ -838,7 +838,7 @@ bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finish all or up to num_queued queued animations for a specific element
|
// Finish all or up to num_queued queued animations for a specific element
|
||||||
bool compositor_animation_finish_element(compositor_t* comp, uint32_t id, int num_queued) {
|
bool compositor_animation_finish_element(compositor_t* comp, element_handle_t id, int num_queued) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -982,7 +982,7 @@ void compositor_mark_all_dirty(compositor_t* comp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Marks the tiles occupied by a specific element as dirty.
|
// Marks the tiles occupied by a specific element as dirty.
|
||||||
bool compositor_mark_content_dirty(compositor_t* comp, uint32_t id) {
|
bool compositor_mark_content_dirty(compositor_t* comp, element_handle_t id) {
|
||||||
if (!comp) {
|
if (!comp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1016,7 +1016,7 @@ bool compositor_is_animating(compositor_t* comp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the specified element is animating or has queued animations.
|
// Returns true if the specified element is animating or has queued animations.
|
||||||
bool compositor_is_element_animating(compositor_t* comp, uint32_t id) {
|
bool compositor_is_element_animating(compositor_t* comp, element_handle_t id) {
|
||||||
if (!comp) return false;
|
if (!comp) return false;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -1038,7 +1038,7 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
|||||||
// OpenMP 2.0 requires signed integers for loop counters
|
// OpenMP 2.0 requires signed integers for loop counters
|
||||||
int total_tiles = (int)(comp->tiles_y * comp->tiles_x);
|
int total_tiles = (int)(comp->tiles_y * comp->tiles_x);
|
||||||
|
|
||||||
// Uncomment when you're ready to test threading again!
|
// Uncomment when ready to test threading again!
|
||||||
//#pragma omp parallel for schedule(dynamic, 8)
|
//#pragma omp parallel for schedule(dynamic, 8)
|
||||||
for (int i = 0; i < total_tiles; i++) {
|
for (int i = 0; i < total_tiles; i++) {
|
||||||
uint32_t ty = (uint32_t)(i / comp->tiles_x);
|
uint32_t ty = (uint32_t)(i / comp->tiles_x);
|
||||||
@@ -1061,7 +1061,7 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SPATIAL HASH: Only process elements that actually touch this tile
|
// SPATIAL HASH: Only process elements that actually touch this tile
|
||||||
std::vector<uint32_t> local_draw_list = comp->tile_map[tile_idx];
|
const std::vector<uint32_t>& local_draw_list = comp->tile_map[tile_idx];
|
||||||
|
|
||||||
// If nothing is in this tile, we're done here
|
// If nothing is in this tile, we're done here
|
||||||
if (local_draw_list.empty()) {
|
if (local_draw_list.empty()) {
|
||||||
@@ -1069,11 +1069,6 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the local elements by z-order
|
|
||||||
std::sort(local_draw_list.begin(), local_draw_list.end(), [comp](uint32_t a, uint32_t b) {
|
|
||||||
return comp->elements_map[a].z_order < comp->elements_map[b].z_order;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Draw only the local elements
|
// Draw only the local elements
|
||||||
for (uint32_t element_id : local_draw_list) {
|
for (uint32_t element_id : local_draw_list) {
|
||||||
const element_t* elem = find_element(comp, element_id);
|
const element_t* elem = find_element(comp, element_id);
|
||||||
|
|||||||
+528
-263
@@ -1,370 +1,635 @@
|
|||||||
// this file is a clusterfuck that works as a demo of the internals.
|
|
||||||
// frontend is hard. todo a real state machine.
|
|
||||||
// want branching? if you try really hard.
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
#include <window.h>
|
#include <window.h>
|
||||||
#include <text.h>
|
#include <text.h>
|
||||||
#include <compositor.h>
|
#include <compositor.h>
|
||||||
#include <image.h>
|
#include <image.h>
|
||||||
#include <audio.h>
|
#include <audio.h>
|
||||||
|
|
||||||
// smartass approach
|
// ---------------------------------------------------------------------------
|
||||||
struct WindowHandle {
|
// RAII handles
|
||||||
window_t* ptr;
|
// ---------------------------------------------------------------------------
|
||||||
WindowHandle(window_t* w) : ptr(w) {}
|
|
||||||
~WindowHandle() { if (ptr) window_destroy(ptr); }
|
// A generic RAII wrapper.
|
||||||
operator window_t* () { return ptr; }
|
template <typename T, void(*DestroyFn)(T*)>
|
||||||
|
struct EngineHandle {
|
||||||
|
T* ptr;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
EngineHandle(T* p = nullptr) : ptr(p) {}
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
~EngineHandle() { if (ptr) DestroyFn(ptr); }
|
||||||
|
|
||||||
|
// DELETE Copy Constructor (Prevents accidental cloning)
|
||||||
|
EngineHandle(const EngineHandle&) = delete;
|
||||||
|
|
||||||
|
// DELETE Copy Assignment (Prevents accidental overwrites)
|
||||||
|
EngineHandle& operator=(const EngineHandle&) = delete;
|
||||||
|
|
||||||
|
// ALLOW Move Constructor (Safely transfer ownership)
|
||||||
|
EngineHandle(EngineHandle&& other) noexcept : ptr(other.ptr) {
|
||||||
|
other.ptr = nullptr; // Nullify the old pointer so it doesn't get destroyed
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOW Move Assignment
|
||||||
|
EngineHandle& operator=(EngineHandle&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (ptr) DestroyFn(ptr); // Clean up our current resource first
|
||||||
|
ptr = other.ptr; // Steal the resource from the other handle
|
||||||
|
other.ptr = nullptr; // Nullify the old one
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit conversion operator
|
||||||
|
operator T* () const { return ptr; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompositorHandle {
|
using WindowHandle = EngineHandle<window_t, window_destroy>;
|
||||||
compositor_t* ptr;
|
using CompositorHandle = EngineHandle<compositor_t, compositor_destroy>;
|
||||||
CompositorHandle(compositor_t* c) : ptr(c) {}
|
using TextHandle = EngineHandle<text_t, text_destroy>;
|
||||||
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
|
using AudioHandle = EngineHandle<audio_t, audio_destroy>;
|
||||||
operator compositor_t* () { return ptr; }
|
using AudioDataHandle = EngineHandle<uint8_t, audio_free_data>;
|
||||||
|
using ImageHandle = EngineHandle<uint32_t, image_free>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Script interpreter
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// A command is an opcode plus a closure that does the actual work.
|
||||||
|
// Keeping the closure allows the command set to directly capture backend
|
||||||
|
// pointers without a fat context struct threaded through everything.
|
||||||
|
//
|
||||||
|
// The closure returns true when the interpreter should YIELD (stop executing
|
||||||
|
// commands this frame and wait). Returning false means continue immediately
|
||||||
|
// to the next command.
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
std::string opcode; // for debugging
|
||||||
|
std::function<bool()> execute;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextHandle {
|
enum class WaitKind {
|
||||||
text_t* ptr;
|
NONE,
|
||||||
TextHandle(text_t* t) : ptr(t) {}
|
CLICK, // wait for mouse click or space
|
||||||
~TextHandle() { if (ptr) text_destroy(ptr); }
|
ANIM_ALL, // wait until compositor_is_animating() == false
|
||||||
operator text_t* () { return ptr; }
|
ANIM_ELEM, // wait until a specific element stops animating
|
||||||
|
END, // script finished, keep rendering until told to quit
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ImageHandle {
|
struct Interpreter {
|
||||||
uint32_t* ptr;
|
std::vector<Command> script;
|
||||||
ImageHandle(uint32_t* p) : ptr(p) {}
|
size_t pc = 0;
|
||||||
~ImageHandle() { if (ptr) image_free(ptr); }
|
|
||||||
operator uint32_t* () { return ptr; }
|
WaitKind waiting = WaitKind::NONE;
|
||||||
|
element_handle_t wait_elem = 0; // valid when waiting == ANIM_ELEM
|
||||||
|
|
||||||
|
// Callbacks into the engine, set before first tick
|
||||||
|
compositor_t* compositor = nullptr;
|
||||||
|
text_t* dialogue = nullptr;
|
||||||
|
text_t* name_box = nullptr;
|
||||||
|
|
||||||
|
// Advance the interpreter by one frame.
|
||||||
|
// clicked: true on the frame a left-click or spacebar press was detected.
|
||||||
|
// Returns true when the script has fully ended and the engine may quit.
|
||||||
|
bool tick(bool clicked) {
|
||||||
|
|
||||||
|
// ---- resolve current wait condition --------------------------------
|
||||||
|
switch (waiting) {
|
||||||
|
|
||||||
|
case WaitKind::CLICK:
|
||||||
|
if (clicked) {
|
||||||
|
// If the typewriter is still running, snap it and stay on
|
||||||
|
// this line - let the player see the full text first.
|
||||||
|
if (text_is_animating(dialogue)) {
|
||||||
|
text_show_all_current(dialogue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
waiting = WaitKind::NONE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WaitKind::ANIM_ALL:
|
||||||
|
if (compositor_is_animating(compositor))
|
||||||
|
return false;
|
||||||
|
waiting = WaitKind::NONE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WaitKind::ANIM_ELEM:
|
||||||
|
if (compositor_is_element_animating(compositor, wait_elem))
|
||||||
|
return false;
|
||||||
|
waiting = WaitKind::NONE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WaitKind::END:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case WaitKind::NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- burn through commands until a yield ---------------------------
|
||||||
|
while (pc < script.size()) {
|
||||||
|
const Command& cmd = script[pc++];
|
||||||
|
bool yield = cmd.execute();
|
||||||
|
if (yield) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ran off the end of the script without an explicit END.
|
||||||
|
waiting = WaitKind::END;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AudioHandle {
|
// ---------------------------------------------------------------------------
|
||||||
audio_t* ptr;
|
// Script builder helpers
|
||||||
AudioHandle(audio_t* a) : ptr(a) {}
|
//
|
||||||
~AudioHandle() { if (ptr) audio_destroy(ptr); }
|
// These push Command objects onto the script vector. They capture the
|
||||||
operator audio_t* () { return ptr; }
|
// interpreter and backend pointers by reference/value as appropriate.
|
||||||
};
|
// All helpers return void and operate on a passed-in script vector so the
|
||||||
|
// build function stays readable.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
struct AudioDataHandle {
|
using Script = std::vector<Command>;
|
||||||
uint8_t* ptr;
|
|
||||||
AudioDataHandle(uint8_t* p) : ptr(p) {}
|
static void cmd_speaker(Script& s, text_t* name_box,
|
||||||
~AudioDataHandle() { if (ptr) audio_free_data(ptr); }
|
element_handle_t name_id, compositor_t* comp,
|
||||||
operator uint8_t* () { return ptr; }
|
const char* speaker)
|
||||||
};
|
{
|
||||||
|
s.push_back({ "SPEAKER", [=]() -> bool {
|
||||||
|
text_clear(name_box);
|
||||||
|
if (speaker && speaker[0])
|
||||||
|
text_push(name_box, speaker, 0.0f, true);
|
||||||
|
compositor_mark_content_dirty(comp, name_id);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_line(Script& s, text_t* dialogue,
|
||||||
|
element_handle_t dialogue_id, compositor_t* comp,
|
||||||
|
const char* text, float speed = 30.0f)
|
||||||
|
{
|
||||||
|
s.push_back({ "LINE", [=]() -> bool {
|
||||||
|
text_clear(dialogue);
|
||||||
|
text_push(dialogue, text, speed, true);
|
||||||
|
compositor_mark_content_dirty(comp, dialogue_id);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_wait_click(Script& s, Interpreter& interp)
|
||||||
|
{
|
||||||
|
s.push_back({ "WAIT_CLICK", [&interp]() -> bool {
|
||||||
|
interp.waiting = WaitKind::CLICK;
|
||||||
|
return true;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_wait_anim_all(Script& s, Interpreter& interp)
|
||||||
|
{
|
||||||
|
s.push_back({ "WAIT_ANIM_ALL", [&interp]() -> bool {
|
||||||
|
interp.waiting = WaitKind::ANIM_ALL;
|
||||||
|
return true;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_wait_anim_elem(Script& s, Interpreter& interp,
|
||||||
|
element_handle_t elem)
|
||||||
|
{
|
||||||
|
s.push_back({ "WAIT_ANIM_ELEM", [&interp, elem]() -> bool {
|
||||||
|
interp.waiting = WaitKind::ANIM_ELEM;
|
||||||
|
interp.wait_elem = elem;
|
||||||
|
return true;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_finish_all(Script& s, compositor_t* comp)
|
||||||
|
{
|
||||||
|
s.push_back({ "ANIM_FINISH_ALL", [=]() -> bool {
|
||||||
|
compositor_animation_finish_all(comp);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_anim_scale(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem, float to,
|
||||||
|
float dur, easing_t ease, bool queue, bool parallel)
|
||||||
|
{
|
||||||
|
s.push_back({ "ANIM_SCALE", [=]() -> bool {
|
||||||
|
compositor_animate_scale(comp, elem, to, dur, ease, queue, parallel);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_anim_move(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem,
|
||||||
|
int32_t x, int32_t y,
|
||||||
|
float dur, easing_t ease, bool queue, bool parallel)
|
||||||
|
{
|
||||||
|
s.push_back({ "ANIM_MOVE", [=]() -> bool {
|
||||||
|
compositor_animate_move(comp, elem, x, y, dur, ease, queue, parallel);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_anim_fade(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem, float to_alpha,
|
||||||
|
float dur, easing_t ease, bool queue, bool parallel)
|
||||||
|
{
|
||||||
|
s.push_back({ "ANIM_FADE", [=]() -> bool {
|
||||||
|
compositor_animate_fade(comp, elem, to_alpha, dur, ease, queue, parallel);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_set_visible(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem, bool visible)
|
||||||
|
{
|
||||||
|
s.push_back({ "ELEM_SHOW/HIDE", [=]() -> bool {
|
||||||
|
compositor_set_visible(comp, elem, visible);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_set_scale(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem, float scale)
|
||||||
|
{
|
||||||
|
s.push_back({ "ELEM_SET_SCALE", [=]() -> bool {
|
||||||
|
compositor_set_scale(comp, elem, scale);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_set_alpha(Script& s, compositor_t* comp,
|
||||||
|
element_handle_t elem, float alpha)
|
||||||
|
{
|
||||||
|
s.push_back({ "ELEM_SET_ALPHA", [=]() -> bool {
|
||||||
|
compositor_set_alpha(comp, elem, alpha);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_audio_fade(Script& s, audio_t* audio,
|
||||||
|
audio_stream_id_t id,
|
||||||
|
float to_vol, float dur)
|
||||||
|
{
|
||||||
|
s.push_back({ "MUSIC_FADE", [=]() -> bool {
|
||||||
|
audio_animate_volume(audio, id, to_vol, dur);
|
||||||
|
return false;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_end(Script& s, Interpreter& interp)
|
||||||
|
{
|
||||||
|
s.push_back({ "END", [&interp]() -> bool {
|
||||||
|
interp.waiting = WaitKind::END;
|
||||||
|
return true;
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// main
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// the clusterfuck begins
|
|
||||||
int main() {
|
int main() {
|
||||||
const int WIDTH = 1280;
|
const int WIDTH = 1280;
|
||||||
const int HEIGHT = 720;
|
const int HEIGHT = 720;
|
||||||
const int CHAR_WIDTH = 400;
|
const int CHAR_WIDTH = 400;
|
||||||
const int CHAR_HEIGHT = 600;
|
const int CHAR_HEIGHT = 600;
|
||||||
|
|
||||||
// Character Positioning Constants
|
const int CENTER_X = WIDTH / 2;
|
||||||
// The vertical baseline for character sprites, making positioning independent of sprite height.
|
|
||||||
const int CHAR_BASELINE_Y = HEIGHT - 140;
|
const int CHAR_BASELINE_Y = HEIGHT - 140;
|
||||||
// The horizontal center for the character on the left.
|
|
||||||
const int LEFT_CHAR_CENTER_X = 60 + (CHAR_WIDTH / 2);
|
const int LEFT_CHAR_CENTER_X = 60 + (CHAR_WIDTH / 2);
|
||||||
// The horizontal center for the character on the right.
|
|
||||||
const int RIGHT_CHAR_CENTER_X = WIDTH - 60 - (CHAR_WIDTH / 2);
|
const int RIGHT_CHAR_CENTER_X = WIDTH - 60 - (CHAR_WIDTH / 2);
|
||||||
// An off-screen position to the left for transitions, based on the final position.
|
|
||||||
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
|
const int OFFSCREEN_LEFT_X = LEFT_CHAR_CENTER_X - 660;
|
||||||
|
const int OFFSCREEN_RIGHT_X = RIGHT_CHAR_CENTER_X + 660;
|
||||||
|
|
||||||
|
// ---- window / compositor / audio ---------------------------------------
|
||||||
|
|
||||||
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
||||||
if (!window) {
|
if (!window) { printf("Failed to create window\n"); return 1; }
|
||||||
printf("Failed to create window\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
||||||
if (!compositor) {
|
if (!compositor) { printf("Failed to initialize compositor\n"); return 1; }
|
||||||
printf("Failed to initialize compositor\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioHandle audio(audio_init());
|
AudioHandle audio(audio_init());
|
||||||
if (!audio) {
|
if (!audio) { printf("Failed to initialize audio\n"); return 1; }
|
||||||
printf("Failed to initialize audio\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
audio_set_global_volume(audio, 1.0f);
|
audio_set_global_volume(audio, 1.0f);
|
||||||
|
|
||||||
int music_size, music_channels, music_rate, music_total_samples;
|
int music_size, music_channels, music_rate, music_total_samples;
|
||||||
AudioDataHandle music_data(audio_load_madpcm("assets/Freikugeln.wav", &music_size, &music_channels, &music_rate, &music_total_samples));
|
AudioDataHandle music_data(audio_load_madpcm(
|
||||||
if (!music_data) {
|
"assets/Freikugeln.wav",
|
||||||
printf("Failed to load audio\n");
|
&music_size, &music_channels, &music_rate, &music_total_samples));
|
||||||
return 1;
|
if (!music_data) { printf("Failed to load audio\n"); return 1; }
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t music_id = audio_play(audio, music_data, music_size, music_channels, music_rate, true);
|
audio_stream_id_t music_id = audio_play(
|
||||||
|
audio, music_data, music_size, music_channels, music_rate, true);
|
||||||
|
|
||||||
|
// ---- assets / compositor elements -------------------------------------
|
||||||
|
|
||||||
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
|
ImageHandle bg(image_load_resized("assets/background.png", WIDTH, HEIGHT));
|
||||||
if (bg) {
|
if (bg) {
|
||||||
uint32_t bg_id = compositor_insert(compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
|
element_handle_t bg_id = compositor_insert(
|
||||||
|
compositor, bg, WIDTH, HEIGHT, 0, 0, 0, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_alpha(compositor, bg_id, 1.0f);
|
compositor_set_alpha(compositor, bg_id, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
|
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
|
||||||
if (textbox) {
|
if (textbox)
|
||||||
// Position via the bottom-left corner for easier alignment with screen bottom
|
compositor_insert(compositor, textbox, WIDTH, 180,
|
||||||
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
|
0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
|
||||||
}
|
|
||||||
|
|
||||||
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
|
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert off-screen left, anchored at bottom-center for natural scaling
|
element_handle_t maksw_happy_id = compositor_insert(
|
||||||
uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy, CHAR_WIDTH, CHAR_HEIGHT, OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
compositor, maksw_happy, CHAR_WIDTH, CHAR_HEIGHT,
|
||||||
|
OFFSCREEN_LEFT_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||||
|
|
||||||
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
|
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert at the final left position, anchored at bottom-center
|
element_handle_t maksw_neutral_id = compositor_insert(
|
||||||
uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral,
|
compositor, maksw_neutral, CHAR_WIDTH, CHAR_HEIGHT,
|
||||||
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||||
compositor_set_visible(compositor, maksw_neutral_id, false);
|
compositor_set_visible(compositor, maksw_neutral_id, false);
|
||||||
|
|
||||||
ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
ImageHandle wikor_img(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||||
// Insert at the final right position, anchored at bottom-center
|
element_handle_t wikor_id = compositor_insert(
|
||||||
uint32_t wikor_id = compositor_insert(compositor, wikor,
|
compositor, wikor_img, CHAR_WIDTH, CHAR_HEIGHT,
|
||||||
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
OFFSCREEN_RIGHT_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||||
|
|
||||||
TextHandle name_text(text_init(300, 100));
|
TextHandle name_text(text_init(300, 100));
|
||||||
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
|
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
|
||||||
// Text is anchored at top-left, positioned relative to textbox
|
element_handle_t name_id = compositor_insert(
|
||||||
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
|
compositor, text_getfb(name_text),
|
||||||
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
|
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
|
|
||||||
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
|
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
|
||||||
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
|
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
|
||||||
// Text is anchored at top-left, positioned relative to textbox
|
element_handle_t dialogue_id = compositor_insert(
|
||||||
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
|
compositor, text_getfb(dialogue_text),
|
||||||
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
|
WIDTH - 140, 160, 60, HEIGHT - 130, 4, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
|
|
||||||
|
// mask: a wipe graphic, starts off-screen above, slides down to cover
|
||||||
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
|
ImageHandle mask(image_load_resized("assets/mask.png", 1280, 1080));
|
||||||
// Use top-left anchor for full-screen wipes
|
element_handle_t mask_id = compositor_insert(
|
||||||
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_visible(compositor, mask_id, false);
|
compositor_set_visible(compositor, mask_id, false);
|
||||||
|
|
||||||
|
// mask2: slides off downward to reveal the new scene
|
||||||
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
|
ImageHandle mask2(image_load_resized("assets/mask2.png", 1280, 1080));
|
||||||
// Use top-left anchor for full-screen wipes
|
element_handle_t mask2_id = compositor_insert(
|
||||||
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||||
compositor_set_visible(compositor, mask2_id, false);
|
compositor_set_visible(compositor, mask2_id, false);
|
||||||
|
|
||||||
|
// ---- build the script -------------------------------------------------
|
||||||
|
//
|
||||||
|
// Shorthand references so the builder lines stay readable.
|
||||||
|
compositor_t* comp = compositor;
|
||||||
|
text_t* dlg = dialogue_text;
|
||||||
|
text_t* nmb = name_text;
|
||||||
|
element_handle_t dlg_id = dialogue_id;
|
||||||
|
|
||||||
|
Interpreter interp;
|
||||||
|
interp.compositor = comp;
|
||||||
|
interp.dialogue = dlg;
|
||||||
|
interp.name_box = nmb;
|
||||||
|
|
||||||
|
Script& S = interp.script;
|
||||||
|
|
||||||
|
// Convenience aliases so each script line stays short.
|
||||||
|
// All lambdas below capture by value (element handles, pointers) which is
|
||||||
|
// safe because all resources outlive the interpreter.
|
||||||
|
|
||||||
|
auto speaker = [&](const char* who) {
|
||||||
|
cmd_speaker(S, nmb, name_id, comp, who);
|
||||||
|
};
|
||||||
|
auto line = [&](const char* text, float speed = 30.0f) {
|
||||||
|
cmd_line(S, dlg, dlg_id, comp, text, speed);
|
||||||
|
};
|
||||||
|
auto wait_click = [&]() { cmd_wait_click(S, interp); };
|
||||||
|
auto wait_anim_all = [&]() { cmd_wait_anim_all(S, interp); };
|
||||||
|
auto wait_anim = [&](element_handle_t e) { cmd_wait_anim_elem(S, interp, e); };
|
||||||
|
auto finish_all = [&]() { cmd_finish_all(S, comp); };
|
||||||
|
auto anim_scale = [&](element_handle_t e, float to, float dur,
|
||||||
|
easing_t ease, bool q = false, bool p = false) {
|
||||||
|
cmd_anim_scale(S, comp, e, to, dur, ease, q, p);
|
||||||
|
};
|
||||||
|
auto anim_move = [&](element_handle_t e, int32_t x, int32_t y,
|
||||||
|
float dur, easing_t ease, bool q = false, bool p = false) {
|
||||||
|
cmd_anim_move(S, comp, e, x, y, dur, ease, q, p);
|
||||||
|
};
|
||||||
|
auto anim_fade = [&](element_handle_t e, float a, float dur,
|
||||||
|
easing_t ease, bool q = false, bool p = false) {
|
||||||
|
cmd_anim_fade(S, comp, e, a, dur, ease, q, p);
|
||||||
|
};
|
||||||
|
auto show = [&](element_handle_t e) { cmd_set_visible(S, comp, e, true); };
|
||||||
|
auto hide = [&](element_handle_t e) { cmd_set_visible(S, comp, e, false); };
|
||||||
|
auto scale = [&](element_handle_t e, float v) { cmd_set_scale(S, comp, e, v); };
|
||||||
|
auto alpha = [&](element_handle_t e, float v) { cmd_set_alpha(S, comp, e, v); };
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Scene 1 - maksw slides in, introduces the engine
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Slide maksw_happy in from off-screen, bob on arrival, scale up as
|
||||||
|
// "active speaker". No WAIT_ANIM here - dialogue starts immediately
|
||||||
|
// and the animation plays alongside the typewriter, which feels natural.
|
||||||
|
anim_move(maksw_happy_id, CENTER_X, CHAR_BASELINE_Y,
|
||||||
|
1.5f, EASE_OUT_CUBIC, true);
|
||||||
|
anim_scale(maksw_happy_id, 1.1f, 0.3f, EASE_IN_OUT_CUBIC, /*queue=*/true, /*parallel=*/false);
|
||||||
|
speaker("maksw");
|
||||||
|
line("Hello and welcome to the Obwarzanek Narrative Engine demo!");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
line("Today I'll present to you-");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
anim_move(wikor_id, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 0.4f, EASE_OUT_CUBIC);
|
||||||
|
anim_move(maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y - 20,
|
||||||
|
0.3f, EASE_OUT_CUBIC);
|
||||||
|
anim_scale(maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
|
||||||
|
anim_scale(wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("wikor");
|
||||||
|
line("Yeah, hello to you too.");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
anim_scale(wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("maksw");
|
||||||
|
line("Oh Wiktor, what a nice surprise!");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
line("...Do you need anything?");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
anim_scale(maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("wikor");
|
||||||
|
line("No, I was just walking by.");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
line("Cool engine though.");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Transition - wipe in, swap scene, wipe out
|
||||||
|
//
|
||||||
|
// Phase 1: mask slides down from above to cover the screen.
|
||||||
|
// We WAIT on that specific element so we know exactly when
|
||||||
|
// the screen is fully hidden - no guessing, no hack cases.
|
||||||
|
//
|
||||||
|
// Phase 2: while hidden, swap sprites and set up the new scene state.
|
||||||
|
// All of this is instant (no animation), which is fine because
|
||||||
|
// the mask is covering everything.
|
||||||
|
//
|
||||||
|
// Phase 3: hide mask, show mask2 (already covering screen at y=0),
|
||||||
|
// slide mask2 downward to reveal the new scene.
|
||||||
|
// We don't WAIT here - dialogue runs alongside the wipe-out,
|
||||||
|
// which looks intentional and cinematic.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Phase 1 - cover
|
||||||
|
finish_all();
|
||||||
|
show(mask_id);
|
||||||
|
anim_move(mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC);
|
||||||
|
wait_anim(mask_id); // block until mask fully covers the screen
|
||||||
|
|
||||||
|
// Phase 2 - swap (mask is covering, player sees nothing)
|
||||||
|
hide(maksw_happy_id);
|
||||||
|
show(maksw_neutral_id);
|
||||||
|
scale(maksw_neutral_id, 1.0f);
|
||||||
|
scale(wikor_id, 1.05f);
|
||||||
|
|
||||||
|
// Phase 3 - uncover
|
||||||
|
hide(mask_id);
|
||||||
|
show(mask2_id);
|
||||||
|
// mask2 starts at y=0 (covering screen), animate it off downward
|
||||||
|
anim_move(mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC);
|
||||||
|
// Dialogue fires immediately - text appears as the wipe reveals the scene
|
||||||
|
speaker("wikor");
|
||||||
|
line("Anyways, how's your day going?");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Scene 2 - neutral maksw, wikor is chatty
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
anim_scale(wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("maksw");
|
||||||
|
line("You hear what I'm hearing?");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
anim_scale(maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("wikor");
|
||||||
|
line("What? That music?");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
anim_scale(wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("maksw");
|
||||||
|
line("Yep! Freikugeln by Johann Strauss II!");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
anim_scale(maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
anim_scale(wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
speaker("wikor");
|
||||||
|
line("Sure thing...");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
line("Like I know who he is.");
|
||||||
|
wait_click();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// Ending - fade to black, fade out music, then quit
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
finish_all();
|
||||||
|
anim_scale(maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
// Reuse mask as a simple alpha-fade overlay (no positional wipe needed)
|
||||||
|
show(mask_id);
|
||||||
|
alpha(mask_id, 0.0f);
|
||||||
|
anim_fade(mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC);
|
||||||
|
cmd_audio_fade(S, audio, music_id, 0.0f, 0.6f);
|
||||||
|
wait_anim(mask_id); // wait until black, then exit
|
||||||
|
cmd_end(S, interp);
|
||||||
|
|
||||||
|
// ---- main loop --------------------------------------------------------
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
||||||
auto last_report_time = last_frame_time;
|
auto last_report_time = last_frame_time;
|
||||||
|
|
||||||
uint64_t frame_count = 0;
|
uint64_t frame_count = 0;
|
||||||
float highest_frame_time = 0.0f;
|
float highest_frame_time = 0.0f;
|
||||||
float frame_time_sum = 0.0f;
|
float frame_time_sum = 0.0f;
|
||||||
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(1);
|
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(1);
|
||||||
|
|
||||||
|
bool mouse_was_down = false;
|
||||||
|
bool key_was_down = false;
|
||||||
|
|
||||||
while (running) {
|
while (running) {
|
||||||
if (window_pollevent(window)) running = false;
|
if (window_pollevent(window)) running = false;
|
||||||
|
|
||||||
auto current_time = std::chrono::high_resolution_clock::now();
|
auto current_time = std::chrono::high_resolution_clock::now();
|
||||||
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
|
std::chrono::duration<float> delta_duration = current_time - last_frame_time;
|
||||||
float delta_time_sec = delta_duration.count();
|
float dt = delta_duration.count();
|
||||||
last_frame_time = current_time;
|
last_frame_time = current_time;
|
||||||
|
|
||||||
|
// FPS reporting
|
||||||
frame_count++;
|
frame_count++;
|
||||||
frame_time_sum += delta_time_sec;
|
frame_time_sum += dt;
|
||||||
if (delta_time_sec > highest_frame_time) highest_frame_time = delta_time_sec;
|
if (dt > highest_frame_time) highest_frame_time = dt;
|
||||||
|
std::chrono::duration<float> since_report = current_time - last_report_time;
|
||||||
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
|
if (since_report >= REPORT_INTERVAL) {
|
||||||
|
|
||||||
if (time_since_last_report >= REPORT_INTERVAL) {
|
|
||||||
if (frame_count > 0) {
|
if (frame_count > 0) {
|
||||||
float avg_delta_time = frame_time_sum / (float)frame_count;
|
float fps = 1.0f / (frame_time_sum / (float)frame_count);
|
||||||
float fps = 1.0f / avg_delta_time;
|
|
||||||
float actual_duration = time_since_last_report.count();
|
|
||||||
|
|
||||||
printf("FPS: %.2f (over %llu frames in %.2f seconds), highest frame time: %.2fms\n",
|
printf("FPS: %.2f (over %llu frames in %.2f seconds), highest frame time: %.2fms\n",
|
||||||
fps, frame_count, actual_duration, highest_frame_time * 1000.0f);
|
fps, frame_count, since_report.count(), highest_frame_time * 1000.0f);
|
||||||
}
|
}
|
||||||
frame_count = 0;
|
frame_count = 0; frame_time_sum = 0.0f; highest_frame_time = 0.0f;
|
||||||
frame_time_sum = 0.0f;
|
|
||||||
highest_frame_time = 0.0f;
|
|
||||||
last_report_time = current_time;
|
last_report_time = current_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool mouse_was_down = false;
|
// Edge-detect click and keypress
|
||||||
bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT);
|
bool mouse_is_down = window_is_mouse_button_down(window, MOUSE_BUTTON_LEFT);
|
||||||
bool mouse_clicked = mouse_is_down && !mouse_was_down;
|
|
||||||
mouse_was_down = mouse_is_down;
|
|
||||||
|
|
||||||
static bool key_was_down = false;
|
|
||||||
bool key_is_down = window_is_key_down(window, KEY_SPACE);
|
bool key_is_down = window_is_key_down(window, KEY_SPACE);
|
||||||
bool key_pressed = key_is_down && !key_was_down;
|
bool clicked = (mouse_is_down && !mouse_was_down) ||
|
||||||
|
(key_is_down && !key_was_down);
|
||||||
|
mouse_was_down = mouse_is_down;
|
||||||
key_was_down = key_is_down;
|
key_was_down = key_is_down;
|
||||||
|
|
||||||
// don't judge me, it will work for now
|
// Tick the interpreter
|
||||||
static unsigned int lines = 1;
|
bool script_done = interp.tick(clicked);
|
||||||
static bool dialog_finished = false;
|
if (script_done) running = false;
|
||||||
static bool transitioning = false;
|
|
||||||
|
|
||||||
if (mouse_clicked || key_pressed || lines == 1 /* hack */) {
|
// Tick subsystems
|
||||||
if (text_is_animating(dialogue_text)) {
|
compositor_tick(compositor, dt);
|
||||||
text_show_all_current(dialogue_text);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
switch (lines) {
|
|
||||||
case 1:
|
|
||||||
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1.0f, EASE_OUT_CUBIC, false);
|
|
||||||
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
|
|
||||||
text_push(name_text, "maksw", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Hello and welcome to the Obwarzanek Narrative Engine demo!", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_push(dialogue_text, "Today I'll present to you-", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y - 20, 0.1f, EASE_OUT_CUBIC, false);
|
|
||||||
compositor_animate_move(compositor, maksw_happy_id, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 0.1f, EASE_IN_CUBIC, true);
|
|
||||||
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "wikor", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Yeah, hello to you too.", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "maksw", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Oh Wiktor, what a nice suprise!", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_push(dialogue_text, "Do you need anything?", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "wikor", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "No, i was just walking by.", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
compositor_finish_animations_all(compositor);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_push(dialogue_text, "Cool engine though.", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
// fade transition
|
|
||||||
compositor_animation_complete_all(compositor);
|
|
||||||
compositor_set_visible(compositor, mask_id, true);
|
|
||||||
compositor_animate_move(compositor, mask_id, 0, 0, 0.5f, EASE_OUT_CUBIC, false);
|
|
||||||
transitioning = true;
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
// transitioning blocks input, empty case not to trigger default and desync lines from actual line
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
compositor_animation_complete_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, wikor_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "maksw", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Can't complain, the winter break is just a week away. Plus the compositor is finally cooperating.", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
compositor_animation_complete_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
compositor_animate_scale(compositor, wikor_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "wikor", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Sure thing..", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
compositor_animation_complete_all(compositor);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_push(dialogue_text, "You're talking to me like i know anything about computers.", 30.0f, true);
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
// fade to black and end
|
|
||||||
compositor_animation_complete_all(compositor);
|
|
||||||
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.3f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
// reuse mask
|
|
||||||
compositor_set_visible(compositor, mask_id, true);
|
|
||||||
compositor_set_alpha(compositor, mask_id, 0.0f);
|
|
||||||
compositor_animate_fade(compositor, mask_id, 1.0f, 0.8f, EASE_IN_OUT_CUBIC, false);
|
|
||||||
|
|
||||||
audio_animate_volume(audio, music_id, 0.0f, 0.6f);
|
if (text_nextframe(name_text, dt)) compositor_mark_content_dirty(compositor, name_id);
|
||||||
|
if (text_nextframe(dialogue_text, dt)) compositor_mark_content_dirty(compositor, dialogue_id);
|
||||||
dialog_finished = true;
|
|
||||||
lines++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
running = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compositor_tick(compositor, delta_time_sec);
|
|
||||||
|
|
||||||
if (transitioning) {
|
|
||||||
if (!compositor_is_element_animating(compositor, mask_id)) {
|
|
||||||
compositor_set_visible(compositor, maksw_happy_id, false);
|
|
||||||
compositor_set_visible(compositor, maksw_neutral_id, true);
|
|
||||||
compositor_set_scale(compositor, maksw_neutral_id, 1.0f);
|
|
||||||
compositor_set_scale(compositor, wikor_id, 1.05f);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
text_clear(name_text);
|
|
||||||
text_push(name_text, "wikor", 0.0f, true);
|
|
||||||
text_push(dialogue_text, "Anyways, how's your day going?", 30.0f, true);
|
|
||||||
|
|
||||||
compositor_set_visible(compositor, mask_id, false);
|
|
||||||
compositor_set_visible(compositor, mask2_id, true);
|
|
||||||
compositor_animate_move(compositor, mask2_id, 0, HEIGHT, 0.5f, EASE_OUT_CUBIC, false);
|
|
||||||
transitioning = false;
|
|
||||||
lines++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialog_finished) {
|
|
||||||
//compositor_mark_content_dirty(compositor, mask_id);
|
|
||||||
if (!compositor_is_element_animating(compositor, mask_id)) {
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text_nextframe(name_text, delta_time_sec))
|
|
||||||
compositor_mark_content_dirty(compositor, name_id);
|
|
||||||
if (text_nextframe(dialogue_text, delta_time_sec))
|
|
||||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
|
||||||
|
|
||||||
const uint32_t* fb = compositor_compose(compositor);
|
const uint32_t* fb = compositor_compose(compositor);
|
||||||
window_blit(window, fb);
|
window_blit(window, fb);
|
||||||
audio_tick(audio, delta_time_sec);
|
audio_tick(audio, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_stop(audio, music_id);
|
audio_stop(audio, music_id);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
+3
-3
@@ -103,9 +103,9 @@ bool window_is_mouse_button_down(window_t* s, int button) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_get_mouse_position(window_t* s, int* x, int* y) {
|
mouse_pos_t window_get_mouse_position(window_t* s) {
|
||||||
if (x) *x = s->mouse_x;
|
mouse_pos_t pos = { s->mouse_x, s->mouse_y };
|
||||||
if (y) *y = s->mouse_y;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_blit(window_t* s, const uint32_t* fb) {
|
void window_blit(window_t* s, const uint32_t* fb) {
|
||||||
|
|||||||
Reference in new issue
Block a user