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
+748
-490
No files matched your search
+9
-8
@@ -12,6 +12,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct audio_t audio_t;
|
||||
typedef uint32_t audio_stream_id_t;
|
||||
|
||||
// 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
|
||||
// loop: if true, the stream restarts on EOF
|
||||
// 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,
|
||||
int size,
|
||||
int channels,
|
||||
@@ -42,22 +43,22 @@ uint32_t audio_play(audio_t* audio,
|
||||
bool loop);
|
||||
|
||||
// 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_resume(audio_t* audio, uint32_t id);
|
||||
bool audio_pause(audio_t* audio, audio_stream_id_t id);
|
||||
bool audio_resume(audio_t* audio, audio_stream_id_t id);
|
||||
|
||||
// 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)
|
||||
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]
|
||||
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()
|
||||
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);
|
||||
|
||||
// Global volume, immediate effect, range [0.0, 1.0]
|
||||
|
||||
+18
-23
@@ -41,6 +41,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct compositor_t compositor_t;
|
||||
typedef uint32_t element_handle_t;
|
||||
|
||||
// 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
|
||||
// The compositor takes a reference, caller must keep alive until removed
|
||||
// 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,
|
||||
uint32_t fb_width,
|
||||
uint32_t fb_height,
|
||||
@@ -64,24 +65,18 @@ uint32_t compositor_insert(compositor_t* comp,
|
||||
anchor_point_t anchor);
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
// Individual setters, immediate effect
|
||||
bool compositor_set_position(compositor_t* comp, uint32_t id,
|
||||
// Setters, immediate effect
|
||||
bool compositor_set_position(compositor_t* comp, element_handle_t id,
|
||||
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);
|
||||
bool compositor_set_scale(compositor_t* comp, uint32_t id,
|
||||
bool compositor_set_scale(compositor_t* comp, element_handle_t id,
|
||||
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 compositor_set_alpha(compositor_t* comp, uint32_t id,
|
||||
bool compositor_set_alpha(compositor_t* comp, element_handle_t id,
|
||||
float alpha);
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
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,
|
||||
float duration_sec, easing_t easing, bool queue);
|
||||
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||
float duration_sec, easing_t easing, bool queue, bool parallel);
|
||||
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
|
||||
float to_alpha,
|
||||
float duration_sec, easing_t easing, bool queue);
|
||||
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||
float duration_sec, easing_t easing, bool queue, bool parallel);
|
||||
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
|
||||
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.
|
||||
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);
|
||||
|
||||
// Returns true if any element is animating or has queued animations.
|
||||
bool compositor_is_animating(compositor_t* comp);
|
||||
|
||||
// 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
|
||||
const uint32_t* compositor_compose(compositor_t* comp);
|
||||
|
||||
// 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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+3
-1
@@ -53,6 +53,8 @@ extern "C" {
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
typedef struct { int x, y; } mouse_pos_t;
|
||||
|
||||
// creates a window with given width, height and title, title can be utf8
|
||||
window_t* window_create(int width, int height, const char* title);
|
||||
// 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
|
||||
bool window_is_mouse_button_down(window_t* s, int button_index);
|
||||
// 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
|
||||
}
|
||||
|
||||
+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) {
|
||||
if (!audio || !data) return 0;
|
||||
|
||||
@@ -218,12 +218,12 @@ uint32_t audio_play(audio_t* audio, const uint8_t* data, int size,
|
||||
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;
|
||||
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;
|
||||
auto it = audio->streams.find(id);
|
||||
if (it == audio->streams.end()) return false;
|
||||
@@ -235,7 +235,7 @@ bool audio_pause(audio_t* audio, uint32_t id) {
|
||||
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;
|
||||
auto it = audio->streams.find(id);
|
||||
if (it == audio->streams.end()) return false;
|
||||
@@ -247,19 +247,19 @@ bool audio_resume(audio_t* audio, uint32_t id) {
|
||||
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;
|
||||
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;
|
||||
auto it = audio->streams.find(id);
|
||||
if (it == audio->streams.end()) return false;
|
||||
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;
|
||||
auto it = audio->streams.find(id);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!audio) return false;
|
||||
auto it = audio->streams.find(id);
|
||||
|
||||
+173
-178
@@ -65,6 +65,7 @@ struct queued_animation_t {
|
||||
bool affects_position;
|
||||
bool affects_scale;
|
||||
bool affects_alpha;
|
||||
bool parallel = false;
|
||||
};
|
||||
|
||||
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 tx = tile_x0; tx < tile_x1; 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);
|
||||
}
|
||||
}
|
||||
@@ -287,123 +299,145 @@ static void update_draw_order(compositor_t* comp, uint32_t id) {
|
||||
extern "C" {
|
||||
|
||||
// Advances all animations by a given time delta.
|
||||
void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
||||
if (!comp) return;
|
||||
void compositor_tick(compositor_t* comp, float delta_time_sec) {
|
||||
if (!comp) return;
|
||||
|
||||
for (auto& pair : comp->elements_map) {
|
||||
element_t* elem = &pair.second;
|
||||
if (!elem->valid) continue;
|
||||
animation_t* anim = &elem->anim;
|
||||
for (auto& pair : comp->elements_map) {
|
||||
element_t* elem = &pair.second;
|
||||
if (!elem->valid) continue;
|
||||
animation_t* anim = &elem->anim;
|
||||
|
||||
// Check if we need to start a queued animation
|
||||
if (anim->type == ANIM_NONE && !elem->anim_queue.empty()) {
|
||||
queued_animation_t& next = elem->anim_queue.front();
|
||||
|
||||
anim->type = next.type;
|
||||
anim->animating_position = next.affects_position;
|
||||
anim->animating_scale = next.affects_scale;
|
||||
anim->animating_alpha = next.affects_alpha;
|
||||
|
||||
if (next.affects_position) {
|
||||
anim->start_x = elem->x;
|
||||
anim->start_y = elem->y;
|
||||
anim->end_x = next.to_x;
|
||||
anim->end_y = next.to_y;
|
||||
anim->position_anim.progress_sec = 0.0f;
|
||||
anim->position_anim.duration_sec = next.duration_sec;
|
||||
anim->position_anim.easing = next.easing;
|
||||
// Are we currently busy doing ANYTHING?
|
||||
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();
|
||||
|
||||
// Route the queued data to the active state
|
||||
if (next.affects_position) {
|
||||
anim->animating_position = true;
|
||||
anim->start_x = elem->x;
|
||||
anim->start_y = elem->y;
|
||||
anim->end_x = next.to_x;
|
||||
anim->end_y = next.to_y;
|
||||
anim->position_anim.progress_sec = 0.0f;
|
||||
anim->position_anim.duration_sec = next.duration_sec;
|
||||
anim->position_anim.easing = next.easing;
|
||||
}
|
||||
if (next.affects_alpha) {
|
||||
anim->animating_alpha = true;
|
||||
anim->start_alpha = elem->alpha;
|
||||
anim->end_alpha = next.to_alpha;
|
||||
anim->alpha_anim.progress_sec = 0.0f;
|
||||
anim->alpha_anim.duration_sec = next.duration_sec;
|
||||
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) {
|
||||
anim->animating_scale = true;
|
||||
anim->start_scale = elem->scale;
|
||||
anim->end_scale = next.to_scale;
|
||||
anim->scale_anim.progress_sec = 0.0f;
|
||||
anim->scale_anim.duration_sec = next.duration_sec;
|
||||
anim->scale_anim.easing = next.easing;
|
||||
}
|
||||
|
||||
// Remove it from the queue
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-evaluate busy status since we just popped new animations
|
||||
is_busy = anim->animating_position || anim->animating_alpha || anim->animating_scale;
|
||||
}
|
||||
if (next.affects_alpha) {
|
||||
anim->start_alpha = elem->alpha;
|
||||
anim->end_alpha = next.to_alpha;
|
||||
anim->alpha_anim.progress_sec = 0.0f;
|
||||
anim->alpha_anim.duration_sec = next.duration_sec;
|
||||
anim->alpha_anim.easing = next.easing;
|
||||
}
|
||||
if (next.affects_scale) {
|
||||
anim->start_scale = elem->scale;
|
||||
anim->end_scale = next.to_scale;
|
||||
anim->scale_anim.progress_sec = 0.0f;
|
||||
anim->scale_anim.duration_sec = next.duration_sec;
|
||||
anim->scale_anim.easing = next.easing;
|
||||
}
|
||||
|
||||
elem->anim_queue.erase(elem->anim_queue.begin());
|
||||
}
|
||||
|
||||
// Skip if nothing is animating
|
||||
if (anim->type == ANIM_NONE) continue;
|
||||
// Skip ticking if nothing is animating at all
|
||||
if (!is_busy) continue;
|
||||
|
||||
mark_element_dirty(comp, elem);
|
||||
bool position_changed = false;
|
||||
bool all_done = true;
|
||||
|
||||
// Update position animation with its own timing
|
||||
if (anim->animating_position) {
|
||||
anim->position_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->position_anim.progress_sec / anim->position_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->x = anim->end_x;
|
||||
elem->y = anim->end_y;
|
||||
anim->animating_position = false;
|
||||
} else {
|
||||
float t_eased = apply_easing(anim->position_anim.easing, t_raw);
|
||||
elem->x = (int32_t)std::round(lerp(static_cast<float>(anim->start_x),
|
||||
static_cast<float>(anim->end_x), t_eased));
|
||||
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
|
||||
static_cast<float>(anim->end_y), t_eased));
|
||||
all_done = false;
|
||||
}
|
||||
position_changed = true;
|
||||
}
|
||||
|
||||
// Update alpha animation with its own timing
|
||||
if (anim->animating_alpha) {
|
||||
anim->alpha_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->alpha = anim->end_alpha;
|
||||
if (elem->alpha == 0.0f) elem->visible = false;
|
||||
anim->animating_alpha = false;
|
||||
} else {
|
||||
float t_eased = apply_easing(anim->alpha_anim.easing, t_raw);
|
||||
elem->alpha = lerp(anim->start_alpha, anim->end_alpha, t_eased);
|
||||
all_done = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update scale animation with its own timing
|
||||
if (anim->animating_scale) {
|
||||
anim->scale_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->scale_anim.progress_sec / anim->scale_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->scale = anim->end_scale;
|
||||
anim->animating_scale = false;
|
||||
} else {
|
||||
float t_eased = apply_easing(anim->scale_anim.easing, t_raw);
|
||||
elem->scale = lerp(anim->start_scale, anim->end_scale, t_eased);
|
||||
all_done = false;
|
||||
}
|
||||
position_changed = true;
|
||||
}
|
||||
|
||||
// If all animations are done, mark as complete
|
||||
if (all_done) {
|
||||
anim->type = ANIM_NONE;
|
||||
}
|
||||
|
||||
// Update spatial hash
|
||||
if (position_changed) {
|
||||
clear_spatial_hash(comp, elem->id);
|
||||
update_spatial_hash(comp, elem);
|
||||
} else {
|
||||
mark_element_dirty(comp, elem);
|
||||
bool position_changed = false;
|
||||
|
||||
// --- UPDATE ACTIVE ANIMATIONS ---
|
||||
|
||||
// Update Position
|
||||
if (anim->animating_position) {
|
||||
anim->position_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->position_anim.progress_sec / anim->position_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->x = anim->end_x;
|
||||
elem->y = anim->end_y;
|
||||
anim->animating_position = false;
|
||||
}
|
||||
else {
|
||||
float t_eased = apply_easing(anim->position_anim.easing, t_raw);
|
||||
// Note: using whatever lerp function you have in your engine
|
||||
elem->x = anim->start_x + (anim->end_x - anim->start_x) * t_eased;
|
||||
elem->y = anim->start_y + (anim->end_y - anim->start_y) * t_eased;
|
||||
}
|
||||
position_changed = true;
|
||||
}
|
||||
|
||||
// Update Alpha
|
||||
if (anim->animating_alpha) {
|
||||
anim->alpha_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->alpha = anim->end_alpha;
|
||||
if (elem->alpha <= 0.0f) elem->visible = false;
|
||||
anim->animating_alpha = false;
|
||||
}
|
||||
else {
|
||||
float t_eased = apply_easing(anim->alpha_anim.easing, t_raw);
|
||||
elem->alpha = anim->start_alpha + (anim->end_alpha - anim->start_alpha) * t_eased;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Scale
|
||||
if (anim->animating_scale) {
|
||||
anim->scale_anim.progress_sec += delta_time_sec;
|
||||
float t_raw = anim->scale_anim.progress_sec / anim->scale_anim.duration_sec;
|
||||
|
||||
if (t_raw >= 1.0f) {
|
||||
elem->scale = anim->end_scale;
|
||||
anim->animating_scale = false;
|
||||
}
|
||||
else {
|
||||
float t_eased = apply_easing(anim->scale_anim.easing, t_raw);
|
||||
elem->scale = anim->start_scale + (anim->end_scale - anim->start_scale) * t_eased;
|
||||
}
|
||||
position_changed = true;
|
||||
}
|
||||
|
||||
// Cleanup empty state marker
|
||||
if (!anim->animating_position && !anim->animating_alpha && !anim->animating_scale) {
|
||||
anim->type = ANIM_NONE;
|
||||
}
|
||||
|
||||
// Update spatial hash
|
||||
if (position_changed) {
|
||||
clear_spatial_hash(comp, elem->id);
|
||||
update_spatial_hash(comp, elem);
|
||||
}
|
||||
else {
|
||||
// Just redrawing pixels, didn't move
|
||||
mark_element_dirty(comp, elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocates and initializes a new compositor instance.
|
||||
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.
|
||||
uint32_t compositor_insert(compositor_t* comp,
|
||||
element_handle_t compositor_insert(compositor_t* comp,
|
||||
const uint32_t* fb,
|
||||
uint32_t fb_width,
|
||||
uint32_t fb_height,
|
||||
@@ -490,7 +524,7 @@ uint32_t compositor_insert(compositor_t* comp,
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return false;
|
||||
}
|
||||
@@ -521,52 +555,8 @@ bool compositor_remove(compositor_t* comp, uint32_t id) {
|
||||
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.
|
||||
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) {
|
||||
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.
|
||||
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;
|
||||
element_t* elem = find_element(comp, id);
|
||||
if (!elem) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
element_t* elem = find_element(comp, id);
|
||||
if (!elem) {
|
||||
@@ -624,7 +621,7 @@ bool compositor_set_visible(compositor_t* comp, uint32_t id, bool visible) {
|
||||
}
|
||||
|
||||
// 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;
|
||||
element_t* elem = find_element(comp, id);
|
||||
if (!elem) {
|
||||
@@ -645,7 +642,7 @@ bool compositor_set_scale(compositor_t* comp, uint32_t id, float scale) {
|
||||
}
|
||||
|
||||
// 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;
|
||||
element_t* elem = find_element(comp, id);
|
||||
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.
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@@ -695,9 +692,9 @@ bool compositor_set_anchor(compositor_t* comp, uint32_t id, anchor_point_t ancho
|
||||
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,
|
||||
float duration_sec, easing_t easing, bool queue) {
|
||||
float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||
if (!comp) {
|
||||
return false;
|
||||
}
|
||||
@@ -716,6 +713,7 @@ bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
||||
queued.to_x = to_x;
|
||||
queued.to_y = to_y;
|
||||
queued.is_move_fade = false;
|
||||
queued.parallel = parallel;
|
||||
queued.affects_position = true;
|
||||
queued.affects_scale = false;
|
||||
queued.affects_alpha = false;
|
||||
@@ -743,8 +741,8 @@ bool compositor_animate_move(compositor_t* comp, uint32_t id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||
float to_scale, float duration_sec, easing_t easing, bool queue) {
|
||||
bool compositor_animate_scale(compositor_t* comp, element_handle_t id,
|
||||
float to_scale, float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||
if (!comp) {
|
||||
return false;
|
||||
}
|
||||
@@ -762,6 +760,7 @@ bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||
queued.duration_sec = duration_sec;
|
||||
queued.to_scale = to_scale;
|
||||
queued.is_move_fade = false;
|
||||
queued.parallel = parallel;
|
||||
queued.affects_position = false;
|
||||
queued.affects_scale = true;
|
||||
queued.affects_alpha = false;
|
||||
@@ -787,8 +786,8 @@ bool compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||
float to_alpha, float duration_sec, easing_t easing, bool queue) {
|
||||
bool compositor_animate_fade(compositor_t* comp, element_handle_t id,
|
||||
float to_alpha, float duration_sec, easing_t easing, bool queue, bool parallel) {
|
||||
if (!comp) {
|
||||
return false;
|
||||
}
|
||||
@@ -808,6 +807,7 @@ bool compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||
queued.duration_sec = duration_sec;
|
||||
queued.to_alpha = to_alpha;
|
||||
queued.is_move_fade = false;
|
||||
queued.parallel = parallel;
|
||||
queued.affects_position = false;
|
||||
queued.affects_scale = false;
|
||||
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
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@@ -982,7 +982,7 @@ void compositor_mark_all_dirty(compositor_t* comp) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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.
|
||||
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;
|
||||
element_t* elem = find_element(comp, id);
|
||||
if (!elem) {
|
||||
@@ -1038,7 +1038,7 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
||||
// OpenMP 2.0 requires signed integers for loop counters
|
||||
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)
|
||||
for (int i = 0; i < total_tiles; i++) {
|
||||
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
|
||||
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 (local_draw_list.empty()) {
|
||||
@@ -1069,11 +1069,6 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
||||
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
|
||||
for (uint32_t element_id : local_draw_list) {
|
||||
const element_t* elem = find_element(comp, element_id);
|
||||
|
||||
+534
-269
@@ -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 <cstdint>
|
||||
#include <cstring>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <window.h>
|
||||
#include <text.h>
|
||||
#include <compositor.h>
|
||||
#include <image.h>
|
||||
#include <audio.h>
|
||||
|
||||
// smartass approach
|
||||
struct WindowHandle {
|
||||
window_t* ptr;
|
||||
WindowHandle(window_t* w) : ptr(w) {}
|
||||
~WindowHandle() { if (ptr) window_destroy(ptr); }
|
||||
operator window_t* () { return ptr; }
|
||||
// ---------------------------------------------------------------------------
|
||||
// RAII handles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A generic RAII wrapper.
|
||||
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 {
|
||||
compositor_t* ptr;
|
||||
CompositorHandle(compositor_t* c) : ptr(c) {}
|
||||
~CompositorHandle() { if (ptr) compositor_destroy(ptr); }
|
||||
operator compositor_t* () { return ptr; }
|
||||
using WindowHandle = EngineHandle<window_t, window_destroy>;
|
||||
using CompositorHandle = EngineHandle<compositor_t, compositor_destroy>;
|
||||
using TextHandle = EngineHandle<text_t, text_destroy>;
|
||||
using AudioHandle = EngineHandle<audio_t, audio_destroy>;
|
||||
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 {
|
||||
text_t* ptr;
|
||||
TextHandle(text_t* t) : ptr(t) {}
|
||||
~TextHandle() { if (ptr) text_destroy(ptr); }
|
||||
operator text_t* () { return ptr; }
|
||||
enum class WaitKind {
|
||||
NONE,
|
||||
CLICK, // wait for mouse click or space
|
||||
ANIM_ALL, // wait until compositor_is_animating() == false
|
||||
ANIM_ELEM, // wait until a specific element stops animating
|
||||
END, // script finished, keep rendering until told to quit
|
||||
};
|
||||
|
||||
struct ImageHandle {
|
||||
uint32_t* ptr;
|
||||
ImageHandle(uint32_t* p) : ptr(p) {}
|
||||
~ImageHandle() { if (ptr) image_free(ptr); }
|
||||
operator uint32_t* () { return ptr; }
|
||||
struct Interpreter {
|
||||
std::vector<Command> script;
|
||||
size_t pc = 0;
|
||||
|
||||
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;
|
||||
AudioHandle(audio_t* a) : ptr(a) {}
|
||||
~AudioHandle() { if (ptr) audio_destroy(ptr); }
|
||||
operator audio_t* () { return ptr; }
|
||||
};
|
||||
// ---------------------------------------------------------------------------
|
||||
// Script builder helpers
|
||||
//
|
||||
// These push Command objects onto the script vector. They capture the
|
||||
// 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 {
|
||||
uint8_t* ptr;
|
||||
AudioDataHandle(uint8_t* p) : ptr(p) {}
|
||||
~AudioDataHandle() { if (ptr) audio_free_data(ptr); }
|
||||
operator uint8_t* () { return ptr; }
|
||||
};
|
||||
using Script = std::vector<Command>;
|
||||
|
||||
static void cmd_speaker(Script& s, text_t* name_box,
|
||||
element_handle_t name_id, compositor_t* comp,
|
||||
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() {
|
||||
const int WIDTH = 1280;
|
||||
const int HEIGHT = 720;
|
||||
const int CHAR_WIDTH = 400;
|
||||
const int CHAR_HEIGHT = 600;
|
||||
|
||||
// Character Positioning Constants
|
||||
// The vertical baseline for character sprites, making positioning independent of sprite height.
|
||||
const int CENTER_X = WIDTH / 2;
|
||||
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);
|
||||
// The horizontal center for the character on the right.
|
||||
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_RIGHT_X = RIGHT_CHAR_CENTER_X + 660;
|
||||
|
||||
// ---- window / compositor / audio ---------------------------------------
|
||||
|
||||
WindowHandle window(window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine"));
|
||||
if (!window) {
|
||||
printf("Failed to create window\n");
|
||||
return 1;
|
||||
}
|
||||
if (!window) { printf("Failed to create window\n"); return 1; }
|
||||
|
||||
CompositorHandle compositor(compositor_init(WIDTH, HEIGHT));
|
||||
if (!compositor) {
|
||||
printf("Failed to initialize compositor\n");
|
||||
return 1;
|
||||
}
|
||||
if (!compositor) { printf("Failed to initialize compositor\n"); return 1; }
|
||||
|
||||
AudioHandle audio(audio_init());
|
||||
if (!audio) {
|
||||
printf("Failed to initialize audio\n");
|
||||
return 1;
|
||||
}
|
||||
AudioHandle audio(audio_init());
|
||||
if (!audio) { printf("Failed to initialize audio\n"); return 1; }
|
||||
|
||||
audio_set_global_volume(audio, 1.0f);
|
||||
|
||||
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));
|
||||
if (!music_data) {
|
||||
printf("Failed to load audio\n");
|
||||
return 1;
|
||||
}
|
||||
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));
|
||||
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));
|
||||
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);
|
||||
}
|
||||
|
||||
ImageHandle textbox(image_load_resized("assets/textbox.png", WIDTH, 180));
|
||||
if (textbox) {
|
||||
// Position via the bottom-left corner for easier alignment with screen bottom
|
||||
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
|
||||
}
|
||||
if (textbox)
|
||||
compositor_insert(compositor, textbox, WIDTH, 180,
|
||||
0, HEIGHT, 3, 1.0f, ANCHOR_BOTTOM_LEFT);
|
||||
|
||||
ImageHandle maksw_happy(image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||
// Insert off-screen left, anchored at bottom-center for natural scaling
|
||||
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);
|
||||
element_handle_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);
|
||||
|
||||
ImageHandle maksw_neutral(image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||
// Insert at the final left position, anchored at bottom-center
|
||||
uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral,
|
||||
CHAR_WIDTH, CHAR_HEIGHT, LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||
element_handle_t maksw_neutral_id = compositor_insert(
|
||||
compositor, maksw_neutral, CHAR_WIDTH, CHAR_HEIGHT,
|
||||
LEFT_CHAR_CENTER_X, CHAR_BASELINE_Y, 1, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||
compositor_set_visible(compositor, maksw_neutral_id, false);
|
||||
|
||||
ImageHandle wikor(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||
// Insert at the final right position, anchored at bottom-center
|
||||
uint32_t wikor_id = compositor_insert(compositor, wikor,
|
||||
CHAR_WIDTH, CHAR_HEIGHT, RIGHT_CHAR_CENTER_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||
ImageHandle wikor_img(image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT));
|
||||
element_handle_t wikor_id = compositor_insert(
|
||||
compositor, wikor_img, CHAR_WIDTH, CHAR_HEIGHT,
|
||||
OFFSCREEN_RIGHT_X, CHAR_BASELINE_Y, 2, 1.0f, ANCHOR_BOTTOM_CENTER);
|
||||
|
||||
TextHandle name_text(text_init(300, 100));
|
||||
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
|
||||
// Text is anchored at top-left, positioned relative to textbox
|
||||
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
|
||||
element_handle_t name_id = compositor_insert(
|
||||
compositor, text_getfb(name_text),
|
||||
300, 100, 60, HEIGHT - 180, 4, 1.0f, ANCHOR_TOP_LEFT);
|
||||
|
||||
TextHandle dialogue_text(text_init(WIDTH - 140, 160));
|
||||
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
|
||||
// Text is anchored at top-left, positioned relative to textbox
|
||||
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
|
||||
element_handle_t dialogue_id = compositor_insert(
|
||||
compositor, text_getfb(dialogue_text),
|
||||
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));
|
||||
// Use top-left anchor for full-screen wipes
|
||||
uint32_t mask_id = compositor_insert(compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||
element_handle_t mask_id = compositor_insert(
|
||||
compositor, mask, 1280, 1080, 0, -1080, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||
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));
|
||||
// Use top-left anchor for full-screen wipes
|
||||
uint32_t mask2_id = compositor_insert(compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||
element_handle_t mask2_id = compositor_insert(
|
||||
compositor, mask2, 1280, 1080, 0, 0, 5, 1.0f, ANCHOR_TOP_LEFT);
|
||||
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;
|
||||
|
||||
auto last_frame_time = std::chrono::high_resolution_clock::now();
|
||||
auto last_report_time = last_frame_time;
|
||||
|
||||
uint64_t frame_count = 0;
|
||||
float highest_frame_time = 0.0f;
|
||||
float frame_time_sum = 0.0f;
|
||||
float highest_frame_time = 0.0f;
|
||||
float frame_time_sum = 0.0f;
|
||||
const std::chrono::duration<float> REPORT_INTERVAL = std::chrono::seconds(1);
|
||||
|
||||
bool mouse_was_down = false;
|
||||
bool key_was_down = false;
|
||||
|
||||
while (running) {
|
||||
if (window_pollevent(window)) running = false;
|
||||
|
||||
auto current_time = std::chrono::high_resolution_clock::now();
|
||||
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;
|
||||
|
||||
// FPS reporting
|
||||
frame_count++;
|
||||
frame_time_sum += delta_time_sec;
|
||||
if (delta_time_sec > highest_frame_time) highest_frame_time = delta_time_sec;
|
||||
|
||||
std::chrono::duration<float> time_since_last_report = current_time - last_report_time;
|
||||
|
||||
if (time_since_last_report >= REPORT_INTERVAL) {
|
||||
frame_time_sum += dt;
|
||||
if (dt > highest_frame_time) highest_frame_time = dt;
|
||||
std::chrono::duration<float> since_report = current_time - last_report_time;
|
||||
if (since_report >= REPORT_INTERVAL) {
|
||||
if (frame_count > 0) {
|
||||
float avg_delta_time = frame_time_sum / (float)frame_count;
|
||||
float fps = 1.0f / avg_delta_time;
|
||||
float actual_duration = time_since_last_report.count();
|
||||
|
||||
float fps = 1.0f / (frame_time_sum / (float)frame_count);
|
||||
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_time_sum = 0.0f;
|
||||
highest_frame_time = 0.0f;
|
||||
frame_count = 0; frame_time_sum = 0.0f; highest_frame_time = 0.0f;
|
||||
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_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_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;
|
||||
|
||||
// don't judge me, it will work for now
|
||||
static unsigned int lines = 1;
|
||||
static bool dialog_finished = false;
|
||||
static bool transitioning = false;
|
||||
// Tick the interpreter
|
||||
bool script_done = interp.tick(clicked);
|
||||
if (script_done) running = false;
|
||||
|
||||
if (mouse_clicked || key_pressed || lines == 1 /* hack */) {
|
||||
if (text_is_animating(dialogue_text)) {
|
||||
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);
|
||||
// Tick subsystems
|
||||
compositor_tick(compositor, dt);
|
||||
|
||||
audio_animate_volume(audio, music_id, 0.0f, 0.6f);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
const uint32_t* fb = compositor_compose(compositor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -103,9 +103,9 @@ bool window_is_mouse_button_down(window_t* s, int button) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void window_get_mouse_position(window_t* s, int* x, int* y) {
|
||||
if (x) *x = s->mouse_x;
|
||||
if (y) *y = s->mouse_y;
|
||||
mouse_pos_t window_get_mouse_position(window_t* s) {
|
||||
mouse_pos_t pos = { s->mouse_x, s->mouse_y };
|
||||
return pos;
|
||||
}
|
||||
|
||||
void window_blit(window_t* s, const uint32_t* fb) {
|
||||
|
||||
Reference in new issue
Block a user