i'll just say it's better, and i ripped out the frontend for now.
This commit is contained in:
1 parent
97946c613a
commit
51c3f6bacc
14 files changed
+875
-1122
No files matched your search
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 222 KiB |
+16
-15
@@ -1,7 +1,6 @@
|
|||||||
// compositor.h
|
// compositor.h
|
||||||
#ifndef COMPOSITOR_H
|
#ifndef COMPOSITOR_H
|
||||||
#define COMPOSITOR_H
|
#define COMPOSITOR_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
@@ -26,7 +25,6 @@ enum easing_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
typedef struct compositor_t compositor_t;
|
typedef struct compositor_t compositor_t;
|
||||||
|
|
||||||
// Initialize compositor with given dimensions
|
// Initialize compositor with given dimensions
|
||||||
@@ -50,18 +48,15 @@ void compositor_remove(compositor_t* comp, uint32_t id);
|
|||||||
void compositor_update(compositor_t* comp, uint32_t id,
|
void compositor_update(compositor_t* comp, uint32_t id,
|
||||||
int32_t x, int32_t y,
|
int32_t x, int32_t y,
|
||||||
int32_t z_order, float scale,
|
int32_t z_order, float scale,
|
||||||
bool visible); // holy clusterfuck
|
bool visible);
|
||||||
|
|
||||||
// Individual setters
|
// Individual setters
|
||||||
void compositor_set_position(compositor_t* comp, uint32_t id,
|
void compositor_set_position(compositor_t* comp, uint32_t id,
|
||||||
int32_t x, int32_t y);
|
int32_t x, int32_t y);
|
||||||
|
|
||||||
void compositor_set_zorder(compositor_t* comp, uint32_t id,
|
void compositor_set_zorder(compositor_t* comp, uint32_t id,
|
||||||
int32_t z_order);
|
int32_t z_order);
|
||||||
|
|
||||||
void compositor_set_visible(compositor_t* comp, uint32_t id,
|
void compositor_set_visible(compositor_t* comp, uint32_t id,
|
||||||
bool visible);
|
bool visible);
|
||||||
|
|
||||||
void compositor_set_scale(compositor_t* comp, uint32_t id,
|
void compositor_set_scale(compositor_t* comp, uint32_t id,
|
||||||
float scale);
|
float scale);
|
||||||
|
|
||||||
@@ -71,20 +66,29 @@ void compositor_tick(compositor_t* comp, float delta_time_sec);
|
|||||||
// Instantly sets the element-wide alpha.
|
// Instantly sets the element-wide alpha.
|
||||||
void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha);
|
void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha);
|
||||||
|
|
||||||
// Starts a "slide" animation on an element.
|
// Animation functions with queue parameter
|
||||||
void compositor_animate_move(compositor_t* comp, uint32_t id,
|
void compositor_animate_move(compositor_t* comp, uint32_t id,
|
||||||
int32_t to_x, int32_t to_y,
|
int32_t to_x, int32_t to_y,
|
||||||
float duration_sec, easing_t easing);
|
float duration_sec, easing_t easing, bool queue = false);
|
||||||
|
|
||||||
// Starts a "fade" or "dissolve" animation on an element.
|
|
||||||
void compositor_animate_fade(compositor_t* comp, uint32_t id,
|
void compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||||
float to_alpha,
|
float to_alpha,
|
||||||
float duration_sec, easing_t easing);
|
float duration_sec, easing_t easing, bool queue = false);
|
||||||
|
|
||||||
// Starts a "scale" animation on an element.
|
|
||||||
void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||||
float to_scale,
|
float to_scale,
|
||||||
float duration_sec, easing_t easing);
|
float duration_sec, easing_t easing, bool queue = false);
|
||||||
|
|
||||||
|
// Immediately complete all animations
|
||||||
|
void compositor_animation_complete(compositor_t* comp, uint32_t id);
|
||||||
|
|
||||||
|
void compositor_animation_complete_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);
|
||||||
|
|
||||||
// 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);
|
||||||
@@ -93,9 +97,6 @@ void compositor_mark_content_dirty(compositor_t* comp, uint32_t id);
|
|||||||
void compositor_mark_all_dirty(compositor_t* comp);
|
void compositor_mark_all_dirty(compositor_t* comp);
|
||||||
|
|
||||||
// Returns the last error message set for this compositor instance.
|
// Returns the last error message set for this compositor instance.
|
||||||
// Returns nullptr if no error has occurred.
|
|
||||||
const char* compositor_get_last_error(compositor_t* comp);
|
const char* compositor_get_last_error(compositor_t* comp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // COMPOSITOR_H
|
#endif // COMPOSITOR_H
|
||||||
+3
-2
@@ -5,10 +5,11 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// Loads an image file as ARGB8888. Returns pointer to pixel data (free with image_free), sets width/height.
|
// Loads an image file as ARGB8888. Returns pointer to pixel data, sets width/height.
|
||||||
uint32_t* image_load(const char* filename, int* width, int* height);
|
uint32_t* image_load(const char* filename, int* width, int* height);
|
||||||
// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data (free with image_free).
|
// Loads and resizes an image file to desired dimensions as ARGB8888. Returns pointer to pixel data.
|
||||||
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height);
|
uint32_t* image_load_resized(const char* filename, int desired_width, int desired_height);
|
||||||
|
// Frees image data.
|
||||||
void image_free(uint32_t* data);
|
void image_free(uint32_t* data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-5
@@ -7,22 +7,28 @@
|
|||||||
struct text_t;
|
struct text_t;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
// initializes a new text renderer with given width and height
|
||||||
text_t* text_init(int width, int height);
|
text_t* text_init(int width, int height);
|
||||||
// shares cache, prefer when using the same font to save memory
|
// shares cache, prefer when using the same font to save memory
|
||||||
text_t* text_init_shared(text_t* source, int width, int height);
|
text_t* text_init_shared(text_t* source, int width, int height);
|
||||||
|
// destroys the text renderer and frees resources
|
||||||
void text_destroy(text_t* text);
|
void text_destroy(text_t* text);
|
||||||
|
// pushes new text buffer to be rendered, can be stacked
|
||||||
void text_push(text_t* text,
|
void text_push(text_t* text,
|
||||||
const char* str,
|
const char* str,
|
||||||
float size,
|
|
||||||
float speed,
|
float speed,
|
||||||
const char* font_path,
|
|
||||||
bool outline);
|
bool outline);
|
||||||
// finish current text immediately
|
// load font and set as current font
|
||||||
|
void text_set_font(text_t* text, const char* font_path, float size);
|
||||||
|
// finish whole current text buffer immediately
|
||||||
void text_show_all_current(text_t* text);
|
void text_show_all_current(text_t* text);
|
||||||
|
// returns true if text is still animating
|
||||||
bool text_is_animating(text_t* text);
|
bool text_is_animating(text_t* text);
|
||||||
// advance, call every frame, returns false when done
|
// advance, call every frame, returns true when it touched the framebuffer
|
||||||
bool text_nextframe(text_t* text, float deltatime);
|
bool text_nextframe(text_t* text, float deltatime);
|
||||||
uint32_t* text_getfb(text_t* text);
|
// gets the framebuffer of the text renderer
|
||||||
|
const uint32_t* text_getfb(text_t* text);
|
||||||
|
// clears the text buffer and the frame buffer
|
||||||
void text_clear(text_t* text);
|
void text_clear(text_t* text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,19 @@ typedef struct window_t window_t;
|
|||||||
#define KEY_0 39
|
#define KEY_0 39
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
// 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
|
||||||
void window_destroy(window_t* s);
|
void window_destroy(window_t* s);
|
||||||
|
// polls window events, returns true if the window should close
|
||||||
bool window_pollevent(window_t* s);
|
bool window_pollevent(window_t* s);
|
||||||
|
// blits the given framebuffer to the window. must be the same size as the window and ARGB8888 format
|
||||||
void window_blit(window_t* s, const uint32_t* framebuffer);
|
void window_blit(window_t* s, const uint32_t* framebuffer);
|
||||||
|
// returns true if the given key is currently held down
|
||||||
bool window_is_key_down(window_t* s, int key_scancode);
|
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);
|
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);
|
void window_get_mouse_position(window_t* s, int* x, int* y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+458
-167
@@ -12,13 +12,14 @@
|
|||||||
// Define the tile size
|
// Define the tile size
|
||||||
#define TILE_SIZE 64
|
#define TILE_SIZE 64
|
||||||
|
|
||||||
|
struct property_anim_t {
|
||||||
|
float progress_sec;
|
||||||
|
float duration_sec;
|
||||||
|
easing_t easing;
|
||||||
|
};
|
||||||
|
|
||||||
struct animation_t {
|
struct animation_t {
|
||||||
animation_type_t type = ANIM_NONE;
|
animation_type_t type = ANIM_NONE;
|
||||||
easing_t easing = EASE_LINEAR;
|
|
||||||
|
|
||||||
// Time
|
|
||||||
float progress_sec = 0.0f;
|
|
||||||
float duration_sec = 0.0f;
|
|
||||||
|
|
||||||
// Start/End state
|
// Start/End state
|
||||||
int32_t start_x = 0, start_y = 0;
|
int32_t start_x = 0, start_y = 0;
|
||||||
@@ -30,8 +31,34 @@ struct animation_t {
|
|||||||
float start_alpha = 1.0f;
|
float start_alpha = 1.0f;
|
||||||
float end_alpha = 1.0f;
|
float end_alpha = 1.0f;
|
||||||
|
|
||||||
// Combined animation (ANIM_MOVE_FADE)
|
// Per-property animation state
|
||||||
bool is_move_fade = false;
|
property_anim_t position_anim;
|
||||||
|
property_anim_t scale_anim;
|
||||||
|
property_anim_t alpha_anim;
|
||||||
|
|
||||||
|
// Track which properties are being animated
|
||||||
|
bool animating_position = false;
|
||||||
|
bool animating_scale = false;
|
||||||
|
bool animating_alpha = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Queued animation structure
|
||||||
|
struct queued_animation_t {
|
||||||
|
animation_type_t type;
|
||||||
|
easing_t easing;
|
||||||
|
float duration_sec;
|
||||||
|
|
||||||
|
// Target values
|
||||||
|
int32_t to_x, to_y;
|
||||||
|
float to_scale;
|
||||||
|
float to_alpha;
|
||||||
|
|
||||||
|
bool is_move_fade;
|
||||||
|
|
||||||
|
// Which properties this animation affects
|
||||||
|
bool affects_position;
|
||||||
|
bool affects_scale;
|
||||||
|
bool affects_alpha;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct element_t {
|
struct element_t {
|
||||||
@@ -46,6 +73,7 @@ struct element_t {
|
|||||||
uint32_t id; // Unique identifier
|
uint32_t id; // Unique identifier
|
||||||
std::vector<uint32_t> occupied_tiles; // List of occupied tiles
|
std::vector<uint32_t> occupied_tiles; // List of occupied tiles
|
||||||
animation_t anim;
|
animation_t anim;
|
||||||
|
std::vector<queued_animation_t> anim_queue;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct compositor_t {
|
struct compositor_t {
|
||||||
@@ -268,83 +296,115 @@ extern "C" {
|
|||||||
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;
|
||||||
|
|
||||||
// Loop over all elements in the map
|
|
||||||
for (auto& pair : comp->elements_map) {
|
for (auto& pair : comp->elements_map) {
|
||||||
element_t* elem = &pair.second;
|
element_t* elem = &pair.second;
|
||||||
animation_t* anim = &elem->anim;
|
animation_t* anim = &elem->anim;
|
||||||
|
|
||||||
if (anim->type == ANIM_NONE) {
|
// Check if we need to start a queued animation
|
||||||
continue; // Not animating
|
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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. Update Progress ---
|
elem->anim_queue.erase(elem->anim_queue.begin());
|
||||||
anim->progress_sec += delta_time_sec;
|
|
||||||
float t_raw = anim->progress_sec / anim->duration_sec;
|
|
||||||
|
|
||||||
// Check for animation end
|
|
||||||
if (t_raw >= 1.0f) {
|
|
||||||
t_raw = 1.0f; // Clamp to 1.0
|
|
||||||
anim->type = ANIM_NONE; // Animation is done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. Apply Easing ---
|
// Skip if nothing is animating
|
||||||
float t_eased = apply_easing(anim->easing, t_raw);
|
if (anim->type == ANIM_NONE) continue;
|
||||||
|
|
||||||
// --- 3. Update Properties ---
|
|
||||||
|
|
||||||
// Mark old position as dirty before we change it
|
|
||||||
// We only need to do this *once* at the start of the tick
|
|
||||||
mark_element_dirty(comp, elem);
|
mark_element_dirty(comp, elem);
|
||||||
|
|
||||||
bool position_changed = false;
|
bool position_changed = false;
|
||||||
|
bool all_done = true;
|
||||||
|
|
||||||
// Animate Move
|
// Update position animation with its own timing
|
||||||
if (anim->type == ANIM_MOVE || anim->is_move_fade) {
|
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),
|
elem->x = (int32_t)std::round(lerp(static_cast<float>(anim->start_x),
|
||||||
static_cast<float>(anim->end_x), t_eased));
|
static_cast<float>(anim->end_x), t_eased));
|
||||||
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
|
elem->y = (int32_t)std::round(lerp(static_cast<float>(anim->start_y),
|
||||||
static_cast<float>(anim->end_y), t_eased));
|
static_cast<float>(anim->end_y), t_eased));
|
||||||
|
all_done = false;
|
||||||
|
}
|
||||||
position_changed = true;
|
position_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animate Fade
|
// Update alpha animation with its own timing
|
||||||
if (anim->type == ANIM_FADE || anim->is_move_fade) {
|
if (anim->animating_alpha) {
|
||||||
elem->alpha = lerp(anim->start_alpha, anim->end_alpha, t_eased);
|
anim->alpha_anim.progress_sec += delta_time_sec;
|
||||||
}
|
float t_raw = anim->alpha_anim.progress_sec / anim->alpha_anim.duration_sec;
|
||||||
|
|
||||||
// Animate Scale
|
if (t_raw >= 1.0f) {
|
||||||
if (anim->type == ANIM_SCALE) {
|
|
||||||
elem->scale = lerp(anim->start_scale, anim->end_scale, t_eased);
|
|
||||||
position_changed = true; // Scale change also affects position/hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 4. Handle End-of-Animation State ---
|
|
||||||
if (anim->type == ANIM_NONE) {
|
|
||||||
// Animation just finished, snap to final values
|
|
||||||
if (anim->is_move_fade) {
|
|
||||||
elem->x = anim->end_x;
|
|
||||||
elem->y = anim->end_y;
|
|
||||||
elem->alpha = anim->end_alpha;
|
elem->alpha = anim->end_alpha;
|
||||||
anim->is_move_fade = false;
|
|
||||||
} else if (anim->type == ANIM_MOVE) {
|
|
||||||
elem->x = anim->end_x;
|
|
||||||
elem->y = anim->end_y;
|
|
||||||
} else if (anim->type == ANIM_FADE) {
|
|
||||||
elem->alpha = anim->end_alpha;
|
|
||||||
// If it faded out, set it to invisible to remove from render
|
|
||||||
if (elem->alpha == 0.0f) elem->visible = false;
|
if (elem->alpha == 0.0f) elem->visible = false;
|
||||||
} else if (anim->type == ANIM_SCALE) {
|
anim->animating_alpha = false;
|
||||||
elem->scale = anim->end_scale;
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 5. Update Spatial Hash ---
|
// Update scale animation with its own timing
|
||||||
// If position or scale changed, the hash must be updated
|
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) {
|
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 {
|
||||||
// If only alpha changed, we just need to mark it dirty
|
|
||||||
mark_element_dirty(comp, elem);
|
mark_element_dirty(comp, elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,10 +681,9 @@ void compositor_set_alpha(compositor_t* comp, uint32_t id, float alpha) {
|
|||||||
|
|
||||||
// --- Animation ---
|
// --- Animation ---
|
||||||
|
|
||||||
// Starts a "slide" animation on an element.
|
|
||||||
void compositor_animate_move(compositor_t* comp, uint32_t id,
|
void compositor_animate_move(compositor_t* comp, uint32_t id,
|
||||||
int32_t to_x, int32_t to_y,
|
int32_t to_x, int32_t to_y,
|
||||||
float duration_sec, easing_t easing) {
|
float duration_sec, easing_t easing, bool queue) {
|
||||||
if (!comp) return;
|
if (!comp) return;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -632,50 +691,41 @@ void compositor_animate_move(compositor_t* comp, uint32_t id,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elem->anim.type = ANIM_MOVE;
|
if (queue) {
|
||||||
elem->anim.easing = easing;
|
queued_animation_t queued;
|
||||||
elem->anim.progress_sec = 0.0f;
|
queued.type = ANIM_MOVE;
|
||||||
elem->anim.duration_sec = duration_sec;
|
queued.easing = easing;
|
||||||
|
queued.duration_sec = duration_sec;
|
||||||
|
queued.to_x = to_x;
|
||||||
|
queued.to_y = to_y;
|
||||||
|
queued.is_move_fade = false;
|
||||||
|
queued.affects_position = true;
|
||||||
|
queued.affects_scale = false;
|
||||||
|
queued.affects_alpha = false;
|
||||||
|
elem->anim_queue.push_back(queued);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel execution with independent timing
|
||||||
elem->anim.start_x = elem->x;
|
elem->anim.start_x = elem->x;
|
||||||
elem->anim.start_y = elem->y;
|
elem->anim.start_y = elem->y;
|
||||||
elem->anim.end_x = to_x;
|
elem->anim.end_x = to_x;
|
||||||
elem->anim.end_y = to_y;
|
elem->anim.end_y = to_y;
|
||||||
|
elem->anim.animating_position = true;
|
||||||
|
|
||||||
elem->anim.is_move_fade = false;
|
// Set position-specific timing
|
||||||
|
elem->anim.position_anim.progress_sec = 0.0f;
|
||||||
|
elem->anim.position_anim.duration_sec = duration_sec;
|
||||||
|
elem->anim.position_anim.easing = easing;
|
||||||
|
|
||||||
|
if (elem->anim.type == ANIM_NONE) {
|
||||||
|
elem->anim.type = ANIM_MOVE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts a "fade" or "dissolve" animation on an element.
|
|
||||||
void compositor_animate_fade(compositor_t* comp, uint32_t id,
|
|
||||||
float to_alpha,
|
|
||||||
float duration_sec, easing_t easing) {
|
|
||||||
if (!comp) return;
|
|
||||||
element_t* elem = find_element(comp, id);
|
|
||||||
if (!elem) {
|
|
||||||
set_error(comp, "Element not found for animate_fade.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're fading in from 0, we must make it visible first
|
|
||||||
if (elem->alpha == 0.0f && to_alpha > 0.0f) {
|
|
||||||
elem->visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
elem->anim.type = ANIM_FADE;
|
|
||||||
elem->anim.easing = easing;
|
|
||||||
elem->anim.progress_sec = 0.0f;
|
|
||||||
elem->anim.duration_sec = duration_sec;
|
|
||||||
|
|
||||||
elem->anim.start_alpha = elem->alpha;
|
|
||||||
elem->anim.end_alpha = std::max(0.0f, std::min(1.0f, to_alpha)); // Clamp 0-1
|
|
||||||
|
|
||||||
elem->anim.is_move_fade = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts a "scale" animation on an element.
|
|
||||||
void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
||||||
float to_scale,
|
float to_scale,
|
||||||
float duration_sec, easing_t easing) {
|
float duration_sec, easing_t easing, bool queue) {
|
||||||
if (!comp) return;
|
if (!comp) return;
|
||||||
element_t* elem = find_element(comp, id);
|
element_t* elem = find_element(comp, id);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
@@ -683,35 +733,204 @@ void compositor_animate_scale(compositor_t* comp, uint32_t id,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
elem->anim.type = ANIM_SCALE;
|
if (queue) {
|
||||||
elem->anim.easing = easing;
|
queued_animation_t queued;
|
||||||
elem->anim.progress_sec = 0.0f;
|
queued.type = ANIM_SCALE;
|
||||||
elem->anim.duration_sec = duration_sec;
|
queued.easing = easing;
|
||||||
|
queued.duration_sec = duration_sec;
|
||||||
elem->anim.start_scale = elem->scale;
|
queued.to_scale = to_scale;
|
||||||
elem->anim.end_scale = to_scale;
|
queued.is_move_fade = false;
|
||||||
|
queued.affects_position = false;
|
||||||
elem->anim.is_move_fade = false;
|
queued.affects_scale = true;
|
||||||
}
|
queued.affects_alpha = false;
|
||||||
|
elem->anim_queue.push_back(queued);
|
||||||
// Example for a combined animation
|
|
||||||
void compositor_animate_move_fade(compositor_t* comp, uint32_t id,
|
|
||||||
int32_t to_x, int32_t to_y, float to_alpha,
|
|
||||||
float duration_sec, easing_t easing) {
|
|
||||||
if (!comp) return;
|
|
||||||
element_t* elem = find_element(comp, id);
|
|
||||||
if (!elem) {
|
|
||||||
set_error(comp, "Element not found for animate_move_fade.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger both
|
// Parallel execution with independent timing
|
||||||
compositor_animate_move(comp, id, to_x, to_y, duration_sec, easing);
|
elem->anim.start_scale = elem->scale;
|
||||||
compositor_animate_fade(comp, id, to_alpha, duration_sec, easing);
|
elem->anim.end_scale = to_scale;
|
||||||
|
elem->anim.animating_scale = true;
|
||||||
|
|
||||||
// Mark as combined
|
// Set scale-specific timing
|
||||||
elem->anim.type = ANIM_MOVE_FADE; // Override type
|
elem->anim.scale_anim.progress_sec = 0.0f;
|
||||||
elem->anim.is_move_fade = true;
|
elem->anim.scale_anim.duration_sec = duration_sec;
|
||||||
|
elem->anim.scale_anim.easing = easing;
|
||||||
|
|
||||||
|
if (elem->anim.type == ANIM_NONE) {
|
||||||
|
elem->anim.type = ANIM_SCALE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void compositor_animate_fade(compositor_t* comp, uint32_t id,
|
||||||
|
float to_alpha,
|
||||||
|
float duration_sec, easing_t easing, bool queue) {
|
||||||
|
if (!comp) return;
|
||||||
|
element_t* elem = find_element(comp, id);
|
||||||
|
if (!elem) {
|
||||||
|
set_error(comp, "Element not found for animate_fade.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
to_alpha = std::max(0.0f, std::min(1.0f, to_alpha));
|
||||||
|
|
||||||
|
if (queue) {
|
||||||
|
queued_animation_t queued;
|
||||||
|
queued.type = ANIM_FADE;
|
||||||
|
queued.easing = easing;
|
||||||
|
queued.duration_sec = duration_sec;
|
||||||
|
queued.to_alpha = to_alpha;
|
||||||
|
queued.is_move_fade = false;
|
||||||
|
queued.affects_position = false;
|
||||||
|
queued.affects_scale = false;
|
||||||
|
queued.affects_alpha = true;
|
||||||
|
elem->anim_queue.push_back(queued);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel execution with independent timing
|
||||||
|
if (elem->alpha == 0.0f && to_alpha > 0.0f) {
|
||||||
|
elem->visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
elem->anim.start_alpha = elem->alpha;
|
||||||
|
elem->anim.end_alpha = to_alpha;
|
||||||
|
elem->anim.animating_alpha = true;
|
||||||
|
|
||||||
|
// Set alpha-specific timing
|
||||||
|
elem->anim.alpha_anim.progress_sec = 0.0f;
|
||||||
|
elem->anim.alpha_anim.duration_sec = duration_sec;
|
||||||
|
elem->anim.alpha_anim.easing = easing;
|
||||||
|
|
||||||
|
if (elem->anim.type == ANIM_NONE) {
|
||||||
|
elem->anim.type = ANIM_FADE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete all animations on a specific element
|
||||||
|
void compositor_animation_complete(compositor_t* comp, uint32_t id) {
|
||||||
|
if (!comp) return;
|
||||||
|
element_t* elem = find_element(comp, id);
|
||||||
|
if (!elem) {
|
||||||
|
set_error(comp, "Element not found for animation_complete.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_element_dirty(comp, elem);
|
||||||
|
bool position_changed = false;
|
||||||
|
|
||||||
|
// Complete current animation
|
||||||
|
if (elem->anim.type != ANIM_NONE) {
|
||||||
|
if (elem->anim.animating_position) {
|
||||||
|
elem->x = elem->anim.end_x;
|
||||||
|
elem->y = elem->anim.end_y;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
if (elem->anim.animating_alpha) {
|
||||||
|
elem->alpha = elem->anim.end_alpha;
|
||||||
|
if (elem->alpha == 0.0f) elem->visible = false;
|
||||||
|
}
|
||||||
|
if (elem->anim.animating_scale) {
|
||||||
|
elem->scale = elem->anim.end_scale;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete all queued animations
|
||||||
|
for (const auto& queued : elem->anim_queue) {
|
||||||
|
if (queued.affects_position) {
|
||||||
|
elem->x = queued.to_x;
|
||||||
|
elem->y = queued.to_y;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
if (queued.affects_alpha) {
|
||||||
|
elem->alpha = queued.to_alpha;
|
||||||
|
if (elem->alpha == 0.0f) elem->visible = false;
|
||||||
|
}
|
||||||
|
if (queued.affects_scale) {
|
||||||
|
elem->scale = queued.to_scale;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear animations
|
||||||
|
elem->anim.type = ANIM_NONE;
|
||||||
|
elem->anim.animating_position = false;
|
||||||
|
elem->anim.animating_scale = false;
|
||||||
|
elem->anim.animating_alpha = false;
|
||||||
|
elem->anim_queue.clear();
|
||||||
|
|
||||||
|
// Update spatial hash
|
||||||
|
if (position_changed) {
|
||||||
|
clear_spatial_hash(comp, elem->id);
|
||||||
|
update_spatial_hash(comp, elem);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mark_element_dirty(comp, elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete ALL animations on ALL elements
|
||||||
|
void compositor_animation_complete_all(compositor_t* comp) {
|
||||||
|
if (!comp) return;
|
||||||
|
|
||||||
|
// Iterate over all elements
|
||||||
|
for (auto& pair : comp->elements_map) {
|
||||||
|
element_t* elem = &pair.second;
|
||||||
|
|
||||||
|
mark_element_dirty(comp, elem);
|
||||||
|
bool position_changed = false;
|
||||||
|
|
||||||
|
// Complete current animation
|
||||||
|
if (elem->anim.type != ANIM_NONE) {
|
||||||
|
if (elem->anim.animating_position) {
|
||||||
|
elem->x = elem->anim.end_x;
|
||||||
|
elem->y = elem->anim.end_y;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
if (elem->anim.animating_alpha) {
|
||||||
|
elem->alpha = elem->anim.end_alpha;
|
||||||
|
if (elem->alpha == 0.0f) elem->visible = false;
|
||||||
|
}
|
||||||
|
if (elem->anim.animating_scale) {
|
||||||
|
elem->scale = elem->anim.end_scale;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete all queued animations
|
||||||
|
for (const auto& queued : elem->anim_queue) {
|
||||||
|
if (queued.affects_position) {
|
||||||
|
elem->x = queued.to_x;
|
||||||
|
elem->y = queued.to_y;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
if (queued.affects_alpha) {
|
||||||
|
elem->alpha = queued.to_alpha;
|
||||||
|
if (elem->alpha == 0.0f) elem->visible = false;
|
||||||
|
}
|
||||||
|
if (queued.affects_scale) {
|
||||||
|
elem->scale = queued.to_scale;
|
||||||
|
position_changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear animations
|
||||||
|
elem->anim.type = ANIM_NONE;
|
||||||
|
elem->anim.animating_position = false;
|
||||||
|
elem->anim.animating_scale = false;
|
||||||
|
elem->anim.animating_alpha = false;
|
||||||
|
elem->anim_queue.clear();
|
||||||
|
|
||||||
|
// Update spatial hash
|
||||||
|
if (position_changed) {
|
||||||
|
clear_spatial_hash(comp, elem->id);
|
||||||
|
update_spatial_hash(comp, elem);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mark_element_dirty(comp, elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Dirty Marking ---
|
// --- Dirty Marking ---
|
||||||
@@ -739,6 +958,40 @@ void compositor_mark_content_dirty(compositor_t* comp, uint32_t id) {
|
|||||||
comp->last_error.clear();
|
comp->last_error.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true if any element has an active animation or queued animations.
|
||||||
|
bool compositor_is_animating(compositor_t* comp) {
|
||||||
|
if (!comp) return false;
|
||||||
|
for (const auto& pair : comp->elements_map) {
|
||||||
|
const element_t& elem = pair.second;
|
||||||
|
const animation_t& anim = elem.anim;
|
||||||
|
|
||||||
|
// Active animation (type set) or any per-property animating flags
|
||||||
|
if (anim.type != ANIM_NONE) return true;
|
||||||
|
if (anim.animating_position || anim.animating_scale || anim.animating_alpha) return true;
|
||||||
|
|
||||||
|
// Pending queued animations
|
||||||
|
if (!elem.anim_queue.empty()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the specified element is animating or has queued animations.
|
||||||
|
bool compositor_is_element_animating(compositor_t* comp, uint32_t id) {
|
||||||
|
if (!comp) return false;
|
||||||
|
element_t* elem = find_element(comp, id);
|
||||||
|
if (!elem) {
|
||||||
|
set_error(comp, "Element with ID " + std::to_string(id) + " not found to check animation.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const animation_t& anim = elem->anim;
|
||||||
|
// Active animation (type set) or any per-property animating flags
|
||||||
|
if (anim.type != ANIM_NONE) return true;
|
||||||
|
if (anim.animating_position || anim.animating_scale || anim.animating_alpha) return true;
|
||||||
|
// Pending queued animations
|
||||||
|
if (!elem->anim_queue.empty()) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Rendering ---
|
// --- Rendering ---
|
||||||
|
|
||||||
// Renders all dirty tiles and returns a pointer to the main framebuffer.
|
// Renders all dirty tiles and returns a pointer to the main framebuffer.
|
||||||
@@ -807,72 +1060,110 @@ const uint32_t* compositor_compose(compositor_t* comp) {
|
|||||||
// --- Blending Loop (with SIMD) ---
|
// --- Blending Loop (with SIMD) ---
|
||||||
int32_t x = blend_x0;
|
int32_t x = blend_x0;
|
||||||
|
|
||||||
#if defined(__SSE2__)
|
#if defined(__SSE2__) || (defined(_M_AMD64) || defined(_M_X64))
|
||||||
// --- SSE2 Path (Processes 4 pixels at a time) ---
|
// Pre-calculate fixed values for the SIMD loop
|
||||||
|
const __m128i v_zero = _mm_setzero_si128();
|
||||||
|
const __m128i v_one = _mm_set1_epi16(1);
|
||||||
|
const __m128i v_255 = _mm_set1_epi16(255);
|
||||||
|
const __m128i v_elem_a_u8 = _mm_set1_epi16((short)elem_a_u8);
|
||||||
|
const __m128i v_opaque_mask = _mm_set1_epi32(0xFF000000); // For setting final alpha to 0xFF
|
||||||
|
|
||||||
// Calculate the end point for the 4-pixel chunks
|
// Determine the loop boundary for 4-pixel chunks
|
||||||
int32_t simd_end = blend_x0 + ((blend_x1 - blend_x0) / 4) * 4;
|
int32_t sse_limit = blend_x1 - (blend_x1 - x) % 4;
|
||||||
|
|
||||||
// Pre-calculate SSE constants
|
for (; x < sse_limit; x += 4) {
|
||||||
const __m128i zero = _mm_setzero_si128();
|
// 1. Map destination x to source x (Need 4 values: x, x+1, x+2, x+3)
|
||||||
const __m128i c255 = _mm_set1_epi16(255);
|
// This assumes inv_scale is pre-calculated. We must do the 4 lookups.
|
||||||
// Mask to force output alpha to 255 (0xFFFF in the 16-bit unpacked world)
|
int32_t src_x0 = (int32_t)((x - elem_x0) * inv_scale);
|
||||||
const __m128i alpha_mask = _mm_set_epi16(0, 0, 0, 0xFFFF, 0, 0, 0, 0xFFFF);
|
int32_t src_x1 = (int32_t)(((x + 1) - elem_x0) * inv_scale);
|
||||||
// Element alpha broadcasted to 4 16-bit words
|
int32_t src_x2 = (int32_t)(((x + 2) - elem_x0) * inv_scale);
|
||||||
const __m128i elem_a_16 = _mm_set1_epi16(elem_a_u8);
|
int32_t src_x3 = (int32_t)(((x + 3) - elem_x0) * inv_scale);
|
||||||
|
|
||||||
for (; x < simd_end; x += 4) {
|
// 2. Sample 4 source pixels (ARGB format)
|
||||||
// 1. GATHER: Map 4 dest x-coords to 4 source x-coords
|
uint32_t src_pixels[4];
|
||||||
int32_t src_x0 = (int32_t)((x + 0 - elem_x0) * inv_scale);
|
src_pixels[0] = src_row[src_x0];
|
||||||
int32_t src_x1 = (int32_t)((x + 1 - elem_x0) * inv_scale);
|
src_pixels[1] = src_row[src_x1];
|
||||||
int32_t src_x2 = (int32_t)((x + 2 - elem_x0) * inv_scale);
|
src_pixels[2] = src_row[src_x2];
|
||||||
int32_t src_x3 = (int32_t)((x + 3 - elem_x0) * inv_scale);
|
src_pixels[3] = src_row[src_x3];
|
||||||
|
|
||||||
// 2. LOAD: Load 4 source pixels and 4 destination pixels
|
__m128i v_src = _mm_loadu_si128((const __m128i*)src_pixels);
|
||||||
__m128i src_px = _mm_set_epi32(src_row[src_x3], src_row[src_x2], src_row[src_x1], src_row[src_x0]);
|
__m128i v_dst = _mm_loadu_si128((const __m128i*) & dst_row[x]);
|
||||||
__m128i dst_px = _mm_loadu_si128((__m128i*)(dst_row + x));
|
|
||||||
|
|
||||||
// 3. UNPACK: Unpack 8-bit channels to 16-bit
|
// --- Unpack 4 pixels from 8-bit (ARGB) to 16-bit (A, R, G, B components) ---
|
||||||
__m128i src_lo = _mm_unpacklo_epi8(src_px, zero); // Pixels 0, 1
|
// v_src_16bit_lo: A0 R0 G0 B0 | A1 R1 G1 B1 (packed 16-bit values, high bits zero)
|
||||||
__m128i src_hi = _mm_unpackhi_epi8(src_px, zero); // Pixels 2, 3
|
// v_src_16bit_hi: A2 R2 G2 B2 | A3 R3 G3 B3
|
||||||
__m128i dst_lo = _mm_unpacklo_epi8(dst_px, zero);
|
__m128i v_src_16bit_lo = _mm_unpacklo_epi8(v_src, v_zero);
|
||||||
__m128i dst_hi = _mm_unpackhi_epi8(dst_px, zero);
|
__m128i v_src_16bit_hi = _mm_unpackhi_epi8(v_src, v_zero);
|
||||||
|
|
||||||
// 4. GET ALPHA: Get pixel alpha (A) from [B,G,R,A]
|
__m128i v_dst_16bit_lo = _mm_unpacklo_epi8(v_dst, v_zero);
|
||||||
// [A0,A0,A0,A0, A1,A1,A1,A1]
|
__m128i v_dst_16bit_hi = _mm_unpackhi_epi8(v_dst, v_zero);
|
||||||
__m128i src_a_lo = _mm_unpacklo_epi64(_mm_shufflelo_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3)), _mm_shufflehi_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3)));
|
|
||||||
// [A2,A2,A2,A2, A3,A3,A3,A3]
|
|
||||||
__m128i src_a_hi = _mm_unpacklo_epi64(_mm_shufflelo_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3)), _mm_shufflehi_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3)));
|
|
||||||
|
|
||||||
// 5. MODULATE ALPHA: (pixel_alpha * elem_alpha) / 255
|
// 3. Modulate alpha (ModAlpha = (SrcAlpha * ElemAlpha) / 255)
|
||||||
src_a_lo = _mm_srli_epi16(_mm_mullo_epi16(src_a_lo, elem_a_16), 8);
|
// Get Src Alpha
|
||||||
src_a_hi = _mm_srli_epi16(_mm_mullo_epi16(src_a_hi, elem_a_16), 8);
|
__m128i v_src_a_lo = _mm_shufflelo_epi16(v_src_16bit_lo, _MM_SHUFFLE(3, 3, 3, 3)); // A0 A0 A0 A0 | A1 A1 A1 A1
|
||||||
|
v_src_a_lo = _mm_shufflehi_epi16(v_src_a_lo, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
__m128i v_src_a_hi = _mm_shufflelo_epi16(v_src_16bit_hi, _MM_SHUFFLE(3, 3, 3, 3)); // A2 A2 A2 A2 | A3 A3 A3 A3
|
||||||
|
v_src_a_hi = _mm_shufflehi_epi16(v_src_a_hi, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
|
||||||
// 6. BLEND: (src * src_a) + (dst * (255 - src_a))
|
// ModAlpha = SrcAlpha * ElemAlpha
|
||||||
__m128i inv_a_lo = _mm_sub_epi16(c255, src_a_lo);
|
__m128i v_mod_a_lo = _mm_mullo_epi16(v_src_a_lo, v_elem_a_u8);
|
||||||
__m128i inv_a_hi = _mm_sub_epi16(c255, src_a_hi);
|
__m128i v_mod_a_hi = _mm_mullo_epi16(v_src_a_hi, v_elem_a_u8);
|
||||||
|
|
||||||
__m128i src_scaled_lo = _mm_mullo_epi16(src_lo, src_a_lo);
|
// ModAlpha = (SrcAlpha * ElemAlpha + 255/2) / 255 (Simple division approximation)
|
||||||
__m128i src_scaled_hi = _mm_mullo_epi16(src_hi, src_a_hi);
|
// For / 255, we use a more accurate approximation or just accept the error.
|
||||||
|
// The scalar path uses (x / 255), we'll approximate with (x + 128) / 256 for a faster result.
|
||||||
|
// ModAlpha_approx = (v_mod_a + 128) >> 8
|
||||||
|
__m128i v_mod_a_lo_div = _mm_add_epi16(v_mod_a_lo, _mm_set1_epi16(128));
|
||||||
|
__m128i v_mod_a_hi_div = _mm_add_epi16(v_mod_a_hi, _mm_set1_epi16(128));
|
||||||
|
__m128i v_mod_a_approx_lo = _mm_srli_epi16(v_mod_a_lo_div, 8); // Now contains ModAlpha_approx for 4 pixels (A0..A3)
|
||||||
|
__m128i v_mod_a_approx_hi = _mm_srli_epi16(v_mod_a_hi_div, 8);
|
||||||
|
|
||||||
__m128i dst_scaled_lo = _mm_mullo_epi16(dst_lo, inv_a_lo);
|
// Replicate ModAlpha_approx for blending (A0 R0 G0 B0, A1 R1 G1 B1)
|
||||||
__m128i dst_scaled_hi = _mm_mullo_epi16(dst_hi, inv_a_hi);
|
__m128i v_mod_a_lo_blend = _mm_unpacklo_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo);
|
||||||
|
v_mod_a_lo_blend = _mm_unpacklo_epi32(v_mod_a_lo_blend, v_mod_a_lo_blend); // ModAlpha_0 for R0 G0 B0
|
||||||
|
__m128i v_mod_a_hi_blend = _mm_unpacklo_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi);
|
||||||
|
v_mod_a_hi_blend = _mm_unpacklo_epi32(v_mod_a_hi_blend, v_mod_a_hi_blend); // ModAlpha_2 for R2 G2 B2
|
||||||
|
|
||||||
__m128i sum_lo = _mm_add_epi16(src_scaled_lo, dst_scaled_lo);
|
// Also need the alpha for the second pixel in each 64-bit lane
|
||||||
__m128i sum_hi = _mm_add_epi16(src_scaled_hi, dst_scaled_hi);
|
__m128i v_mod_a_lo_1 = _mm_unpackhi_epi16(v_mod_a_approx_lo, v_mod_a_approx_lo);
|
||||||
|
v_mod_a_lo_blend = _mm_unpackhi_epi64(v_mod_a_lo_blend, v_mod_a_lo_1); // A0 for R0 G0 B0 | A1 for R1 G1 B1
|
||||||
|
__m128i v_mod_a_hi_1 = _mm_unpackhi_epi16(v_mod_a_approx_hi, v_mod_a_approx_hi);
|
||||||
|
v_mod_a_hi_blend = _mm_unpackhi_epi64(v_mod_a_hi_blend, v_mod_a_hi_1); // A2 for R2 G2 B2 | A3 for R3 G3 B3
|
||||||
|
|
||||||
// Divide by 255 (by shifting right 8)
|
// ModAlpha_blend now contains: [A0 B0 G0 R0 | A1 B1 G1 R1] and [A2 B2 G2 R2 | A3 B3 G3 R3] (reversed order for convenience)
|
||||||
__m128i out_lo = _mm_srli_epi16(sum_lo, 8);
|
// It's crucial: the alpha value is replicated to the R, G, B positions of its corresponding pixel.
|
||||||
__m128i out_hi = _mm_srli_epi16(sum_hi, 8);
|
|
||||||
|
|
||||||
// 7. SET ALPHA: Force output alpha to 255
|
// Calculate InvAlpha = 255 - ModAlpha
|
||||||
out_lo = _mm_or_si128(out_lo, alpha_mask);
|
__m128i v_inv_a_lo_blend = _mm_sub_epi16(v_255, v_mod_a_lo_blend);
|
||||||
out_hi = _mm_or_si128(out_hi, alpha_mask);
|
__m128i v_inv_a_hi_blend = _mm_sub_epi16(v_255, v_mod_a_hi_blend);
|
||||||
|
|
||||||
// 8. PACK & STORE: Pack 16-bit channels back to 8-bit
|
// 4. Blend: (Src * ModA + Dst * InvA) / 255
|
||||||
__m128i packed_out = _mm_packus_epi16(out_lo, out_hi);
|
// Src * ModA
|
||||||
_mm_storeu_si128((__m128i*)(dst_row + x), packed_out);
|
__m128i v_src_x_mod_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_lo), v_mod_a_lo_blend);
|
||||||
|
__m128i v_src_x_mod_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_src_16bit_hi), v_mod_a_hi_blend);
|
||||||
|
|
||||||
|
// Dst * InvA
|
||||||
|
__m128i v_dst_x_inv_a_lo = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_lo), v_inv_a_lo_blend);
|
||||||
|
__m128i v_dst_x_inv_a_hi = _mm_mullo_epi16(_mm_andnot_si128(v_opaque_mask, v_dst_16bit_hi), v_inv_a_hi_blend);
|
||||||
|
|
||||||
|
// Sum
|
||||||
|
__m128i v_sum_lo = _mm_add_epi16(v_src_x_mod_a_lo, v_dst_x_inv_a_lo);
|
||||||
|
__m128i v_sum_hi = _mm_add_epi16(v_src_x_mod_a_hi, v_dst_x_inv_a_hi);
|
||||||
|
|
||||||
|
// Divide by 255: (v_sum + 128) / 256 (Fast approx)
|
||||||
|
__m128i v_out_16bit_lo = _mm_srli_epi16(_mm_add_epi16(v_sum_lo, _mm_set1_epi16(128)), 8);
|
||||||
|
__m128i v_out_16bit_hi = _mm_srli_epi16(_mm_add_epi16(v_sum_hi, _mm_set1_epi16(128)), 8);
|
||||||
|
|
||||||
|
// Pack the results back to 8-bit, keeping the alpha from v_opaque_mask (0xFF)
|
||||||
|
__m128i v_out_8bit_packed = _mm_packus_epi16(v_out_16bit_lo, v_out_16bit_hi);
|
||||||
|
|
||||||
|
// OR with the Opaque Alpha mask
|
||||||
|
__m128i v_final_out = _mm_or_si128(v_out_8bit_packed, v_opaque_mask);
|
||||||
|
|
||||||
|
// Store the result
|
||||||
|
_mm_storeu_si128((__m128i*) & dst_row[x], v_final_out);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
// --- Scalar Path (Processes remaining pixels 1 by 1) ---
|
// --- Scalar Path (Processes remaining pixels 1 by 1) ---
|
||||||
// This loop handles the "tail" on SSE2, or all pixels on ARM.
|
// This loop handles the "tail" on SSE2, or all pixels on ARM.
|
||||||
for (; x < blend_x1; x++) {
|
for (; x < blend_x1; x++) {
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ uint32_t* image_load_resized(const char* filename, int desired_width, int desire
|
|||||||
// Allocate buffer for resized image
|
// Allocate buffer for resized image
|
||||||
unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4);
|
unsigned char* resized = (unsigned char*)malloc(desired_width * desired_height * 4);
|
||||||
if (!resized) {
|
if (!resized) {
|
||||||
fprintf(stderr, "image.cpp 41: malloc failed!");
|
fprintf(stderr, "image.cpp 43: malloc failed!");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+90
-745
@@ -1,8 +1,6 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <unordered_map>
|
|
||||||
#include <string>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <window.h>
|
#include <window.h>
|
||||||
@@ -10,527 +8,15 @@
|
|||||||
#include <compositor.h>
|
#include <compositor.h>
|
||||||
#include <image.h>
|
#include <image.h>
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// COMMAND SYSTEM
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
enum CommandType {
|
|
||||||
CMD_SHOW,
|
|
||||||
CMD_HIDE,
|
|
||||||
CMD_MOVE,
|
|
||||||
CMD_CHANGE_SPRITE,
|
|
||||||
CMD_SET_Z,
|
|
||||||
CMD_SET_SCALE,
|
|
||||||
CMD_SPEAK,
|
|
||||||
CMD_CLEAR,
|
|
||||||
CMD_WAIT,
|
|
||||||
CMD_BACKGROUND,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum TransitionType {
|
|
||||||
TRANS_NONE,
|
|
||||||
TRANS_FADE,
|
|
||||||
TRANS_SLIDE_LEFT,
|
|
||||||
TRANS_SLIDE_RIGHT,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdShow {
|
|
||||||
const char* character;
|
|
||||||
const char* sprite;
|
|
||||||
int x, y, z;
|
|
||||||
TransitionType transition;
|
|
||||||
float duration_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdHide {
|
|
||||||
const char* character;
|
|
||||||
TransitionType transition;
|
|
||||||
float duration_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdMove {
|
|
||||||
const char* character;
|
|
||||||
int x, y;
|
|
||||||
TransitionType transition;
|
|
||||||
float duration_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdChangeSprite {
|
|
||||||
const char* character;
|
|
||||||
const char* sprite;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdSetZ {
|
|
||||||
const char* character;
|
|
||||||
int z;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdSetScale {
|
|
||||||
const char* character;
|
|
||||||
float scale;
|
|
||||||
TransitionType transition;
|
|
||||||
float duration_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdSpeak {
|
|
||||||
const char* speaker;
|
|
||||||
const char* text;
|
|
||||||
float text_speed;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CmdWait {};
|
|
||||||
struct CmdClear {};
|
|
||||||
struct CmdBackground {
|
|
||||||
const char* filename;
|
|
||||||
TransitionType transition;
|
|
||||||
float duration_ms;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Command {
|
|
||||||
CommandType type;
|
|
||||||
bool blocking;
|
|
||||||
|
|
||||||
union {
|
|
||||||
CmdShow show;
|
|
||||||
CmdHide hide;
|
|
||||||
CmdMove move;
|
|
||||||
CmdChangeSprite change_sprite;
|
|
||||||
CmdSetZ set_z;
|
|
||||||
CmdSetScale set_scale;
|
|
||||||
CmdSpeak speak;
|
|
||||||
CmdWait wait;
|
|
||||||
CmdClear clear;
|
|
||||||
CmdBackground background;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Scene {
|
|
||||||
const Command* commands;
|
|
||||||
int total_commands;
|
|
||||||
int current_command;
|
|
||||||
|
|
||||||
void reset() { current_command = 0; }
|
|
||||||
bool has_next() const { return current_command < total_commands; }
|
|
||||||
const Command* current() const {
|
|
||||||
if (current_command < total_commands) return &commands[current_command];
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
void next() {
|
|
||||||
if (current_command < total_commands) current_command++;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Command Show(const char* character, const char* sprite, int x, int y, int z,
|
|
||||||
TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = true)
|
|
||||||
{
|
|
||||||
return Command{
|
|
||||||
.type = CMD_SHOW,
|
|
||||||
.blocking = blocking,
|
|
||||||
.show = CmdShow{character, sprite, x, y, z, trans, duration}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command Hide(const char* character, TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = true)
|
|
||||||
{
|
|
||||||
return Command{
|
|
||||||
.type = CMD_HIDE,
|
|
||||||
.blocking = blocking,
|
|
||||||
.hide = CmdHide{character, trans, duration}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command Speak(const char* speaker, const char* text, float text_speed = 30.0f, bool blocking = true)
|
|
||||||
{
|
|
||||||
return Command{
|
|
||||||
.type = CMD_SPEAK,
|
|
||||||
.blocking = blocking,
|
|
||||||
.speak = CmdSpeak{speaker, text, text_speed}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command Wait(bool blocking = true)
|
|
||||||
{
|
|
||||||
return Command{ .type = CMD_WAIT, .blocking = blocking, .wait = {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command Clear(bool blocking = true)
|
|
||||||
{
|
|
||||||
return Command{ .type = CMD_CLEAR, .blocking = blocking, .clear = {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command SetZ(const char* character, int z, bool blocking = false)
|
|
||||||
{
|
|
||||||
return Command{ .type = CMD_SET_Z, .blocking = blocking, .set_z = CmdSetZ{character, z} };
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Command SetScale(const char* character, float scale, TransitionType trans = TRANS_NONE, float duration = 0.0f, bool blocking = false)
|
|
||||||
{
|
|
||||||
return Command{ .type = CMD_SET_SCALE, .blocking = blocking, .set_scale = CmdSetScale{character, scale, trans, duration} };
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ANIMATION TRACKER
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
struct ActiveAnimation {
|
|
||||||
float remaining_time;
|
|
||||||
|
|
||||||
ActiveAnimation(float duration) : remaining_time(duration) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class AnimationTracker {
|
|
||||||
private:
|
|
||||||
std::vector<ActiveAnimation> active_animations;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void add_animation(float duration_sec) {
|
|
||||||
if (duration_sec > 0.0f) {
|
|
||||||
active_animations.push_back(ActiveAnimation(duration_sec));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(float delta_time) {
|
|
||||||
for (size_t i = 0; i < active_animations.size(); ) {
|
|
||||||
active_animations[i].remaining_time -= delta_time;
|
|
||||||
if (active_animations[i].remaining_time <= 0.0f) {
|
|
||||||
// Animation finished, remove it
|
|
||||||
active_animations.erase(active_animations.begin() + i);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_active_animations() const {
|
|
||||||
return !active_animations.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
active_animations.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
int count() const {
|
|
||||||
return (int)active_animations.size(); // it won't overflow.. right?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// CHARACTER MANAGEMENT
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
struct SpriteElement {
|
|
||||||
uint32_t* image_data;
|
|
||||||
uint32_t element_id;
|
|
||||||
bool visible;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Character {
|
|
||||||
std::string name;
|
|
||||||
std::unordered_map<std::string, SpriteElement> sprites;
|
|
||||||
int width, height;
|
|
||||||
int current_x, current_y;
|
|
||||||
int current_z;
|
|
||||||
float current_scale;
|
|
||||||
std::string current_sprite;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CharacterManager {
|
|
||||||
private:
|
|
||||||
std::unordered_map<std::string, Character> characters;
|
|
||||||
compositor_t* compositor;
|
|
||||||
int screen_width;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CharacterManager(compositor_t* comp, int screen_w)
|
|
||||||
: compositor(comp), screen_width(screen_w) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_character(const char* name, int width, int height) {
|
|
||||||
Character ch;
|
|
||||||
ch.name = name;
|
|
||||||
ch.width = width;
|
|
||||||
ch.height = height;
|
|
||||||
ch.current_x = 0;
|
|
||||||
ch.current_y = 0;
|
|
||||||
ch.current_z = 1;
|
|
||||||
ch.current_scale = 1.0f;
|
|
||||||
ch.current_sprite = "";
|
|
||||||
characters[name] = ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
void load_sprite(const char* name, const char* emotion, const char* filename,
|
|
||||||
int width, int height, int initial_z = 1) {
|
|
||||||
auto it = characters.find(name);
|
|
||||||
if (it == characters.end()) return;
|
|
||||||
|
|
||||||
uint32_t* img = image_load_resized(filename, width, height);
|
|
||||||
if (!img) return;
|
|
||||||
|
|
||||||
SpriteElement elem;
|
|
||||||
elem.image_data = img;
|
|
||||||
elem.element_id = compositor_insert(compositor, img, width, height, 0, 0, initial_z);
|
|
||||||
elem.visible = false;
|
|
||||||
|
|
||||||
compositor_set_alpha(compositor, elem.element_id, 0.0f);
|
|
||||||
compositor_set_visible(compositor, elem.element_id, false);
|
|
||||||
|
|
||||||
it->second.sprites[emotion] = elem;
|
|
||||||
}
|
|
||||||
|
|
||||||
Character* get_character(const char* name) {
|
|
||||||
auto it = characters.find(name);
|
|
||||||
if (it != characters.end()) return &it->second;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t get_active_element_id(Character* ch) {
|
|
||||||
if (!ch || ch->current_sprite.empty()) return 0;
|
|
||||||
auto it = ch->sprites.find(ch->current_sprite);
|
|
||||||
if (it == ch->sprites.end()) return 0;
|
|
||||||
return it->second.element_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void show_character(const char* name, int x, int y, int z, const char* sprite_name,
|
|
||||||
TransitionType transition, float duration_ms) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
if (!ch) return;
|
|
||||||
|
|
||||||
for (auto& pair : ch->sprites) {
|
|
||||||
if (pair.first != sprite_name && pair.second.visible) {
|
|
||||||
compositor_set_visible(compositor, pair.second.element_id, false);
|
|
||||||
compositor_set_alpha(compositor, pair.second.element_id, 0.0f);
|
|
||||||
pair.second.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = ch->sprites.find(sprite_name);
|
|
||||||
if (it == ch->sprites.end()) return;
|
|
||||||
|
|
||||||
SpriteElement& elem = it->second;
|
|
||||||
float duration_sec = duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
ch->current_x = x;
|
|
||||||
ch->current_y = y;
|
|
||||||
ch->current_z = z;
|
|
||||||
ch->current_sprite = sprite_name;
|
|
||||||
elem.visible = true;
|
|
||||||
|
|
||||||
compositor_set_zorder(compositor, elem.element_id, z);
|
|
||||||
|
|
||||||
if (transition == TRANS_FADE) {
|
|
||||||
compositor_set_position(compositor, elem.element_id, x, y);
|
|
||||||
compositor_set_visible(compositor, elem.element_id, true);
|
|
||||||
compositor_animate_fade(compositor, elem.element_id, 1.0f, duration_sec, EASE_LINEAR);
|
|
||||||
}
|
|
||||||
else if (transition == TRANS_SLIDE_LEFT) {
|
|
||||||
int start_x = -ch->width;
|
|
||||||
compositor_set_position(compositor, elem.element_id, start_x, y);
|
|
||||||
compositor_set_alpha(compositor, elem.element_id, 1.0f);
|
|
||||||
compositor_set_visible(compositor, elem.element_id, true);
|
|
||||||
compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC);
|
|
||||||
}
|
|
||||||
else if (transition == TRANS_SLIDE_RIGHT) {
|
|
||||||
int start_x = screen_width;
|
|
||||||
compositor_set_position(compositor, elem.element_id, start_x, y);
|
|
||||||
compositor_set_alpha(compositor, elem.element_id, 1.0f);
|
|
||||||
compositor_set_visible(compositor, elem.element_id, true);
|
|
||||||
compositor_animate_move(compositor, elem.element_id, x, y, duration_sec, EASE_OUT_CUBIC);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
compositor_set_position(compositor, elem.element_id, x, y);
|
|
||||||
compositor_set_alpha(compositor, elem.element_id, 1.0f);
|
|
||||||
compositor_set_visible(compositor, elem.element_id, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hide_character(const char* name, TransitionType transition, float duration_ms) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
if (!ch) return;
|
|
||||||
|
|
||||||
float duration_sec = duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
for (auto& pair : ch->sprites) {
|
|
||||||
if (pair.second.visible) {
|
|
||||||
if (transition == TRANS_FADE) {
|
|
||||||
compositor_animate_fade(compositor, pair.second.element_id, 0.0f, duration_sec, EASE_LINEAR);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
compositor_set_visible(compositor, pair.second.element_id, false);
|
|
||||||
compositor_set_alpha(compositor, pair.second.element_id, 0.0f);
|
|
||||||
}
|
|
||||||
pair.second.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ch->current_sprite = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
void move_character(const char* name, int x, int y, TransitionType transition, float duration_ms) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
uint32_t id = get_active_element_id(ch);
|
|
||||||
if (id == 0) return;
|
|
||||||
|
|
||||||
ch->current_x = x;
|
|
||||||
ch->current_y = y;
|
|
||||||
float duration_sec = duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
if (transition == TRANS_SLIDE_LEFT || transition == TRANS_SLIDE_RIGHT) {
|
|
||||||
compositor_animate_move(compositor, id, x, y, duration_sec, EASE_IN_OUT_QUAD);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
compositor_set_position(compositor, id, x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void change_sprite(const char* name, const char* new_emotion) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
if (!ch) return;
|
|
||||||
|
|
||||||
if (!ch->current_sprite.empty()) {
|
|
||||||
auto old_it = ch->sprites.find(ch->current_sprite);
|
|
||||||
if (old_it != ch->sprites.end()) {
|
|
||||||
compositor_set_visible(compositor, old_it->second.element_id, false);
|
|
||||||
compositor_set_alpha(compositor, old_it->second.element_id, 0.0f);
|
|
||||||
old_it->second.visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto new_it = ch->sprites.find(new_emotion);
|
|
||||||
if (new_it != ch->sprites.end()) {
|
|
||||||
compositor_set_position(compositor, new_it->second.element_id,
|
|
||||||
ch->current_x, ch->current_y);
|
|
||||||
compositor_set_zorder(compositor, new_it->second.element_id, ch->current_z);
|
|
||||||
compositor_set_alpha(compositor, new_it->second.element_id, 1.0f);
|
|
||||||
compositor_set_visible(compositor, new_it->second.element_id, true);
|
|
||||||
new_it->second.visible = true;
|
|
||||||
ch->current_sprite = new_emotion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_z(const char* name, int z) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
uint32_t id = get_active_element_id(ch);
|
|
||||||
if (id == 0) return;
|
|
||||||
|
|
||||||
ch->current_z = z;
|
|
||||||
compositor_set_zorder(compositor, id, z);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_scale(const char* name, float scale, TransitionType transition, float duration_ms) {
|
|
||||||
Character* ch = get_character(name);
|
|
||||||
uint32_t id = get_active_element_id(ch);
|
|
||||||
if (id == 0) return;
|
|
||||||
|
|
||||||
ch->current_scale = scale;
|
|
||||||
float duration_sec = duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
if (transition == TRANS_FADE) {
|
|
||||||
compositor_animate_scale(compositor, id, scale, duration_sec, EASE_IN_OUT_QUAD);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
compositor_set_scale(compositor, id, scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
~CharacterManager() {
|
|
||||||
for (auto& pair : characters) {
|
|
||||||
for (auto& sprite : pair.second.sprites) {
|
|
||||||
image_free(sprite.second.image_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// COMMAND EXECUTOR
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
class CommandExecutor {
|
|
||||||
private:
|
|
||||||
CharacterManager* char_manager;
|
|
||||||
compositor_t* compositor;
|
|
||||||
text_t* name_text;
|
|
||||||
text_t* dialogue_text;
|
|
||||||
uint32_t name_id;
|
|
||||||
uint32_t dialogue_id;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CommandExecutor(CharacterManager* cm, compositor_t* comp,
|
|
||||||
text_t* nt, text_t* dt, uint32_t nid, uint32_t did)
|
|
||||||
: char_manager(cm), compositor(comp), name_text(nt), dialogue_text(dt),
|
|
||||||
name_id(nid), dialogue_id(did) {
|
|
||||||
}
|
|
||||||
|
|
||||||
float execute(const Command* cmd) {
|
|
||||||
switch (cmd->type) {
|
|
||||||
case CMD_SHOW:
|
|
||||||
char_manager->show_character(cmd->show.character, cmd->show.x, cmd->show.y,
|
|
||||||
cmd->show.z, cmd->show.sprite,
|
|
||||||
cmd->show.transition, cmd->show.duration_ms);
|
|
||||||
return cmd->show.duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
case CMD_HIDE:
|
|
||||||
char_manager->hide_character(cmd->hide.character,
|
|
||||||
cmd->hide.transition, cmd->hide.duration_ms);
|
|
||||||
return cmd->hide.duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
case CMD_MOVE:
|
|
||||||
char_manager->move_character(cmd->move.character, cmd->move.x, cmd->move.y,
|
|
||||||
cmd->move.transition, cmd->move.duration_ms);
|
|
||||||
return cmd->move.duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
case CMD_CHANGE_SPRITE:
|
|
||||||
char_manager->change_sprite(cmd->change_sprite.character, cmd->change_sprite.sprite);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMD_SET_Z:
|
|
||||||
char_manager->set_z(cmd->set_z.character, cmd->set_z.z);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMD_SET_SCALE:
|
|
||||||
char_manager->set_scale(cmd->set_scale.character, cmd->set_scale.scale,
|
|
||||||
cmd->set_scale.transition, cmd->set_scale.duration_ms);
|
|
||||||
return cmd->set_scale.duration_ms / 1000.0f;
|
|
||||||
|
|
||||||
case CMD_SPEAK:
|
|
||||||
if (cmd->speak.speaker) {
|
|
||||||
text_push(name_text, cmd->speak.speaker, 34.0f, 0.0f, "assets/Vera.ttf", true);
|
|
||||||
}
|
|
||||||
if (cmd->speak.text) {
|
|
||||||
text_push(dialogue_text, cmd->speak.text, 24.0f, cmd->speak.text_speed, "assets/Vera.ttf", true);
|
|
||||||
}
|
|
||||||
compositor_mark_content_dirty(compositor, name_id);
|
|
||||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMD_CLEAR:
|
|
||||||
text_clear(name_text);
|
|
||||||
text_clear(dialogue_text);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case CMD_WAIT:
|
|
||||||
case CMD_BACKGROUND:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// MAIN PROGRAM
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
enum AppState {
|
|
||||||
STATE_IDLE,
|
|
||||||
STATE_ANIMATING,
|
|
||||||
STATE_WAITING_FOR_INPUT
|
|
||||||
};
|
|
||||||
|
|
||||||
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_HEIGHT = 600;
|
||||||
|
const int CHAR_Y_OFFSET = 140;
|
||||||
|
const int POS_Y = HEIGHT - CHAR_HEIGHT - CHAR_Y_OFFSET;
|
||||||
|
|
||||||
window_t* window = window_create(WIDTH, HEIGHT, "Visual Novel Engine");
|
window_t* window = window_create(WIDTH, HEIGHT, u8"The Obwarzanek Narrative Engine");
|
||||||
if (!window) {
|
if (!window) {
|
||||||
printf("Failed to create window\n");
|
printf("Failed to create window\n");
|
||||||
return 1;
|
return 1;
|
||||||
@@ -549,196 +35,54 @@ int main() {
|
|||||||
compositor_set_alpha(compositor, bg_id, 1.0f);
|
compositor_set_alpha(compositor, bg_id, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
const int CHAR_WIDTH = 400;
|
|
||||||
const int CHAR_HEIGHT = 600;
|
|
||||||
const int CHAR_Y_OFFSET = 140;
|
|
||||||
const int POS_Y = HEIGHT - CHAR_HEIGHT - CHAR_Y_OFFSET;
|
|
||||||
|
|
||||||
CharacterManager char_manager(compositor, WIDTH);
|
|
||||||
|
|
||||||
char_manager.add_character("maksw", CHAR_WIDTH, CHAR_HEIGHT);
|
|
||||||
char_manager.add_character("wikor", CHAR_WIDTH, CHAR_HEIGHT);
|
|
||||||
|
|
||||||
char_manager.load_sprite("maksw", "neutral", "assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT);
|
|
||||||
char_manager.load_sprite("wikor", "neutral", "assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT);
|
|
||||||
|
|
||||||
uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180);
|
uint32_t* textbox = image_load_resized("assets/textbox.png", WIDTH, 180);
|
||||||
if (textbox) {
|
if (textbox) {
|
||||||
uint32_t box_id = compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3);
|
compositor_insert(compositor, textbox, WIDTH, 180, 0, HEIGHT - 180, 3);
|
||||||
compositor_set_alpha(compositor, box_id, 1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text_t* name_text = text_init(WIDTH, HEIGHT);
|
uint32_t* maksw_happy = image_load_resized("assets/maksw.png", CHAR_WIDTH, CHAR_HEIGHT);
|
||||||
|
uint32_t maksw_happy_id = compositor_insert(compositor, maksw_happy,
|
||||||
|
CHAR_WIDTH, CHAR_HEIGHT, -600, POS_Y, 1);
|
||||||
|
|
||||||
|
uint32_t* maksw_neutral = image_load_resized("assets/maksw2.png", CHAR_WIDTH, CHAR_HEIGHT);
|
||||||
|
uint32_t maksw_neutral_id = compositor_insert(compositor, maksw_neutral,
|
||||||
|
CHAR_WIDTH, CHAR_HEIGHT, 60, POS_Y, 1);
|
||||||
|
compositor_set_visible(compositor, maksw_neutral_id, false);
|
||||||
|
|
||||||
|
uint32_t* wikor = image_load_resized("assets/wikor.png", CHAR_WIDTH, CHAR_HEIGHT);
|
||||||
|
uint32_t wikor_id = compositor_insert(compositor, wikor,
|
||||||
|
CHAR_WIDTH, CHAR_HEIGHT, WIDTH - CHAR_WIDTH - 60, POS_Y, 2);
|
||||||
|
|
||||||
|
text_t* name_text = text_init(300, 100);
|
||||||
|
text_set_font(name_text, "assets/Aller_Rg.ttf", 40.0f);
|
||||||
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
|
uint32_t name_id = compositor_insert(compositor, text_getfb(name_text),
|
||||||
WIDTH, HEIGHT, 60, HEIGHT - 170, 4);
|
300, 100, 60, HEIGHT - 180, 4);
|
||||||
compositor_set_alpha(compositor, name_id, 1.0f);
|
|
||||||
|
|
||||||
text_t* dialogue_text = text_init_shared(name_text, WIDTH - 140, HEIGHT);
|
text_t* dialogue_text = text_init(WIDTH - 140, 160);
|
||||||
|
text_set_font(dialogue_text, "assets/Vera.ttf", 30.0f);
|
||||||
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
|
uint32_t dialogue_id = compositor_insert(compositor, text_getfb(dialogue_text),
|
||||||
WIDTH - 140, HEIGHT, 60, HEIGHT - 130, 4);
|
WIDTH - 140, 160, 60, HEIGHT - 130, 4);
|
||||||
compositor_set_alpha(compositor, dialogue_id, 1.0f);
|
|
||||||
|
|
||||||
// --- Scene Script ---
|
uint32_t* black = (uint32_t*)malloc(WIDTH * HEIGHT * sizeof(uint32_t));
|
||||||
Command scene_commands[] = {
|
for (int i = 0; i < WIDTH * HEIGHT; i++) {
|
||||||
// entrances
|
black[i] = 0xFF000000;
|
||||||
Show("maksw", "neutral", 100, POS_Y, 2, TRANS_SLIDE_LEFT, 500.0f, false),
|
}
|
||||||
Show("wikor", "neutral", 800, POS_Y, 1, TRANS_SLIDE_RIGHT, 500.0f, true),
|
uint32_t black_id = compositor_insert(compositor, black,
|
||||||
|
WIDTH, HEIGHT, 0, 0, 5);
|
||||||
|
compositor_set_alpha(compositor, black_id, 0.0f);
|
||||||
|
compositor_set_visible(compositor, black_id, false);
|
||||||
|
|
||||||
// wilczur starts (maksw)
|
compositor_animate_move(compositor, maksw_happy_id, 60, POS_Y, 1.0f, EASE_OUT_CUBIC);
|
||||||
SetScale("maksw", 1.05f, TRANS_FADE, 200.0f),
|
compositor_animate_scale(compositor, maksw_happy_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC, true);
|
||||||
SetZ("maksw", 3),
|
text_push(name_text, "maksw", 0.0f, true);
|
||||||
SetScale("wikor", 1.0f, TRANS_FADE, 200.0f),
|
text_push(dialogue_text,
|
||||||
SetZ("wikor", 1),
|
"Hey there! Welcome to the Obwarzanek Narrative Engine demo. "
|
||||||
|
"This is a simple visual novel engine showcasing text rendering, "
|
||||||
Speak(u8"wilczur", u8"Long day, huh? Feels like we ran a marathon we didn't sign up for."),
|
"image compositing, and basic animation features.",
|
||||||
Wait(),
|
30.0f, true);
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"I thought the report would close this morning. Guess not."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// wiktor responds (wikor)
|
|
||||||
SetScale("wikor", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("wikor", 3),
|
|
||||||
SetScale("maksw", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 1),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"You always underestimate bureaucracy's appetite for time."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"It eats minutes like some folks eat sweets."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// narrator (reset both scales)
|
|
||||||
//SetScale("maksw", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
//SetZ("maksw", 1),
|
|
||||||
SetScale("wikor", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"", u8"The lights in the corridor hummed, steady and indifferent."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// wilczur again
|
|
||||||
SetScale("maksw", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 3),
|
|
||||||
//SetScale("wikor", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
//SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"I keep telling myself 'five minutes' and then three episodes later it's midnight."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"Maybe I'm cursed to wait in dim rooms forever."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// wiktor
|
|
||||||
SetScale("wikor", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("wikor", 3),
|
|
||||||
SetScale("maksw", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 1),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"Or blessed with patience you haven't earned yet."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"Either way, keep the coffee coming and nobody gets dramatic."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// narrator
|
|
||||||
//SetScale("maksw", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
//SetZ("maksw", 1),
|
|
||||||
SetScale("wikor", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"", u8"A faint orange band slid behind the clouds like a secret smile."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// wilczur
|
|
||||||
SetScale("maksw", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 3),
|
|
||||||
//SetScale("wikor", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
//SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"Did you see that? A flare, or the heavens showing off."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"If it's a flare, somebody's throwing a party I wasn't invited to."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// wiktor
|
|
||||||
SetScale("wikor", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("wikor", 3),
|
|
||||||
SetScale("maksw", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 1),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"Not every bright thing is celebration. Could be dust, reflection."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"But reflection's a good guess — keeps us calmer than panic does."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// narrator
|
|
||||||
//SetScale("maksw", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
//SetZ("maksw", 1),
|
|
||||||
SetScale("wikor", 1.0f, TRANS_FADE, 150.0f),
|
|
||||||
SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"", u8"The distant rumble that followed was more felt than heard."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// final small exchange
|
|
||||||
SetScale("maksw", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 3),
|
|
||||||
//SetScale("wikor", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
//SetZ("wikor", 1),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"If the sky's putting on a show, at least the scenery's decent."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"wilczur", u8"Make a note: next time, bring proper snacks."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
SetScale("wikor", 1.05f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("wikor", 3),
|
|
||||||
SetScale("maksw", 1.0f, TRANS_FADE, 200.0f),
|
|
||||||
SetZ("maksw", 1),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"Noted. I'll smuggle provisions like a cunning admiral."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
Speak(u8"admirał wiktorov", u8"Till then, keep an eye on the horizon and your soda stash safe."),
|
|
||||||
Wait(),
|
|
||||||
Clear(),
|
|
||||||
|
|
||||||
// exits
|
|
||||||
Hide("maksw", TRANS_FADE, 500.0f, false),
|
|
||||||
Hide("wikor", TRANS_FADE, 500.0f, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
Scene scene = { scene_commands, sizeof(scene_commands) / sizeof(Command), 0 };
|
|
||||||
CommandExecutor executor(&char_manager, compositor, name_text, dialogue_text,
|
|
||||||
name_id, dialogue_id);
|
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
|
||||||
AppState current_state = STATE_IDLE;
|
|
||||||
AnimationTracker anim_tracker; // Track multiple animations
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -777,9 +121,6 @@ int main() {
|
|||||||
last_report_time = current_time; // Mark the current time as the new report start
|
last_report_time = current_time; // Mark the current time as the new report start
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update animation tracker
|
|
||||||
anim_tracker.update(delta_time_sec);
|
|
||||||
|
|
||||||
static bool mouse_was_down = false;
|
static bool mouse_was_down = false;
|
||||||
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;
|
bool mouse_clicked = mouse_is_down && !mouse_was_down;
|
||||||
@@ -790,74 +131,78 @@ int main() {
|
|||||||
bool key_pressed = key_is_down && !key_was_down;
|
bool key_pressed = key_is_down && !key_was_down;
|
||||||
key_was_down = key_is_down;
|
key_was_down = key_is_down;
|
||||||
|
|
||||||
bool advance_input = mouse_clicked || key_pressed;
|
static unsigned int lines = 0;
|
||||||
|
static bool dialog_finished = false;
|
||||||
|
|
||||||
// Check if animations finished
|
if (mouse_clicked || key_pressed) {
|
||||||
if (current_state == STATE_ANIMATING) {
|
|
||||||
if (!anim_tracker.has_active_animations()) {
|
|
||||||
current_state = STATE_IDLE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (advance_input) {
|
|
||||||
if (text_is_animating(dialogue_text)) {
|
if (text_is_animating(dialogue_text)) {
|
||||||
text_show_all_current(dialogue_text);
|
text_show_all_current(dialogue_text);
|
||||||
}
|
}
|
||||||
else if (current_state == STATE_WAITING_FOR_INPUT) {
|
else {
|
||||||
current_state = STATE_IDLE;
|
if (lines == 0) {
|
||||||
|
// line 2
|
||||||
|
compositor_animation_complete_all(compositor);
|
||||||
|
compositor_animate_scale(compositor, maksw_happy_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC);
|
||||||
|
compositor_animate_scale(compositor, wikor_id, 1.1f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
text_clear(dialogue_text);
|
||||||
|
text_clear(name_text);
|
||||||
|
text_push(name_text, "wikor", 0.0f, true);
|
||||||
|
text_push(dialogue_text, "Hello to you too, maksw..", 30.0f, true);
|
||||||
|
lines++;
|
||||||
}
|
}
|
||||||
else if (current_state == STATE_ANIMATING) {
|
else if (lines == 1) {
|
||||||
// Skip all remaining animations
|
// line 3
|
||||||
anim_tracker.clear();
|
compositor_animation_complete_all(compositor);
|
||||||
current_state = STATE_IDLE;
|
compositor_animate_scale(compositor, wikor_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC);
|
||||||
|
compositor_set_visible(compositor, maksw_happy_id, false);
|
||||||
|
compositor_set_visible(compositor, maksw_neutral_id, true);
|
||||||
|
compositor_animate_scale(compositor, maksw_neutral_id, 1.05f, 0.3f, EASE_IN_OUT_CUBIC);
|
||||||
|
text_clear(dialogue_text);
|
||||||
|
text_clear(name_text);
|
||||||
|
text_push(name_text, "maksw", 0.0f, true);
|
||||||
|
text_push(dialogue_text, "why so.. serious?", 30.0f, true);
|
||||||
|
lines++;
|
||||||
}
|
}
|
||||||
|
else if (lines == 2) {
|
||||||
|
// fade out
|
||||||
|
compositor_animation_complete_all(compositor);
|
||||||
|
compositor_animate_scale(compositor, maksw_neutral_id, 1.0f, 0.2f, EASE_IN_OUT_CUBIC);
|
||||||
|
compositor_set_visible(compositor, black_id, true);
|
||||||
|
compositor_animate_fade(compositor, black_id, 1.0f, 1.5f, EASE_IN_OUT_CUBIC);
|
||||||
|
dialog_finished = true;
|
||||||
|
lines++;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// Process commands if idle
|
|
||||||
while (current_state == STATE_IDLE) {
|
|
||||||
if (!scene.has_next()) {
|
|
||||||
running = false;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Command* cmd = scene.current();
|
|
||||||
float duration = executor.execute(cmd);
|
|
||||||
|
|
||||||
// NEW: Add animation to tracker
|
|
||||||
if (duration > 0.0f) {
|
|
||||||
anim_tracker.add_animation(duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.next();
|
|
||||||
|
|
||||||
// Check blocking flag
|
|
||||||
if (cmd->blocking) {
|
|
||||||
if (cmd->type == CMD_WAIT) {
|
|
||||||
current_state = STATE_WAITING_FOR_INPUT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (anim_tracker.has_active_animations()) {
|
|
||||||
current_state = STATE_ANIMATING;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If blocking but no animations, continue to next command
|
|
||||||
}
|
|
||||||
// If non-blocking, loop continues immediately to next command
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compositor_tick(compositor, delta_time_sec);
|
compositor_tick(compositor, delta_time_sec);
|
||||||
|
|
||||||
text_nextframe(name_text, delta_time_sec);
|
if (dialog_finished) {
|
||||||
text_nextframe(dialogue_text, delta_time_sec);
|
compositor_mark_content_dirty(compositor, black_id);
|
||||||
|
if (compositor_is_element_animating(compositor, black_id) == false) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text_nextframe(name_text, delta_time_sec))
|
||||||
compositor_mark_content_dirty(compositor, name_id);
|
compositor_mark_content_dirty(compositor, name_id);
|
||||||
|
if (text_nextframe(dialogue_text, delta_time_sec))
|
||||||
compositor_mark_content_dirty(compositor, dialogue_id);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maksw_happy) image_free(maksw_happy);
|
||||||
|
if (maksw_neutral) image_free(maksw_neutral);
|
||||||
|
if (wikor) image_free(wikor);
|
||||||
|
|
||||||
|
if (black) free(black);
|
||||||
if (bg) image_free(bg);
|
if (bg) image_free(bg);
|
||||||
if (textbox) image_free(textbox);
|
if (textbox) image_free(textbox);
|
||||||
|
|
||||||
text_destroy(name_text);
|
text_destroy(name_text);
|
||||||
text_destroy(dialogue_text);
|
text_destroy(dialogue_text);
|
||||||
compositor_destroy(compositor);
|
compositor_destroy(compositor);
|
||||||
|
|||||||
+200
-110
@@ -1,6 +1,3 @@
|
|||||||
#include <text.h>
|
|
||||||
|
|
||||||
// C/C++ Standard Libraries
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -11,9 +8,17 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream> // For std::clamp
|
#include <iostream>
|
||||||
|
#include <text.h>
|
||||||
|
|
||||||
|
// SSE2 intrinsics for x86/x64
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86)
|
||||||
|
#define USE_SSE2 1
|
||||||
|
#include <immintrin.h>
|
||||||
|
#else
|
||||||
|
#define USE_SSE2 0
|
||||||
|
#endif
|
||||||
|
|
||||||
// Embed stb_truetype implementation
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#include "stb_truetype.h"
|
#include "stb_truetype.h"
|
||||||
|
|
||||||
@@ -157,6 +162,10 @@ struct text_t {
|
|||||||
shared_cache_t* cache;
|
shared_cache_t* cache;
|
||||||
bool owns_cache; // True if this text_t owns the cache and should delete it
|
bool owns_cache; // True if this text_t owns the cache and should delete it
|
||||||
|
|
||||||
|
// Default font settings
|
||||||
|
std::string default_font_path;
|
||||||
|
float default_font_size = 16.0f;
|
||||||
|
|
||||||
// Rendering State (per-instance)
|
// Rendering State (per-instance)
|
||||||
std::vector<text_params_t> text_queue;
|
std::vector<text_params_t> text_queue;
|
||||||
int queue_index = 0;
|
int queue_index = 0;
|
||||||
@@ -445,6 +454,61 @@ static uint32_t alpha_blend(uint32_t dest, uint32_t src) {
|
|||||||
return (out_a << 24) | (out_r << 16) | (out_g << 8) | out_b;
|
return (out_a << 24) | (out_r << 16) | (out_g << 8) | out_b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if USE_SSE2
|
||||||
|
// SSE2-accelerated alpha blending for 4 pixels at once
|
||||||
|
static inline void alpha_blend_sse2(uint32_t* dest, __m128i src_pixels, __m128i src_rgb_mask) {
|
||||||
|
// Load 4 destination pixels
|
||||||
|
__m128i dest_pixels = _mm_loadu_si128((__m128i*)dest);
|
||||||
|
|
||||||
|
// Extract alpha channels
|
||||||
|
__m128i src_a = _mm_srli_epi32(src_pixels, 24);
|
||||||
|
__m128i dest_a = _mm_srli_epi32(dest_pixels, 24);
|
||||||
|
|
||||||
|
// Check for trivial cases per pixel would be complex, so we just blend
|
||||||
|
// For optimization, you could check if all alphas are 0 or 255
|
||||||
|
|
||||||
|
// Unpack to 16-bit for precision
|
||||||
|
__m128i zero = _mm_setzero_si128();
|
||||||
|
|
||||||
|
// Extract RGBA components (low 8 pixels as 16-bit)
|
||||||
|
__m128i src_lo = _mm_unpacklo_epi8(src_pixels, zero);
|
||||||
|
__m128i src_hi = _mm_unpackhi_epi8(src_pixels, zero);
|
||||||
|
__m128i dest_lo = _mm_unpacklo_epi8(dest_pixels, zero);
|
||||||
|
__m128i dest_hi = _mm_unpackhi_epi8(dest_pixels, zero);
|
||||||
|
|
||||||
|
// Replicate alpha to all channels
|
||||||
|
__m128i src_a_lo = _mm_shufflelo_epi16(src_lo, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
src_a_lo = _mm_shufflehi_epi16(src_a_lo, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
__m128i src_a_hi = _mm_shufflelo_epi16(src_hi, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
src_a_hi = _mm_shufflehi_epi16(src_a_hi, _MM_SHUFFLE(3, 3, 3, 3));
|
||||||
|
|
||||||
|
// Compute: src * src_a
|
||||||
|
__m128i src_mul_lo = _mm_mullo_epi16(src_lo, src_a_lo);
|
||||||
|
__m128i src_mul_hi = _mm_mullo_epi16(src_hi, src_a_hi);
|
||||||
|
|
||||||
|
// Compute: 255 - src_a
|
||||||
|
__m128i inv_src_a_lo = _mm_sub_epi16(_mm_set1_epi16(255), src_a_lo);
|
||||||
|
__m128i inv_src_a_hi = _mm_sub_epi16(_mm_set1_epi16(255), src_a_hi);
|
||||||
|
|
||||||
|
// Compute: dest * (255 - src_a)
|
||||||
|
__m128i dest_mul_lo = _mm_mullo_epi16(dest_lo, inv_src_a_lo);
|
||||||
|
__m128i dest_mul_hi = _mm_mullo_epi16(dest_hi, inv_src_a_hi);
|
||||||
|
|
||||||
|
// Add and divide by 255 (approximate with >> 8)
|
||||||
|
__m128i result_lo = _mm_add_epi16(src_mul_lo, dest_mul_lo);
|
||||||
|
__m128i result_hi = _mm_add_epi16(src_mul_hi, dest_mul_hi);
|
||||||
|
|
||||||
|
result_lo = _mm_srli_epi16(result_lo, 8);
|
||||||
|
result_hi = _mm_srli_epi16(result_hi, 8);
|
||||||
|
|
||||||
|
// Pack back to 8-bit
|
||||||
|
__m128i result = _mm_packus_epi16(result_lo, result_hi);
|
||||||
|
|
||||||
|
// Store result
|
||||||
|
_mm_storeu_si128((__m128i*)dest, result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Draws a glyph using its cached SDF bitmap.
|
// Draws a glyph using its cached SDF bitmap.
|
||||||
static void draw_glyph(text_t* text,
|
static void draw_glyph(text_t* text,
|
||||||
int x, int y,
|
int x, int y,
|
||||||
@@ -461,16 +525,14 @@ static void draw_glyph(text_t* text,
|
|||||||
int y1 = y + glyph->h;
|
int y1 = y + glyph->h;
|
||||||
|
|
||||||
// --- SDF Parameters ---
|
// --- SDF Parameters ---
|
||||||
// These must match the values used in get_glyph
|
|
||||||
const float padding_percent = 0.15f;
|
const float padding_percent = 0.15f;
|
||||||
const float onedge_value = 128.0f; // 0-255 range midpoint
|
const float onedge_value = 128.0f;
|
||||||
float padding = std::max(1.0f, size * padding_percent);
|
float padding = std::max(1.0f, size * padding_percent);
|
||||||
float pixel_dist_scale = onedge_value / padding;
|
float pixel_dist_scale = onedge_value / padding;
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|
||||||
// Calculate outline width and anti-alias smoothing
|
|
||||||
float outline_width = std::max(1.0f, size * 0.05f);
|
float outline_width = std::max(1.0f, size * 0.05f);
|
||||||
float aa_width = 0.75f; // Anti-aliasing width in pixels
|
float aa_width = 0.75f;
|
||||||
|
|
||||||
// Clip to framebuffer
|
// Clip to framebuffer
|
||||||
int gx0 = (x0 < 0) ? -x0 : 0;
|
int gx0 = (x0 < 0) ? -x0 : 0;
|
||||||
@@ -481,41 +543,79 @@ static void draw_glyph(text_t* text,
|
|||||||
uint32_t color_rgb = color & 0x00FFFFFF;
|
uint32_t color_rgb = color & 0x00FFFFFF;
|
||||||
|
|
||||||
for (int gy = gy0; gy < gy1; ++gy) {
|
for (int gy = gy0; gy < gy1; ++gy) {
|
||||||
for (int gx = gx0; gx < gx1; ++gx) {
|
int fb_y = y0 + gy;
|
||||||
|
uint32_t* dest_row = &text->framebuffer[fb_y * text->width + x0];
|
||||||
|
|
||||||
// 1. Get SDF value (0-255)
|
#if USE_SSE2
|
||||||
uint8_t sdf_raw = glyph->data[gy * glyph->w + gx];
|
// Process 4 pixels at a time with SSE2
|
||||||
|
int gx = gx0;
|
||||||
|
__m128i color_rgb_vec = _mm_set1_epi32(color_rgb);
|
||||||
|
|
||||||
// 2. Convert to signed pixel distance (Value > 0 is INSIDE, < 0 is OUTSIDE)
|
for (; gx + 3 < gx1; gx += 4) {
|
||||||
float dist = (sdf_raw - onedge_value) / pixel_dist_scale;
|
// Process 4 pixels
|
||||||
|
uint8_t sdf_vals[4];
|
||||||
|
float alphas[4];
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
sdf_vals[i] = glyph->data[gy * glyph->w + (gx + i)];
|
||||||
|
float dist = (sdf_vals[i] - onedge_value) / pixel_dist_scale;
|
||||||
|
|
||||||
float alpha = 0.0f;
|
|
||||||
if (outline) {
|
if (outline) {
|
||||||
// Render the outline
|
|
||||||
// 1. Inner mask (the fill)
|
|
||||||
float edge_inner_0 = -aa_width;
|
float edge_inner_0 = -aa_width;
|
||||||
float edge_inner_1 = +aa_width;
|
float edge_inner_1 = +aa_width;
|
||||||
float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist);
|
float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist);
|
||||||
|
|
||||||
// 2. Outer mask (the fill + outline)
|
float edge_outer_0 = -outline_width - aa_width;
|
||||||
|
float edge_outer_1 = -outline_width + aa_width;
|
||||||
|
float mask_outer = smoothstep(edge_outer_0, edge_outer_1, dist);
|
||||||
|
|
||||||
|
alphas[i] = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f);
|
||||||
|
} else {
|
||||||
|
alphas[i] = smoothstep(-aa_width, aa_width, dist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build source pixels
|
||||||
|
uint32_t src_pixels[4];
|
||||||
|
bool any_visible = false;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uint32_t alpha_byte = (uint32_t)(alphas[i] * 255.0f);
|
||||||
|
src_pixels[i] = (alpha_byte << 24) | color_rgb;
|
||||||
|
if (alpha_byte > 0) any_visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (any_visible) {
|
||||||
|
__m128i src_vec = _mm_loadu_si128((__m128i*)src_pixels);
|
||||||
|
alpha_blend_sse2(&dest_row[gx], src_vec, color_rgb_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle remaining pixels
|
||||||
|
for (; gx < gx1; ++gx) {
|
||||||
|
#else
|
||||||
|
for (int gx = gx0; gx < gx1; ++gx) {
|
||||||
|
#endif
|
||||||
|
uint8_t sdf_raw = glyph->data[gy * glyph->w + gx];
|
||||||
|
float dist = (sdf_raw - onedge_value) / pixel_dist_scale;
|
||||||
|
|
||||||
|
float alpha = 0.0f;
|
||||||
|
if (outline) {
|
||||||
|
float edge_inner_0 = -aa_width;
|
||||||
|
float edge_inner_1 = +aa_width;
|
||||||
|
float mask_inner = smoothstep(edge_inner_0, edge_inner_1, dist);
|
||||||
|
|
||||||
float edge_outer_0 = -outline_width - aa_width;
|
float edge_outer_0 = -outline_width - aa_width;
|
||||||
float edge_outer_1 = -outline_width + aa_width;
|
float edge_outer_1 = -outline_width + aa_width;
|
||||||
float mask_outer = smoothstep(edge_outer_0, edge_outer_1, dist);
|
float mask_outer = smoothstep(edge_outer_0, edge_outer_1, dist);
|
||||||
|
|
||||||
alpha = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f);
|
alpha = std::clamp(mask_outer - mask_inner, 0.0f, 1.0f);
|
||||||
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Render the fill
|
|
||||||
// Use smoothstep for anti-aliasing around the 0-edge
|
|
||||||
alpha = smoothstep(-aa_width, aa_width, dist);
|
alpha = smoothstep(-aa_width, aa_width, dist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alpha == 0.0f) continue;
|
if (alpha == 0.0f) continue;
|
||||||
|
|
||||||
int fb_x = x0 + gx;
|
int fb_x = x0 + gx;
|
||||||
int fb_y = y0 + gy;
|
|
||||||
|
|
||||||
uint32_t* dest_pixel = &text->framebuffer[fb_y * text->width + fb_x];
|
uint32_t* dest_pixel = &text->framebuffer[fb_y * text->width + fb_x];
|
||||||
uint32_t src_pixel = ((uint32_t)(alpha * 255.0f) << 24) | color_rgb;
|
uint32_t src_pixel = ((uint32_t)(alpha * 255.0f) << 24) | color_rgb;
|
||||||
|
|
||||||
@@ -524,7 +624,6 @@ static void draw_glyph(text_t* text,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Public C-Style API ---
|
// --- Public C-Style API ---
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -617,24 +716,43 @@ extern "C" {
|
|||||||
delete text;
|
delete text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Font Configuration ---
|
||||||
|
|
||||||
|
// Sets the default font path and size to be used by subsequent text_push calls.
|
||||||
|
// Pass NULL for font_path to keep the current default font.
|
||||||
|
// Pass size <= 0 to keep the current default size.
|
||||||
|
void text_set_font(text_t* text, const char* font_path, float size) {
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
if (font_path != nullptr) {
|
||||||
|
text->default_font_path = font_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 0.0f) {
|
||||||
|
text->default_font_size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Text Queue & State ---
|
// --- Text Queue & State ---
|
||||||
|
|
||||||
// Adds a block of text to the rendering queue.
|
// Adds a block of text to the rendering queue.
|
||||||
void text_push(text_t* text,
|
void text_push(text_t* text,
|
||||||
const char* str,
|
const char* str,
|
||||||
float size,
|
|
||||||
float speed,
|
float speed,
|
||||||
const char* font_path,
|
|
||||||
bool outline)
|
bool outline)
|
||||||
{
|
{
|
||||||
if (!text || !str) return;
|
if (!text || !str) return;
|
||||||
|
|
||||||
text_params_t p;
|
text_params_t p;
|
||||||
p.text = str;
|
p.text = str;
|
||||||
p.size = std::max(1.0f, size);
|
|
||||||
|
p.size = text->default_font_size;
|
||||||
|
|
||||||
p.speed = speed;
|
p.speed = speed;
|
||||||
p.outline = outline;
|
p.outline = outline;
|
||||||
p.font_path = (font_path ? font_path : "");
|
|
||||||
|
p.font_path = text->default_font_path;
|
||||||
|
|
||||||
p.lines_computed = false; // Mark for precomputation
|
p.lines_computed = false; // Mark for precomputation
|
||||||
|
|
||||||
text->text_queue.push_back(std::move(p));
|
text->text_queue.push_back(std::move(p));
|
||||||
@@ -643,7 +761,23 @@ extern "C" {
|
|||||||
// Clears the framebuffer and resets the text queue and all state.
|
// Clears the framebuffer and resets the text queue and all state.
|
||||||
void text_clear(text_t* text) {
|
void text_clear(text_t* text) {
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
#if USE_SSE2
|
||||||
|
// Clear framebuffer with SSE2
|
||||||
|
size_t total_pixels = text->width * text->height;
|
||||||
|
__m128i zero = _mm_setzero_si128();
|
||||||
|
|
||||||
|
size_t i = 0;
|
||||||
|
for (; i + 3 < total_pixels; i += 4) {
|
||||||
|
_mm_storeu_si128((__m128i*) & text->framebuffer[i], zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear remaining pixels
|
||||||
|
for (; i < total_pixels; i++) {
|
||||||
|
text->framebuffer[i] = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t));
|
memset(text->framebuffer, 0, text->width * text->height * sizeof(uint32_t));
|
||||||
|
#endif
|
||||||
text->current_x = text->padding_x;
|
text->current_x = text->padding_x;
|
||||||
text->current_y = text->padding_y;
|
text->current_y = text->padding_y;
|
||||||
text->text_queue.clear();
|
text->text_queue.clear();
|
||||||
@@ -673,30 +807,25 @@ extern "C" {
|
|||||||
// Advances the text animation and renders new characters.
|
// Advances the text animation and renders new characters.
|
||||||
// deltatime is the time elapsed since the last frame (in seconds).
|
// deltatime is the time elapsed since the last frame (in seconds).
|
||||||
bool text_nextframe(text_t* text, float deltatime) {
|
bool text_nextframe(text_t* text, float deltatime) {
|
||||||
if (!text) return true;
|
if (!text) return false;
|
||||||
if (text->queue_index >= text->text_queue.size()) {
|
if (text->queue_index >= text->text_queue.size()) {
|
||||||
return true; // Nothing to do
|
return false; // Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
text_params_t* params = &text->text_queue[text->queue_index];
|
text_params_t* params = &text->text_queue[text->queue_index];
|
||||||
|
|
||||||
// Load font
|
// Load font if needed
|
||||||
if (!params->font_path.empty()) {
|
if (!params->font_path.empty()) {
|
||||||
cached_font_t* font = load_font(text, params->font_path);
|
cached_font_t* font = load_font(text, params->font_path);
|
||||||
if (font) {
|
if (font) text->current_font = font;
|
||||||
text->current_font = font;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!text->current_font) {
|
if (!text->current_font) {
|
||||||
if (text->cache->font_cache.empty()) {
|
if (text->cache->font_cache.empty()) return false;
|
||||||
fprintf(stderr, "Error: text_nextframe() called with no valid font loaded.\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
text->current_font = &text->cache->font_cache.begin()->second;
|
text->current_font = &text->cache->font_cache.begin()->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate layout on first frame
|
// Precompute layout
|
||||||
precompute_line_breaks(text, params);
|
precompute_line_breaks(text, params);
|
||||||
|
|
||||||
const stbtt_fontinfo* info = &text->current_font->info;
|
const stbtt_fontinfo* info = &text->current_font->info;
|
||||||
@@ -705,100 +834,76 @@ extern "C" {
|
|||||||
stbtt_GetFontVMetrics(info, &ascent, &descent, &line_gap);
|
stbtt_GetFontVMetrics(info, &ascent, &descent, &line_gap);
|
||||||
float baseline = ascent * scale;
|
float baseline = ascent * scale;
|
||||||
|
|
||||||
// --- Update animation progress using deltatime ---
|
const size_t total_cp = utf8_count_codepoints(params->text);
|
||||||
size_t total_cp = utf8_count_codepoints(params->text);
|
|
||||||
|
bool did_draw = false;
|
||||||
|
|
||||||
|
// Update animation progress
|
||||||
if (params->speed > 0.0f && text->char_index < (int)total_cp) {
|
if (params->speed > 0.0f && text->char_index < (int)total_cp) {
|
||||||
// The animation speed (params->speed) is now characters per second.
|
|
||||||
// Multiply by deltatime to get the progress to advance this frame.
|
|
||||||
text->char_progress += params->speed * deltatime;
|
text->char_progress += params->speed * deltatime;
|
||||||
|
int new_index = static_cast<int>(text->char_progress);
|
||||||
// Only update char_index if it has advanced past the current index.
|
if (new_index > text->char_index) {
|
||||||
// This prevents the animation from advancing multiple characters in a single frame
|
text->char_index = std::min(new_index, (int)total_cp);
|
||||||
// if the deltatime is large, but still allows continuous float progress.
|
did_draw = true; // Will draw new characters
|
||||||
int new_char_index = static_cast<int>(text->char_progress);
|
|
||||||
|
|
||||||
// We only want to draw characters *up to* the new index, but we
|
|
||||||
// still need to check if the new index is greater than the last frame's index.
|
|
||||||
if (new_char_index > text->char_index) {
|
|
||||||
text->char_index = new_char_index;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (params->speed <= 0.0f) {
|
else if (params->speed <= 0.0f) {
|
||||||
|
if (text->char_index < (int)total_cp) {
|
||||||
text->char_index = (int)total_cp;
|
text->char_index = (int)total_cp;
|
||||||
|
did_draw = true; // Draw all instantly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Cap char_index to the total number of codepoints
|
|
||||||
text->char_index = (int)std::min((size_t)text->char_index, total_cp);
|
|
||||||
|
|
||||||
// Only draw NEW characters
|
// Draw only new characters
|
||||||
size_t draw_start = text->last_rendered_char; // cp index
|
size_t draw_start = text->last_rendered_char;
|
||||||
size_t draw_end = text->char_index; // cp index
|
size_t draw_end = text->char_index;
|
||||||
|
|
||||||
// Check if any new characters should be drawn
|
|
||||||
if (draw_start < draw_end) {
|
if (draw_start < draw_end) {
|
||||||
// Drawing loop - only for NEW characters
|
|
||||||
for (const auto& line : params->lines) {
|
for (const auto& line : params->lines) {
|
||||||
// Skip lines that are completely rendered
|
if (line.end_cp_index <= draw_start) continue;
|
||||||
if (line.end_cp_index <= draw_start) {
|
if (line.start_cp_index >= draw_end) break;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip lines that haven't started yet
|
|
||||||
if (line.start_cp_index >= draw_end) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
text->current_x = text->padding_x;
|
text->current_x = text->padding_x;
|
||||||
text->current_y = line.y_offset;
|
text->current_y = line.y_offset;
|
||||||
|
|
||||||
// Calculate x position up to where we need to draw
|
// Advance current_x to draw_start
|
||||||
// Iterate through the line bytes and sum advances for cps before draw_start
|
|
||||||
size_t byte_i = line.start_byte_index;
|
size_t byte_i = line.start_byte_index;
|
||||||
size_t cp_i = line.start_cp_index;
|
size_t cp_i = line.start_cp_index;
|
||||||
|
|
||||||
while (byte_i < line.end_byte_index && cp_i < draw_start) {
|
while (byte_i < line.end_byte_index && cp_i < draw_start) {
|
||||||
uint32_t cp = utf8_next(params->text, byte_i);
|
uint32_t cp = utf8_next(params->text, byte_i);
|
||||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||||
if (glyph) {
|
if (glyph) text->current_x += glyph->advance;
|
||||||
text->current_x += glyph->advance;
|
|
||||||
}
|
|
||||||
++cp_i;
|
++cp_i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw only NEW characters on this line
|
// Draw new characters
|
||||||
byte_i = line.start_byte_index;
|
byte_i = line.start_byte_index;
|
||||||
cp_i = line.start_cp_index;
|
cp_i = line.start_cp_index;
|
||||||
|
|
||||||
// advance byte_i to the first drawable codepoint (max(line.start_cp_index, draw_start))
|
|
||||||
while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) {
|
while (byte_i < line.end_byte_index && cp_i < std::max(line.start_cp_index, draw_start)) {
|
||||||
utf8_next(params->text, byte_i);
|
utf8_next(params->text, byte_i);
|
||||||
++cp_i;
|
++cp_i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now iterate and draw until cp reaches min(line.end_cp_index, draw_end)
|
|
||||||
while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) {
|
while (byte_i < line.end_byte_index && cp_i < std::min(line.end_cp_index, draw_end)) {
|
||||||
uint32_t cp = utf8_next(params->text, byte_i);
|
uint32_t cp = utf8_next(params->text, byte_i);
|
||||||
if (cp == (uint32_t)'\n') { ++cp_i; continue; }
|
if (cp == (uint32_t)'\n') { ++cp_i; continue; }
|
||||||
|
|
||||||
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
const cached_glyph_t* glyph = get_glyph(text, cp, params->size);
|
||||||
if (!glyph) {
|
if (!glyph) { ++cp_i; continue; }
|
||||||
++cp_i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x_base = static_cast<int>(text->current_x + glyph->xoff);
|
int x_base = static_cast<int>(text->current_x + glyph->xoff);
|
||||||
int y_base = static_cast<int>(text->current_y + baseline + glyph->yoff);
|
int y_base = static_cast<int>(text->current_y + baseline + glyph->yoff);
|
||||||
|
|
||||||
if (params->outline) {
|
if (params->outline) draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true);
|
||||||
draw_glyph(text, x_base, y_base, params->color_outline, glyph, params->size, true);
|
|
||||||
}
|
|
||||||
draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false);
|
draw_glyph(text, x_base, y_base, params->color_main, glyph, params->size, false);
|
||||||
|
|
||||||
text->current_x += glyph->advance;
|
text->current_x += glyph->advance;
|
||||||
++cp_i;
|
++cp_i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update tracking only if we drew something new
|
|
||||||
text->last_rendered_char = (int)text->char_index;
|
text->last_rendered_char = (int)text->char_index;
|
||||||
|
did_draw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle block transition
|
// Handle block transition
|
||||||
@@ -806,29 +911,14 @@ extern "C" {
|
|||||||
text->queue_index++;
|
text->queue_index++;
|
||||||
text->char_index = 0;
|
text->char_index = 0;
|
||||||
text->char_progress = 0.0f;
|
text->char_progress = 0.0f;
|
||||||
text->last_rendered_char = 0; // Reset for next block
|
|
||||||
// Only advance Y if there was a previous block rendered (to prevent double-spacing on first block)
|
|
||||||
if (text->queue_index > 1) {
|
|
||||||
text->current_y += text->last_line_height; // Advance y-pos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if queue is finished
|
|
||||||
if (text->queue_index >= text->text_queue.size()) {
|
|
||||||
text->text_queue.clear();
|
|
||||||
text->queue_index = 0;
|
|
||||||
text->char_index = 0;
|
|
||||||
text->char_progress = 0.0f;
|
|
||||||
text->last_rendered_char = 0;
|
text->last_rendered_char = 0;
|
||||||
|
if (text->queue_index > 1) text->current_y += text->last_line_height;
|
||||||
text->current_x = text->padding_x;
|
|
||||||
text->current_y = text->padding_y;
|
|
||||||
text->last_line_height = 0.0f;
|
|
||||||
|
|
||||||
return true; // Finished
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // Not finished
|
// Return true if something was drawn or queue finished
|
||||||
|
if (did_draw || text->queue_index >= text->text_queue.size()) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the currently running text block has not yet displayed all characters.
|
// Returns true if the currently running text block has not yet displayed all characters.
|
||||||
@@ -844,7 +934,7 @@ extern "C" {
|
|||||||
// --- Accessors ---
|
// --- Accessors ---
|
||||||
|
|
||||||
// Returns a pointer to the internal framebuffer.
|
// Returns a pointer to the internal framebuffer.
|
||||||
uint32_t* text_getfb(text_t* text) {
|
const uint32_t* text_getfb(text_t* text) {
|
||||||
return text ? text->framebuffer : nullptr;
|
return text ? text->framebuffer : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+81
-77
@@ -5,130 +5,134 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
struct window_t {
|
struct window_t {
|
||||||
SDL_Window* window = nullptr;
|
SDL_Window* window;
|
||||||
SDL_Renderer* renderer = nullptr;
|
SDL_Renderer* renderer;
|
||||||
SDL_Texture* texture = nullptr;
|
SDL_Texture* texture;
|
||||||
int width = 0;
|
int width;
|
||||||
int height = 0;
|
int height;
|
||||||
uint32_t last_tick_time;
|
|
||||||
|
|
||||||
const uint8_t* keyboard_state = nullptr;
|
const uint8_t* keyboard_state;
|
||||||
int mouse_x = 0;
|
int mouse_x;
|
||||||
int mouse_y = 0;
|
int mouse_y;
|
||||||
uint32_t mouse_buttons = 0; // Bitmask for L/M/R/X1/X2 buttons
|
uint32_t mouse_buttons;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Internal helper
|
||||||
|
static void window_cleanup(window_t* s) {
|
||||||
|
if (!s) return;
|
||||||
|
if (s->texture) SDL_DestroyTexture(s->texture);
|
||||||
|
if (s->renderer) SDL_DestroyRenderer(s->renderer);
|
||||||
|
if (s->window) SDL_DestroyWindow(s->window);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
window_t* window_create(int width, int height, const char* title) {
|
window_t* window_create(int width, int height, const char* title) {
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) != 0) return nullptr;
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
|
std::fprintf(stderr, "SDL_InitSubSystem failed: %s\n", SDL_GetError());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
window_t* s = new window_t;
|
window_t* s = new window_t{};
|
||||||
if (!s) return nullptr;
|
if (!s) return nullptr;
|
||||||
|
|
||||||
s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0);
|
|
||||||
if (!s->window) { delete s; return nullptr; }
|
|
||||||
|
|
||||||
s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED);
|
|
||||||
if (!s->renderer) { delete s; return nullptr; }
|
|
||||||
|
|
||||||
// Use ARGB8888 format for 32-bit framebuffer
|
|
||||||
s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, width, height);
|
|
||||||
if (!s->texture) { delete s; return nullptr; }
|
|
||||||
|
|
||||||
// --- Input Initialization ---
|
|
||||||
// Get the pointer to the keyboard state array
|
|
||||||
s->keyboard_state = SDL_GetKeyboardState(nullptr);
|
|
||||||
|
|
||||||
s->width = width;
|
s->width = width;
|
||||||
s->height = height;
|
s->height = height;
|
||||||
|
|
||||||
|
s->window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, 0);
|
||||||
|
if (!s->window) {
|
||||||
|
std::fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||||
|
delete s;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->renderer = SDL_CreateRenderer(s->window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
if (!s->renderer) {
|
||||||
|
std::fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError());
|
||||||
|
window_cleanup(s);
|
||||||
|
delete s;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->texture = SDL_CreateTexture(s->renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING, width, height);
|
||||||
|
if (!s->texture) {
|
||||||
|
std::fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError());
|
||||||
|
window_cleanup(s);
|
||||||
|
delete s;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->keyboard_state = SDL_GetKeyboardState(nullptr);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_destroy(window_t* s) {
|
void window_destroy(window_t* s) {
|
||||||
if (s->texture) SDL_DestroyTexture(s->texture);
|
if (!s) return;
|
||||||
if (s->renderer) SDL_DestroyRenderer(s->renderer);
|
|
||||||
if (s->window) SDL_DestroyWindow(s->window);
|
|
||||||
|
|
||||||
// Reset state pointers after destruction
|
window_cleanup(s);
|
||||||
s->texture = nullptr;
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
s->renderer = nullptr;
|
delete s;
|
||||||
s->window = nullptr;
|
|
||||||
|
|
||||||
SDL_Quit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool window_pollevent(window_t* s) {
|
bool window_pollevent(window_t* s) {
|
||||||
SDL_Event event;
|
SDL_Event ev;
|
||||||
bool quit_requested = false;
|
bool quit = false;
|
||||||
|
|
||||||
// Process all events in the queue
|
while (SDL_PollEvent(&ev)) {
|
||||||
while (SDL_PollEvent(&event)) {
|
if (ev.type == SDL_QUIT)
|
||||||
if (event.type == SDL_QUIT) {
|
quit = true;
|
||||||
quit_requested = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y);
|
s->mouse_buttons = SDL_GetMouseState(&s->mouse_x, &s->mouse_y);
|
||||||
|
return quit;
|
||||||
return quit_requested;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a key (identified by its Scancode) is currently held down.
|
bool window_is_key_down(window_t* s, int sc) {
|
||||||
// SDL_SCANCODE_* constants should be used by the caller.
|
if (!s->keyboard_state) return false;
|
||||||
bool window_is_key_down(window_t* s, int key_scancode) {
|
if (sc < 0 || sc >= SDL_NUM_SCANCODES) return false;
|
||||||
// Check for valid scancode and that the state array is initialized
|
return s->keyboard_state[sc] != 0;
|
||||||
if (key_scancode >= 0 && key_scancode < SDL_NUM_SCANCODES && s->keyboard_state) {
|
}
|
||||||
return s->keyboard_state[key_scancode] == 1;
|
|
||||||
|
bool window_is_mouse_button_down(window_t* s, int button) {
|
||||||
|
switch (button) {
|
||||||
|
case MOUSE_BUTTON_LEFT: return s->mouse_buttons & SDL_BUTTON_LMASK;
|
||||||
|
case MOUSE_BUTTON_MIDDLE: return s->mouse_buttons & SDL_BUTTON_MMASK;
|
||||||
|
case MOUSE_BUTTON_RIGHT: return s->mouse_buttons & SDL_BUTTON_RMASK;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a mouse button is currently held down.
|
|
||||||
// button_index should be one of MOUSE_BUTTON_LEFT, MOUSE_BUTTON_MIDDLE, or MOUSE_BUTTON_RIGHT.
|
|
||||||
bool window_is_mouse_button_down(window_t* s, int button_index) {
|
|
||||||
if (button_index == MOUSE_BUTTON_LEFT) {
|
|
||||||
return s->mouse_buttons & SDL_BUTTON_LMASK;
|
|
||||||
}
|
|
||||||
if (button_index == MOUSE_BUTTON_MIDDLE) {
|
|
||||||
return s->mouse_buttons & SDL_BUTTON_MMASK;
|
|
||||||
}
|
|
||||||
if (button_index == MOUSE_BUTTON_RIGHT) {
|
|
||||||
return s->mouse_buttons & SDL_BUTTON_RMASK;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the current mouse cursor position relative to the window.
|
|
||||||
void window_get_mouse_position(window_t* s, int* x, int* y) {
|
void window_get_mouse_position(window_t* s, int* x, int* y) {
|
||||||
if (x) *x = s->mouse_x;
|
if (x) *x = s->mouse_x;
|
||||||
if (y) *y = s->mouse_y;
|
if (y) *y = s->mouse_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void window_blit(window_t* s, const uint32_t* framebuffer) {
|
void window_blit(window_t* s, const uint32_t* fb) {
|
||||||
if (!s->texture) return; // Prevent crash if texture wasn't created
|
if (!s->texture) return;
|
||||||
|
|
||||||
void* pixels;
|
void* pixels;
|
||||||
int pitch;
|
int pitch;
|
||||||
|
|
||||||
// Lock and update the texture
|
|
||||||
SDL_LockTexture(s->texture, nullptr, &pixels, &pitch);
|
SDL_LockTexture(s->texture, nullptr, &pixels, &pitch);
|
||||||
|
|
||||||
// Copy the framebuffer. Use memcpy for the whole thing if pitch is what's expected (pitch == width * 4)
|
const int expected_pitch = s->width * 4;
|
||||||
// Note: The loop-based copy is safer if pitch != width * 4, but let's stick to the row-by-row copy for safety as in the original.
|
const size_t total = (size_t)s->width * (size_t)s->height * 4;
|
||||||
|
|
||||||
|
if (pitch == expected_pitch) {
|
||||||
|
std::memcpy(pixels, fb, total);
|
||||||
|
} else {
|
||||||
for (int y = 0; y < s->height; ++y) {
|
for (int y = 0; y < s->height; ++y) {
|
||||||
const uint32_t* src = framebuffer + y * s->width;
|
uint8_t* row = (uint8_t*)pixels + y * pitch;
|
||||||
// Calculate destination address for the start of the current row
|
std::memcpy(row, fb + y * s->width, expected_pitch);
|
||||||
uint32_t* dst = (uint32_t*)((uint8_t*)pixels + y * pitch);
|
}
|
||||||
// Copy one row (s->width * 4 bytes)
|
|
||||||
memcpy(dst, src, s->width * sizeof(uint32_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UnlockTexture(s->texture);
|
SDL_UnlockTexture(s->texture);
|
||||||
|
|
||||||
// Rendering
|
|
||||||
SDL_RenderClear(s->renderer);
|
SDL_RenderClear(s->renderer);
|
||||||
SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr);
|
SDL_RenderCopy(s->renderer, s->texture, nullptr, nullptr);
|
||||||
SDL_RenderPresent(s->renderer);
|
SDL_RenderPresent(s->renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -73,18 +73,22 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||||
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
||||||
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
||||||
|
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
||||||
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
||||||
|
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||||
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
||||||
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
||||||
|
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
||||||
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
||||||
|
<CodeAnalysisRuleSet>NativeRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||||
<ClCompile>
|
<ClCompile>
|
||||||
@@ -194,6 +198,7 @@
|
|||||||
<Image Include="assets\wikor.png" />
|
<Image Include="assets\wikor.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Font Include="assets\Aller_Rg.ttf" />
|
||||||
<Font Include="assets\Vera.ttf" />
|
<Font Include="assets\Vera.ttf" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
|
|||||||
@@ -81,5 +81,8 @@
|
|||||||
<Font Include="assets\Vera.ttf">
|
<Font Include="assets\Vera.ttf">
|
||||||
<Filter>Assets</Filter>
|
<Filter>Assets</Filter>
|
||||||
</Font>
|
</Font>
|
||||||
|
<Font Include="assets\Aller_Rg.ttf">
|
||||||
|
<Filter>Assets</Filter>
|
||||||
|
</Font>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
Reference in new issue
Block a user