Started with ui theme, ui layout and general ui rendering impl. Not working.

This commit is contained in:
Dennis Eichhorn 2025-01-18 01:36:08 +01:00
parent 38f9178831
commit 208bff208f
46 changed files with 2694 additions and 1048 deletions

View File

@ -372,8 +372,6 @@ void thrd_ams_remove_asset(AssetManagementSystem* ams, const char* name, Asset*
Asset* ams_reserve_asset(AssetManagementSystem* ams, byte type, const char* name, uint32 size, uint32 overhead = 0)
{
ASSERT_SIMPLE(strlen(name) < HASH_MAP_MAX_KEY_LENGTH - 1);
AssetComponent* ac = &ams->asset_components[type];
uint16 elements = ams_calculate_chunks(ac, size, overhead);
@ -430,8 +428,6 @@ Asset* thrd_ams_reserve_asset(AssetManagementSystem* ams, byte type, const char*
DEBUG_MEMORY_RESERVE((uintptr_t) asset_data, asset.ram_size, 180);
ASSERT_SIMPLE(strlen(name) < HASH_MAP_MAX_KEY_LENGTH - 1);
return (Asset *) hashmap_insert(&ams->hash_map, name, (byte *) &asset)->value;
}
@ -464,6 +460,11 @@ void thrd_ams_update(AssetManagementSystem* ams, uint64 time, uint64 dt)
++ams->asset_components[asset->component_id].asset_count;
if ((asset->state & ASSET_STATE_RAM_GC) || (asset->state & ASSET_STATE_VRAM_GC)) {
// @todo Currently we cannot really delete based on last access since we are not updating the last_access reliably
// This is usually the case for global/static assets such as font, ui_asset = ui vertices, ...
// The reason for this is that we store a reference to those assets in a global struct
// One solution could be to manually update the last_access at the end of every frame (shouldn't be THAT many assets)?
// Maybe we can even implement a scene specific post_scene() function that does this for scene specific assets e.g. post_scene_scene1();
if ((asset->state & ASSET_STATE_RAM_GC)
&& (asset->state & ASSET_STATE_VRAM_GC)
&& time - asset->last_access <= dt

View File

@ -23,6 +23,12 @@
* this AppCmdBuffer interacts with the individual systems and manually call those
*/
#include "AppCmdBuffer.h"
#include "../camera/Camera.h"
#include "../ui/UILayout.h"
#include "../ui/UITheme.h"
#include "../system/FileUtils.cpp"
// @todo Move the different functions to their own respective files (e.g. CmdAsset.cpp, CmdLayout.cpp)
inline
void cmd_buffer_create(AppCmdBuffer* cb, BufferMemory* buf, int32 commands_count)
@ -40,10 +46,32 @@ void cmd_asset_load_enqueue(AppCmdBuffer* cb, Command* cmd)
queue_enqueue_wait_atomic(cb->assets_to_load, (byte *) cmd->data);
}
// This doesn't load the file directly but tells (most likely) a worker thread to load a file
static inline
void cmd_file_load_enqueue(AppCmdBuffer* cb, Command* cmd)
{
// cmd->data structure:
// start with a pointer to a callback function
// file path
queue_enqueue_wait_atomic(cb->files_to_load, (byte *) cmd->data);
}
static inline
void cmd_file_load(AppCmdBuffer* cb, Command* cmd)
{
FileBody file;
file_read((const char *) cmd->data + sizeof(CommandFunction), &file, cb->thrd_mem_vol);
// WARNING: This is not the normal cmd.callback
// This is a special callback part of the cmd data;
CommandFunction callback = *((CommandFunction *) cmd->data);
callback(&file);
}
static inline
void* cmd_func_run(AppCmdBuffer* cb, Command* cmd)
{
CommandFunc func = *((CommandFunc *) cmd->data);
CommandFunction func = *((CommandFunction *) cmd->data);
return func(cmd);
}
@ -187,10 +215,10 @@ void thrd_cmd_insert(AppCmdBuffer* cb, CommandType type, const char* data)
thrd_cmd_insert(cb, &cmd);
}
inline void thrd_cmd_func_insert(AppCmdBuffer* cb, CommandType type, CommandFunc* func) {
inline void thrd_cmd_func_insert(AppCmdBuffer* cb, CommandType type, CommandFunction* func) {
Command cmd;
cmd.type = CMD_FUNC_RUN;
*((CommandFunc *) cmd.data) = *func;
*((CommandFunction *) cmd.data) = *func;
thrd_cmd_insert(cb, &cmd);
}
@ -211,10 +239,10 @@ inline void thrd_cmd_audio_play(AppCmdBuffer* cb, const char* data) {
thrd_cmd_insert(cb, &cmd);
}
inline void thrd_cmd_func_run(AppCmdBuffer* cb, CommandFunc* func) {
inline void thrd_cmd_func_run(AppCmdBuffer* cb, CommandFunction* func) {
Command cmd;
cmd.type = CMD_FUNC_RUN;
*((CommandFunc *) cmd.data) = *func;
*((CommandFunction *) cmd.data) = *func;
thrd_cmd_insert(cb, &cmd);
}
@ -251,13 +279,13 @@ inline void thrd_cmd_font_load(AppCmdBuffer* cb, const char* data) {
thrd_cmd_insert(cb, &cmd);
}
inline Asset* cmd_asset_load(AppCmdBuffer* cb, int32 asset_id)
inline Asset* cmd_asset_load_sync(AppCmdBuffer* cb, int32 asset_id)
{
int32 archive_id = (asset_id >> 24) & 0xFF;
return asset_archive_asset_load(&cb->asset_archives[archive_id], asset_id, cb->ams, cb->mem_vol);
}
inline Asset* cmd_asset_load(AppCmdBuffer* cb, const char* asset_id_str)
inline Asset* cmd_asset_load_sync(AppCmdBuffer* cb, const char* asset_id_str)
{
int32 asset_id = (int32) str_to_int(asset_id_str);
int32 archive_id = (asset_id >> 24) & 0xFF;
@ -309,11 +337,11 @@ inline Asset* cmd_audio_play(AppCmdBuffer* cb, const char* name) {
return asset;
}
inline void* cmd_func_run(AppCmdBuffer* cb, CommandFunc func) {
inline void* cmd_func_run(AppCmdBuffer* cb, CommandFunction func) {
return func(NULL);
}
inline Asset* cmd_texture_load(AppCmdBuffer* cb, int32 asset_id) {
inline Asset* cmd_texture_load_sync(AppCmdBuffer* cb, int32 asset_id) {
// Check if asset already loaded
char id_str[9];
int_to_hex(asset_id, id_str);
@ -339,7 +367,7 @@ inline Asset* cmd_texture_load(AppCmdBuffer* cb, int32 asset_id) {
return asset;
}
inline Asset* cmd_texture_load(AppCmdBuffer* cb, const char* name) {
inline Asset* cmd_texture_load_sync(AppCmdBuffer* cb, const char* name) {
// Check if asset already loaded
Asset* asset = thrd_ams_get_asset_wait(cb->ams, name);
@ -363,7 +391,7 @@ inline Asset* cmd_texture_load(AppCmdBuffer* cb, const char* name) {
return asset;
}
inline Asset* cmd_font_load(AppCmdBuffer* cb, int32 asset_id) {
inline Asset* cmd_font_load_sync(AppCmdBuffer* cb, int32 asset_id) {
// Check if asset already loaded
char id_str[9];
int_to_hex(asset_id, id_str);
@ -387,7 +415,7 @@ inline Asset* cmd_font_load(AppCmdBuffer* cb, int32 asset_id) {
return asset;
}
inline Asset* cmd_font_load(AppCmdBuffer* cb, const char* name) {
inline Asset* cmd_font_load_sync(AppCmdBuffer* cb, const char* name) {
// Check if asset already loaded
Asset* asset = thrd_ams_get_asset_wait(cb->ams, name);
@ -409,10 +437,135 @@ inline Asset* cmd_font_load(AppCmdBuffer* cb, const char* name) {
return asset;
}
inline
UILayout* cmd_layout_load_sync(
AppCmdBuffer* cb,
UILayout* layout, const char* layout_path
) {
FileBody layout_file;
file_read(layout_path, &layout_file, cb->mem_vol);
layout_from_data(layout_file.content, layout);
return layout;
}
inline
UIThemeStyle* cmd_theme_load_sync(
AppCmdBuffer* cb,
UIThemeStyle* theme, const char* theme_path
) {
FileBody theme_file;
file_read(theme_path, &theme_file, cb->mem_vol);
theme_from_data(theme_file.content, theme);
return theme;
}
inline
void cmd_layout_populate_sync(
AppCmdBuffer* cb,
UILayout* layout, UIThemeStyle* theme,
const Camera* camera
) {
layout_from_theme(layout, theme, camera);
}
inline
UILayout* cmd_ui_load_sync(
AppCmdBuffer* cb,
UILayout* layout, const char* layout_path,
UIThemeStyle* general_theme,
UIThemeStyle* theme, const char* theme_path,
const Camera* camera
) {
cmd_layout_load_sync(cb, layout, layout_path);
cmd_layout_populate_sync(cb, layout, general_theme, camera);
cmd_theme_load_sync(cb, theme, theme_path);
cmd_layout_populate_sync(cb, layout, theme, camera);
return layout;
}
static inline
UILayout* cmd_ui_load(AppCmdBuffer* cb, Command* cmd)
{
byte* pos = cmd->data;
UILayout* layout = (UILayout *) pos;
pos += sizeof(uintptr_t);
char* layout_path = (char *) pos;
str_move_to((char **) &pos, '\0'); ++pos;
UIThemeStyle* general_theme = (UIThemeStyle *) pos;
pos += sizeof(uintptr_t);
UIThemeStyle* theme = (UIThemeStyle *) pos;
pos += sizeof(uintptr_t);
char* theme_path = (char *) pos;
str_move_to((char **) &pos, '\0'); ++pos;
Camera* camera = (Camera *) pos;
return cmd_ui_load_sync(
cb,
layout, layout_path,
general_theme,
theme, theme_path,
camera
);
}
inline
void thrd_cmd_ui_load(
AppCmdBuffer* cb,
UILayout* layout, const char* layout_path,
UIThemeStyle* general_theme,
UIThemeStyle* theme, const char* theme_path,
const Camera* camera,
CommandFunction callback
) {
Command cmd;
cmd.type = CMD_UI_LOAD;
cmd.callback = callback;
byte* pos = cmd.data;
// Layout pointer
*((uintptr_t *) pos) = (uintptr_t) layout;
pos += sizeof(uintptr_t);
// Layout path
pos += str_copy_until((char *) pos, layout_path, '\0');
*pos = '\0'; ++pos;
// General theme pointer
*((uintptr_t *) pos) = (uintptr_t) general_theme;
pos += sizeof(uintptr_t);
// Theme pointer
*((uintptr_t *) pos) = (uintptr_t) theme;
pos += sizeof(uintptr_t);
// Theme path
pos += str_copy_until((char *) pos, theme_path, '\0');
*pos = '\0'; ++pos;
// Camera pointer
*((uintptr_t *) pos) = (uintptr_t) camera;
thrd_cmd_insert(cb, &cmd);
}
// @question In some cases we don't remove an element if it couldn't get completed
// Would it make more sense to remove it and add a new follow up command automatically in such cases?
// e.g. couldn't play audio since it isn't loaded -> queue for asset load -> queue for internal play
// I gues this only makes sense if we would switch to a queue
// I guess this only makes sense if we would switch to a queue
// @question Some of the functions create another async call
// E.g. do something that requires an asset, if asset not available queue for asset load.
// Do we really want to do that or do we instead want to load the asset right then and there
// If we do it right then and DON'T defer it, this would also solve the first question
void cmd_iterate(AppCmdBuffer* cb)
{
int32 last_element = 0;
@ -431,7 +584,9 @@ void cmd_iterate(AppCmdBuffer* cb)
case CMD_ASSET_LOAD: {
cmd_asset_load(cb, cmd);
} break;
case CMD_FILE_LOAD: {} break;
case CMD_FILE_LOAD: {
cmd_file_load(cb, cmd);
} break;
case CMD_TEXTURE_LOAD: {
remove = cmd_texture_load_async(cb, cmd) != NULL;
} break;
@ -456,6 +611,9 @@ void cmd_iterate(AppCmdBuffer* cb)
case CMD_SHADER_LOAD: {
remove = cmd_shader_load(cb, cmd) != NULL;
} break;
case CMD_UI_LOAD: {
remove = cmd_ui_load(cb, cmd) != NULL;
} break;
default: {
UNREACHABLE();
}
@ -466,10 +624,14 @@ void cmd_iterate(AppCmdBuffer* cb)
continue;
}
if (cmd->callback) {
cmd->callback(cmd);
}
chunk_free_element(&cb->commands, free_index, bit_index);
// @performance This adds some unnecessary overhead.
// It would be much better, if we could define cb->last_element as the limit in the for loop
// It would be better, if we could define cb->last_element as the limit in the for loop
if (chunk_id == cb->last_element) {
break;
}

View File

@ -20,6 +20,7 @@
#include "../asset/AssetManagementSystem.h"
#include "../object/Texture.h"
#include "../memory/Queue.h"
#include "../system/FileUtils.cpp"
#include "Command.h"
struct AppCmdBuffer {
@ -40,6 +41,7 @@ struct AppCmdBuffer {
AssetManagementSystem* ams;
AssetArchive* asset_archives;
Queue* assets_to_load;
Queue* files_to_load;
AudioMixer* mixer;
GpuApiType gpu_api;
};
@ -48,13 +50,13 @@ struct AppCmdBuffer {
#include "../gpuapi/opengl/AppCmdBuffer.h"
#elif VULKAN
inline void* cmd_shader_load(AppCmdBuffer* cb, Command* cmd) { return NULL; }
inline void* cmd_shader_load(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
inline void* cmd_shader_load_sync(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
#elif DIRECTX
inline void* cmd_shader_load(AppCmdBuffer* cb, Command* cmd) { return NULL; }
inline void* cmd_shader_load(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
inline void* cmd_shader_load_sync(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
#else
inline void* cmd_shader_load(AppCmdBuffer* cb, Command* cmd) { return NULL; }
inline void* cmd_shader_load(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
inline void* cmd_shader_load_sync(AppCmdBuffer* cb, void* shader, int32* shader_ids) { return NULL; }
#endif
#endif

View File

@ -11,6 +11,7 @@
#include "../stdlib/Types.h"
// @question Consider to rename internal enum values to contain the word INTERNAL
enum CommandType {
CMD_FUNC_RUN,
CMD_ASSET_ENQUEUE,
@ -23,13 +24,15 @@ enum CommandType {
CMD_AUDIO_PLAY,
CMD_AUDIO_ENQUEUE, // Only for internal use
CMD_SHADER_LOAD,
CMD_UI_LOAD,
};
typedef void* (*CommandFunction)(void* data);
struct Command {
CommandType type;
byte data[28]; // @todo to be adjusted
CommandFunction callback;
byte data[256]; // @todo to be adjusted
};
typedef void* (*CommandFunc)(Command*);
#endif

View File

@ -116,21 +116,21 @@ void font_from_file_txt(
++pos;
}
if (strcmp(block_name, "texture") == 0) {
if (str_compare(block_name, "texture") == 0) {
while (*pos != '\n') {
*texture_pos++ = *pos++;
}
*texture_pos++ = '\0';
} else if (strcmp(block_name, "font_size") == 0) {
} else if (str_compare(block_name, "font_size") == 0) {
font->size = strtof(pos, &pos);
} else if (strcmp(block_name, "line_height") == 0) {
} else if (str_compare(block_name, "line_height") == 0) {
font->line_height = strtof(pos, &pos);
} else if (strcmp(block_name, "image_width") == 0) {
} else if (str_compare(block_name, "image_width") == 0) {
image_width = strtoul(pos, &pos, 10);
} else if (strcmp(block_name, "image_height") == 0) {
} else if (str_compare(block_name, "image_height") == 0) {
image_height = strtoul(pos, &pos, 10);
} else if (strcmp(block_name, "glyph_count") == 0) {
} else if (str_compare(block_name, "glyph_count") == 0) {
// glyph_count has to be the last general element
font->glyph_count = strtoul(pos, &pos, 10);
start = false;

View File

@ -442,223 +442,6 @@ v2_f32 vertex_text_create(
return {rendered_width, rendered_height};
}
// @todo implement shadow (offset + angle + diffuse) or should this be a shader only thing? if so this would be a problem for us since we are handling text in the same shader as simple shapes
// we might want to implement distance field font atlas
f32 ui_text_create(
Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex,
UITheme* theme, UIElement* element
) {
if (element->vertex_count > 0) {
memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
return vertices[element->vertex_count - 1].position.x;
}
// @performance see comment for setup_theme()
// Load element data
HashEntryVoidP* element_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, element->name, element->id);
UIAttributeGroup* element_group = (UIAttributeGroup *) element_entry->value;
// Load general style
UIAttribute* style = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_STYLE);
HashEntryVoidP* style_entry = NULL;
UIAttributeGroup* style_group = NULL;
if (style) {
style_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, style->value_str);
style_group = (UIAttributeGroup *) style_entry->value;
}
UIAttribute* x;
UIAttribute* y;
// Load parent data (for position data)
UIAttribute* parent = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_PARENT);
if (parent) {
HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, parent->value_str);
UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value;
x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
// @question Do we have more values which can be inherited from the parent?
// We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly
} else {
x = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
}
UIAttribute* width = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH);
UIAttribute* height = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT);
UIAttribute* align_h = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_H);
UIAttribute* align_v = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_V);
UIAttribute* text = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_CONTENT);
UIAttribute* size = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_SIZE);
UIAttribute* color_index = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_COLOR);
int32 length = utf8_strlen(text->value_str);
bool is_ascii = strlen(text->value_str) == length;
f32 scale = size->value_float / theme->font.size;
// If we do a different alignment we need to pre-calculate the width and height
if (align_h != NULL || align_v != NULL) {
f32 tmp_width = (f32) width->value_int;
f32 tmp_height = (f32) height->value_int;
if (align_h != NULL && align_v != NULL) {
text_calculate_dimensions(&tmp_width, &tmp_height, &theme->font, text->value_str, is_ascii, scale, length);
} else if (align_h != NULL) {
tmp_width = text_calculate_dimensions_width(&theme->font, text->value_str, is_ascii, scale, length);
} else {
tmp_height = text_calculate_dimensions_height(&theme->font, text->value_str, scale, length);
}
if (align_h->value_int == UI_ALIGN_H_RIGHT) {
x -= width->value_int;
} else if (align_h->value_int == UI_ALIGN_H_CENTER) {
x -= width->value_int / 2;
}
if (align_v->value_int == UI_ALIGN_V_TOP) {
y -= height->value_int;
} else if (align_v->value_int == UI_ALIGN_V_CENTER) {
y -= height->value_int / 2;
}
}
int32 start = *index;
f32 offset_x = (f32) x->value_int;
f32 offset_y = (f32) y->value_int;
int32 first_char = is_ascii ? text->value_str[0] : utf8_get_char_at(text->value_str, 0);
for (int32 i = (first_char == '\n' ? 1 : 0); i < length; ++i) {
int32 character = is_ascii ? text->value_str[i] : utf8_get_char_at(text->value_str, i);
if (character == '\n') {
offset_y += theme->font.line_height * scale;
offset_x = (f32) x->value_int;
continue;
}
Glyph* glyph = font_glyph_find(&theme->font, character);
if (!glyph) {
continue;
}
f32 offset_y2 = offset_y + glyph->metrics.offset_y * scale;
offset_x += glyph->metrics.offset_x * scale;
// @performance Consider to handle whitespaces just by offsetting
vertex_rect_create(
vertices, index, zindex,
offset_x, offset_y2, glyph->metrics.width * scale, glyph->metrics.height * scale, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
color_index->value_float, glyph->coords.x1, glyph->coords.y1, glyph->coords.x2, glyph->coords.y2
);
offset_x += (glyph->metrics.width + glyph->metrics.advance_x) * scale;
}
element->vertex_count = *index - start;
memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
// @todo See todo of vertex_text function
// @performance use elements->vertices and also cache result in there
return offset_x;
}
void ui_button_create(
Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex,
UITheme* theme, UIElement* element
)
{
// @todo handle different states and ongoing animations
// We cannot return early in such cases
if (element->vertex_count > 0) {
memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
return;
}
// @performance see comment for setup_theme()
// Load element data
HashEntryVoidP* element_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, element->name, element->id);
UIAttributeGroup* element_group = (UIAttributeGroup *) element_entry->value;
// Load general style
UIAttribute* style = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_STYLE);
HashEntryVoidP* style_entry = NULL;
UIAttributeGroup* style_group = NULL;
if (style) {
style_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, style->value_str);
style_group = (UIAttributeGroup *) style_entry->value;
}
UIAttribute* x;
UIAttribute* y;
// Load parent data (for position data)
UIAttribute* parent = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_PARENT);
if (parent) {
HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->primary_scene->hash_map, parent->value_str);
UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value;
x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
// @question Do we have more values which can be inherited from the parent?
// We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly
} else {
x = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
}
UIAttribute* width = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH);
UIAttribute* height = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT);
UIAttribute* align_h = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_H);
UIAttribute* align_v = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_V);
UIAttribute* text = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_CONTENT);
UIAttribute* size = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_SIZE);
UIAttribute* color_index = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_COLOR);
// @todo Above we only handle the default values, what about state dependent values like hover, active?
// Simply check the state here and load the child_entries based on the state
// However, for that we need the current state of the button... should this be in a separate button object,
// that also holds position information for hover checks etc. Or should that state be stored in the theme data?
// Right now we could make these checks right here anyway but in the future we don't want to update the rendering data
// every frame if we don't have to. We don't want immediate mode! We only want to update the UI if there is a change.
// If a change (or state change like hover) triggers a complete update of all elements or just a sub region update
// remains TBD
int32 start = *index;
vertex_rect_border_create(
vertices, index, zindex,
x->value_float, y->value_float, width->value_float, height->value_float, 1, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
12, 0.0f, 0.0f
);
vertex_rect_create(
vertices, index, zindex,
x->value_float + 1, y->value_float + 1, width->value_float - 2, height->value_float - 2, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
14, 0.0f, 0.0f
);
zindex = nextafterf(zindex, INFINITY);
vertex_text_create(
vertices, index, zindex,
x->value_float, y->value_float, width->value_float, height->value_float, align_h->value_int, align_v->value_int,
&theme->font, text->value_str, size->value_float, color_index->value_float
);
element->vertex_count = *index - start;
memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
}
inline
void entity_world_space(f32* world_space, const f32* local_space, const f32* model_mat)
{

View File

@ -30,4 +30,223 @@ void ui_input_create(Vertex3DTextureColorIndex* __restrict vertices, uint32* __r
);
}
// @todo implement shadow (offset + angle + diffuse) or should this be a shader only thing? if so this would be a problem for us since we are handling text in the same shader as simple shapes
// we might want to implement distance field font atlas
f32 ui_text_create(
Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex,
UIThemeStyle* theme, UIElement* element
) {
if (element->vertex_count > 0) {
memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
return vertices[element->vertex_count - 1].position.x;
}
// @performance see comment for setup_theme()
// Load element data
HashEntryVoidP* element_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, element->name, element->id);
UIAttributeGroup* element_group = (UIAttributeGroup *) element_entry->value;
// Load general style
UIAttribute* style = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_STYLE);
HashEntryVoidP* style_entry = NULL;
UIAttributeGroup* style_group = NULL;
if (style) {
style_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, style->value_str);
style_group = (UIAttributeGroup *) style_entry->value;
}
UIAttribute* x;
UIAttribute* y;
// Load parent data (for position data)
UIAttribute* parent = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_PARENT);
if (parent) {
HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, parent->value_str);
UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value;
x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
// @question Do we have more values which can be inherited from the parent?
// We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly
} else {
x = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
}
UIAttribute* width = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH);
UIAttribute* height = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT);
UIAttribute* align_h = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_H);
UIAttribute* align_v = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_V);
UIAttribute* text = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_CONTENT);
UIAttribute* size = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_SIZE);
UIAttribute* color_index = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_COLOR);
int32 length = utf8_strlen(text->value_str);
bool is_ascii = strlen(text->value_str) == length;
f32 scale = size->value_float / theme->font->size;
// If we do a different alignment we need to pre-calculate the width and height
if (align_h != NULL || align_v != NULL) {
f32 tmp_width = (f32) width->value_int;
f32 tmp_height = (f32) height->value_int;
if (align_h != NULL && align_v != NULL) {
text_calculate_dimensions(&tmp_width, &tmp_height, theme->font, text->value_str, is_ascii, scale, length);
} else if (align_h != NULL) {
tmp_width = text_calculate_dimensions_width(theme->font, text->value_str, is_ascii, scale, length);
} else {
tmp_height = text_calculate_dimensions_height(theme->font, text->value_str, scale, length);
}
if (align_h->value_int == UI_ALIGN_H_RIGHT) {
x -= width->value_int;
} else if (align_h->value_int == UI_ALIGN_H_CENTER) {
x -= width->value_int / 2;
}
if (align_v->value_int == UI_ALIGN_V_TOP) {
y -= height->value_int;
} else if (align_v->value_int == UI_ALIGN_V_CENTER) {
y -= height->value_int / 2;
}
}
int32 start = *index;
f32 offset_x = (f32) x->value_int;
f32 offset_y = (f32) y->value_int;
int32 first_char = is_ascii ? text->value_str[0] : utf8_get_char_at(text->value_str, 0);
for (int32 i = (first_char == '\n' ? 1 : 0); i < length; ++i) {
int32 character = is_ascii ? text->value_str[i] : utf8_get_char_at(text->value_str, i);
if (character == '\n') {
offset_y += theme->font->line_height * scale;
offset_x = (f32) x->value_int;
continue;
}
Glyph* glyph = font_glyph_find(theme->font, character);
if (!glyph) {
continue;
}
f32 offset_y2 = offset_y + glyph->metrics.offset_y * scale;
offset_x += glyph->metrics.offset_x * scale;
// @performance Consider to handle whitespaces just by offsetting
vertex_rect_create(
vertices, index, zindex,
offset_x, offset_y2, glyph->metrics.width * scale, glyph->metrics.height * scale, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
color_index->value_float, glyph->coords.x1, glyph->coords.y1, glyph->coords.x2, glyph->coords.y2
);
offset_x += (glyph->metrics.width + glyph->metrics.advance_x) * scale;
}
element->vertex_count = *index - start;
memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
// @todo See todo of vertex_text function
// @performance use elements->vertices and also cache result in there
return offset_x;
}
void ui_button_create(
Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex,
UIThemeStyle* theme, UIElement* element
)
{
// @todo handle different states and ongoing animations
// We cannot return early in such cases
if (element->vertex_count > 0) {
memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
return;
}
// @performance see comment for setup_theme()
// Load element data
HashEntryVoidP* element_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, element->name, element->id);
UIAttributeGroup* element_group = (UIAttributeGroup *) element_entry->value;
// Load general style
UIAttribute* style = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_STYLE);
HashEntryVoidP* style_entry = NULL;
UIAttributeGroup* style_group = NULL;
if (style) {
style_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, style->value_str);
style_group = (UIAttributeGroup *) style_entry->value;
}
UIAttribute* x;
UIAttribute* y;
// Load parent data (for position data)
UIAttribute* parent = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_PARENT);
if (parent) {
HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, parent->value_str);
UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value;
x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
// @question Do we have more values which can be inherited from the parent?
// We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly
} else {
x = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_X);
y = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_POSITION_Y);
}
UIAttribute* width = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH);
UIAttribute* height = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT);
UIAttribute* align_h = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_H);
UIAttribute* align_v = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_ALIGN_V);
UIAttribute* text = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_CONTENT);
UIAttribute* size = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_SIZE);
UIAttribute* color_index = ui_attribute_from_group(element_group, UI_ATTRIBUTE_TYPE_FONT_COLOR);
// @todo Above we only handle the default values, what about state dependent values like hover, active?
// Simply check the state here and load the child_entries based on the state
// However, for that we need the current state of the button... should this be in a separate button object,
// that also holds position information for hover checks etc. Or should that state be stored in the theme data?
// Right now we could make these checks right here anyway but in the future we don't want to update the rendering data
// every frame if we don't have to. We don't want immediate mode! We only want to update the UI if there is a change.
// If a change (or state change like hover) triggers a complete update of all elements or just a sub region update
// remains TBD
int32 start = *index;
vertex_rect_border_create(
vertices, index, zindex,
x->value_float, y->value_float, width->value_float, height->value_float, 1, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
12, 0.0f, 0.0f
);
vertex_rect_create(
vertices, index, zindex,
x->value_float + 1, y->value_float + 1, width->value_float - 2, height->value_float - 2, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM,
14, 0.0f, 0.0f
);
zindex = nextafterf(zindex, INFINITY);
vertex_text_create(
vertices, index, zindex,
x->value_float, y->value_float, width->value_float, height->value_float, align_h->value_int, align_v->value_int,
theme->font, text->value_str, size->value_float, color_index->value_float
);
element->vertex_count = *index - start;
memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count);
}
#endif

View File

@ -20,7 +20,7 @@ void* cmd_shader_load(AppCmdBuffer* cb, Command* cmd) {
return NULL;
}
void* cmd_shader_load(AppCmdBuffer* cb, Shader* shader, int32* shader_ids) {
void* cmd_shader_load_sync(AppCmdBuffer* cb, Shader* shader, int32* shader_ids) {
char asset_id[9];
int32 shader_assets[SHADER_TYPE_SIZE];

View File

@ -740,7 +740,7 @@ bool gl_extensions_load()
}
// umm count = end - pos;
// OpenGL->SupportsSRGBFramebuffer = strcmp(count, pos, "WGL_EXT_framebuffer_sRGB") == 0 || strcmp(count, pos, "WGL_ARB_framebuffer_sRGB") == 0;
// OpenGL->SupportsSRGBFramebuffer = str_compare(count, pos, "WGL_EXT_framebuffer_sRGB") == 0 || str_compare(count, pos, "WGL_ARB_framebuffer_sRGB") == 0;
pos = end;
}

View File

@ -23,10 +23,11 @@
#include <vulkan/vulkan.h>
#include <vector>
#include "../../stdlib/Types.h"
#include "ShaderUtils.h"
#include "../../utils/StringUtils.h"
#include "../../utils/TestUtils.h"
#include "../../log/Log.h"
#include "../../memory/RingMemory.h"
#include "ShaderUtils.h"
PACKED_STRUCT;
// WARNING: indices values start at one (are offset by +1) because 0 means no value in our implementation
@ -65,7 +66,7 @@ int32 vulkan_check_validation_layer_support(const char** validation_layers, uint
bool layerFound = false;
for (uint32 j = 0; j < layer_count; ++j) {
if (strcmp(validation_layers[i], available_layers[j].layerName) == 0) {
if (str_compare(validation_layers[i], available_layers[j].layerName) == 0) {
layerFound = true;
break;
}
@ -90,7 +91,7 @@ int32 vulkan_check_extension_support(const char** extensions, uint32 extension_c
bool layerFound = false;
for (uint32 j = 0; j < ext_count; ++j) {
if (strcmp(extensions[i], available_extensions[j].extensionName) == 0) {
if (str_compare(extensions[i], available_extensions[j].extensionName) == 0) {
layerFound = true;
break;
}
@ -227,7 +228,7 @@ bool vulkan_device_supports_extensions(VkPhysicalDevice device, const char** dev
for (int32 i = 0; i < device_extension_count; ++i) {
bool found = false;
for (int32 j = 0; j < extension_count; ++j) {
if (strcmp(device_extensions[i], available_extensions[j].extensionName) == 0) {
if (str_compare(device_extensions[i], available_extensions[j].extensionName) == 0) {
found = true;
break;
}

288
math/Evaluator.h Normal file
View File

@ -0,0 +1,288 @@
/**
* Jingga
*
* @copyright Jingga
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
#ifndef TOS_MATH_EVALUATOR_H
#define TOS_MATH_EVALUATOR_H
#include "../stdlib/Types.h"
#include "../utils/StringUtils.h"
#include <stdio.h>
#include <stdlib.h>
#define EVALUATOR_MAX_STACK_SIZE 256
// Stack for operators
struct EvaluatorOperatorStack {
char items[EVALUATOR_MAX_STACK_SIZE];
int32 top;
};
// Stack for values
struct EvaluatorValueStack {
f32 items[EVALUATOR_MAX_STACK_SIZE];
int32 top;
};
struct EvaluatorVariable {
char name[32];
f32 value;
};
static inline
const char* evaluator_skip_whitespace(const char* str) {
while (*str && (*str == ' ' || *str == '\t')) {
++str;
}
return str;
}
// Stack operations
void evaluator_push_operator(EvaluatorOperatorStack* stack, char op) {
if (stack->top >= EVALUATOR_MAX_STACK_SIZE - 1) {
return;
}
stack->items[++stack->top] = op;
}
char evaluator_pop_operator(EvaluatorOperatorStack* stack) {
ASSERT_SIMPLE(stack->top >= 0);
return stack->items[stack->top--];
}
char evaluator_peek_operator(EvaluatorOperatorStack* stack) {
if (stack->top < 0) {
return '\0';
}
return stack->items[stack->top];
}
void evaluator_push_value(EvaluatorValueStack* stack, f32 value) {
if (stack->top >= EVALUATOR_MAX_STACK_SIZE - 1) {
return;
}
stack->items[++stack->top] = value;
}
f32 evaluator_pop_value(EvaluatorValueStack* stack) {
ASSERT_SIMPLE(stack->top >= 0);
return stack->items[stack->top--];
}
// Precedence of operators
int32 evaluator_precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
default:
return 0;
}
}
// Apply an operator to two values
f32 evaluator_apply_operator(char op, f32 a, f32 b) {
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
return a / b;
default: {
UNREACHABLE();
}
}
}
f32 evaluator_evaluate_function(const char* name, const char* args);
// Shunting-yard algorithm to evaluate the expression
f32 evaluator_evaluate_expression(const char* expr) {
EvaluatorOperatorStack operators = { .top = -1 };
EvaluatorValueStack values = { .top = -1 };
const char* ptr = expr;
while (*ptr) {
ptr = evaluator_skip_whitespace(ptr);
if (str_is_num(*ptr) || *ptr == '.') {
// Parse number
char* end;
f32 value = strtof(ptr, &end);
evaluator_push_value(&values, value);
ptr = end;
} else if (str_is_alpha(*ptr)) {
// Parse function
const char* start = ptr;
while (str_is_alphanum(*ptr)) {
++ptr;
}
ASSERT_SIMPLE(*ptr == '(');
++ptr;
const char* args_start = ptr;
int32 depth = 1;
while (*ptr && depth > 0) {
if (*ptr == '(') {
++depth;
} else if (*ptr == ')') {
--depth;
}
++ptr;
}
ASSERT_SIMPLE(depth == 0);
char name[32];
memcpy(name, start, ptr - start);
name[ptr - start] = '\0';
char args[128];
memcpy(args, args_start, ptr - args_start - 1);
args[ptr - args_start - 1] = '\0';
f32 result = evaluator_evaluate_function(name, args);
evaluator_push_value(&values, result);
} else if (*ptr == '(') {
// Open parenthesis
evaluator_push_operator(&operators, *ptr);
++ptr;
} else if (*ptr == ')') {
// Close parenthesis
while (evaluator_peek_operator(&operators) != '(') {
char op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(op, a, b));
}
evaluator_pop_operator(&operators); // Remove '('
++ptr;
} else if (strchr("+-*/", *ptr)) {
// Operator
char op = *ptr;
while (evaluator_precedence(evaluator_peek_operator(&operators)) >= evaluator_precedence(op)) {
char top_op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(top_op, a, b));
}
evaluator_push_operator(&operators, op);
++ptr;
} else {
UNREACHABLE();
}
}
// Process remaining operators
while (operators.top >= 0) {
char op = evaluator_pop_operator(&operators);
f32 b = evaluator_pop_value(&values);
f32 a = evaluator_pop_value(&values);
evaluator_push_value(&values, evaluator_apply_operator(op, a, b));
}
return evaluator_pop_value(&values);
}
// Evaluate built-in functions
f32 evaluator_evaluate_function(const char* name, const char* args) {
if (str_compare(name, "min") == 0) {
const char* comma = strchr(args, ',');
if (!comma) {
return 0.0;
}
char arg1[64], arg2[64];
memcpy(arg1, args, comma - args);
arg1[comma - args] = '\0';
str_copy_short(arg2, comma + 1);
f32 val1 = evaluator_evaluate_expression(arg1);
f32 val2 = evaluator_evaluate_expression(arg2);
return OMS_MIN(val1, val2);
} else if (str_compare(name, "max") == 0) {
const char* comma = strchr(args, ',');
if (!comma) {
return 0.0;
}
char arg1[64], arg2[64];
memcpy(arg1, args, comma - args);
arg1[comma - args] = '\0';
str_copy_short(arg2, comma + 1);
f32 val1 = evaluator_evaluate_expression(arg1);
f32 val2 = evaluator_evaluate_expression(arg2);
return OMS_MAX(val1, val2);
}
return 0.0;
}
f32 evaluator_evaluate(char* expr, int32 variable_count = 0, const EvaluatorVariable* variables = NULL) {
// Handle variables
const char* ptr = expr;
int32 available_variables = variable_count;
while (*ptr && available_variables) {
// Skip none-alpha values
while (!str_is_alpha(*ptr) && *ptr != '\0') {
++ptr;
}
if (*ptr == '\0') {
continue;
}
// Potential variable name
for (int32 i = 0; i < variable_count; ++i) {
size_t len = strlen(variables[i].name);
// Check if string is variable (must be followed by a whitespace or end of string)
if (str_compare(ptr, variables[i].name, len) == 0 && (ptr[len] == ' ' || ptr[len] == '\0')) {
// Remove variable
str_remove(expr, ptr - expr, len);
// Replace variable with value
char value[25];
int32 value_length = float_to_str(variables[i].value, value, 4);
str_insert(expr, ptr - expr, value);
--available_variables;
break;
}
}
// Move past string, this should work regardless of whether we found a variable or not
while (str_is_alpha(*ptr) && *ptr != '\0') {
++ptr;
}
}
// Evaluate math formula
return evaluator_evaluate_expression(expr);
}
#endif

View File

@ -38,12 +38,12 @@ void module_file_parse(const char* path, Module* module, RingMemory* ring)
strncpy_s(value, MAX_LENGTH, space + 1, MAX_LENGTH - 1);
value[MAX_LENGTH - 1] = '\0';
if (strcmp(name, "name") == 0) {
if (str_compare(name, "name") == 0) {
strncpy_s(module->name, MAX_LENGTH, value, sizeof(module->name) - 1);
module->name[strlen(value)] = '\0';
} else if (strcmp(name, "version") == 0) {
} else if (str_compare(name, "version") == 0) {
module->version = (byte) atol(value);
} else if (strcmp(name, "type") == 0) {
} else if (str_compare(name, "type") == 0) {
module->type = (ModuleType) atol(value);
}
}

View File

@ -193,8 +193,9 @@ void hashmap_insert(HashMap* hm, const char* key, int32 value) {
HashEntryInt32* entry = (HashEntryInt32 *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
// @performance Do we really need strncpy? Either use memcpy or strcpy?! Same goes for all the other cases below
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
@ -219,7 +220,9 @@ void hashmap_insert(HashMap* hm, const char* key, int64 value) {
HashEntryInt64* entry = (HashEntryInt64 *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
@ -244,7 +247,9 @@ void hashmap_insert(HashMap* hm, const char* key, uintptr_t value) {
HashEntryUIntPtr* entry = (HashEntryUIntPtr *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
@ -269,7 +274,9 @@ void hashmap_insert(HashMap* hm, const char* key, void* value) {
HashEntryVoidP* entry = (HashEntryVoidP *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
@ -294,7 +301,9 @@ void hashmap_insert(HashMap* hm, const char* key, f32 value) {
HashEntryFloat* entry = (HashEntryFloat *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->value = value;
@ -319,10 +328,12 @@ void hashmap_insert(HashMap* hm, const char* key, const char* value) {
HashEntryStr* entry = (HashEntryStr *) chunk_get_element(&hm->buf, element, true);
entry->element_id = element;
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
strncpy(entry->value, value, HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->value, value, HASH_MAP_MAX_KEY_LENGTH);
entry->value[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->next = NULL;
@ -348,7 +359,9 @@ HashEntry* hashmap_insert(HashMap* hm, const char* key, byte* value) {
entry->value = (byte *) entry + sizeof(HashEntry);
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
memcpy(entry->value, value, hm->buf.chunk_size - sizeof(HashEntry));
@ -378,7 +391,9 @@ HashEntry* hashmap_reserve(HashMap* hm, const char* key) {
entry->value = (byte *) entry + sizeof(HashEntry);
strncpy(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->next = NULL;
@ -422,7 +437,9 @@ HashEntry* hashmap_get_reserve(HashMap* hm, const char* key)
entry_new->value = (byte *) entry_new + sizeof(HashEntry);
strncpy(entry_new->key, key, HASH_MAP_MAX_KEY_LENGTH);
// Ensure key length
str_move_to_pos(&key, -HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry_new->key, key, HASH_MAP_MAX_KEY_LENGTH);
entry_new->key[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
if (entry) {
@ -621,7 +638,7 @@ void hashmap_insert(HashMap* hm, int32 key, const char* value) {
entry->key = key;
strncpy(entry->value, value, HASH_MAP_MAX_KEY_LENGTH);
str_copy_short(entry->value, value, HASH_MAP_MAX_KEY_LENGTH);
entry->value[HASH_MAP_MAX_KEY_LENGTH - 1] = '\0';
entry->next = NULL;

View File

@ -14,7 +14,6 @@
#include "../hash/GeneralHash.h"
#include "../memory/RingMemory.h"
#define PERFECT_HASH_MAP_MAX_KEY_LENGTH 32
typedef uint64 (*perfect_hash_function)(const char* key, int32 seed);
const perfect_hash_function PERFECT_HASH_FUNCTIONS[] = {
@ -29,43 +28,43 @@ const perfect_hash_function PERFECT_HASH_FUNCTIONS[] = {
struct PerfectHashEntryInt32 {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
int32 value;
};
struct PerfectHashEntryInt64 {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
int64 value;
};
struct PerfectHashEntryUIntPtr {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
uintptr_t value;
};
struct PerfectHashEntryVoidP {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
void* value;
};
struct PerfectHashEntryFloat {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
f32 value;
};
struct PerfectHashEntryStr {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char value[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
char value[HASH_MAP_MAX_KEY_LENGTH];
};
struct PerfectHashEntry {
int64 element_id;
char key[PERFECT_HASH_MAP_MAX_KEY_LENGTH];
char key[HASH_MAP_MAX_KEY_LENGTH];
byte* value;
};
@ -244,7 +243,7 @@ void perfect_hashmap_insert(PerfectHashMap* hm, const char* key, const char* val
PerfectHashEntryStr* entry = (PerfectHashEntryStr *) (hm->hash_entries + hm->entry_size * index);
entry->element_id = index;
str_copy_short(entry->key, key);
memcpy(entry->value, value, PERFECT_HASH_MAP_MAX_KEY_LENGTH);
memcpy(entry->value, value, HASH_MAP_MAX_KEY_LENGTH);
}
inline

View File

@ -153,6 +153,24 @@ struct v4_byte {
};
};
struct v4_int16 {
union {
struct {
int16 x, y;
union {
int16 z, width;
};
union {
int16 w, height;
};
};
int16 v[4];
};
};
struct v2_int32 {
union {
struct {

View File

@ -25,6 +25,7 @@ struct PoolWorker {
void* result;
RingMemory ring;
ThreadPoolJobFunc func;
ThreadPoolJobFunc callback;
};
struct Worker {

View File

@ -1,16 +1,16 @@
#ifndef TOS_UI_ALIGNMENT_H
#define TOS_UI_ALIGNMENT_H
enum UIAlignH {
UI_ALIGN_H_LEFT,
UI_ALIGN_H_CENTER,
UI_ALIGN_H_RIGHT,
};
#include "../stdlib/Types.h"
enum UIAlignV {
UI_ALIGN_V_BOTTOM,
UI_ALIGN_V_CENTER,
UI_ALIGN_V_TOP,
enum UIAlign : byte {
UI_ALIGN_H_LEFT = 1 << 0,
UI_ALIGN_H_CENTER = 1 << 1,
UI_ALIGN_H_RIGHT = 1 << 2,
UI_ALIGN_V_BOTTOM = 1 << 3,
UI_ALIGN_V_CENTER = 1 << 4,
UI_ALIGN_V_TOP = 1 << 5,
};
#endif

23
ui/UIAnimation.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef TOS_UI_ANIMATION_H
#define TOS_UI_ANIMATION_H
#include "../stdlib/Types.h"
#include "../animation/AnimationEaseType.h"
#define UI_ANIMATION_ACTIVE_FLAG 0x80000000
struct UIAnimationState {
uint64 start;
uint32 duration;
// Element specific use (e.g. cursor uses 0/1 for visible/invisible for blinking)
// The highest bit indicates if the animation is active or not
int32 state;
AnimationEaseType anim_type;
};
struct UIAnimation {
uint32 duration;
AnimationEaseType anim_type;
};
#endif

View File

@ -1,216 +0,0 @@
#ifndef TOS_UI_STYLE_H
#define TOS_UI_STYLE_H
#include "../stdlib/Types.h"
struct UIAttribute {
// Attributes use ids (=type name) instead of strings
int32 attribute_id;
union {
char value_str[32];
int32 value_int;
f32 value_float;
v4_f32 value_v4_f32;
};
};
struct UIAttributeGroup {
int32 attribute_size;
UIAttribute* attributes;
};
enum UIAttributeType {
UI_ATTRIBUTE_TYPE_TYPE,
UI_ATTRIBUTE_TYPE_STYLE,
UI_ATTRIBUTE_TYPE_DIMENSION_X,
UI_ATTRIBUTE_TYPE_DIMENSION_Y,
UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH,
UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT,
UI_ATTRIBUTE_TYPE_CONTENT,
UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_H,
UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_V,
UI_ATTRIBUTE_TYPE_FONT_NAME,
UI_ATTRIBUTE_TYPE_FONT_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_FONT_COLOR,
UI_ATTRIBUTE_TYPE_FONT_SIZE,
UI_ATTRIBUTE_TYPE_FONT_WEIGHT,
UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT,
UI_ATTRIBUTE_TYPE_ALIGN_H,
UI_ATTRIBUTE_TYPE_ALIGN_V,
UI_ATTRIBUTE_TYPE_ZINDEX,
UI_ATTRIBUTE_TYPE_POSITION_X,
UI_ATTRIBUTE_TYPE_POSITION_Y,
UI_ATTRIBUTE_TYPE_PARENT,
UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE,
UI_ATTRIBUTE_TYPE_BORDER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_BORDER_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH,
UI_ATTRIBUTE_TYPE_PADDING,
UI_ATTRIBUTE_TYPE_PADDING_TOP,
UI_ATTRIBUTE_TYPE_PADDING_RIGHT,
UI_ATTRIBUTE_TYPE_PADDING_BOTTOM,
UI_ATTRIBUTE_TYPE_PADDING_LEFT,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE,
// @todo This isn't enough, we can have many animations (position, size, colors, ...)
// Maybe we need to define an animation child which overwrites the defined values
// Maybe it should use the same system as state dependent values like hover, active, ...
UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION,
UI_ATTRIBUTE_TYPE_TRANSITION_DURATION,
UI_ATTRIBUTE_TYPE_SIZE,
};
UIAttribute* ui_attribute_from_group(UIAttributeGroup* group, UIAttributeType type)
{
if (!group->attributes) {
return NULL;
}
int32 left = 0;
int32 right = type;
// Binary search since attributes are sorted by attribute_id
while (left <= right) {
int32 mid = left + (right - left) / 2;
if (group->attributes[mid].attribute_id == type) {
return &group->attributes[mid];
} else if (group->attributes[mid].attribute_id < type) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return NULL;
}
constexpr const char* ui_attribute_type_to_string(UIAttributeType e)
{
switch (e) {
case UI_ATTRIBUTE_TYPE_TYPE:
return "type";
case UI_ATTRIBUTE_TYPE_STYLE:
return "style";
case UI_ATTRIBUTE_TYPE_DIMENSION_X:
return "x";
case UI_ATTRIBUTE_TYPE_DIMENSION_Y:
return "y";
case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH:
return "width";
case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT:
return "height";
case UI_ATTRIBUTE_TYPE_FONT_NAME:
return "font_name";
case UI_ATTRIBUTE_TYPE_FONT_COLOR:
return "font_color";
case UI_ATTRIBUTE_TYPE_FONT_SIZE:
return "font_size";
case UI_ATTRIBUTE_TYPE_FONT_WEIGHT:
return "font_weight";
case UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT:
return "font_line_height";
case UI_ATTRIBUTE_TYPE_ALIGN_H:
return "align_h";
case UI_ATTRIBUTE_TYPE_ALIGN_V:
return "align_v";
case UI_ATTRIBUTE_TYPE_ZINDEX:
return "zindex";
case UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR:
return "background_color";
case UI_ATTRIBUTE_TYPE_BACKGROUND_IMG:
return "background_img";
case UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY:
return "background_img_opacity";
case UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V:
return "background_img_position_v";
case UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H:
return "background_img_position_h";
case UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE:
return "background_img_style";
case UI_ATTRIBUTE_TYPE_BORDER_COLOR:
return "border_color";
case UI_ATTRIBUTE_TYPE_BORDER_WIDTH:
return "border_width";
case UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR:
return "border_top_color";
case UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH:
return "border_top_width";
case UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR:
return "border_right_color";
case UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH:
return "border_right_width";
case UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR:
return "border_bottom_color";
case UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH:
return "border_bottom_width";
case UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR:
return "border_left_color";
case UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH:
return "border_left_width";
case UI_ATTRIBUTE_TYPE_PADDING:
return "padding";
case UI_ATTRIBUTE_TYPE_PADDING_TOP:
return "padding_top";
case UI_ATTRIBUTE_TYPE_PADDING_RIGHT:
return "padding_right";
case UI_ATTRIBUTE_TYPE_PADDING_BOTTOM:
return "padding_bottom";
case UI_ATTRIBUTE_TYPE_PADDING_LEFT:
return "padding_left";
case UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR:
return "shadow_inner_color";
case UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE:
return "shadow_inner_angle";
case UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE:
return "shadow_inner_distance";
case UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR:
return "shadow_outer_color";
case UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE:
return "shadow_outer_angle";
case UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE:
return "shadow_outer_distance";
case UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION:
return "transition_animation";
case UI_ATTRIBUTE_TYPE_TRANSITION_DURATION:
return "transition_duration";
}
return NULL;
}
#endif

13
ui/UIButton.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_BUTTON_H
#define TOS_UI_BUTTON_H
#include "../stdlib/Types.h"
struct UIButtonState {
};
struct UIButton {
};
#endif

13
ui/UICursor.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_CURSOR_H
#define TOS_UI_CURSOR_H
#include "../stdlib/Types.h"
struct UICursorState {
};
struct UICursor {
};
#endif

View File

@ -2,51 +2,44 @@
#define TOS_UI_ELEMENT_H
#include "../stdlib/Types.h"
#include "UIElementType.h"
#include "../object/Vertex.h"
#include <immintrin.h>
#include <xmmintrin.h>
struct UIElementDimension {
int16 x1;
int16 y1;
int16 x2;
int16 y2;
enum UIElementType : byte {
UI_ELEMENT_TYPE_BUTTON,
UI_ELEMENT_TYPE_SELECT,
UI_ELEMENT_TYPE_INPUT,
UI_ELEMENT_TYPE_TEXTAREA,
UI_ELEMENT_TYPE_IMAGE,
UI_ELEMENT_TYPE_TEXT,
UI_ELEMENT_TYPE_LINK,
UI_ELEMENT_TYPE_TABLE,
UI_ELEMENT_TYPE_VIEW_WINDOW,
UI_ELEMENT_TYPE_VIEW_PANEL,
UI_ELEMENT_TYPE_VIEW_TAB,
UI_ELEMENT_TYPE_CURSOR,
UI_ELEMENT_TYPE_SIZE,
};
#define UI_ELEMENT_STATE_VISIBLE 1
#define UI_ELEMENT_STATE_ACTIVE 2
#define UI_ELEMENT_STATE_FOCUSED 4
#define UI_ELEMENT_STATE_CLICKED 8
#define UI_ELEMENT_STATE_ANIMATION 16
enum UIElementState : byte {
UI_ELEMENT_STATE_ACTIVE = 1 << 0,
UI_ELEMENT_STATE_VISIBLE = 1 << 1,
UI_ELEMENT_STATE_FOCUSED = 1 << 2,
UI_ELEMENT_STATE_CLICKABLE = 1 << 3,
UI_ELEMENT_STATE_ANIMATION = 1 << 4,
};
struct UIElement {
const char* name;
int32 id;
// @see UIElementState
byte state_flag;
UIElementType type;
bool is_dynamic;
int16 window_id;
int16 panel_id;
UIStyleType style_old;
UIStyleType style_new;
UIElement* parent;
void* state;
void* details[UI_STYLE_TYPE_SIZE];
UIElementDimension dimension;
int32 state_flag;
f32 anim_elapsed;
int16 scroll_x;
int16 scroll_y;
// @todo animation state
// @todo cache vertex result for default, hover etc.
int32 vertex_count;
Vertex3DTextureColorIndex* vertices; // WARNING: This is not the official holder of the memory, its in UILayout
// @todo We could even have a pointer that points into the complete ui array making it possible to simply replace this section
// This is something we wanted to do anyway when updating sub regions on the gpu memory
uint16 children_count;
UIElement** children;
};
#endif

View File

@ -2,55 +2,116 @@
#define TOS_UI_ELEMENT_TYPE_H
#include <stdio.h>
#include "../stdlib/Types.h"
#include "UIButton.h"
#include "UISelect.h"
#include "UIInput.h"
#include "UIText.h"
#include "UITextarea.h"
#include "UIImage.h"
#include "UILink.h"
#include "UIWindow.h"
#include "UITable.h"
#include "UIPanel.h"
#include "UITab.h"
#include "UICursor.h"
enum UIElementType {
UI_ELEMENT_TYPE_BUTTON,
UI_ELEMENT_TYPE_SELECT,
UI_ELEMENT_TYPE_DROPDOWN,
UI_ELEMENT_TYPE_TEXTFIELD,
UI_ELEMENT_TYPE_TEXTAREA,
UI_ELEMENT_TYPE_IMAGE,
UI_ELEMENT_TYPE_TEXT,
UI_ELEMENT_TYPE_LINK,
UI_ELEMENT_TYPE_TABLE,
UI_ELEMENT_TYPE_VIEW_WINDOW,
UI_ELEMENT_TYPE_VIEW_PANEL,
UI_ELEMENT_TYPE_VIEW_TAB,
UI_ELEMENT_TYPE_SIZE,
};
constexpr const char* ui_element_type_to_string(UIElementType e)
constexpr
int32 ui_element_type_size(UIElementType e)
{
switch (e) {
case UI_ELEMENT_TYPE_BUTTON:
return "button";
return sizeof(UIButton);
case UI_ELEMENT_TYPE_SELECT:
return "select";
case UI_ELEMENT_TYPE_DROPDOWN:
return "dropdown";
case UI_ELEMENT_TYPE_TEXTFIELD:
return "textfield";
case UI_ELEMENT_TYPE_TEXTAREA:
return "textarea";
case UI_ELEMENT_TYPE_IMAGE:
return "image";
return sizeof(UISelect);
case UI_ELEMENT_TYPE_INPUT:
return sizeof(UIInput);
case UI_ELEMENT_TYPE_TEXT:
return "text";
return sizeof(UIText);
case UI_ELEMENT_TYPE_TEXTAREA:
return sizeof(UITextarea);
case UI_ELEMENT_TYPE_IMAGE:
return sizeof(UIImage);
case UI_ELEMENT_TYPE_LINK:
return "link";
return sizeof(UILink);
case UI_ELEMENT_TYPE_TABLE:
return "table";
return sizeof(UITable);
case UI_ELEMENT_TYPE_VIEW_WINDOW:
return "view_window";
return sizeof(UIWindow);
case UI_ELEMENT_TYPE_VIEW_PANEL:
return "view_panel";
return sizeof(UIPanel);
case UI_ELEMENT_TYPE_VIEW_TAB:
return "view_tab";
return sizeof(UITab);
case UI_ELEMENT_TYPE_CURSOR:
return sizeof(UICursor);
default: {
UNREACHABLE();
}
}
}
constexpr
int32 ui_element_state_size(UIElementType e)
{
switch (e) {
case UI_ELEMENT_TYPE_BUTTON:
return sizeof(UIButtonState);
case UI_ELEMENT_TYPE_SELECT:
return sizeof(UISelectState);
case UI_ELEMENT_TYPE_INPUT:
return sizeof(UIInputState);
case UI_ELEMENT_TYPE_TEXT:
return sizeof(UITextState);
case UI_ELEMENT_TYPE_TEXTAREA:
return sizeof(UITextareaState);
case UI_ELEMENT_TYPE_IMAGE:
return sizeof(UIImageState);
case UI_ELEMENT_TYPE_LINK:
return sizeof(UILinkState);
case UI_ELEMENT_TYPE_TABLE:
return sizeof(UITableState);
case UI_ELEMENT_TYPE_VIEW_WINDOW:
return sizeof(UIWindowState);
case UI_ELEMENT_TYPE_VIEW_PANEL:
return sizeof(UIPanelState);
case UI_ELEMENT_TYPE_VIEW_TAB:
return sizeof(UITabState);
case UI_ELEMENT_TYPE_CURSOR:
return sizeof(UICursorState);
default: {
UNREACHABLE();
}
}
}
constexpr
int32 ui_element_type_to_id(const char* str)
{
if (str_compare("button", str) == 0) {
return UI_ELEMENT_TYPE_BUTTON;
} else if (str_compare("select", str) == 0) {
return UI_ELEMENT_TYPE_SELECT;
} else if (str_compare("input", str) == 0) {
return UI_ELEMENT_TYPE_INPUT;
} else if (str_compare("textarea", str) == 0) {
return UI_ELEMENT_TYPE_TEXTAREA;
} else if (str_compare("image", str) == 0) {
return UI_ELEMENT_TYPE_IMAGE;
} else if (str_compare("text", str) == 0) {
return UI_ELEMENT_TYPE_TEXT;
} else if (str_compare("link", str) == 0) {
return UI_ELEMENT_TYPE_LINK;
} else if (str_compare("table", str) == 0) {
return UI_ELEMENT_TYPE_TABLE;
} else if (str_compare("view_window", str) == 0) {
return UI_ELEMENT_TYPE_VIEW_WINDOW;
} else if (str_compare("view_panel", str) == 0) {
return UI_ELEMENT_TYPE_VIEW_PANEL;
} else if (str_compare("view_tab", str) == 0) {
return UI_ELEMENT_TYPE_VIEW_TAB;
}
return NULL;
return -1;
}
#endif

13
ui/UIImage.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_IMAGE_H
#define TOS_UI_IMAGE_H
#include "../stdlib/Types.h"
struct UIImageState {
};
struct UIImage {
};
#endif

103
ui/UIInput.h Normal file
View File

@ -0,0 +1,103 @@
#ifndef TOS_UI_INPUT_H
#define TOS_UI_INPUT_H
#include "../stdlib/Types.h"
#include "../camera/Camera.h"
#include "attribute/UIAttributeBorder.h"
#include "attribute/UIAttributeShadow.h"
#include "attribute/UIAttributeFont.h"
#include "attribute/UIAttributeBackground.h"
#include "attribute/UIAttributeDimension.h"
#include "UIAnimation.h"
#include "UIStyleType.h"
#include "UIElement.h"
#include "../math/Evaluator.h"
enum UIInputType : byte {
UI_INPUT_TYPE_TEXT,
UI_INPUT_TYPE_NUMERIC,
UI_INPUT_TYPE_DATE,
UI_INPUT_TYPE_DATE_TIME,
UI_INPUT_TYPE_TIME,
};
struct UIInputState {
char content[512];
char* content_ref;
uint16 cursor_pos_x;
uint16 cursor_pos_y;
UIAnimationState animation;
UIInputType type;
int32 min_value;
int32 max_value;
uint16 max_input_length;
};
struct UIInput {
UIAttributeDimension dimension;
byte opacity; // 1 byte alpha channel
byte padding;
// Animation used to get into this style
UIAnimation animation;
UIAttributeFont font;
UIAttributeBackground background;
UIAttributeBorder border;
UIAttributeShadow shadow_outer;
UIAttributeShadow shadow_inner;
};
void ui_input_state_populate(const UIAttributeGroup* group, UIInputState* state) {
for (int32 i = 0; i < group->attribute_size; ++i) {
switch (group->attributes[i].attribute_id) {
case UI_ATTRIBUTE_TYPE_TYPE: {
state->type = (UIInputType) group->attributes[i].value_int;
} break;
case UI_ATTRIBUTE_TYPE_MIN_VALUE: {
state->min_value = group->attributes[i].value_int;
} break;
case UI_ATTRIBUTE_TYPE_MAX_VALUE: {
state->max_value = group->attributes[i].value_int;
} break;
case UI_ATTRIBUTE_TYPE_MAX_INPUT_LENGTH: {
state->max_input_length = (uint16) group->attributes[i].value_int;
} break;
}
}
}
void ui_input_element_populate(
const UIAttributeGroup* group,
UIElement* element,
UIStyleType style_type,
EvaluatorVariable* variables
) {
if (element->parent) {
// @bug How to ensure that the parent is initialized before the child element
// Currently the order of the initialization depends on the theme file, NOT the layout file
// We could fix it by loading the style based on the layout order but this would result in many misses when looking up styles
// The reason for these misses are, that often only 1-2 style_types exist per element
UIInput* parent = (UIInput *) element->parent->details[UI_STYLE_TYPE_DEFAULT];
variables[2].value = parent->dimension.dimension.x;
variables[3].value = parent->dimension.dimension.y;
variables[4].value = parent->dimension.dimension.width;
variables[5].value = parent->dimension.dimension.height;
}
UIInput* e = (UIInput *) element->details[style_type];
// First set all values, which we can set immediately
for (int32 i = 0; i < group->attribute_size; ++i) {
switch (group->attributes[i].attribute_id) {
case UI_ATTRIBUTE_TYPE_DIMENSION_X:
case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH:
case UI_ATTRIBUTE_TYPE_DIMENSION_Y:
case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT: {
ui_theme_assign_dimension(&e->dimension, &group->attributes[i], 6, variables);
} break;
}
}
}
#endif

13
ui/UILabel.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_LABEL_H
#define TOS_UI_LABEL_H
#include "../stdlib/Types.h"
struct UILabelState {
};
struct UILabel {
};
#endif

View File

@ -1,10 +1,19 @@
#ifndef TOS_UI_LAYOUT_H
#define TOS_UI_LAYOUT_H
#include <string.h>
#include "../stdlib/Types.h"
#include "../stdlib/HashMap.h"
#include "UIElement.h"
#include "../asset/Asset.h"
#include "../camera/Camera.h"
#include "../system/FileUtils.cpp"
#include "UITheme.h"
#include "UIElement.h"
#include "UIElementType.h"
#define UI_LAYOUT_VERSION 1
// Modified for every scene
struct UILayout {
@ -18,130 +27,567 @@ struct UILayout {
// Contains all UI elements also dynamic ones (e.g. movable windows)
uint32* ui_chroma_codes;
// Contains constant UI elements that usually don't change (e.g. HUD)
uint32* ui_chroma_codes_static;
// Used to directly find element by name
// The values are the UIElements
// The values are pointers to the UIElements
// @todo Should be a perfect hash map
HashMap hash_map;
int32 vertex_size;
// UI data
// Only the size of the fixed layout, doesn't include the theme specific data
uint32 layout_size;
// Total possible size
uint32 data_size;
// Holds the ui elements
// The structure of the data is as follows:
// 1. HashMap data (points to 2.a.)
// 2. UIElements (default)
// a. General UIElement
// b. Element specific state
// c. Element specific style
// 3. Additional UI styles (see c.), dynamically created when the theme is loaded
// We effectively create a tree in data where the individual elements can get directly accessed through the hashmap
byte* data;
// Changes on a as needed basis
uint32 vertex_size_static;
bool static_content_changed;
// Changes every frame
uint32 vertex_size_dynamic;
bool dynamic_content_changed;
// Contains both static and dynamic content
// @todo The vertices shouldn't be an Asset, it's more like a ECS, maybe it's not even in RAM and only in VRAM?!
// One of the reasons for this being an asset is also that it is easy to log ram/vram usage but that can be fixed
Asset* ui_asset;
// @question Should we maybe also hold the font atlas asset here AND the color palette?
// Total count of the ui_asset vertices
uint32 vertex_size;
// Defines the length of the static vertex array
int32 vertex_size_static;
// @question Should we maybe also hold the font atlas asset here AND the color palette?
};
void count_direct_child_elements(UIElement* element, const char* pos, int32 parent_level)
{
// Find amount of child elements
// We have to perform a lookahead since this determins the since this determines the size of our current element
uint16 direct_child_elements = 0;
int32 level = 0;
while (*pos != '\0') {
while (*pos == ' ' || *pos == '\t') {
++pos;
++level;
}
if (level > parent_level + 4) {
// This element is a childrens child and not a direct child
continue;
} else if (level <= parent_level) {
// We are no longer inside of element
break;
}
}
element->children_count = direct_child_elements;
}
void assign_child_elements(UILayout* layout, UIElement* element, char* pos, int32 parent_level) {
int32 current_child_pos = 0;
char block_name[28];
int32 level = 0;
while (*pos != '\0') {
while (*pos == ' ' || *pos == '\t') {
++pos;
++level;
}
if (level > parent_level + 4) {
// This element is a childrens child and not a direct child
continue;
} else if (level <= parent_level) {
// We are no longer inside of element
break;
}
str_copy_move_until(&pos, block_name, ":");
str_move_past(&pos, '\n');
element->children[current_child_pos] = (UIElement *) (
layout->data
+ ((HashEntryInt64 *) hashmap_get_entry(&layout->hash_map, block_name))->value
);
element->children[current_child_pos]->parent = element;
++current_child_pos;
}
}
// WARNING: theme needs to have memory already reserved and assigned to data
void layout_from_file_txt(
UILayout* layout,
const char* path,
RingMemory* ring
) {
FileBody file;
file_read(path, &file, ring);
ASSERT_SIMPLE(file.size);
char* pos = (char *) file.content;
// move past the version string
pos += 8;
// Use version for different handling
int32 version = strtol(pos, &pos, 10); ++pos;
// 1. Iteration: We have to find how many elements are defined in the layout file.
// Therefore we have to do an initial iteration
int32 temp_element_count = 0;
while (*pos != '\0') {
// Skip all white spaces
str_skip_empty(&pos);
++temp_element_count;
// Go to the next line
str_move_past(&pos, '\n');
}
// 2. Iteration: Fill HashMap
// @performance This is probably horrible since we are not using a perfect hashing function (1 hash -> 1 index)
// I wouldn't be surprised if we have a 50% hash overlap (2 hashes -> 1 index)
hashmap_create(&layout->hash_map, temp_element_count, sizeof(HashEntryVoidP), layout->data);
int64 hm_size = hashmap_size(&layout->hash_map);
pos = (char *) file.content;
// move past version string
str_move_past(&pos, '\n');
char block_name[28];
char block_type[28];
byte* element_data = layout->data + hm_size;
int32 level;
while (*pos != '\0') {
if (*pos == '\n') {
++pos;
continue;
}
level = 0;
while (*pos == ' ' || *pos == '\t') {
++pos;
++level;
}
if (*pos == '\n' || *pos == '\0') {
continue;
}
str_copy_move_until(&pos, block_name, ":"); ++pos;
str_copy_move_until(&pos, block_type, " \r\n");
str_move_past(&pos, '\n');
UIElement* element = (UIElement *) element_data;
element->type = (UIElementType) ui_element_type_to_id(block_type);
element->children = (UIElement **) (element_data + sizeof(UIElement));
count_direct_child_elements(element, pos, level);
// Every UIElement can have child elements, we need to store the pointers to those elements
element_data += sizeof(UIElement) + sizeof(UIElement*) * element->children_count;
// We also put the state data after this element
element->state = element_data;
element_data += ui_element_state_size(element->type);
// We also put the default element data after this element
// Depending on the theme we will have also additional styles (e.g. :active, :hidden, ...)
element->details[0] = element_data;
// @performance We should probably make sure the data is nicely aligned here
element_data += ui_element_type_size(element->type);
// Insert new element
hashmap_insert(&layout->hash_map, block_name, element_data - layout->data);
}
// 3. Iteration: Create child references
pos = (char *) file.content;
// move past version string
str_move_past(&pos, '\n');
while (*pos != '\0') {
if (*pos == '\n') {
++pos;
continue;
}
level = 0;
while (*pos == ' ' || *pos == '\t') {
++pos;
++level;
}
if (*pos == '\n' || *pos == '\0') {
continue;
}
str_copy_move_until(&pos, block_name, ":");
str_move_past(&pos, '\n');
UIElement* element = (UIElement *) (layout->data + ((HashEntryInt64 *) hashmap_get_entry(&layout->hash_map, block_name))->value);
assign_child_elements(layout, element, pos, level);
}
}
static inline
void ui_layout_serialize_element(HashEntryInt64* entry, byte* data, byte** pos, const byte* start)
{
// @performance Are we sure the data is nicely aligned?
// Probably depends on the from_txt function and the start of layout->data
UIElement* element = (UIElement *) (data + entry->value);
**pos = element->state_flag;
*pos += sizeof(element->state_flag);
**pos = element->type;
*pos += sizeof(element->type);
**pos = element->style_old;
*pos += sizeof(element->style_old);
**pos = element->style_new;
*pos += sizeof(element->style_new);
// Parent
if (element->parent) {
*((uint64 *) *pos) = SWAP_ENDIAN_LITTLE((uint64) ((uintptr_t) element->parent - (uintptr_t) data));
} else {
memset(*pos, 0, sizeof(uint64));
}
*pos += sizeof(uint64);
// State
if (element->state) {
*((uint64 *) *pos) = SWAP_ENDIAN_LITTLE((uint64) ((uintptr_t) element->state - (uintptr_t) data));
} else {
memset(*pos, 0, sizeof(uint64));
}
*pos += sizeof(uint64);
// Details
for (int32 j = 0; j < UI_STYLE_TYPE_SIZE; ++j) {
if (element->details[j]) {
*((uint64 *) *pos) = SWAP_ENDIAN_LITTLE((uint64) ((uintptr_t) element->details[j] - (uintptr_t) data));
} else {
memset(*pos, 0, sizeof(uint64));
}
*pos += sizeof(uint64);
}
*((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(element->children_count);
*pos += sizeof(element->children_count);
for (int32 j = 0; j < element->children_count; ++j) {
*((uint64 *) *pos) = SWAP_ENDIAN_LITTLE((uint64) ((uintptr_t) element->children[j] - (uintptr_t) data));
*pos += sizeof(uint64);
}
// @performance, shouldn't it just be memset for both elements?
// state element data e.g. UIInputState
memcpy(*pos, element->state, ui_element_state_size(element->type));
*pos += ui_element_state_size(element->type);
// detailed element data e.g. UIInput
memcpy(*pos, element->details[0], ui_element_type_size(element->type));
*pos += ui_element_type_size(element->type);
}
int32 layout_to_data(
const UILayout* layout,
byte* data
) {
byte* pos = data;
byte* max_pos = data;
// version
*((int32 *) pos) = SWAP_ENDIAN_LITTLE(UI_LAYOUT_VERSION);
pos += sizeof(int32);
// hashmap
byte* start = pos;
pos += hashmap_dump(&layout->hash_map, pos);
// UIElement data
for (uint32 i = 0; i < layout->hash_map.buf.count; ++i) {
if (!layout->hash_map.table[i]) {
continue;
}
HashEntryInt64* entry = (HashEntryInt64 *) layout->hash_map.table[i];
pos = start + entry->value;
ui_layout_serialize_element(entry, layout->data, &pos, start);
if (pos > max_pos) {
max_pos = pos;
}
// save all the next elements
while (entry->next) {
pos = start + entry->value;
ui_layout_serialize_element(entry, layout->data, &pos, start);
if (pos > max_pos) {
max_pos = pos;
}
entry = entry->next;
}
}
return (int32) (max_pos - data);
}
static inline
void ui_layout_parse_element(HashEntryInt64* entry, byte* data, const byte** pos)
{
// @performance Are we sure the data is nicely aligned?
// Probably depends on the from_txt function and the start of layout->data
// Change offset to pointer
entry->value = (uintptr_t) data + entry->value;
UIElement* element = (UIElement *) entry->value;
element->state_flag = **pos;
*pos += sizeof(element->state_flag);
element->type = (UIElementType) **pos;
*pos += sizeof(element->type);
element->style_old = (UIStyleType) **pos;
*pos += sizeof(element->style_old);
element->style_new = (UIStyleType) **pos;
*pos += sizeof(element->style_new);
// Parent
uint64 temp = SWAP_ENDIAN_LITTLE(*((uint64 *) *pos));
element->parent = temp
? (UIElement *) (data + temp)
: NULL;
*pos += sizeof(uint64);
// State
temp = SWAP_ENDIAN_LITTLE(*((uint64 *) *pos));
element->state = temp
? data + temp
: NULL;
*pos += sizeof(uint64);
// Details
for (int32 j = 0; j < UI_STYLE_TYPE_SIZE; ++j) {
temp = SWAP_ENDIAN_LITTLE(*((uint64 *) *pos));
element->details[j] = temp
? data + temp
: NULL;
*pos += sizeof(uint64);
}
element->children_count = SWAP_ENDIAN_LITTLE(*((uint16 *) *pos));
*pos += sizeof(element->children_count);
for (int32 j = 0; j < element->children_count; ++j) {
temp = SWAP_ENDIAN_LITTLE(*((uint64 *) *pos));
element->children[j] = temp
? (UIElement *) (data + temp)
: NULL; // this should never happen since the children_count only gets incremented if it has a child
if (element->children[j]) {
element->children[j]->parent = element;
}
*pos += sizeof(uint64);
}
// @performance, shouldn't it just be memset for both elements?
// state element data e.g. UIInput
memcpy(element->state, *pos, ui_element_state_size(element->type));
*pos += ui_element_state_size(element->type);
// detailed element data e.g. UIInput
memcpy(element->details[0], *pos, ui_element_type_size(element->type));
*pos += ui_element_type_size(element->type);
}
// The size of layout->data should be the file size + a bunch of additional data for additional theme dependent "UIElements->details".
// Yes, this means we have a little too much data but not by a lot
int32 layout_from_data(
const byte* data,
UILayout* layout
) {
const byte* pos = data;
const byte* max_pos = pos;
int32 version = *((int32 *) pos);
pos += sizeof(version);
// Prepare hashmap (incl. reserve memory) by initializing it the same way we originally did
// Of course we still need to populate the data using hashmap_load()
// The value is a int64 (because this is the value of the chunk buffer size but the hashmap only allows int32)
hashmap_create(&layout->hash_map, (int32) SWAP_ENDIAN_LITTLE(*((uint64 *) pos)), sizeof(HashEntryInt64), layout->data);
const byte* start = data;
pos += hashmap_load(&layout->hash_map, pos);
// layout data
for (int32 i = 0; i < layout->hash_map.buf.count; ++i) {
if (!layout->hash_map.table[i]) {
continue;
}
HashEntryInt64* entry = (HashEntryInt64 *) layout->hash_map.table[i];
pos = start + entry->value;
ui_layout_parse_element(entry, layout->data, &pos);
if (pos > max_pos) {
max_pos = pos;
}
// save all the next elements
while (entry->next) {
pos = start + entry->value;
ui_layout_parse_element(entry, layout->data, &pos);
if (pos > max_pos) {
max_pos = pos;
}
entry = entry->next;
}
}
layout->layout_size = (uint32) (max_pos - data);
return (int32) layout->layout_size;
}
// @performance Implement a way to only load a specific element and all its children
// This way we can re-load specific elements on change and we could also greatly reduce the setup time by ignoring ui elements that are rarely visible
void layout_from_theme(
UILayout* layout,
const UIThemeStyle* theme,
const Camera* camera
) {
EvaluatorVariable variables[] = {
{ "vw", (f32) camera->viewport_width },
{ "vh", (f32) camera->viewport_height },
{ "px", 0.0 }, // Placeholder for parent values
{ "py", 0.0 }, // Placeholder for parent values
{ "pw", 0.0 }, // Placeholder for parent values
{ "ph", 0.0 }, // Placeholder for parent values
};
// Current position where we can the different sub elements (e.g. :hover, :active, ...)
byte* dynamic_pos = layout->data + layout->layout_size;
// We first need to handle the default element -> iterate all elements but only handle the default style
// @todo do here
for (int32 i = 0; i < theme->hash_map.buf.count; ++i) {
if (!theme->hash_map.table[i]) {
continue;
}
HashEntryUIntPtr* style_entry = (HashEntryUIntPtr *) theme->hash_map.table[i];
// We don't handle special styles here, only the default one
if (strchr(style_entry->key, ':')) {
continue;
}
HashEntry* entry = hashmap_get_entry(&layout->hash_map, style_entry->key);
if (!entry) {
// Couldn't even find the base element
continue;
}
// Populate default element
UIElement* element = (UIElement *) entry->value;
UIAttributeGroup* group = (UIAttributeGroup *) style_entry->value;
switch (element->type) {
case UI_ELEMENT_TYPE_INPUT: {
ui_input_state_populate(group, (UIInputState *) element->state);
ui_input_element_populate(group, element, UI_STYLE_TYPE_DEFAULT, variables);
} break;
}
}
// We iterate every style
// 1. Fill default element if it is default style
// 2. Create and fill new element if it isn't default style (e.g. :hover)
for (int32 i = 0; i < theme->hash_map.buf.count; ++i) {
if (!theme->hash_map.table[i]) {
continue;
}
HashEntryUIntPtr* style_entry = (HashEntryUIntPtr *) theme->hash_map.table[i];
// We only handle special styles here, not the default one
char* special = strchr(style_entry->key, ':');
if (!special) {
// The default element was already handled outside this loop
continue;
}
UIStyleType style_type = (UIStyleType) ui_style_type_to_id(special);
char pure_name[HASH_MAP_MAX_KEY_LENGTH];
str_copy_until(style_entry->key, pure_name, ':');
HashEntry* entry = hashmap_get_entry(&layout->hash_map, pure_name);
if (!entry) {
// Couldn't even find the base element
continue;
}
UIElement* element = (UIElement *) entry->value;
// Doesn't exist (usually the first load, but exists when we resize our window)
if (!element->details[style_type]) {
element->details[style_type] = dynamic_pos;
dynamic_pos += ui_element_type_size(element->type);
}
// The style inherits from the default element
memcpy(element->details[style_type], element->details[0], ui_element_type_size(element->type));
// Populate element details
UIAttributeGroup* group = (UIAttributeGroup *) style_entry->value;
switch (element->type) {
case UI_ELEMENT_TYPE_INPUT: {
ui_input_element_populate(group, element, style_type, variables);
} break;
}
}
}
inline
uint32 layout_element_from_location(UILayout* layout, uint16 x, uint16 y)
{
return layout->ui_chroma_codes[layout->width * y / 4 + x / 4];
}
// This function should only get called if the location of a UI Element changes
// @performance How to handle moving elements (= dragging a window). We don't want to update this while dragging!
void layout_chroma_codes_update(UILayout* layout)
{
// Reset all
memcpy(layout->ui_chroma_codes, layout->ui_chroma_codes_static, layout->width * layout->height * sizeof(uint32));
// @question Are the dimension values below even absolute? They may be in relation to the parent?!
for (int32 i = 0; i < layout->hash_map.buf.count; ++i) {
if (!layout->hash_map.table[i]) {
continue;
}
HashEntry* entry = (HashEntry *) layout->hash_map.table[i];
UIElement* element = (UIElement *) entry->value;
if (element->is_dynamic) {
continue;
}
int32 y_start = element->dimension.y1 / 4;
int32 y_end = element->dimension.y2 / 4;
int32 x_start = element->dimension.x1 / 4;
int32 x_end = element->dimension.x2 / 4;
for (int32 y = y_start; y < y_end; ++y) {
int32 y_offset = layout->width * y;
for (int32 x = x_start; x < x_end; ++x) {
layout->ui_chroma_codes[y_offset + x] = (uint32) element->id;
}
}
// Now handle all next elements
while (entry->next) {
entry = entry->next;
element = (UIElement *) entry->value;
y_start = element->dimension.y1 / 4;
y_end = element->dimension.y2 / 4;
x_start = element->dimension.x1 / 4;
x_end = element->dimension.x2 / 4;
for (int32 y = y_start; y < y_end; ++y) {
int32 y_offset = layout->width * y;
for (int32 x = x_start; x < x_end; ++x) {
layout->ui_chroma_codes[y_offset + x] = (uint32) element->id;
}
}
}
}
}
void layout_chroma_codes_update_static(UILayout* layout)
{
// Reset all
memset(layout->ui_chroma_codes_static, 0, layout->width * layout->height * sizeof(uint32));
// @question Are the dimension values below even absolute? They may be in relation to the parent?!
for (int32 i = 0; i < layout->hash_map.buf.count; ++i) {
if (!layout->hash_map.table[i]) {
continue;
}
HashEntry* entry = (HashEntry *) layout->hash_map.table[i];
UIElement* element = (UIElement *) entry->value;
if (!element->is_dynamic) {
continue;
}
int32 y_start = element->dimension.y1 / 4;
int32 y_end = element->dimension.y2 / 4;
int32 x_start = element->dimension.x1 / 4;
int32 x_end = element->dimension.x2 / 4;
for (int32 y = y_start; y < y_end; ++y) {
int32 y_offset = layout->width * y;
for (int32 x = x_start; x < x_end; ++x) {
layout->ui_chroma_codes_static[y_offset + x] = (uint32) element->id;
}
}
// Now handle all next elements
while (entry->next) {
entry = entry->next;
element = (UIElement *) entry->value;
y_start = element->dimension.y1 / 4;
y_end = element->dimension.y2 / 4;
x_start = element->dimension.x1 / 4;
x_end = element->dimension.x2 / 4;
for (int32 y = y_start; y < y_end; ++y) {
int32 y_offset = layout->width * y;
for (int32 x = x_start; x < x_end; ++x) {
layout->ui_chroma_codes_static[y_offset + x] = (uint32) element->id;
}
}
}
}
}
#endif

13
ui/UILink.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_LINK_H
#define TOS_UI_LINK_H
#include "../stdlib/Types.h"
struct UILinkState {
};
struct UILink {
};
#endif

13
ui/UIPanel.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_PANEL_H
#define TOS_UI_PANEL_H
#include "../stdlib/Types.h"
struct UIPanelState {
};
struct UIPanel {
};
#endif

13
ui/UISelect.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_SELECT_H
#define TOS_UI_SELECT_H
#include "../stdlib/Types.h"
struct UISelectState {
};
struct UISelect {
};
#endif

34
ui/UIStyleType.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef TOS_UI_STYLE_TYPE_H
#define TOS_UI_STYLE_TYPE_H
#include "../stdlib/Types.h"
enum UIStyleType : byte {
UI_STYLE_TYPE_DEFAULT, // = :visible
UI_STYLE_TYPE_HIDDEN,
UI_STYLE_TYPE_ACTIVE, // e.g. input
UI_STYLE_TYPE_DISABLED, // disabled form elements
UI_STYLE_TYPE_HOVER, // e.g. button
UI_STYLE_TYPE_MANUAL,
UI_STYLE_TYPE_SIZE,
};
constexpr
int32 ui_style_type_to_id(const char* str)
{
if (str_compare(":hidden", str) == 0) {
return UI_STYLE_TYPE_HIDDEN;
} else if (str_compare(":active", str) == 0) {
return UI_STYLE_TYPE_ACTIVE;
} else if (str_compare(":diabled", str) == 0) {
return UI_STYLE_TYPE_DISABLED;
} else if (str_compare(":hover", str) == 0) {
return UI_STYLE_TYPE_HOVER;
} else if (str_compare(":manual", str) == 0) {
return UI_STYLE_TYPE_MANUAL;
}
return -1;
}
#endif

13
ui/UITab.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_TAB_H
#define TOS_UI_TAB_H
#include "../stdlib/Types.h"
struct UITabState {
};
struct UITab {
};
#endif

13
ui/UITable.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_TABLE_H
#define TOS_UI_TABLE_H
#include "../stdlib/Types.h"
struct UITableState {
};
struct UITable {
};
#endif

13
ui/UIText.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_TEXT_H
#define TOS_UI_TEXT_H
#include "../stdlib/Types.h"
struct UITextState {
};
struct UIText {
};
#endif

13
ui/UITextarea.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_TEXTAREA_H
#define TOS_UI_TEXTAREA_H
#include "../stdlib/Types.h"
struct UITextareaState {
};
struct UITextarea {
};
#endif

View File

@ -9,7 +9,7 @@
#include "../font/Font.h"
#include "../system/FileUtils.cpp"
#include "UIAttribute.h"
#include "attribute/UIAttribute.h"
#include "UIElementType.h"
#define UI_THEME_VERSION 1
@ -20,43 +20,16 @@
// WARNING: Make sure the order of this struct and UITheme is the same for the first elements
// This allows us to cast between both
struct UIThemeStyle {
byte* data;
// A theme may have N named styles
// The hashmap contains the offset where the respective style can be found
// @performance Switch to perfect hash map
HashMap hash_map;
byte* data;
// It feels weird that this is here, especially considering we could have multiple fonts
Font* font;
};
// General theme for all scenes
struct UITheme {
// This one usually remains unchanged unless someone changes the theme
UIThemeStyle ui_general;
// This one is scene specific and is loaded with the scene
// We have a pointer that references the currently active scene
// The other two elements contain the actual data.
// This allows us to easily pre-fetch scene styles and the pointer allows us to easily switch between them
// When loading a new scene we simply use the style that is currently not pointed to by primary_scene
UIThemeStyle* primary_scene;
UIThemeStyle ui_scene1;
UIThemeStyle ui_scene2;
// @question This basically means we only support 1 font for the UI ?!
// This is probably something to re-consider
Font font;
// @todo add cursor styles
// @todo what about ui audio?
char name[32];
};
// @performance Consider to replace HashEntryInt64 with HashEntryVoidP.
// This way we wouldn't have to do theme->data + entry->value and could just use entry->value
// Of course this means during the saving and loading we need to convert to and from offsets
// The problem is that the actual dumping and loading for that part doesn't happen in the hashmap but in the chunk_memory
// The chunk_memory doesn't know how the value look like -> cannot do the conversion
inline
UIAttributeGroup* theme_style_group(UIThemeStyle* theme, const char* group_name)
{
@ -66,7 +39,7 @@ UIAttributeGroup* theme_style_group(UIThemeStyle* theme, const char* group_name)
return NULL;
}
return (UIAttributeGroup *) (theme->data + entry->value);
return (UIAttributeGroup *) entry->value;
}
inline
@ -78,9 +51,10 @@ UIAttributeGroup* theme_style_group(UIThemeStyle* theme, const char* group_name,
return NULL;
}
return (UIAttributeGroup *) (theme->data + entry->value);
return (UIAttributeGroup *) entry->value;
}
static inline
int compare_by_attribute_id(const void* a, const void* b) {
UIAttribute* attr_a = (UIAttribute *) a;
UIAttribute* attr_b = (UIAttribute *) b;
@ -117,11 +91,6 @@ void theme_from_file_txt(
// Use version for different handling
int32 version = strtol(pos, &pos, 10); ++pos;
bool block_open = false;
char block_name[32];
char attribute_name[32];
bool last_token_newline = false;
// We have to find how many groups are defined in the theme file.
// Therefore we have to do an initial iteration
int32 temp_group_count = 0;
@ -134,13 +103,8 @@ void theme_from_file_txt(
++temp_group_count;
}
// Go to the end of the line
str_move_to(&pos, '\n');
// Go to next line
if (*pos != '\0') {
++pos;
}
// Go to the next line
str_move_past(&pos, '\n');
}
// @performance This is probably horrible since we are not using a perfect hashing function (1 hash -> 1 index)
@ -155,6 +119,10 @@ void theme_from_file_txt(
// move past version string
str_move_past(&pos, '\n');
bool block_open = false;
char block_name[28];
char attribute_name[32];
bool last_token_newline = false;
while (*pos != '\0') {
str_skip_whitespace(&pos);
@ -175,7 +143,7 @@ void theme_from_file_txt(
last_token_newline = false;
if (!block_open) {
str_copy_move_until(&pos, block_name, " \n", sizeof(" \n") - 1);
str_copy_move_until(&pos, block_name, " \r\n");
// All blocks need to start with #. In the past this wasn't the case and may not be in the future. This is why we keep this if here.
if (*block_name == '#' || *block_name == '.') {
@ -200,166 +168,16 @@ void theme_from_file_txt(
continue;
}
str_copy_move_until(&pos, attribute_name, " :\n", sizeof(" :\n") - 1);
str_copy_move_until(&pos, attribute_name, " :\n");
// Skip any white spaces or other delimeter
str_skip_list(&pos, " \t:", sizeof(" \t:") - 1);
ASSERT_SIMPLE((*pos != '\0' && *pos != '\n'));
// Handle different attribute types
// Parse attribute value
UIAttribute attribute = {};
if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_TYPE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_TYPE;
char str[32];
str_copy_move_until(&pos, str, '\n');
for (int32 j = 0; j < UI_ELEMENT_TYPE_SIZE; ++j) {
if (strcmp(str, ui_element_type_to_string((UIElementType) j)) == 0) {
attribute.value_int = j;
break;
}
}
++pos;
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_STYLE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_STYLE;
str_copy_move_until(&pos, attribute.value_str, '\n');
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_FONT_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_SIZE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_FONT_SIZE;
attribute.value_float = strtof(pos, &pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_WEIGHT), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_FONT_WEIGHT;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT;
attribute.value_float = strtof(pos, &pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_ALIGN_H), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_ALIGN_H;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_ALIGN_V), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_ALIGN_V;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_ZINDEX), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_ZINDEX;
attribute.value_float = SWAP_ENDIAN_LITTLE(strtof(pos, &pos));
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_IMG), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_IMG;
str_copy_move_until(&pos, attribute.value_str, '\n');
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY;
attribute.value_float = SWAP_ENDIAN_LITTLE(strtof(pos, &pos));
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_WIDTH), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_WIDTH;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_PADDING), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_PADDING;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_PADDING_TOP), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_PADDING_TOP;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_PADDING_RIGHT), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_PADDING_RIGHT;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_PADDING_BOTTOM), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_PADDING_BOTTOM;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_PADDING_LEFT), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_PADDING_LEFT;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE;
attribute.value_float = strtof(pos, &pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR), attribute_name) == 0) {
++pos; // Skip '#'
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR;
hexstr_to_rgba(&attribute.value_v4_f32, pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE;
attribute.value_float = strtof(pos, &pos);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION;
attribute.value_int = strtoul(pos, &pos, 10);
} else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_TRANSITION_DURATION), attribute_name) == 0) {
attribute.attribute_id = UI_ATTRIBUTE_TYPE_TRANSITION_DURATION;
attribute.value_float = strtof(pos, &pos);
} else {
str_move_to(&pos, '\n');
continue;
}
ui_attribute_parse_value(&attribute, attribute_name, pos);
// Again, currently this if check is redundant but it wasn't in the past and we may need it again in the future.
if (block_name[0] == '#' || block_name[0] == '.') {
@ -381,6 +199,52 @@ void theme_from_file_txt(
qsort(temp_group->attributes, temp_group->attribute_size, sizeof(UIAttribute), compare_by_attribute_id);
}
static inline
void ui_theme_parse_group(HashEntryInt64* entry, byte* data, const byte** pos)
{
// @performance Are we sure the data is nicely aligned?
// Probably depends on the from_txt function and the start of theme->data
// Change offset to pointer
entry->value = (uintptr_t) data + entry->value;
UIAttributeGroup* group = (UIAttributeGroup *) entry->value;
group->attribute_size = SWAP_ENDIAN_LITTLE(*((int32 *) *pos));
*pos += sizeof(group->attribute_size);
for (uint32 j = 0; j < group->attribute_size; ++j) {
group->attributes[j].attribute_id = (UIAttributeType) SWAP_ENDIAN_LITTLE(*((uint16 *) *pos));
*pos += sizeof(group->attributes[j].attribute_id);
group->attributes[j].datatype = *((UIAttributeDataType *) *pos);
*pos += sizeof(group->attributes[j].datatype);
if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) {
group->attributes[j].value_int = SWAP_ENDIAN_LITTLE(*((int32 *) *pos));
*pos += sizeof(group->attributes[j].value_int);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) {
group->attributes[j].value_float = SWAP_ENDIAN_LITTLE(*((f32 *) *pos));
*pos += sizeof(group->attributes[j].value_float);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_STR) {
memcpy(group->attributes[j].value_str, *pos, sizeof(group->attributes[j].value_str));
*pos += sizeof(group->attributes[j].value_str);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_V4_F32) {
group->attributes[j].value_v4_f32.x = SWAP_ENDIAN_LITTLE(*((f32 *) *pos));
*pos += sizeof(group->attributes[j].value_v4_f32.x);
group->attributes[j].value_v4_f32.y = SWAP_ENDIAN_LITTLE(*((f32 *) *pos));
*pos += sizeof(group->attributes[j].value_v4_f32.y);
group->attributes[j].value_v4_f32.z = SWAP_ENDIAN_LITTLE(*((f32 *) *pos));
*pos += sizeof(group->attributes[j].value_v4_f32.z);
group->attributes[j].value_v4_f32.w = SWAP_ENDIAN_LITTLE(*((f32 *) *pos));
*pos += sizeof(group->attributes[j].value_v4_f32.w);
}
}
}
// Memory layout (data) - This is not the layout of the file itself, just how we represent it in memory
// Hashmap
// UIAttributeGroup - This is where the pointers point to (or what the offset represents)
@ -401,6 +265,7 @@ int32 theme_from_data(
UIThemeStyle* theme
) {
const byte* pos = data;
const byte* max_pos = data;
int32 version = *((int32 *) pos);
pos += sizeof(version);
@ -422,33 +287,25 @@ int32 theme_from_data(
HashEntryInt64* entry = (HashEntryInt64 *) theme->hash_map.table[i];
// This way we now could access the data directly without the silly theme->data + entry->value calc.
pos = start + entry->value;
UIAttributeGroup* group = (UIAttributeGroup *) (theme->data + entry->value);
group->attribute_size = SWAP_ENDIAN_LITTLE(*((int32 *) pos));
pos += sizeof(group->attribute_size);
// @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases
memcpy(group->attributes, pos, group->attribute_size * sizeof(UIAttribute));
pos += group->attribute_size * sizeof(UIAttribute);
ui_theme_parse_group(entry, theme->data, &pos);
if (pos > max_pos) {
max_pos = pos;
}
// load all the next elements
while (entry->next) {
pos = start + entry->value;
group = (UIAttributeGroup *) (theme->data + entry->value);
group->attribute_size = SWAP_ENDIAN_LITTLE(*((int32 *) pos));
pos += sizeof(group->attribute_size);
// @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases
memcpy(group->attributes, pos, group->attribute_size * sizeof(UIAttribute));
pos += group->attribute_size * sizeof(UIAttribute);
ui_theme_parse_group(entry, theme->data, &pos);
if (pos > max_pos) {
max_pos = pos;
}
entry = entry->next;
}
}
return (int32) (pos - data);
return (int32) (max_pos - data);
}
// Calculates the maximum theme size
@ -461,6 +318,49 @@ int64 theme_data_size(const UIThemeStyle* theme)
+ theme->hash_map.buf.count * UI_ATTRIBUTE_TYPE_SIZE * sizeof(UIAttribute);
}
// @todo Why do even need **pos, shouldn't it just be *pos
static inline
void ui_theme_serialize_group(HashEntryInt64* entry, byte* data, byte** pos, const byte* start)
{
// @performance Are we sure the data is nicely aligned?
// Probably depends on the from_txt function and the start of theme->data
UIAttributeGroup* group = (UIAttributeGroup *) (data + entry->value);
*((int32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attribute_size);
*pos += sizeof(group->attribute_size);
for (uint32 j = 0; j < group->attribute_size; ++j) {
*((uint16 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].attribute_id);
*pos += sizeof(group->attributes[j].attribute_id);
*((byte *) *pos) = group->attributes[j].datatype;
*pos += sizeof(group->attributes[j].datatype);
if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) {
*((int32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_int);
*pos += sizeof(group->attributes[j].value_int);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_INT) {
*((f32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_float);
*pos += sizeof(group->attributes[j].value_float);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_STR) {
memcpy(*pos, group->attributes[j].value_str, sizeof(group->attributes[j].value_str));
*pos += sizeof(group->attributes[j].value_str);
} else if (group->attributes[j].datatype == UI_ATTRIBUTE_DATA_TYPE_V4_F32) {
*((f32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_v4_f32.x);
*pos += sizeof(group->attributes[j].value_v4_f32.x);
*((f32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_v4_f32.y);
*pos += sizeof(group->attributes[j].value_v4_f32.y);
*((f32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_v4_f32.z);
*pos += sizeof(group->attributes[j].value_v4_f32.z);
*((f32 *) *pos) = SWAP_ENDIAN_LITTLE(group->attributes[j].value_v4_f32.w);
*pos += sizeof(group->attributes[j].value_v4_f32.w);
}
}
}
// File layout - binary
// version
// hashmap size
@ -481,6 +381,7 @@ int32 theme_to_data(
byte* data
) {
byte* pos = data;
byte* max_pos = data;
// version
*((int32 *) pos) = SWAP_ENDIAN_LITTLE(UI_THEME_VERSION);
@ -500,32 +401,23 @@ int32 theme_to_data(
HashEntryInt64* entry = (HashEntryInt64 *) theme->hash_map.table[i];
pos = start + entry->value;
UIAttributeGroup* group = (UIAttributeGroup *) (theme->data + entry->value);
*((int32 *) pos) = SWAP_ENDIAN_LITTLE(group->attribute_size);
pos += sizeof(group->attribute_size);
// @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases
memcpy(pos, group->attributes, group->attribute_size * sizeof(UIAttribute));
pos += sizeof(UIAttribute);
ui_theme_serialize_group(entry, theme->data, &pos, start);
if (pos > max_pos) {
max_pos = pos;
}
// save all the next elements
while (entry->next) {
pos = start + entry->value;
group = (UIAttributeGroup *) (theme->data + entry->value);
*((int32 *) pos) = SWAP_ENDIAN_LITTLE(group->attribute_size);
pos += sizeof(group->attribute_size);
// @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases
memcpy(pos, group->attributes, group->attribute_size * sizeof(UIAttribute));
pos += sizeof(UIAttribute);
ui_theme_serialize_group(entry, theme->data, &pos, start);
if (pos > max_pos) {
max_pos = pos;
}
entry = entry->next;
}
}
return (int32) (pos - data);
return (int32) (max_pos - data);
}
#endif

31
ui/UIWindow.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef TOS_UI_WINDOW_H
#define TOS_UI_WINDOW_H
#include "../stdlib/Types.h"
#include "attribute/UIAttributeBorder.h"
#include "attribute/UIAttributeShadow.h"
#include "attribute/UIAttributeFont.h"
#include "attribute/UIAttributeBackground.h"
#include "UIAnimation.h"
#include "UIStyleType.h"
struct UIWindowState {
};
struct UIWindow {
v4_int16 dimension;
UIAnimation animation;
byte padding;
byte alignment;
byte opacity;
uintptr_t background;
UIBackgroundStyle background_style;
UIAttributeBorder border;
UIAttributeShadow shadow_outer;
UIAttributeShadow shadow_inner;
UIWindow* styles[UI_STYLE_TYPE_SIZE];
};
#endif

246
ui/attribute/UIAttribute.h Normal file
View File

@ -0,0 +1,246 @@
#ifndef TOS_UI_ATTRIBUTE_H
#define TOS_UI_ATTRIBUTE_H
#include "../../stdlib/Types.h"
#include "../../utils/StringUtils.h"
#include "../../math/Evaluator.h"
#include "UIAttributeType.h"
#include "UIAttributeDimension.h"
enum UIAttributeDataType : byte {
UI_ATTRIBUTE_DATA_TYPE_INT,
UI_ATTRIBUTE_DATA_TYPE_F32,
UI_ATTRIBUTE_DATA_TYPE_STR,
UI_ATTRIBUTE_DATA_TYPE_V4_F32,
};
struct UIAttribute {
// Attributes use ids (=type name) instead of strings
UIAttributeType attribute_id;
UIAttributeDataType datatype;
union {
// @performance The string makes this struct really large when it is not needed in 95% of the cases
char value_str[32];
int32 value_int;
f32 value_float;
v4_f32 value_v4_f32;
};
};
struct UIAttributeGroup {
int32 attribute_size;
UIAttribute* attributes;
};
UIAttribute* ui_attribute_from_group(UIAttributeGroup* group, UIAttributeType type)
{
if (!group->attributes) {
return NULL;
}
int32 left = 0;
int32 right = type;
// Binary search since attributes are sorted by attribute_id
// @performance Consider Eytzinger
while (left <= right) {
int32 mid = left + (right - left) / 2;
if (group->attributes[mid].attribute_id == type) {
return &group->attributes[mid];
} else if (group->attributes[mid].attribute_id < type) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return NULL;
}
constexpr
int32 ui_attribute_type_to_id(const char* attribute_name)
{
if (str_compare(attribute_name, "x") == 0) {
return UI_ATTRIBUTE_TYPE_DIMENSION_X;
} else if (str_compare(attribute_name, "y") == 0) {
return UI_ATTRIBUTE_TYPE_DIMENSION_Y;
} else if (str_compare(attribute_name, "width") == 0) {
return UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH;
} else if (str_compare(attribute_name, "height") == 0) {
return UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT;
} else if (str_compare(attribute_name, "font_name") == 0) {
return UI_ATTRIBUTE_TYPE_FONT_NAME;
} else if (str_compare(attribute_name, "font_color") == 0) {
return UI_ATTRIBUTE_TYPE_FONT_COLOR;
} else if (str_compare(attribute_name, "font_size") == 0) {
return UI_ATTRIBUTE_TYPE_FONT_SIZE;
} else if (str_compare(attribute_name, "font_weight") == 0) {
return UI_ATTRIBUTE_TYPE_FONT_WEIGHT;
} else if (str_compare(attribute_name, "font_line_height") == 0) {
return UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT;
} else if (str_compare(attribute_name, "align_h") == 0) {
return UI_ATTRIBUTE_TYPE_ALIGN_H;
} else if (str_compare(attribute_name, "align_v") == 0) {
return UI_ATTRIBUTE_TYPE_ALIGN_V;
} else if (str_compare(attribute_name, "zindex") == 0) {
return UI_ATTRIBUTE_TYPE_ZINDEX;
} else if (str_compare(attribute_name, "style1") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE1;
} else if (str_compare(attribute_name, "style2") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE2;
} else if (str_compare(attribute_name, "style3") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE3;
} else if (str_compare(attribute_name, "style4") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE4;
} else if (str_compare(attribute_name, "style5") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE5;
} else if (str_compare(attribute_name, "style6") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE6;
} else if (str_compare(attribute_name, "style7") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE7;
} else if (str_compare(attribute_name, "style8") == 0) {
return UI_ATTRIBUTE_TYPE_STYLE8;
} else if (str_compare(attribute_name, "foreground_color") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_COLOR;
} else if (str_compare(attribute_name, "foreground_img") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_IMG;
} else if (str_compare(attribute_name, "foreground_img_opacity") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_OPACITY;
} else if (str_compare(attribute_name, "foreground_img_position_v") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_POSITION_V;
} else if (str_compare(attribute_name, "foreground_img_position_h") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_POSITION_H;
} else if (str_compare(attribute_name, "foreground_img_style") == 0) {
return UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_STYLE;
} else if (str_compare(attribute_name, "background_color") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR;
} else if (str_compare(attribute_name, "background_img") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_IMG;
} else if (str_compare(attribute_name, "background_img_opacity") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY;
} else if (str_compare(attribute_name, "background_img_position_v") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V;
} else if (str_compare(attribute_name, "background_img_position_h") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H;
} else if (str_compare(attribute_name, "background_img_style") == 0) {
return UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE;
} else if (str_compare(attribute_name, "border_color") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_COLOR;
} else if (str_compare(attribute_name, "border_width") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_WIDTH;
} else if (str_compare(attribute_name, "border_top_color") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR;
} else if (str_compare(attribute_name, "border_top_width") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH;
} else if (str_compare(attribute_name, "border_right_color") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR;
} else if (str_compare(attribute_name, "border_right_width") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH;
} else if (str_compare(attribute_name, "border_bottom_color") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR;
} else if (str_compare(attribute_name, "border_bottom_width") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH;
} else if (str_compare(attribute_name, "border_left_color") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR;
} else if (str_compare(attribute_name, "border_left_width") == 0) {
return UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH;
} else if (str_compare(attribute_name, "padding") == 0) {
return UI_ATTRIBUTE_TYPE_PADDING;
} else if (str_compare(attribute_name, "padding_top") == 0) {
return UI_ATTRIBUTE_TYPE_PADDING_TOP;
} else if (str_compare(attribute_name, "padding_right") == 0) {
return UI_ATTRIBUTE_TYPE_PADDING_RIGHT;
} else if (str_compare(attribute_name, "padding_bottom") == 0) {
return UI_ATTRIBUTE_TYPE_PADDING_BOTTOM;
} else if (str_compare(attribute_name, "padding_left") == 0) {
return UI_ATTRIBUTE_TYPE_PADDING_LEFT;
} else if (str_compare(attribute_name, "scroll_style") == 0) {
return UI_ATTRIBUTE_TYPE_SCROLL_STYLE;
} else if (str_compare(attribute_name, "scroll_x") == 0) {
return UI_ATTRIBUTE_TYPE_SCROLL_X;
} else if (str_compare(attribute_name, "scroll_y") == 0) {
return UI_ATTRIBUTE_TYPE_SCROLL_Y;
} else if (str_compare(attribute_name, "shadow_inner_color") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR;
} else if (str_compare(attribute_name, "shadow_inner_angle") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE;
} else if (str_compare(attribute_name, "shadow_inner_distance") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE;
} else if (str_compare(attribute_name, "shadow_outer_color") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR;
} else if (str_compare(attribute_name, "shadow_outer_angle") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE;
} else if (str_compare(attribute_name, "shadow_outer_distance") == 0) {
return UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE;
} else if (str_compare(attribute_name, "transition_animation") == 0) {
return UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION;
} else if (str_compare(attribute_name, "transition_duration") == 0) {
return UI_ATTRIBUTE_TYPE_TRANSITION_DURATION;
}
return -1;
}
inline
void ui_attribute_parse_value(UIAttribute* attr, const char* attribute_name, const char* pos)
{
attr->attribute_id = (UIAttributeType) ui_attribute_type_to_id(attribute_name);
char value[64];
str_copy_until(pos, value, "\r\n");
if (is_integer(value)) {
attr->value_int = strtoul(value, NULL, 10);
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_INT;
} else if (is_float(value)) {
attr->value_float = strtof(value, NULL);
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_F32;
} else if (is_hex_color(value)) {
++pos; // Skip '#'
hexstr_to_rgba(&attr->value_v4_f32, pos);
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_V4_F32;
} else {
str_copy_until(attr->value_str, value, "\r\n");
attr->datatype = UI_ATTRIBUTE_DATA_TYPE_STR;
}
}
inline
void ui_theme_assign_f32(f32* a, const UIAttribute* attr, int32 variable_count, const EvaluatorVariable* variables)
{
if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_INT) {
*a = (f32) attr->value_int;
} else if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_F32) {
*a = (f32) attr->value_float;
} else if (attr->datatype == UI_ATTRIBUTE_DATA_TYPE_STR) {
char value[32];
memcpy(value, attr->value_str, ARRAY_COUNT(attr->value_str));
*a = (f32) evaluator_evaluate(value, variable_count, variables);
}
}
inline
void ui_theme_assign_dimension(UIAttributeDimension* dimension, const UIAttribute* attr, int32 variable_count, const EvaluatorVariable* variables)
{
switch (attr->attribute_id) {
case UI_ATTRIBUTE_TYPE_DIMENSION_X: {
ui_theme_assign_f32(&dimension->dimension.x, attr, variable_count, variables);
} break;
case UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH: {
ui_theme_assign_f32(&dimension->dimension.width, attr, variable_count, variables);
} break;
case UI_ATTRIBUTE_TYPE_DIMENSION_Y: {
ui_theme_assign_f32(&dimension->dimension.y, attr, variable_count, variables);
} break;
case UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT: {
ui_theme_assign_f32(&dimension->dimension.height, attr, variable_count, variables);
} break;
default: {
UNREACHABLE();
}
}
}
#endif

View File

@ -0,0 +1,21 @@
#ifndef TOS_UI_ATTRIBUTE_BACKGROUND_STYLE_H
#define TOS_UI_ATTRIBUTE_BACKGROUND_STYLE_H
#include "../../stdlib/Types.h"
enum UIBackgroundStyle : byte {
UI_BACKGROUND_STYLE_NONE = 1 << 0,
UI_BACKGROUND_STYLE_COLOR_IMG = 1 << 1, // 0 = color, 1 = img
UI_BACKGROUND_STYLE_STRETCH = 1 << 2, // 0 = none, 1 = stretch
UI_BACKGROUND_STYLE_REPEAT = 1 << 3, // 0 = none, 1 = repeat
};
struct UIAttributeBackground {
UIBackgroundStyle background_style;
union {
void* background_image;
uint32 background_color;
};
};
#endif

View File

@ -0,0 +1,12 @@
#ifndef TOS_UI_ATTRIBUTE_BORDER_H
#define TOS_UI_ATTRIBUTE_BORDER_H
#include "../../stdlib/Types.h"
struct UIAttributeBorder {
// 4 bits per side
uint16 thickness;
uint32 color;
};
#endif

View File

@ -0,0 +1,56 @@
#ifndef TOS_UI_ATTRIBUTE_DIMENSION_H
#define TOS_UI_ATTRIBUTE_DIMENSION_H
#include "../../stdlib/Types.h"
enum UIDimensionFlag : byte {
// Are the values relative (based on container) or absolute
UI_DIMENSION_POS_RELATIVE = 1 << 0,
UI_DIMENSION_DIM_RELATIVE = 1 << 1,
// Are the values dynamically calculated?
UI_DIMENSION_POS_DYN = 1 << 2,
UI_DIMENSION_DIM_DYN = 1 << 3,
// Are the values modifiable by the user (live)
UI_DIMENSION_POS_MODIFIABLE = 1 << 4,
UI_DIMENSION_DIM_MODIFIABLE = 1 << 5,
};
struct UIAttributeDimension {
// @see UIDimensionFlag
byte flag;
// @see UIAlign
byte alignment;
v4_f32 dimension;
/*
// We commented this out since we will try to work around it for now by simply reloading the UI,
// whenever a in-game window gets resized, same as resizing the actual game window
union {
struct {
f32 x, y;
f32 width, height;
};
// We can never have position and dimension both be dynamic
// This isn't really a technical limitation, it's more a "what happens in reality" kind of reason
// This allows us to save 40 bytes
struct {
char x_str[24];
char y_str[24];
f32 width, height;
};
struct {
f32 x, y;
char width_str[24];
char height_str[24];
};
};
*/
};
#endif

View File

@ -0,0 +1,21 @@
#ifndef TOS_UI_ATTRIBUTE_FONT_H
#define TOS_UI_ATTRIBUTE_FONT_H
#include "../../stdlib/Types.h"
enum UIFontDecoration : byte {
UI_FONT_DECORATION_UNDERLINE = 1 << 0,
UI_FONT_DECORATION_ITALIC = 1 << 1,
};
struct UIAttributeFont {
f32 size;
uint32 color;
f32 weight;
UIAttributeShadow shadow_outer;
byte decoration;
// @todo family?
};
#endif

View File

@ -0,0 +1,13 @@
#ifndef TOS_UI_ATTRIBUTE_SHADOW_H
#define TOS_UI_ATTRIBUTE_SHADOW_H
#include "../../stdlib/Types.h"
struct UIAttributeShadow {
f32 angle;
uint32 color;
byte fade;
byte offset;
};
#endif

View File

@ -0,0 +1,109 @@
#ifndef TOS_UI_ATTRIBUTE_TYPE_H
#define TOS_UI_ATTRIBUTE_TYPE_H
#include "../../stdlib/Types.h"
enum UIAttributeType : uint16 {
UI_ATTRIBUTE_TYPE_NONE,
UI_ATTRIBUTE_TYPE_TYPE,
UI_ATTRIBUTE_TYPE_MIN_VALUE,
UI_ATTRIBUTE_TYPE_MAX_VALUE,
UI_ATTRIBUTE_TYPE_MAX_INPUT_LENGTH,
UI_ATTRIBUTE_TYPE_DIMENSION_X,
UI_ATTRIBUTE_TYPE_DIMENSION_Y,
UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH,
UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT,
UI_ATTRIBUTE_TYPE_CONTENT,
UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_H,
UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_V,
UI_ATTRIBUTE_TYPE_FONT_NAME,
UI_ATTRIBUTE_TYPE_FONT_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_FONT_COLOR,
UI_ATTRIBUTE_TYPE_FONT_SIZE,
UI_ATTRIBUTE_TYPE_FONT_WEIGHT,
UI_ATTRIBUTE_TYPE_FONT_LINE_HEIGHT,
UI_ATTRIBUTE_TYPE_ALIGN_H,
UI_ATTRIBUTE_TYPE_ALIGN_V,
UI_ATTRIBUTE_TYPE_ZINDEX,
UI_ATTRIBUTE_TYPE_POSITION_X,
UI_ATTRIBUTE_TYPE_POSITION_Y,
UI_ATTRIBUTE_TYPE_PARENT,
// Sub styles for components
// E.g. scroll bar usess style1 for background box and style2 for movable bar
UI_ATTRIBUTE_TYPE_STYLE1,
UI_ATTRIBUTE_TYPE_STYLE2,
UI_ATTRIBUTE_TYPE_STYLE3,
UI_ATTRIBUTE_TYPE_STYLE4,
UI_ATTRIBUTE_TYPE_STYLE5,
UI_ATTRIBUTE_TYPE_STYLE6,
UI_ATTRIBUTE_TYPE_STYLE7,
UI_ATTRIBUTE_TYPE_STYLE8,
UI_ATTRIBUTE_TYPE_FOREGROUND_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_FOREGROUND_COLOR,
UI_ATTRIBUTE_TYPE_FOREGROUND_IMG,
UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_OPACITY,
UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_POSITION_V,
UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_POSITION_H,
UI_ATTRIBUTE_TYPE_FOREGROUND_IMG_STYLE,
UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_V,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H,
UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE,
UI_ATTRIBUTE_TYPE_BORDER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_BORDER_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_TOP_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_RIGHT_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_RIGHT_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_BOTTOM_WIDTH,
UI_ATTRIBUTE_TYPE_BORDER_LEFT_COLOR,
UI_ATTRIBUTE_TYPE_BORDER_LEFT_WIDTH,
UI_ATTRIBUTE_TYPE_PADDING,
UI_ATTRIBUTE_TYPE_PADDING_TOP,
UI_ATTRIBUTE_TYPE_PADDING_RIGHT,
UI_ATTRIBUTE_TYPE_PADDING_BOTTOM,
UI_ATTRIBUTE_TYPE_PADDING_LEFT,
UI_ATTRIBUTE_TYPE_SCROLL_STYLE,
UI_ATTRIBUTE_TYPE_SCROLL_X,
UI_ATTRIBUTE_TYPE_SCROLL_Y,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE,
UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR_INDEX,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE,
UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE,
// @todo This isn't enough, we can have many animations (position, size, colors, ...)
// Maybe we need to define an animation child which overwrites the defined values
// Maybe it should use the same system as state dependent values like hover, active, ...
UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION,
UI_ATTRIBUTE_TYPE_TRANSITION_DURATION,
UI_ATTRIBUTE_TYPE_SIZE,
};
#endif

View File

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <x86intrin.h>
// @todo consider to log/ignore outliers
uint64_t measure_cycles(int count, size_t (*func)(const char *), const char *str) {
uint64_t start = __rdtsc();
for (int = 0; i < count; ++i) {
func(str);
}
uint64_t end = __rdtsc();
return end - start;
}
void compare_strlen(int count, const char *str) {
uint64_t normal_cycles = measure_cycles(count, strlen, str);
uint64_t optimized_cycles = measure_cycles(count, strlen_optimized, str);
printf("String length: %zu\n", strlen(str));
printf("Normal strlen cycles: %lu\n", normal_cycles);
printf("Optimized strlen cycles: %lu\n", optimized_cycles);
printf("Speedup: %.2fx\n", (double)normal_cycles / optimized_cycles);
}
char* generate_random_string(size_t length) {
char *str = (char *) malloc(length + 1);
for (size_t i = 0; i < length; i++) {
str[i] = (char) rand();
}
str[length] = '\0';
return str;
}
int main() {
srand((unsigned int) time(NULL));
size_t lengths[] = {5, 16, 64, 256, 1024, 4096};
const size_t num_lengths = sizeof(lengths) / sizeof(lengths[0]);
for (size_t i = 0; i < num_lengths; i++) {
size_t length = lengths[i];
char *random_string = generate_random_string(length);
printf("Test %zu (length: %zu):\n", i + 1, length);
compare_strlen(100000, random_string);
free(random_string);
printf("\n");
}
return 0;
}

View File

@ -15,6 +15,7 @@
#include <ctype.h>
#include "../stdlib/Types.h"
#include "../utils/TestUtils.h"
inline
int32 utf8_encode(uint32 codepoint, char* out)
@ -198,6 +199,132 @@ void wchar_to_char(const char* __restrict str, char* __restrict dest)
*dest = '\0';
}
inline
bool is_float(const char* str) {
bool has_dot = false;
if (*str == '-' || *str == '+') {
str++;
}
while (*str) {
if (*str == '.') {
if (has_dot) {
return false;
}
has_dot = true;
} else if (*str < '0' || *str > '9') {
return false;
}
++str;
}
return has_dot;
}
inline
bool is_integer(const char* str) {
if (*str == '-' || *str == '+') {
str++;
}
if (*str == '\0') {
return false;
}
bool is_int = false;
while (*str) {
if (*str < '0' || *str > '9') {
return false;
}
++str;
is_int = true;
}
return is_int;
}
inline
bool is_hex_color(const char* str)
{
if (str[0] != '#') {
return false;
}
++str;
while (*str) {
if ((*str < 'A' || *str > 'F')
|| (*str < 'a' || *str > 'f')
|| (*str < '0' || *str > '9')
) {
return false;
}
++str;
}
return true;
}
inline
bool str_is_alpha(char str) {
return (str < 'A' || str > 'F')
|| (str < 'a' || str > 'f');
}
inline
bool str_is_num(char str) {
return str < '0' || str > '9';
}
inline
bool str_is_alphanum(char str) {
return (str < 'A' || str > 'F')
|| (str < 'a' || str > 'f')
|| (str < '0' || str > '9');
}
inline
size_t str_length(const char* str)
{
const char* s = str;
// Quick check for very short strings
while ((uintptr_t) s % sizeof(uintptr_t) != 0) {
if (*s == '\0') {
return s - str;
}
++s;
}
// Process words at a time
const uintptr_t* word_ptr = (const uintptr_t *) s;
const uintptr_t word_mask = (uintptr_t) -1 / 0xFF; // 0x01010101...
while (true) {
uintptr_t word = *word_ptr++;
// Detect null byte using word-level operation
if ((word - word_mask) & ~word & (word_mask << 7)) {
break;
}
}
// Backtrack to find the exact null byte
s = (const char *) (word_ptr - 1);
for (size_t i = 0; i < sizeof(uintptr_t); ++i) {
if (s[i] == '\0') {
return s + i - str;
}
}
return 0;
}
inline constexpr
int64 str_to_int(const char* str)
{
@ -391,33 +518,6 @@ int32 is_eol(const char* str)
return 0;
}
inline
void str_copy_until(const char* __restrict src, char* __restrict dest, char delim)
{
while (*src != delim && *src != '\0') {
*dest++ = *src++;
}
*dest = '\0';
}
inline
void str_copy_until(const char* __restrict src, char* __restrict dest, const char* __restrict delim, int32 len)
{
while (*src != '\0') {
for (int32 i = 0; i < len; ++i) {
if (*src == delim[i]) {
*dest = '\0';
return;
}
}
*dest++ = *src++;
}
*dest = '\0';
}
inline
int32 str_copy_until(char* __restrict dest, const char* __restrict src, char delim)
{
@ -432,11 +532,32 @@ int32 str_copy_until(char* __restrict dest, const char* __restrict src, char del
return len;
}
// @todo Inconsistent parameter order of dest and src with other functions
inline
void str_copy_short(char* __restrict dest, const char* __restrict src, int32 length, char delim = '\0')
void str_copy_until(const char* __restrict src, char* __restrict dest, const char* __restrict delim)
{
size_t len = strlen(delim);
while (*src != '\0') {
for (int32 i = 0; i < len; ++i) {
if (*src == delim[i]) {
*dest = '\0';
return;
}
}
*dest++ = *src;
++src;
}
*dest = '\0';
}
inline
void str_copy_short(char* __restrict dest, const char* __restrict src, int32 length)
{
int32 i = -1;
while (*src != delim && ++i < length) {
while (*src != '\0' && ++i < length) {
*dest++ = *src++;
}
@ -444,9 +565,9 @@ void str_copy_short(char* __restrict dest, const char* __restrict src, int32 len
}
inline
void str_copy_short(char* __restrict dest, const char* __restrict src, char delim = '\0')
void str_copy_short(char* __restrict dest, const char* __restrict src)
{
while (*src != delim) {
while (*src != '\0') {
*dest++ = *src++;
}
@ -454,7 +575,7 @@ void str_copy_short(char* __restrict dest, const char* __restrict src, char deli
}
inline
void str_copy_long(char* __restrict dest, const char* __restrict src, char delim = '\0')
void str_copy_long(char* __restrict dest, const char* __restrict src)
{
char* d = dest;
const char *s = src;
@ -495,10 +616,12 @@ void str_copy_move_until(char** __restrict src, char* __restrict dest, char deli
}
inline
void str_copy_move_until(char** __restrict src, char* __restrict dest, const char* __restrict delim, int32 len)
void str_copy_move_until(char** __restrict src, char* __restrict dest, const char* __restrict delim)
{
size_t len = strlen(delim);
while (**src != '\0') {
for (int32 i = 0; i < len; ++i) {
for (size_t i = 0; i < len; ++i) {
if (**src == delim[i]) {
*dest = '\0';
return;
@ -649,6 +772,12 @@ void str_insert(char* __restrict dst, size_t insert_pos, const char* __restrict
memcpy(dst + insert_pos, src, src_length);
}
inline
void str_remove(char* __restrict dst, size_t remove_pos, size_t remove_length) {
size_t src_length = strlen(dst);
memmove(dst + remove_pos, dst + remove_pos + remove_length, src_length - (remove_pos + remove_length) + 1);
}
inline
char* strtok(char* str, const char* __restrict delim, char* *key) {
char* result;
@ -726,6 +855,7 @@ void create_const_name(unsigned char* name)
*name = '\0';
}
constexpr inline
int32 str_compare(const char* str1, const char* str2)
{
byte c1, c2;
@ -733,11 +863,7 @@ int32 str_compare(const char* str1, const char* str2)
do {
c1 = (byte) *str1++;
c2 = (byte) *str2++;
if (c1 == '\0') {
return c1 - c2;
}
} while (c1 == c2);
} while (c1 == c2 && c1 != '\0');
return c1 - c2;
}
@ -900,6 +1026,16 @@ void str_move_to(char** str, char delim)
}
}
inline
void str_move_to_pos(const char** str, int32 pos)
{
if (pos >= 0) {
*str += pos;
} else {
*str = OMS_MAX(*str + (strlen(*str) - pos), *str);
}
}
inline
void str_move_past(char** str, char delim)
{
@ -930,11 +1066,10 @@ bool str_is_comment(char* str)
return (*str == '/' && str[1] == '/') || (*str == '/' && str[1] == '*');
}
// @question Isn't that basically like move_to? Consider to unify
inline
void str_skip(char** str, char delim)
{
while (**str == delim) {
while (**str && **str == delim) {
++(*str);
}
}
@ -942,7 +1077,7 @@ void str_skip(char** str, char delim)
inline
void str_skip_whitespace(char** str)
{
while (**str == ' ' || **str == '\t') {
while (**str && (**str == ' ' || **str == '\t')) {
++(*str);
}
}
@ -1024,6 +1159,55 @@ void str_pad(const char* input, char* output, char pad, size_t len) {
}
}
inline
int32 float_to_str(f64 value, char* buffer, int32 precision = 5)
{
ASSERT_SIMPLE(precision < 6);
char* start = buffer;
if (value < 0) {
*buffer++ = '-';
value = -value;
}
static const float powers_of_ten[] = {
1.0f, 10.0f, 100.0f, 1000.0f, 10000.0f, 100000.0f
};
f32 scale = powers_of_ten[precision];
value = OMS_ROUND_POSITIVE(value * scale) / scale;
// Handle integer part
int32 int_part = (int32) value;
f64 frac_part = value - int_part;
char temp[20];
int32 index = 0;
do {
temp[index++] = (int_part % 10) + '0';
int_part /= 10;
} while (int_part > 0);
while (index > 0) {
*buffer++ = temp[--index];
}
// Handle fractional part
if (precision > 0) {
*buffer++ = '.';
while (precision--) {
frac_part *= 10;
int32 digit = (int32) frac_part;
*buffer++ = (char) (digit + '0');
frac_part -= digit;
}
}
return (int32) (buffer - start);
}
void sprintf_fast(char* __restrict buffer, const char* __restrict format, ...) {
va_list args;
va_start(args, format);
@ -1077,46 +1261,7 @@ void sprintf_fast(char* __restrict buffer, const char* __restrict format, ...) {
format = prec_ptr - 1;
}
if (val < 0) {
*buffer++ = '-';
val = -val;
}
if (precision < 6) {
static const float powers_of_ten[] = {
1.0f, 10.0f, 100.0f, 1000.0f, 10000.0f, 100000.0f
};
f32 scale = powers_of_ten[precision];
val = OMS_ROUND_POSITIVE(val * scale) / scale;
}
// Handle integer part
int32 int_part = (int32) val;
f64 frac_part = val - int_part;
char temp[20];
int32 index = 0;
do {
temp[index++] = (int_part % 10) + '0';
int_part /= 10;
} while (int_part > 0);
while (index > 0) {
*buffer++ = temp[--index];
}
// Handle fractional part
if (precision > 0) {
*buffer++ = '.';
while (precision--) {
frac_part *= 10;
int32 digit = (int32) frac_part;
*buffer++ = (char) (digit + '0');
frac_part -= digit;
}
}
buffer += float_to_str(val, buffer, precision);
} break;
default: {
// Handle unknown format specifiers
@ -1189,46 +1334,7 @@ void sprintf_fast_iter(char* buffer, const char* format, ...) {
format = prec_ptr - 1;
}
if (val < 0) {
*buffer++ = '-';
val = -val;
}
if (precision < 6) {
static const float powers_of_ten[] = {
1.0f, 10.0f, 100.0f, 1000.0f, 10000.0f, 100000.0f
};
f32 scale = powers_of_ten[precision];
val = OMS_ROUND_POSITIVE(val * scale) / scale;
}
// Handle integer part
int32 int_part = (int32) val;
f64 frac_part = val - int_part;
char temp[20];
int32 index = 0;
do {
temp[index++] = (int_part % 10) + '0';
int_part /= 10;
} while (int_part > 0);
while (index > 0) {
*buffer++ = temp[--index];
}
// Handle fractional part
if (precision > 0) {
*buffer++ = '.';
while (precision--) {
frac_part *= 10;
int32 digit = (int32) frac_part;
*buffer++ = (char) (digit + '0');
frac_part -= digit;
}
}
buffer += float_to_str(val, buffer, precision);
} break;
default: {
// Handle unknown format specifiers