diff --git a/asset/AssetManagementSystem.h b/asset/AssetManagementSystem.h index cb7fb64..2cc257c 100644 --- a/asset/AssetManagementSystem.h +++ b/asset/AssetManagementSystem.h @@ -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 diff --git a/command/AppCmdBuffer.cpp b/command/AppCmdBuffer.cpp index c45a633..faa7129 100644 --- a/command/AppCmdBuffer.cpp +++ b/command/AppCmdBuffer.cpp @@ -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; } diff --git a/command/AppCmdBuffer.h b/command/AppCmdBuffer.h index 41d4236..1b6f1cc 100644 --- a/command/AppCmdBuffer.h +++ b/command/AppCmdBuffer.h @@ -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 \ No newline at end of file diff --git a/command/Command.h b/command/Command.h index 3a6baf6..54cbd21 100644 --- a/command/Command.h +++ b/command/Command.h @@ -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 \ No newline at end of file diff --git a/font/Font.h b/font/Font.h index 9d9be80..99adfb1 100644 --- a/font/Font.h +++ b/font/Font.h @@ -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; diff --git a/gpuapi/RenderUtils.h b/gpuapi/RenderUtils.h index 21c2dd9..91fc251 100644 --- a/gpuapi/RenderUtils.h +++ b/gpuapi/RenderUtils.h @@ -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) { diff --git a/gpuapi/UIUtils.h b/gpuapi/UIUtils.h index a2420e1..360de9e 100644 --- a/gpuapi/UIUtils.h +++ b/gpuapi/UIUtils.h @@ -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 \ No newline at end of file diff --git a/gpuapi/opengl/AppCmdBuffer.h b/gpuapi/opengl/AppCmdBuffer.h index 6e560f6..4bfe048 100644 --- a/gpuapi/opengl/AppCmdBuffer.h +++ b/gpuapi/opengl/AppCmdBuffer.h @@ -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]; diff --git a/gpuapi/opengl/OpenglWin32.h b/gpuapi/opengl/OpenglWin32.h index dae220b..061784d 100644 --- a/gpuapi/opengl/OpenglWin32.h +++ b/gpuapi/opengl/OpenglWin32.h @@ -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; } diff --git a/gpuapi/vulkan/VulkanUtils.h b/gpuapi/vulkan/VulkanUtils.h index 5b91900..ad97688 100644 --- a/gpuapi/vulkan/VulkanUtils.h +++ b/gpuapi/vulkan/VulkanUtils.h @@ -23,10 +23,11 @@ #include #include #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; } diff --git a/math/Evaluator.h b/math/Evaluator.h new file mode 100644 index 0000000..3d7a544 --- /dev/null +++ b/math/Evaluator.h @@ -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 +#include + +#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 \ No newline at end of file diff --git a/module/ModuleManager.h b/module/ModuleManager.h index d255ded..a3b81e8 100644 --- a/module/ModuleManager.h +++ b/module/ModuleManager.h @@ -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); } } diff --git a/stdlib/HashMap.h b/stdlib/HashMap.h index 3407cb5..2ee5305 100644 --- a/stdlib/HashMap.h +++ b/stdlib/HashMap.h @@ -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; diff --git a/stdlib/PerfectHashMap.h b/stdlib/PerfectHashMap.h index af3840c..bdea724 100644 --- a/stdlib/PerfectHashMap.h +++ b/stdlib/PerfectHashMap.h @@ -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 diff --git a/stdlib/Types.h b/stdlib/Types.h index 48ce68b..89f533e 100644 --- a/stdlib/Types.h +++ b/stdlib/Types.h @@ -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 { diff --git a/thread/ThreadJob.h b/thread/ThreadJob.h index 8b24773..592a74d 100644 --- a/thread/ThreadJob.h +++ b/thread/ThreadJob.h @@ -25,6 +25,7 @@ struct PoolWorker { void* result; RingMemory ring; ThreadPoolJobFunc func; + ThreadPoolJobFunc callback; }; struct Worker { diff --git a/ui/UIAlignment.h b/ui/UIAlignment.h index 5722664..c7a5b9d 100644 --- a/ui/UIAlignment.h +++ b/ui/UIAlignment.h @@ -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 \ No newline at end of file diff --git a/ui/UIAnimation.h b/ui/UIAnimation.h new file mode 100644 index 0000000..0c572e3 --- /dev/null +++ b/ui/UIAnimation.h @@ -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 \ No newline at end of file diff --git a/ui/UIAttribute.h b/ui/UIAttribute.h deleted file mode 100644 index 7fcf274..0000000 --- a/ui/UIAttribute.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/ui/UIButton.h b/ui/UIButton.h new file mode 100644 index 0000000..bb571cb --- /dev/null +++ b/ui/UIButton.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_BUTTON_H +#define TOS_UI_BUTTON_H + +#include "../stdlib/Types.h" + +struct UIButtonState { +}; + +struct UIButton { + +}; + +#endif \ No newline at end of file diff --git a/ui/UICursor.h b/ui/UICursor.h new file mode 100644 index 0000000..9abe87f --- /dev/null +++ b/ui/UICursor.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_CURSOR_H +#define TOS_UI_CURSOR_H + +#include "../stdlib/Types.h" + +struct UICursorState { +}; + +struct UICursor { + +}; + +#endif \ No newline at end of file diff --git a/ui/UIElement.h b/ui/UIElement.h index 4a1a7c5..44012ea 100644 --- a/ui/UIElement.h +++ b/ui/UIElement.h @@ -2,51 +2,44 @@ #define TOS_UI_ELEMENT_H #include "../stdlib/Types.h" -#include "UIElementType.h" -#include "../object/Vertex.h" -#include -#include - -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 \ No newline at end of file diff --git a/ui/UIElementType.h b/ui/UIElementType.h index 7ec82be..c506bd3 100644 --- a/ui/UIElementType.h +++ b/ui/UIElementType.h @@ -2,55 +2,116 @@ #define TOS_UI_ELEMENT_TYPE_H #include +#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 \ No newline at end of file diff --git a/ui/UIImage.h b/ui/UIImage.h new file mode 100644 index 0000000..dc4c1bf --- /dev/null +++ b/ui/UIImage.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_IMAGE_H +#define TOS_UI_IMAGE_H + +#include "../stdlib/Types.h" + +struct UIImageState { +}; + +struct UIImage { + +}; + +#endif \ No newline at end of file diff --git a/ui/UIInput.h b/ui/UIInput.h new file mode 100644 index 0000000..595edde --- /dev/null +++ b/ui/UIInput.h @@ -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 \ No newline at end of file diff --git a/ui/UILabel.h b/ui/UILabel.h new file mode 100644 index 0000000..e2f5b7d --- /dev/null +++ b/ui/UILabel.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_LABEL_H +#define TOS_UI_LABEL_H + +#include "../stdlib/Types.h" + +struct UILabelState { +}; + +struct UILabel { + +}; + +#endif \ No newline at end of file diff --git a/ui/UILayout.h b/ui/UILayout.h index e5c54e6..7f894fe 100644 --- a/ui/UILayout.h +++ b/ui/UILayout.h @@ -1,10 +1,19 @@ #ifndef TOS_UI_LAYOUT_H #define TOS_UI_LAYOUT_H +#include #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 \ No newline at end of file diff --git a/ui/UILink.h b/ui/UILink.h new file mode 100644 index 0000000..ea8a35e --- /dev/null +++ b/ui/UILink.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_LINK_H +#define TOS_UI_LINK_H + +#include "../stdlib/Types.h" + +struct UILinkState { +}; + +struct UILink { + +}; + +#endif \ No newline at end of file diff --git a/ui/UIPanel.h b/ui/UIPanel.h new file mode 100644 index 0000000..eb2d872 --- /dev/null +++ b/ui/UIPanel.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_PANEL_H +#define TOS_UI_PANEL_H + +#include "../stdlib/Types.h" + +struct UIPanelState { +}; + +struct UIPanel { + +}; + +#endif \ No newline at end of file diff --git a/ui/UISelect.h b/ui/UISelect.h new file mode 100644 index 0000000..59f0af6 --- /dev/null +++ b/ui/UISelect.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_SELECT_H +#define TOS_UI_SELECT_H + +#include "../stdlib/Types.h" + +struct UISelectState { +}; + +struct UISelect { + +}; + +#endif \ No newline at end of file diff --git a/ui/UIStyleType.h b/ui/UIStyleType.h new file mode 100644 index 0000000..0bb5a40 --- /dev/null +++ b/ui/UIStyleType.h @@ -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 \ No newline at end of file diff --git a/ui/UITab.h b/ui/UITab.h new file mode 100644 index 0000000..5213848 --- /dev/null +++ b/ui/UITab.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_TAB_H +#define TOS_UI_TAB_H + +#include "../stdlib/Types.h" + +struct UITabState { +}; + +struct UITab { + +}; + +#endif \ No newline at end of file diff --git a/ui/UITable.h b/ui/UITable.h new file mode 100644 index 0000000..a1b5597 --- /dev/null +++ b/ui/UITable.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_TABLE_H +#define TOS_UI_TABLE_H + +#include "../stdlib/Types.h" + +struct UITableState { +}; + +struct UITable { + +}; + +#endif \ No newline at end of file diff --git a/ui/UIText.h b/ui/UIText.h new file mode 100644 index 0000000..39b4c58 --- /dev/null +++ b/ui/UIText.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_TEXT_H +#define TOS_UI_TEXT_H + +#include "../stdlib/Types.h" + +struct UITextState { +}; + +struct UIText { + +}; + +#endif \ No newline at end of file diff --git a/ui/UITextarea.h b/ui/UITextarea.h new file mode 100644 index 0000000..2b742a0 --- /dev/null +++ b/ui/UITextarea.h @@ -0,0 +1,13 @@ +#ifndef TOS_UI_TEXTAREA_H +#define TOS_UI_TEXTAREA_H + +#include "../stdlib/Types.h" + +struct UITextareaState { +}; + +struct UITextarea { + +}; + +#endif \ No newline at end of file diff --git a/ui/UITheme.h b/ui/UITheme.h index 1f8329d..485c89d 100644 --- a/ui/UITheme.h +++ b/ui/UITheme.h @@ -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 \ No newline at end of file diff --git a/ui/UIWindow.h b/ui/UIWindow.h new file mode 100644 index 0000000..014a285 --- /dev/null +++ b/ui/UIWindow.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttribute.h b/ui/attribute/UIAttribute.h new file mode 100644 index 0000000..736f221 --- /dev/null +++ b/ui/attribute/UIAttribute.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeBackground.h b/ui/attribute/UIAttributeBackground.h new file mode 100644 index 0000000..3575447 --- /dev/null +++ b/ui/attribute/UIAttributeBackground.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeBorder.h b/ui/attribute/UIAttributeBorder.h new file mode 100644 index 0000000..f2d78d5 --- /dev/null +++ b/ui/attribute/UIAttributeBorder.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeDimension.h b/ui/attribute/UIAttributeDimension.h new file mode 100644 index 0000000..07a8929 --- /dev/null +++ b/ui/attribute/UIAttributeDimension.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeFont.h b/ui/attribute/UIAttributeFont.h new file mode 100644 index 0000000..5844c43 --- /dev/null +++ b/ui/attribute/UIAttributeFont.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeShadow.h b/ui/attribute/UIAttributeShadow.h new file mode 100644 index 0000000..4e4e400 --- /dev/null +++ b/ui/attribute/UIAttributeShadow.h @@ -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 \ No newline at end of file diff --git a/ui/attribute/UIAttributeType.h b/ui/attribute/UIAttributeType.h new file mode 100644 index 0000000..34f9ed8 --- /dev/null +++ b/ui/attribute/UIAttributeType.h @@ -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 \ No newline at end of file diff --git a/utils/PerformanceProfiler.h b/utils/PerformanceProfiler.h new file mode 100644 index 0000000..84a2bf1 --- /dev/null +++ b/utils/PerformanceProfiler.h @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include + +// @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; +} \ No newline at end of file diff --git a/utils/StringUtils.h b/utils/StringUtils.h index e3f30b7..fa8a3d4 100644 --- a/utils/StringUtils.h +++ b/utils/StringUtils.h @@ -15,6 +15,7 @@ #include #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