├── .gitignore ├── .leif ├── .config │ └── pulse │ │ └── cookie ├── .leif │ ├── .config │ │ └── pulse │ │ │ └── cookie │ └── .leif │ │ └── .config │ │ └── pulse │ │ └── cookie └── assets │ ├── fonts │ └── inter.ttf │ └── textures │ ├── .config │ └── pulse │ │ └── cookie │ ├── arrow-down.png │ └── tick.png ├── Makefile ├── PKGBUILD ├── README.md ├── branding ├── div-showcase.gif ├── styling-elements.png └── ui-elements.png ├── compile_commands.json ├── include └── leif │ └── leif.h ├── install-macos.sh ├── install.sh ├── leif.c └── vendor ├── glad ├── include │ ├── KHR │ │ └── khrplatform.h │ └── glad │ │ └── glad.h └── src │ └── glad.c ├── stb_image └── stb_image.h ├── stb_image_resize └── stb_image_resize2.h └── stb_truetype └── stb_truetype.h /.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | bin/ 3 | lib/ 4 | -------------------------------------------------------------------------------- /.leif/.config/pulse/cookie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/.config/pulse/cookie -------------------------------------------------------------------------------- /.leif/.leif/.config/pulse/cookie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/.leif/.config/pulse/cookie -------------------------------------------------------------------------------- /.leif/.leif/.leif/.config/pulse/cookie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/.leif/.leif/.config/pulse/cookie -------------------------------------------------------------------------------- /.leif/assets/fonts/inter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/assets/fonts/inter.ttf -------------------------------------------------------------------------------- /.leif/assets/textures/.config/pulse/cookie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/assets/textures/.config/pulse/cookie -------------------------------------------------------------------------------- /.leif/assets/textures/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/assets/textures/arrow-down.png -------------------------------------------------------------------------------- /.leif/assets/textures/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/.leif/assets/textures/tick.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | INCS=`pkg-config --cflags glfw3 cglm` -Ivendor/glad/include -Ivendor/stb_image/ -Ivendor/stb_truetype -Ivendor/stb_image_resize 3 | CFLAGS+=${INCS} -DLF_GLFW -O3 -ffast-math 4 | all: lib/leif.a 5 | 6 | lib/leif.a: lib/leif.o 7 | ar cr lib/libleif.a lib/*.o 8 | lib/leif.o: lib 9 | ${CC} ${CFLAGS} -c leif.c -o lib/leif.o 10 | ${CC} -c vendor/glad/src/glad.c -o lib/glad.o 11 | cp -r .leif ~/ 12 | lib: 13 | mkdir lib 14 | clean: 15 | rm -r ./lib 16 | 17 | install: 18 | cp lib/libleif.a /usr/local/lib/ 19 | cp -r include/leif /usr/local/include/ 20 | 21 | uninstall: 22 | rm -f /usr/local/lib/libleif.a 23 | rm -rf /usr/local/include/leif/ 24 | rm -rf ~/.leif/ 25 | 26 | .PHONY: all test clean 27 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Contributor: Luxzi 2 | 3 | pkgname="libleif" 4 | _pkgname="leif" 5 | pkgver='1.0' 6 | pkgrel=1 7 | pkgdesc="Minimal, configurable & GPU accelerated Immediate Mode UI Library written with modern OpenGL" 8 | arch=('x86_64') 9 | url="https://github.com/cococry/leif" 10 | license=('GPL') 11 | groups=() 12 | depends=() 13 | makedepends=('git' 'make' 'gcc' 'glfw') 14 | provides=('libleif') 15 | source=("${_pkgname}::git+https://github.com/cococry/${_pkgname}.git") 16 | sha256sums=('SKIP') 17 | 18 | pkgver() { 19 | cd $_pkgname 20 | echo $pkgver 21 | } 22 | 23 | build() { 24 | cd $_pkgname 25 | make 26 | } 27 | 28 | package() { 29 | cd $_pkgname 30 | sudo make install 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leif 2 | 3 | ## Highlights 4 | 5 | Leif is UI in C/C++ made easy. The library is designed to be really easy to setup and provide maximal functionality. 6 | All of that while retaining a concise and readable codebase. 7 | 8 | #### Theming & Styling support 9 | The library comes with a fully-featured property system where each UI element can be customized 10 | to your liking. Every visible part of your UI can be configured in no time. 11 | 12 | #### Exposed Rendering API 13 | Leif comes with a very performant inhouse batch renderer. The renderers drawing API is fully exposed 14 | to the user. This expands the scope of application of the library a lot as you can also utilize it 15 | for various non-UI components of your project. 16 | 17 | #### Exposed State 18 | The UI state of leif is nearly completly exposed to the user which, with the rendering system, 19 | makes it really easy to add new UI components client-side. 20 | 21 | #### Complete Input-Handling System 22 | The library contains an input subsystem that can be utilized by the user for various different 23 | tasks (eg. is_key_down, is_mouse_button_released, etc.) which makes development a whole lot easier. 24 | 25 | 26 | ### Dependencies 27 | 28 | | Dependency | Reason of Usage | 29 | | ----------------|-------------| 30 | | [glad](https://github.com/Dav1dde/glad) | Loading OpenGL functions | 31 | | [stb_image](https://github.com/nothings/stb/blob/master/stb_image.h) | Loading image files into memory | 32 | | [stb_image_resize2](https://github.com/nothings/stb/blob/master/stb_image_resize2.h) | Resizing images | 33 | | [stb_truetype](https://github.com/nothings/stb/blob/master/stb_truetype.h) | Loading font glyphs from font files | 34 | | [cglm](https://github.com/recp/cglm) | Linear Algebra Math | 35 | | [*GLFW](https://github.com/glfw/glfw) | Handling windowing, input etc. | 36 | 37 | *: This library is an optional library and will be replacable with other libraries 38 | 39 | 40 | ## Installation 41 | 42 | Installing the library is made very easy by the use of an install script: 43 | ```console 44 | git clone https://github.com/cococry/leif 45 | cd leif 46 | ./install.sh 47 | ``` 48 | 49 | ## Usage 50 | 51 | Initialize Leif before using and after creating a window with your windowing backend: 52 | ```c 53 | lf_init_glfw(displayWidth, displayHeight, glfwWindow); 54 | ``` 55 | 56 | Within the main loop of your program, call the lf_begin() and lf_end() functions within which you can render UI elements. 57 | 58 | #### Simple hello-world application with GLFW for windowing 59 | ```c 60 | #include 61 | #include 62 | 63 | int main() { 64 | glfwInit(); 65 | GLFWwindow* window = glfwCreateWindow(800, 600, "Hello", NULL, NULL); 66 | 67 | glfwMakeContextCurrent(window); 68 | 69 | lf_init_glfw(800, 600, window); 70 | 71 | while(!glfwWindowShouldClose(window)) { 72 | glClear(GL_COLOR_BUFFER_BIT); 73 | glClearColor(0.1f, 0.1f, 0.1f, 1.0f); 74 | 75 | lf_begin(); 76 | 77 | lf_text("Hello, Leif!"); 78 | 79 | lf_end(); 80 | glfwSwapBuffers(window); 81 | glfwPollEvents(); 82 | } 83 | lf_terminate(); 84 | glfwDestroyWindow(window); 85 | glfwTerminate(); 86 | } 87 | ``` 88 | 89 | 90 | #### Hello-World Plus 91 | ```c 92 | #include 93 | #include 94 | 95 | static int winw = 800, winh = 800; 96 | 97 | static void resizecb(GLFWwindow* window, int w, int h) { 98 | winw = w; 99 | winh = h; 100 | glViewport(0, 0, w, h); 101 | // Resize the leif display on window resize 102 | lf_resize_display(w, h); 103 | } 104 | 105 | int main() { 106 | glfwInit(); 107 | GLFWwindow* window = glfwCreateWindow(winw, winh, "Hello", NULL, NULL); 108 | 109 | glfwMakeContextCurrent(window); 110 | 111 | glfwSetFramebufferSizeCallback(window, resizecb); 112 | 113 | lf_init_glfw(winw, winh, window); 114 | 115 | // Loading a bigger font (replace /home/cococry with your user) 116 | LfFont bigfont = lf_load_font("/home/cococry/.leif/assets/fonts/inter.ttf", 50); 117 | 118 | while(!glfwWindowShouldClose(window)) { 119 | glClear(GL_COLOR_BUFFER_BIT); 120 | glClearColor(0.1f, 0.1f, 0.1f, 1.0f); 121 | 122 | // Starting leif context 123 | lf_begin(); 124 | 125 | const char* text = "Hello, Leif!"; 126 | const char* btntext = "Exit"; 127 | 128 | // Defining properties of the button 129 | LfUIElementProps btnprops = lf_get_theme().button_props; 130 | btnprops.margin_left = 0.0f; btnprops.margin_top = 15.0f; btnprops.border_width = 0.0f; btnprops.corner_radius = 9.0f; 131 | btnprops.text_color = LF_WHITE; 132 | btnprops.color = (LfColor){90, 90, 90, 255}; 133 | 134 | // Beginnig a new container 135 | { 136 | // Styling the container (setting corner radius) 137 | LfUIElementProps props = lf_get_theme().div_props; 138 | props.corner_radius = 10.0f; 139 | lf_push_style_props(props); 140 | 141 | // Positioning the container in the center 142 | float width = 400.0f, height = 400.0f; 143 | lf_div_begin(((vec2s){(winw - width) / 2.0f, (winh - height) / 2.0f}), ((vec2s){width, height}), false); 144 | 145 | // Popping the container's props again 146 | lf_pop_style_props(); 147 | } 148 | 149 | /* Text */ 150 | { 151 | // Setting big font 152 | lf_push_font(&bigfont); 153 | // Center the text horizontally 154 | lf_set_ptr_x_absolute((winw - lf_text_dimension(text).x) / 2.0f); 155 | // Center the text (and button) vertically 156 | lf_set_ptr_y_absolute((winh - (lf_text_dimension(text).y + lf_text_dimension(btntext).y + btnprops.padding * 2.0f + btnprops.margin_top)) / 2.0f); 157 | 158 | // Remove any margin 159 | LfUIElementProps props = lf_get_theme().text_props; 160 | props.margin_left = 0.0f; props.margin_right = 0.0f; 161 | // Push the style props 162 | lf_push_style_props(props); 163 | 164 | // Render the text 165 | lf_text(text); 166 | 167 | // Pop the style props 168 | lf_pop_style_props(); 169 | 170 | // Unsetting big font 171 | lf_pop_font(); 172 | } 173 | 174 | // Advance into the next line 175 | lf_next_line(); 176 | 177 | /* Exit Button */ 178 | { 179 | const float width = 180.0f; 180 | 181 | lf_push_style_props(btnprops); 182 | // Center the button horizontally 183 | lf_set_ptr_x_absolute((winw - (width + btnprops.padding * 2.0f)) / 2.0f); 184 | 185 | // Rendering a button with fixed scale (-1 stands for "use normal height") 186 | if(lf_button_fixed(btntext, width, -1) == LF_CLICKED) { 187 | // Closing the window when you pressed the button 188 | glfwSetWindowShouldClose(window, 1); 189 | } 190 | 191 | lf_pop_style_props(); 192 | } 193 | 194 | // Ending the container 195 | lf_div_end(); 196 | 197 | // Ending leif context 198 | lf_end(); 199 | 200 | glfwSwapBuffers(window); 201 | glfwPollEvents(); 202 | } 203 | 204 | lf_terminate(); 205 | glfwDestroyWindow(window); 206 | glfwTerminate(); 207 | } 208 | ``` 209 | 210 | 211 | 212 | ## Building your project 213 | 214 | To build your project, link it with leif and its depdencies. 215 | ```console 216 | gcc -o project project.c -lleif -lglfw -lm -lGL -lclipboard 217 | ``` 218 | 219 | ## Real world usage 220 | 221 | But how does the leif library perform in real applications? I am currently working on a music player called [lyssa](https://github.com/cococry/lyssa). The frontend of the player 222 | is written entirely with leif. You can see a brief look at of the application below. 223 | 224 | 225 | 226 | A [task management app](https://github.com/cococry/todo) that i also wrote with the leif library: 227 | 228 | 229 | ## Contributing 230 | 231 | You can contribute to Leif by: 232 | - [Fixing bugs or contributing to features](https://github.com/cococry/lyssa/issues) 233 | - [Changing features or adding new functionality](https://github.com/cococry/lyssa/pulls) 234 | 235 | 236 | -------------------------------------------------------------------------------- /branding/div-showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/branding/div-showcase.gif -------------------------------------------------------------------------------- /branding/styling-elements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/branding/styling-elements.png -------------------------------------------------------------------------------- /branding/ui-elements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/leif/21a71a3629877d57ba83b42b36fd17db2e621a07/branding/ui-elements.png -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "arguments": [ 4 | "/usr/bin/gcc", 5 | "-Ivendor/glad/include", 6 | "-Ivendor/stb_image/", 7 | "-Ivendor/stb_truetype", 8 | "-Ivendor/stb_image_resize", 9 | "-DLF_GLFW", 10 | "-O3", 11 | "-ffast-math", 12 | "-c", 13 | "-o", 14 | "lib/leif.o", 15 | "leif.c" 16 | ], 17 | "directory": "/home/cococry/dev/leif", 18 | "file": "/home/cococry/dev/leif/leif.c", 19 | "output": "/home/cococry/dev/leif/lib/leif.o" 20 | }, 21 | { 22 | "arguments": [ 23 | "/usr/bin/gcc", 24 | "-c", 25 | "-o", 26 | "lib/glad.o", 27 | "vendor/glad/src/glad.c" 28 | ], 29 | "directory": "/home/cococry/dev/leif", 30 | "file": "/home/cococry/dev/leif/vendor/glad/src/glad.c", 31 | "output": "/home/cococry/dev/leif/lib/glad.o" 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /include/leif/leif.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define LF_PRIMARY_ITEM_COLOR (LfColor){133, 138, 148, 255} 10 | #define LF_SECONDARY_ITEM_COLOR (LfColor){96, 100, 107, 255} 11 | 12 | #define LF_NO_COLOR (LfColor){0, 0, 0, 0} 13 | #define LF_WHITE (LfColor){255, 255, 255, 255} 14 | #define LF_BLACK (LfColor){0, 0, 0, 255} 15 | #define LF_RED (LfColor){255, 0, 0, 255} 16 | #define LF_GREEN (LfColor){0, 255, 0, 255} 17 | #define LF_BLUE (LfColor){0, 0, 255, 255} 18 | 19 | typedef struct { 20 | uint8_t r, g, b, a; 21 | } LfColor; 22 | 23 | // --- Events --- 24 | typedef struct { 25 | int32_t keycode; 26 | bool happened, pressed; 27 | } LfKeyEvent; 28 | 29 | typedef struct { 30 | int32_t button_code; 31 | bool happened, pressed; 32 | } LfMouseButtonEvent; 33 | 34 | typedef struct { 35 | int32_t x, y; 36 | bool happened; 37 | } LfCursorPosEvent; 38 | 39 | typedef struct { 40 | int32_t xoffset, yoffset; 41 | bool happened; 42 | } LfScrollEvent; 43 | 44 | typedef struct { 45 | int32_t charcode; 46 | bool happened; 47 | } LfCharEvent; 48 | 49 | typedef struct { 50 | uint32_t id; 51 | uint32_t width, height; 52 | } LfTexture; 53 | 54 | typedef struct { 55 | void* cdata; 56 | void* font_info; 57 | uint32_t tex_width, tex_height; 58 | uint32_t line_gap_add, font_size; 59 | LfTexture bitmap; 60 | 61 | uint32_t num_glyphs; 62 | } LfFont; 63 | 64 | typedef enum { 65 | LF_TEX_FILTER_LINEAR = 0, 66 | LF_TEX_FILTER_NEAREST 67 | } LfTextureFiltering; 68 | 69 | typedef struct { 70 | float width, height; 71 | int32_t end_x, end_y; 72 | uint32_t rendered_count; 73 | } LfTextProps; 74 | 75 | typedef struct { 76 | int32_t cursor_index, width, height, start_height; 77 | char* buf; 78 | uint32_t buf_size; 79 | char* placeholder; 80 | bool selected; 81 | 82 | uint32_t max_chars; 83 | 84 | int32_t selection_start, selection_end, 85 | mouse_selection_start, mouse_selection_end; 86 | int32_t selection_dir, mouse_dir; 87 | 88 | bool _init; 89 | 90 | void (*char_callback)(char); 91 | void (*insert_override_callback)(void*); 92 | void (*key_callback)(void*); 93 | 94 | bool retain_height; 95 | } LfInputField; 96 | 97 | typedef struct { 98 | void* val; 99 | int32_t handle_pos; 100 | bool _init; 101 | float min, max; 102 | bool held, selcted; 103 | float width; 104 | float height; 105 | uint32_t handle_size; 106 | LfColor handle_color; 107 | } LfSlider; 108 | 109 | typedef enum { 110 | LF_RELEASED = -1, 111 | LF_IDLE = 0, 112 | LF_HOVERED = 1, 113 | LF_CLICKED = 2, 114 | LF_HELD = 3, 115 | } LfClickableItemState; 116 | 117 | 118 | typedef struct { 119 | LfColor color, hover_color; 120 | LfColor text_color, hover_text_color; 121 | LfColor border_color; 122 | float padding; 123 | float margin_left; 124 | float margin_right; 125 | float margin_top; 126 | float margin_bottom; 127 | float border_width; 128 | float corner_radius; 129 | } LfUIElementProps; 130 | 131 | typedef struct { 132 | vec2s pos, size; 133 | } LfAABB; 134 | 135 | typedef struct { 136 | LfUIElementProps button_props, div_props, text_props, image_props, 137 | inputfield_props, checkbox_props, slider_props, scrollbar_props; 138 | LfFont font; 139 | bool div_smooth_scroll; 140 | float div_scroll_acceleration, div_scroll_max_velocity; 141 | float div_scroll_amount_px; 142 | float div_scroll_velocity_deceleration; 143 | 144 | float scrollbar_width; 145 | } LfTheme; 146 | 147 | typedef struct { 148 | int64_t id; 149 | LfAABB aabb; 150 | LfClickableItemState interact_state; 151 | 152 | bool scrollable; 153 | 154 | vec2s total_area; 155 | } LfDiv; 156 | 157 | typedef void (*LfMenuItemCallback)(uint32_t*); 158 | 159 | void lf_init_glfw(uint32_t display_width, uint32_t display_height, void* glfw_window); 160 | 161 | void lf_terminate(); 162 | 163 | LfTheme lf_default_theme(); 164 | 165 | LfTheme lf_get_theme(); 166 | 167 | void lf_set_theme(LfTheme theme); 168 | 169 | void lf_resize_display(uint32_t display_width, uint32_t display_height); 170 | 171 | LfFont lf_load_font(const char* filepath, uint32_t size); 172 | 173 | LfFont lf_load_font_ex(const char* filepath, uint32_t size, uint32_t bitmap_w, uint32_t bitmap_h); 174 | 175 | LfTexture lf_load_texture(const char* filepath, bool flip, LfTextureFiltering filter); 176 | 177 | LfTexture lf_load_texture_resized(const char* filepath, bool flip, LfTextureFiltering filter, uint32_t w, uint32_t h); 178 | 179 | LfTexture lf_load_texture_resized_factor(const char* filepath, bool flip, LfTextureFiltering filter, float wfactor, float hfactor); 180 | 181 | LfTexture lf_load_texture_from_memory(const void* data, size_t size, bool flip, LfTextureFiltering filter); 182 | 183 | LfTexture lf_load_texture_from_memory_resized(const void* data, size_t size, bool flip, LfTextureFiltering filter, uint32_t w, uint32_t h); 184 | 185 | LfTexture lf_load_texture_from_memory_resized_factor(const void* data, size_t size, bool flip, LfTextureFiltering filter, float wfactor, float hfactor); 186 | 187 | LfTexture lf_load_texture_from_memory_resized_to_fit(const void* data, size_t size, bool flip, LfTextureFiltering filter, int32_t container_w, int32_t container_h); 188 | 189 | unsigned char* lf_load_texture_data(const char* filepath, int32_t* width, int32_t* height, int32_t* channels, bool flip); 190 | 191 | unsigned char* lf_load_texture_data_resized(const char* filepath, int32_t w, int32_t h, int32_t* channels, bool flip); 192 | 193 | unsigned char* lf_load_texture_data_resized_factor(const char* filepath, int32_t wfactor, int32_t hfactor, int32_t* width, int32_t* height, int32_t* channels, bool flip); 194 | 195 | unsigned char* lf_load_texture_data_from_memory(const void* data, size_t size, int32_t* width, int32_t* height, int32_t* channels, bool flip); 196 | 197 | unsigned char* lf_load_texture_data_from_memory_resized(const void* data, size_t size, int32_t* channels, int32_t* o_w, int32_t* o_h, bool flip, uint32_t w, uint32_t h); 198 | 199 | unsigned char* lf_load_texture_data_from_memory_resized_to_fit_ex(const void* data, size_t size, int32_t* o_width, int32_t* o_height, int32_t i_channels, 200 | int32_t i_width, int32_t i_height, bool flip, int32_t container_w, int32_t container_h); 201 | 202 | unsigned char* lf_load_texture_data_from_memory_resized_to_fit(const void* data, size_t size, int32_t* o_width, int32_t* o_height, int32_t* o_channels, 203 | bool flip, int32_t container_w, int32_t container_h); 204 | 205 | unsigned char* lf_load_texture_data_from_memory_resized_factor(const void* data, size_t size, int32_t* width, int32_t* height, int32_t* channels, bool flip, float wfactor, float hfactor); 206 | 207 | void lf_create_texture_from_image_data(LfTextureFiltering filter, uint32_t* id, int32_t width, int32_t height, int32_t channels, unsigned char* data); 208 | 209 | void lf_free_texture(LfTexture* tex); 210 | 211 | void lf_free_font(LfFont* font); 212 | 213 | LfFont lf_load_font_asset(const char* asset_name, const char* file_extension, uint32_t font_size); 214 | 215 | LfTexture lf_load_texture_asset(const char* asset_name, const char* file_extension); 216 | 217 | void lf_add_key_callback(void* cb); 218 | 219 | void lf_add_mouse_button_callback(void* cb); 220 | 221 | void lf_add_scroll_callback(void* cb); 222 | 223 | void lf_add_cursor_pos_callback(void* cb); 224 | 225 | bool lf_key_went_down(uint32_t key); 226 | 227 | bool lf_key_is_down(uint32_t key); 228 | 229 | bool lf_key_is_released(uint32_t key); 230 | 231 | bool lf_key_changed(uint32_t key); 232 | 233 | bool lf_mouse_button_went_down(uint32_t button); 234 | 235 | bool lf_mouse_button_is_down(uint32_t button); 236 | 237 | bool lf_mouse_button_is_released(uint32_t button); 238 | 239 | bool lf_mouse_button_changed(uint32_t button); 240 | 241 | bool lf_mouse_button_went_down_on_div(uint32_t button); 242 | 243 | bool lf_mouse_button_is_released_on_div(uint32_t button); 244 | 245 | bool lf_mouse_button_changed_on_div(uint32_t button); 246 | 247 | double lf_get_mouse_x(); 248 | 249 | double lf_get_mouse_y(); 250 | 251 | double lf_get_mouse_x_delta(); 252 | 253 | double lf_get_mouse_y_delta(); 254 | 255 | double lf_get_mouse_scroll_x(); 256 | 257 | double lf_get_mouse_scroll_y(); 258 | 259 | #define lf_div_begin(pos, size, scrollable) {\ 260 | static float scroll = 0.0f; \ 261 | static float scroll_velocity = 0.0f; \ 262 | _lf_div_begin_loc(pos, size, scrollable, &scroll, &scroll_velocity, __FILE__, __LINE__);\ 263 | } 264 | 265 | #define lf_div_begin_ex(pos, size, scrollable, scroll_ptr, scroll_velocity_ptr) _lf_div_begin_loc(pos, size, scrollable, scroll_ptr, scroll_velocity_ptr, __FILE__, __LINE__); 266 | 267 | LfDiv* _lf_div_begin_loc(vec2s pos, vec2s size, bool scrollable, float* scroll, 268 | float* scroll_velocity, const char* file, int32_t line); 269 | 270 | void lf_div_end(); 271 | 272 | LfClickableItemState _lf_item_loc(vec2s size, const char* file, int32_t line); 273 | #define lf_item(size) _lf_item_loc(size, __FILE__, __LINE__) 274 | 275 | #define lf_button(text) _lf_button_loc(text, __FILE__, __LINE__) 276 | LfClickableItemState _lf_button_loc(const char* text, const char* file, int32_t line); 277 | 278 | #define lf_button_wide(text) _lf_button_wide_loc(text, __FILE__, __LINE__) 279 | LfClickableItemState _lf_button_wide_loc(const wchar_t* text, const char* file, int32_t line); 280 | 281 | #define lf_image_button(img) _lf_image_button_loc(img, __FILE__, __LINE__) 282 | LfClickableItemState _lf_image_button_loc(LfTexture img, const char* file, int32_t line); 283 | 284 | #define lf_image_button_fixed(img, width, height) _lf_image_button_fixed_loc(img, width, height, __FILE__, __LINE__) 285 | LfClickableItemState _lf_image_button_fixed_loc(LfTexture img, float width, float height, const char* file, int32_t line); 286 | 287 | #define lf_button_fixed(text, width, height) _lf_button_fixed_loc(text, width, height, __FILE__, __LINE__) 288 | LfClickableItemState _lf_button_fixed_loc(const char* text, float width, float height, const char* file, int32_t line); 289 | 290 | #define lf_button_fixed_wide(text, width, height) _lf_button_fixed_loc_wide(text, width, height, __FILE__, __LINE__) 291 | LfClickableItemState _lf_button_fixed_wide_loc(const wchar_t* text, float width, float height, const char* file, int32_t line); 292 | 293 | #define lf_slider_int(slider) _lf_slider_int_loc(slider, __FILE__, __LINE__) 294 | LfClickableItemState _lf_slider_int_loc(LfSlider* slider, const char* file, int32_t line); 295 | 296 | #define lf_slider_int_inl_ex(slider_val, slider_min, slider_max, slider_width, slider_height, slider_handle_size, state) { \ 297 | static LfSlider slider = { \ 298 | .val = slider_val, \ 299 | .handle_pos = 0, \ 300 | .min = slider_min, \ 301 | .max = slider_max, \ 302 | .width = slider_width, \ 303 | .height = slider_height, \ 304 | .handle_size = slider_handle_size \ 305 | }; \ 306 | state = lf_slider_int(&slider); \ 307 | } \ 308 | 309 | #define lf_slider_int_inl(slider_val, slider_min, slider_max, state) lf_slider_int_inl_ex(slider_val, slider_min, slider_max, lf_get_current_div().aabb.size.x / 2.0f, 5, 0, state) 310 | 311 | #define lf_progress_bar_val(width, height, min, max, val) _lf_progress_bar_val_loc(width, height, min, max, val, __FILE__, __LINE__) 312 | LfClickableItemState _lf_progress_bar_val_loc(float width, float height, int32_t min, int32_t max, int32_t val, const char* file, int32_t line); 313 | 314 | #define lf_progress_bar_int(val, min, max, width, height) _lf_progress_bar_int_loc(val, min, max, width, height, __FILE__, __LINE__) 315 | LfClickableItemState _lf_progress_bar_int_loc(float val, float min, float max, float width, float height, const char* file, int32_t line); 316 | 317 | #define lf_progress_stripe_int(slider) _lf_progresss_stripe_int_loc(slider , __FILE__, __LINE__) 318 | LfClickableItemState _lf_progress_stripe_int_loc(LfSlider* slider, const char* file, int32_t line); 319 | 320 | #define lf_checkbox(text, val, tick_color, tex_color) _lf_checkbox_loc(text, val, tick_color, tex_color, __FILE__, __LINE__) 321 | LfClickableItemState _lf_checkbox_loc(const char* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line); 322 | 323 | #define lf_checkbox_wide(text, val, tick_color, tex_color) _lf_checkbox_wide_loc(text, val, tick_color, tex_color, __FILE__, __LINE__) 324 | LfClickableItemState _lf_checkbox_wide_loc(const wchar_t* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line); 325 | 326 | #define lf_menu_item_list(items, item_count, selected_index, per_cb, vertical) _lf_menu_item_list_loc(__FILE__, __LINE__, items, item_count, selected_index, per_cb, vertical) 327 | int32_t _lf_menu_item_list_loc(const char** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line); 328 | 329 | #define lf_menu_item_list_wide(items, item_count, selected_index, per_cb, vertical) _lf_menu_item_list_loc_wide(__FILE__, __LINE__, items, item_count, selected_index, per_cb, vertical) 330 | int32_t _lf_menu_item_list_loc_wide(const wchar_t** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line); 331 | 332 | #define lf_dropdown_menu(items, placeholder, item_count, width, height, selected_index, opened) _lf_dropdown_menu_loc(items, placeholder, item_count, width, height, selected_index, opened, __FILE__, __LINE__) 333 | void _lf_dropdown_menu_loc(const char** items, const char* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line); 334 | 335 | #define lf_dropdown_menu_wide(items, placeholder, item_count, width, height, selected_index, opened) _lf_dropdown_menu_loc_wide(items, placeholder, item_count, width, height, selected_index, opened, __FILE__, __LINE__) 336 | void _lf_dropdown_menu_loc_wide(const wchar_t** items, const wchar_t* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line); 337 | 338 | #define lf_input_text_inl_ex(buffer, buffer_size, input_width, placeholder_str) \ 339 | { \ 340 | static LfInputField input = { \ 341 | .cursor_index = 0, \ 342 | .width = input_width, \ 343 | .buf = buffer, \ 344 | .buf_size = buffer_size, \ 345 | .placeholder = (char*)placeholder_str, \ 346 | .selected = false \ 347 | }; \ 348 | _lf_input_text_loc(&input, __FILE__, __LINE__); \ 349 | } \ 350 | 351 | #define lf_input_text_inl(buffer, buffer_size) lf_input_text_inl_ex(buffer, buffer_size, (int32_t)(lf_get_current_div().aabb.size.x / 2), "") 352 | 353 | #define lf_input_text(input) _lf_input_text_loc(input, __FILE__, __LINE__) 354 | void _lf_input_text_loc(LfInputField* input, const char* file, int32_t line); 355 | 356 | #define lf_input_int(input) _lf_input_int_loc(input, __FILE__, __LINE__) 357 | void _lf_input_int_loc(LfInputField* input, const char* file, int32_t line); 358 | 359 | #define lf_input_float(input) _lf_input_float_loc(input, __FILE__, __LINE__) 360 | void _lf_input_float_loc(LfInputField* input, const char* file, int32_t line); 361 | 362 | void lf_input_insert_char_idx(LfInputField* input, char c, uint32_t idx); 363 | 364 | void lf_input_insert_str_idx(LfInputField* input, const char* insert, uint32_t len, uint32_t idx); 365 | 366 | void lf_input_field_unselect_all(LfInputField* input); 367 | 368 | bool lf_input_grabbed(); 369 | 370 | void lf_div_grab(LfDiv div); 371 | 372 | void lf_div_ungrab(); 373 | 374 | bool lf_div_grabbed(); 375 | 376 | LfDiv lf_get_grabbed_div(); 377 | 378 | #define lf_begin() _lf_begin_loc(__FILE__, __LINE__) 379 | void _lf_begin_loc(const char* file, int32_t line); 380 | 381 | void lf_end(); 382 | 383 | void lf_next_line(); 384 | 385 | vec2s lf_text_dimension(const char* str); 386 | 387 | vec2s lf_text_dimension_ex(const char* str, float wrap_point); 388 | 389 | vec2s lf_text_dimension_wide(const wchar_t* str); 390 | 391 | vec2s lf_text_dimension_wide_ex(const wchar_t* str, float wrap_point); 392 | 393 | vec2s lf_button_dimension(const char* text); 394 | 395 | float lf_get_text_end(const char* str, float start_x); 396 | 397 | void lf_text(const char* text); 398 | 399 | void lf_text_wide(const wchar_t* text); 400 | 401 | void lf_set_text_wrap(bool wrap); 402 | 403 | LfDiv lf_get_current_div(); 404 | 405 | LfDiv lf_get_selected_div(); 406 | 407 | LfDiv* lf_get_current_div_ptr(); 408 | 409 | LfDiv* lf_get_selected_div_ptr(); 410 | 411 | void lf_set_ptr_x(float x); 412 | 413 | void lf_set_ptr_y(float y); 414 | 415 | void lf_set_ptr_x_absolute(float x); 416 | 417 | void lf_set_ptr_y_absolute(float y); 418 | 419 | float lf_get_ptr_x(); 420 | 421 | float lf_get_ptr_y(); 422 | 423 | uint32_t lf_get_display_width(); 424 | 425 | uint32_t lf_get_display_height(); 426 | 427 | void lf_push_font(LfFont* font); 428 | 429 | void lf_pop_font(); 430 | 431 | LfTextProps lf_text_render(vec2s pos, const char* str, LfFont font, LfColor color, 432 | int32_t wrap_point, vec2s stop_point, bool no_render, bool render_solid, int32_t start_index, int32_t end_index); 433 | 434 | LfTextProps lf_text_render_wchar(vec2s pos, const wchar_t* str, LfFont font, LfColor color, 435 | int32_t wrap_point, vec2s stop_point, bool no_render, bool render_solid, int32_t start_index, int32_t end_index); 436 | 437 | void lf_rect_render(vec2s pos, vec2s size, LfColor color, LfColor border_color, float border_width, float corner_radius); 438 | 439 | void lf_image_render(vec2s pos, LfColor color, LfTexture tex, LfColor border_color, float border_width, float corner_radius); 440 | 441 | bool lf_point_intersects_aabb(vec2s p, LfAABB aabb); 442 | 443 | bool lf_aabb_intersects_aabb(LfAABB a, LfAABB b); 444 | 445 | void lf_push_style_props(LfUIElementProps props); 446 | 447 | void lf_pop_style_props(); 448 | 449 | bool lf_hovered(vec2s pos, vec2s size); 450 | 451 | bool lf_area_hovered(vec2s pos, vec2s size); 452 | 453 | LfCursorPosEvent lf_mouse_move_event(); 454 | 455 | LfMouseButtonEvent lf_mouse_button_event(); 456 | 457 | LfScrollEvent lf_mouse_scroll_event(); 458 | 459 | LfKeyEvent lf_key_event(); 460 | 461 | LfCharEvent lf_char_event(); 462 | 463 | void lf_set_cull_start_x(float x); 464 | 465 | void lf_set_cull_start_y(float y); 466 | 467 | void lf_set_cull_end_x(float x); 468 | 469 | void lf_set_cull_end_y(float y); 470 | 471 | void lf_unset_cull_start_x(); 472 | 473 | void lf_unset_cull_start_y(); 474 | 475 | void lf_unset_cull_end_x(); 476 | 477 | void lf_unset_cull_end_y(); 478 | 479 | void lf_set_image_color(LfColor color); 480 | 481 | void lf_unset_image_color(); 482 | 483 | void lf_set_current_div_scroll(float scroll); 484 | 485 | float lf_get_current_div_scroll(); 486 | 487 | void lf_set_current_div_scroll_velocity(float scroll_velocity); 488 | 489 | float lf_get_current_div_scroll_velocity(); 490 | 491 | void lf_set_line_height(uint32_t line_height); 492 | 493 | uint32_t lf_get_line_height(); 494 | 495 | void lf_set_line_should_overflow(bool overflow); 496 | 497 | void lf_set_div_hoverable(bool hoverable); 498 | 499 | void lf_push_element_id(int64_t id); 500 | 501 | void lf_pop_element_id(); 502 | 503 | LfColor lf_color_brightness(LfColor color, float brightness); 504 | 505 | LfColor lf_color_alpha(LfColor color, uint8_t a); 506 | 507 | vec4s lf_color_to_zto(LfColor color); 508 | 509 | LfColor lf_color_from_hex(uint32_t hex); 510 | 511 | LfColor lf_color_from_zto(vec4s zto); 512 | 513 | void lf_image(LfTexture tex); 514 | 515 | void lf_rect(float width, float height, LfColor color, float corner_radius); 516 | 517 | void lf_seperator(); 518 | 519 | void lf_set_clipboard_text(const char* text); 520 | 521 | char* lf_get_clipboard_text(); 522 | 523 | void lf_set_no_render(bool no_render); 524 | -------------------------------------------------------------------------------- /install-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if Homebrew is installed 4 | if ! command -v brew &> /dev/null; then 5 | echo "Homebrew is not installed. Please install it first." 6 | echo "You can install it by running:" 7 | echo '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"' 8 | exit 1 9 | fi 10 | 11 | # Install required packages 12 | brew install exiftool jq glfw cglm 2>/dev/null 13 | 14 | # Check Mac's graphics capabilities 15 | METAL_SUPPORT=$(system_profiler SPDisplaysDataType | grep "Metal: Supported") 16 | if [[ -n "$METAL_SUPPORT" ]]; then 17 | echo "Your Mac supports Metal, which should be sufficient for graphics operations." 18 | else 19 | echo "Metal support not detected. This project may face compatibility issues on your system." 20 | echo "Proceeding with installation anyway..." 21 | fi 22 | 23 | # Build and install cglm if not already installed 24 | if ! pkg-config --exists cglm; then 25 | echo "cglm not found on the system" 26 | echo "building cglm" 27 | git clone https://github.com/recp/cglm 28 | cd cglm 29 | mkdir build 30 | cd build 31 | cmake .. 32 | make -j$(sysctl -n hw.ncpu) 33 | sudo make install 34 | cd ../../ 35 | rm -rf cglm 36 | fi 37 | 38 | # Build and install libclipboard 39 | if ! pkg-config --exists libclipboard; then 40 | echo "libclipboard not found on the system" 41 | echo "building libclipboard" 42 | git clone https://github.com/jtanx/libclipboard 43 | cd libclipboard 44 | cmake . 45 | make -j$(sysctl -n hw.ncpu) 46 | sudo make install 47 | cd .. 48 | rm -rf libclipboard 49 | fi 50 | 51 | # Modify the Makefile to use Homebrew's include and lib directories 52 | sed -i '' 's/-I\/usr\/local\/include/-I\/opt\/homebrew\/include/g' Makefile 53 | sed -i '' 's/-L\/usr\/local\/lib/-L\/opt\/homebrew\/lib/g' Makefile 54 | 55 | # Build Leif 56 | make -j$(sysctl -n hw.ncpu) && sudo make install 57 | cp -rf ./.leif ~/ 58 | 59 | echo "=====================" 60 | echo "INSTALLATION FINISHED" 61 | echo "=====================" 62 | echo "If you encounter any issues running Leif, please contact the project maintainers" 63 | echo "for guidance on macOS compatibility." -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # Function to install packages using apt (Debian/Ubuntu) 5 | install_with_apt() { 6 | sudo apt update 7 | sudo apt-get install mesa-utils 8 | } 9 | 10 | # Function to install packages using yum (Red Hat/CentOS) 11 | install_with_yum() { 12 | sudo yum install glx-utils 13 | } 14 | 15 | # Function to install packages using pacman (Arch Linux) 16 | install_with_pacman() { 17 | sudo pacman -Sy --noconfirm glxinfo 18 | } 19 | 20 | if [ -f /etc/arch-release ]; then 21 | install_with_pacman 22 | elif [ -f /etc/debian_version ]; then 23 | install_with_apt 24 | elif [ -f /etc/redhat-release ] || [ -f /etc/centos-release ]; then 25 | install_with_yum 26 | else 27 | echo "Your linux distro is not supported currently." 28 | echo "You need to manually install those packages: exiftool, jq, glfw" 29 | fi 30 | 31 | get_opengl_version() { 32 | glxinfo | grep "OpenGL version string" | awk '{print $4}' 33 | } 34 | 35 | REQUIRED_VERSION="4.5" 36 | CURRENT_VERSION=$(get_opengl_version) 37 | 38 | if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$CURRENT_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then 39 | echo "Your OpenGL version is $CURRENT_VERSION. OpenGL version $REQUIRED_VERSION or higher is required." 40 | echo "Please update your OpenGL drivers." 41 | exit 1 42 | fi 43 | 44 | echo "OpenGL $CURRENT_VERSION is sufficient." 45 | 46 | if ! pkg-config cglm; then 47 | echo "cglm not found on the system" 48 | echo "building cglm" 49 | git clone https://github.com/recp/cglm 50 | cd cglm 51 | mkdir build 52 | cd build 53 | cmake .. 54 | make -j$(nproc) 55 | sudo make install 56 | cd ../../ 57 | rm -rf cglm 58 | fi 59 | 60 | if ! pkg-config libclipboard; then 61 | echo "libclipboard not found on the system" 62 | echo "building libclipboard" 63 | git clone https://github.com/jtanx/libclipboard 64 | cd libclipboard 65 | cmake . 66 | make -j$(nproc) 67 | sudo make install 68 | cd .. 69 | rm -rf libclipboard 70 | fi 71 | 72 | make -j$(nproc) && sudo make install 73 | cp -rf ./.leif ~/ 74 | 75 | echo "=====================" 76 | echo "INSTALLATION FINISHED" 77 | echo "=====================" 78 | -------------------------------------------------------------------------------- /leif.c: -------------------------------------------------------------------------------- 1 | #include "include/leif/leif.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #define STB_IMAGE_IMPLEMENTATION 7 | #include 8 | #define STB_TRUETYPE_IMPLEMENTATION 9 | #include 10 | #define STB_IMAGE_RESIZE_IMPLEMENTATION 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef _WIN32 28 | #define HOMEDIR "USERPROFILE" 29 | #else 30 | #define HOMEDIR (char*)"HOME" 31 | #endif 32 | 33 | #ifdef LF_GLFW 34 | #ifndef GLFW_INCLUDE_NONE 35 | #define GLFW_INCLUDE_NONE 36 | #endif 37 | #include 38 | #endif /* ifdef */ 39 | 40 | #define MAX(a, b) a > b ? a : b 41 | #define MIN(a, b) a < b ? a : b 42 | 43 | #define LF_TRACE(...) { printf("Leif: [TRACE]: "); printf(__VA_ARGS__); printf("\n"); } 44 | #ifdef LF_DEBUG 45 | #define LF_DEBUG(...) { printf("Leif: [DEBUG]: "); printf(__VA_ARGS__); printf("\n"); } 46 | #else 47 | #define LF_DEBUG(...) 48 | #endif // LF_DEBUG 49 | #define LF_INFO(...) { printf("Leif: [INFO]: "); printf(__VA_ARGS__); printf("\n"); } 50 | #define LF_WARN(...) { printf("Leif: [WARN]: "); printf(__VA_ARGS__); printf("\n"); } 51 | #define LF_ERROR(...) { printf("[LEIF ERROR]: "); printf(__VA_ARGS__); printf("\n"); } 52 | 53 | #ifdef _MSC_VER 54 | #define D_BREAK __debugbreak 55 | #elif defined(__clang__) || defined(__GNUC__) 56 | #define D_BREAK __builtin_trap 57 | #else 58 | #define D_BREAK 59 | #endif 60 | 61 | #ifdef _DEBUG 62 | #define LF_ASSERT(cond, ...) { if(cond) {} else { printf("[LEIF]: Assertion failed: '"); printf(__VA_ARGS__); printf("' in file '%s' on line %i.\n", __FILE__, __LINE__); D_BREAK(); }} 63 | #else 64 | #define LF_ASSERT(cond, ...) 65 | #endif // _DEBUG 66 | 67 | #define LF_STACK_INIT_CAP 4 68 | 69 | #ifdef LF_GLFW 70 | #define MAX_KEYS GLFW_KEY_LAST 71 | #define MAX_MOUSE_BUTTONS GLFW_MOUSE_BUTTON_LAST 72 | #define KEY_CALLBACK_t GLFWkeyfun 73 | #define MOUSE_BUTTON_CALLBACK_t GLFWmousebuttonfun 74 | #define SCROLL_CALLBACK_t GLFWscrollfun 75 | #define CURSOR_CALLBACK_t GLFWcursorposfun 76 | #else 77 | 78 | #define MAX_KEYS 0 79 | #define MAX_MOUSE_BUTTONS 0 80 | #define KEY_CALLBACK_t void* 81 | #define MOUSE_BUTTON_CALLBACK_t void* 82 | #define SCROLL_CALLBACK_t void* 83 | #define CURSOR_CALLBACK_t void* 84 | #endif 85 | #define MAX_RENDER_BATCH 10000 86 | #define MAX_TEX_COUNT_BATCH 32 87 | #define MAX_KEY_CALLBACKS 4 88 | #define MAX_MOUSE_BTTUON_CALLBACKS 4 89 | #define MAX_SCROLL_CALLBACKS 4 90 | #define MAX_CURSOR_POS_CALLBACKS 4 91 | 92 | #define DJB2_INIT 5381 93 | 94 | // -- Struct Defines --- 95 | typedef struct { 96 | uint32_t id; 97 | } LfShader; 98 | 99 | typedef struct { 100 | vec2 pos; // 8 Bytes 101 | vec4 border_color; // 16 Bytes 102 | float border_width; // 4 Bytes 103 | vec4 color; // 16 Bytes 104 | vec2 texcoord; // 8 Bytes 105 | float tex_index; // 4 Bytes 106 | vec2 scale; // 8 Bytes 107 | vec2 pos_px; // 8 Bytes 108 | float corner_radius; // 4 Bytes 109 | vec2 min_coord, max_coord; // 16 Bytes 110 | } Vertex; // 88 Bytes per vertex 111 | 112 | typedef struct { 113 | bool keys[MAX_KEYS]; 114 | bool keys_changed[MAX_KEYS]; 115 | } LfKeyboard; 116 | 117 | typedef struct { 118 | bool buttons_current[MAX_MOUSE_BUTTONS]; 119 | bool buttons_last[MAX_MOUSE_BUTTONS]; 120 | 121 | double xpos, ypos, xpos_last, ypos_last, xpos_delta, ypos_delta; 122 | bool first_mouse_press; 123 | double xscroll_delta, yscroll_delta; 124 | } LfMouse; 125 | 126 | typedef struct { 127 | bool is_dragging; 128 | vec2s start_cursor_pos; 129 | float start_scroll; 130 | } DragState; 131 | 132 | // State of input 133 | typedef struct { 134 | LfKeyboard keyboard; 135 | LfMouse mouse; 136 | 137 | // List of callbacks (user defined) 138 | KEY_CALLBACK_t key_cbs[MAX_KEY_CALLBACKS]; 139 | MOUSE_BUTTON_CALLBACK_t mouse_button_cbs[MAX_MOUSE_BTTUON_CALLBACKS]; 140 | SCROLL_CALLBACK_t scroll_cbs[MAX_SCROLL_CALLBACKS]; 141 | CURSOR_CALLBACK_t cursor_pos_cbs[MAX_CURSOR_POS_CALLBACKS]; 142 | 143 | uint32_t key_cb_count, mouse_button_cb_count, scroll_cb_count, cursor_pos_cb_count; 144 | } InputState; 145 | 146 | // State of the batch renderer 147 | typedef struct { 148 | LfShader shader; 149 | uint32_t vao, vbo, ibo; 150 | uint32_t vert_count; 151 | Vertex* verts; 152 | vec4s vert_pos[4]; 153 | LfTexture textures[MAX_TEX_COUNT_BATCH]; 154 | uint32_t tex_index, tex_count,index_count; 155 | } RenderState; 156 | 157 | typedef struct { 158 | LfUIElementProps* data; 159 | uint32_t count, cap; 160 | } PropsStack; 161 | 162 | typedef struct { 163 | bool init; 164 | 165 | // Window 166 | uint32_t dsp_w, dsp_h; 167 | void* window_handle; 168 | 169 | RenderState render; 170 | InputState input; 171 | LfTheme theme; 172 | 173 | LfDiv current_div, prev_div; 174 | int32_t current_line_height, prev_line_height; 175 | vec2s pos_ptr, prev_pos_ptr; 176 | 177 | 178 | // Pushable variables 179 | LfFont* font_stack, *prev_font_stack; 180 | LfUIElementProps div_props, prev_props_stack; 181 | LfColor image_color_stack; 182 | int64_t element_id_stack; 183 | 184 | PropsStack props_stack; 185 | 186 | // Event references 187 | LfKeyEvent key_ev; 188 | LfMouseButtonEvent mb_ev; 189 | LfCursorPosEvent cp_ev; 190 | LfScrollEvent scr_ev; 191 | LfCharEvent ch_ev; 192 | 193 | vec2s cull_start, cull_end; 194 | 195 | LfTexture tex_arrow_down, tex_tick; 196 | 197 | bool text_wrap, line_overflow, div_hoverable, input_grabbed; 198 | 199 | uint64_t active_element_id; 200 | 201 | float* scroll_velocity_ptr; 202 | float* scroll_ptr; 203 | 204 | LfDiv selected_div, selected_div_tmp, scrollbar_div, grabbed_div; 205 | 206 | uint32_t drawcalls; 207 | 208 | bool entered_div; 209 | 210 | bool div_velocity_accelerating; 211 | 212 | float last_time, delta_time; 213 | clipboard_c* clipboard; 214 | 215 | bool renderer_render; 216 | 217 | DragState drag_state; 218 | } LfState; 219 | 220 | typedef enum { 221 | INPUT_INT = 0, 222 | INPUT_FLOAT, 223 | INPUT_TEXT 224 | } InputFieldType; 225 | 226 | // Static object to retrieve state data during runtime 227 | static LfState state; 228 | 229 | // --- Renderer --- 230 | static uint32_t shader_create(GLenum type, const char* src); 231 | static LfShader shader_prg_create(const char* vert_src, const char* frag_src); 232 | static void shader_set_mat(LfShader prg, const char* name, mat4 mat); 233 | static void set_projection_matrix(); 234 | static void renderer_init(); 235 | static void renderer_flush(); 236 | static void renderer_begin(); 237 | 238 | static LfTextProps text_render_simple(vec2s pos, const char* text, LfFont font, LfColor font_color, bool no_render); 239 | static LfTextProps text_render_simple_wide(vec2s pos, const wchar_t* text, LfFont font, LfColor font_color, bool no_render); 240 | 241 | 242 | static LfClickableItemState button_ex(const char* file, int32_t line, vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color, vec2s hitbox_override); 243 | static LfClickableItemState button(const char* file, int32_t line, vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color); 244 | static LfClickableItemState div_container(vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color); 245 | static void next_line_on_overflow(vec2s size, float xoffset); 246 | static bool item_should_cull(LfAABB item); 247 | static void draw_scrollbar_on(LfDiv* div); 248 | 249 | static void input_field(LfInputField* input, InputFieldType type, const char* file, int32_t line); 250 | 251 | LfFont load_font(const char* filepath, uint32_t pixelsize, uint32_t tex_width, uint32_t tex_height, uint32_t line_gap_add); 252 | static LfFont get_current_font(); 253 | 254 | static LfClickableItemState button_element_loc(void* text, const char* file, int32_t line, bool wide); 255 | static LfClickableItemState button_fixed_element_loc(void* text, float width, float height, const char* file, int32_t line, bool wide); 256 | static LfClickableItemState checkbox_element_loc(void* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line, bool wide); 257 | static void dropdown_menu_item_loc(void** items, void* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line, bool wide); 258 | static int32_t menu_item_list_item_loc(void** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line, bool wide); 259 | 260 | // --- Utility --- 261 | static int32_t get_max_char_height_font(LfFont font); 262 | 263 | static void remove_i_str(char *str, int32_t index); 264 | static void remove_substr_str(char *str, int start_index, int end_index); 265 | static void insert_i_str(char *str, char ch, int32_t index); 266 | static void insert_str_str(char *source, const char *insert, int32_t index); 267 | static void substr_str(const char* str, int start_index, int end_index, char* substring); 268 | 269 | static int map_vals(int value, int from_min, int from_max, int to_min, int to_max); 270 | 271 | // --- Input --- 272 | #ifdef LF_GLFW 273 | static void glfw_key_callback(GLFWwindow* window, int32_t key, int scancode, int action, int mods); 274 | static void glfw_mouse_button_callback(GLFWwindow* window, int32_t button, int action, int mods); 275 | static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset); 276 | static void glfw_cursor_callback(GLFWwindow* window, double xpos, double ypos); 277 | static void glfw_char_callback(GLFWwindow* window, uint32_t charcode); 278 | #endif 279 | 280 | static void update_input(); 281 | static void clear_events(); 282 | 283 | static uint64_t djb2_hash(uint64_t hash, const void* buf, size_t size); 284 | 285 | static void props_stack_create(PropsStack* stack); 286 | static void props_stack_resize(PropsStack* stack, uint32_t newcap); 287 | static void props_stack_push(PropsStack* stack, LfUIElementProps props); 288 | static LfUIElementProps props_stack_pop(PropsStack* stack); 289 | static LfUIElementProps props_stack_peak(PropsStack* stack); 290 | static bool props_stack_empty(PropsStack* stack); 291 | 292 | static LfUIElementProps get_props_for(LfUIElementProps props); 293 | 294 | // --- Static Functions --- 295 | uint32_t shader_create(GLenum type, const char* src) { 296 | // Create && compile the shader with opengl 297 | uint32_t shader = glCreateShader(type); 298 | glShaderSource(shader, 1, &src, NULL); 299 | glCompileShader(shader); 300 | 301 | // Check for compilation errors 302 | int32_t compiled; 303 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 304 | 305 | if(!compiled) { 306 | LF_ERROR("Failed to compile %s shader.", type == GL_VERTEX_SHADER ? "vertex" : "fragment"); 307 | char info[512]; 308 | glGetShaderInfoLog(shader, 512, NULL, info); 309 | LF_INFO("%s", info); 310 | glDeleteShader(shader); 311 | } 312 | return shader; 313 | } 314 | 315 | LfShader shader_prg_create(const char* vert_src, const char* frag_src) { 316 | // Creating vertex & fragment shader with the shader API 317 | uint32_t vertex_shader = shader_create(GL_VERTEX_SHADER, vert_src); 318 | uint32_t fragment_shader = shader_create(GL_FRAGMENT_SHADER, frag_src); 319 | 320 | // Creating & linking the shader program with OpenGL 321 | LfShader prg; 322 | prg.id = glCreateProgram(); 323 | glAttachShader(prg.id, vertex_shader); 324 | glAttachShader(prg.id, fragment_shader); 325 | glLinkProgram(prg.id); 326 | 327 | // Check for linking errors 328 | int32_t linked; 329 | glGetProgramiv(prg.id, GL_LINK_STATUS, &linked); 330 | 331 | if(!linked) { 332 | LF_ERROR("Failed to link shader program."); 333 | char info[512]; 334 | glGetProgramInfoLog(prg.id, 512, NULL, info); 335 | LF_INFO("%s", info); 336 | glDeleteShader(vertex_shader); 337 | glDeleteShader(fragment_shader); 338 | glDeleteProgram(prg.id); 339 | return prg; 340 | } 341 | 342 | // Delete the shaders after 343 | glDeleteShader(vertex_shader); 344 | glDeleteShader(fragment_shader); 345 | return prg; 346 | } 347 | 348 | void shader_set_mat(LfShader prg, const char* name, mat4 mat) { 349 | glUniformMatrix4fv(glGetUniformLocation(prg.id, name), 1, GL_FALSE, mat[0]); 350 | } 351 | 352 | void set_projection_matrix() { 353 | float left = 0.0f; 354 | float right = state.dsp_w; 355 | float bottom = state.dsp_h; 356 | float top = 0.0f; 357 | float near = 0.1f; 358 | float far = 100.0f; 359 | 360 | // Create the orthographic projection matrix 361 | mat4 orthoMatrix = GLM_MAT4_IDENTITY_INIT; 362 | orthoMatrix[0][0] = 2.0f / (right - left); 363 | orthoMatrix[1][1] = 2.0f / (top - bottom); 364 | orthoMatrix[2][2] = -1; 365 | orthoMatrix[3][0] = -(right + left) / (right - left); 366 | orthoMatrix[3][1] = -(top + bottom) / (top - bottom); 367 | 368 | shader_set_mat(state.render.shader, "u_proj", orthoMatrix); 369 | } 370 | 371 | void renderer_init() { 372 | // OpenGL Setup 373 | glEnable(GL_BLEND); 374 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 375 | 376 | state.render.vert_count = 0; 377 | state.render.verts = (Vertex*)malloc(sizeof(Vertex) * MAX_RENDER_BATCH * 4); 378 | 379 | /* Creating vertex array & vertex buffer for the batch renderer */ 380 | glCreateVertexArrays(1, &state.render.vao); 381 | glBindVertexArray(state.render.vao); 382 | 383 | glCreateBuffers(1, &state.render.vbo); 384 | glBindBuffer(GL_ARRAY_BUFFER, state.render.vbo); 385 | glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * MAX_RENDER_BATCH * 4, NULL, 386 | GL_DYNAMIC_DRAW); 387 | 388 | uint32_t* indices = (uint32_t*)malloc(sizeof(uint32_t) * MAX_RENDER_BATCH * 6); 389 | 390 | uint32_t offset = 0; 391 | for (uint32_t i = 0; i < MAX_RENDER_BATCH * 6; i += 6) { 392 | indices[i + 0] = offset + 0; 393 | indices[i + 1] = offset + 1; 394 | indices[i + 2] = offset + 2; 395 | 396 | indices[i + 3] = offset + 2; 397 | indices[i + 4] = offset + 3; 398 | indices[i + 5] = offset + 0; 399 | offset += 4; 400 | } 401 | glCreateBuffers(1, &state.render.ibo); 402 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, state.render.ibo); 403 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_RENDER_BATCH * 6 * sizeof(uint32_t), indices, GL_STATIC_DRAW); 404 | 405 | free(indices); 406 | // Setting the vertex layout 407 | glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL); 408 | glEnableVertexAttribArray(0); 409 | 410 | glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t)offsetof(Vertex, border_color)); 411 | glEnableVertexAttribArray(1); 412 | 413 | glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t)offsetof(Vertex, border_width)); 414 | glEnableVertexAttribArray(2); 415 | 416 | glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t)offsetof(Vertex, color)); 417 | glEnableVertexAttribArray(3); 418 | 419 | glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, texcoord)); 420 | glEnableVertexAttribArray(4); 421 | 422 | glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, tex_index)); 423 | glEnableVertexAttribArray(5); 424 | 425 | glVertexAttribPointer(6, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, scale)); 426 | glEnableVertexAttribArray(6); 427 | 428 | glVertexAttribPointer(7, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, pos_px)); 429 | glEnableVertexAttribArray(7); 430 | 431 | glVertexAttribPointer(8, 1, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, corner_radius)); 432 | glEnableVertexAttribArray(8); 433 | 434 | glVertexAttribPointer(10, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, min_coord)); 435 | glEnableVertexAttribArray(10); 436 | 437 | glVertexAttribPointer(11, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)(intptr_t*)offsetof(Vertex, max_coord)); 438 | glEnableVertexAttribArray(11); 439 | 440 | // Creating the shader for the batch renderer 441 | const char* vert_src = 442 | "#version 450 core\n" 443 | "layout (location = 0) in vec2 a_pos;\n" 444 | "layout (location = 1) in vec4 a_border_color;\n" 445 | "layout (location = 2) in float a_border_width;\n" 446 | "layout (location = 3) in vec4 a_color;\n" 447 | "layout (location = 4) in vec2 a_texcoord;\n" 448 | "layout (location = 5) in float a_tex_index;\n" 449 | "layout (location = 6) in vec2 a_scale;\n" 450 | "layout (location = 7) in vec2 a_pos_px;\n" 451 | "layout (location = 8) in float a_corner_radius;\n" 452 | "layout (location = 10) in vec2 a_min_coord;\n" 453 | "layout (location = 11) in vec2 a_max_coord;\n" 454 | 455 | "uniform mat4 u_proj;\n" 456 | "out vec4 v_border_color;\n" 457 | "out float v_border_width;\n" 458 | "out vec4 v_color;\n" 459 | "out vec2 v_texcoord;\n" 460 | "out float v_tex_index;\n" 461 | "flat out vec2 v_scale;\n" 462 | "flat out vec2 v_pos_px;\n" 463 | "flat out float v_is_gradient;\n" 464 | "out float v_corner_radius;\n" 465 | "out vec2 v_min_coord;\n" 466 | "out vec2 v_max_coord;\n" 467 | 468 | "void main() {\n" 469 | "v_color = a_color;\n" 470 | "v_texcoord = a_texcoord;\n" 471 | "v_tex_index = a_tex_index;\n" 472 | "v_border_color = a_border_color;\n" 473 | "v_border_width = a_border_width;\n" 474 | "v_scale = a_scale;\n" 475 | "v_pos_px = a_pos_px;\n" 476 | "v_corner_radius = a_corner_radius;\n" 477 | "v_min_coord = a_min_coord;\n" 478 | "v_max_coord = a_max_coord;\n" 479 | "gl_Position = u_proj * vec4(a_pos.x, a_pos.y, 0.0f, 1.0);\n" 480 | "}\n"; 481 | 482 | 483 | const char* frag_src = "#version 450 core\n" 484 | "out vec4 o_color;\n" 485 | "in vec4 v_color;\n" 486 | "in float v_tex_index;\n" 487 | "in vec4 v_border_color;\n" 488 | "in float v_border_width;\n" 489 | "in vec2 v_texcoord;\n" 490 | "flat in vec2 v_scale;\n" 491 | "flat in vec2 v_pos_px;\n" 492 | "in float v_corner_radius;\n" 493 | "uniform sampler2D u_textures[32];\n" 494 | "uniform vec2 u_screen_size;\n" 495 | "in vec2 v_min_coord;\n" 496 | "in vec2 v_max_coord;\n" 497 | 498 | "float rounded_box_sdf(vec2 center_pos, vec2 size, float radius) {\n" 499 | " return length(max(abs(center_pos)-size+radius,0.0))-radius;\n" 500 | "}\n" 501 | 502 | "void main() {\n" 503 | " if(u_screen_size.y - gl_FragCoord.y < v_min_coord.y && v_min_coord.y != -1) {\n" 504 | " discard;\n" 505 | " }\n" 506 | " if(u_screen_size.y - gl_FragCoord.y > v_max_coord.y && v_max_coord.y != -1) {\n" 507 | " discard;\n" 508 | " }\n" 509 | " if ((gl_FragCoord.x < v_min_coord.x && v_min_coord.x != -1) || (gl_FragCoord.x > v_max_coord.x && v_max_coord.x != -1)) {\n" 510 | " discard;\n" 511 | " }\n" 512 | " vec2 size = v_scale;\n" 513 | " vec4 opaque_color, display_color;\n" 514 | " if(v_tex_index == -1) {\n" 515 | " opaque_color = v_color;\n" 516 | " } else {\n" 517 | " opaque_color = texture(u_textures[int(v_tex_index)], v_texcoord) * v_color;\n" 518 | " }\n" 519 | " if(v_corner_radius != 0.0f) {" 520 | " display_color = opaque_color;\n" 521 | " vec2 location = vec2(v_pos_px.x, -v_pos_px.y);\n" 522 | " location.y += u_screen_size.y - size.y;\n" 523 | " float edge_softness = 1.0f;\n" 524 | " float radius = v_corner_radius * 2.0f;\n" 525 | " float distance = rounded_box_sdf(gl_FragCoord.xy - location - (size/2.0f), size / 2.0f, radius);\n" 526 | " float smoothed_alpha = 1.0f-smoothstep(0.0f, edge_softness * 2.0f,distance);\n" 527 | " vec3 fill_color;\n" 528 | " if(v_border_width != 0.0f) {\n" 529 | " vec2 location_border = vec2(location.x + v_border_width, location.y + v_border_width);\n" 530 | " vec2 size_border = vec2(size.x - v_border_width * 2, size.y - v_border_width * 2);\n" 531 | " float distance_border = rounded_box_sdf(gl_FragCoord.xy - location_border - (size_border / 2.0f), size_border / 2.0f, radius);\n" 532 | " if(distance_border <= 0.0f) {\n" 533 | " fill_color = display_color.xyz;\n" 534 | " } else {\n" 535 | " fill_color = v_border_color.xyz;\n" 536 | " }\n" 537 | " } else {\n" 538 | " fill_color = display_color.xyz;\n" 539 | " }\n" 540 | " if(v_border_width != 0.0f)\n" 541 | " o_color = mix(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(fill_color, smoothed_alpha), smoothed_alpha);\n" 542 | " else\n" 543 | " o_color = mix(vec4(0.0f, 0.0f, 0.0f, 0.0f), vec4(fill_color, display_color.a), smoothed_alpha);\n" 544 | " } else {\n" 545 | " vec4 fill_color = opaque_color;\n" 546 | " if(v_border_width != 0.0f) {\n" 547 | " vec2 location = vec2(v_pos_px.x, -v_pos_px.y);\n" 548 | " location.y += u_screen_size.y - size.y;\n" 549 | " vec2 location_border = vec2(location.x + v_border_width, location.y + v_border_width);\n" 550 | " vec2 size_border = vec2(v_scale.x - v_border_width * 2, v_scale.y - v_border_width * 2);\n" 551 | " float distance_border = rounded_box_sdf(gl_FragCoord.xy - location_border - (size_border / 2.0f), size_border / 2.0f, v_corner_radius);\n" 552 | " if(distance_border > 0.0f) {\n" 553 | " fill_color = v_border_color;\n" 554 | "}\n" 555 | " }\n" 556 | " o_color = fill_color;\n" 557 | " }\n" 558 | "}\n"; 559 | state.render.shader = shader_prg_create(vert_src, frag_src); 560 | 561 | // initializing vertex position data 562 | state.render.vert_pos[0] = (vec4s){-0.5f, -0.5f, 0.0f, 1.0f}; 563 | state.render.vert_pos[1] = (vec4s){0.5f, -0.5f, 0.0f, 1.0f}; 564 | state.render.vert_pos[2] = (vec4s){0.5f, 0.5f, 0.0f, 1.0f}; 565 | state.render.vert_pos[3] = (vec4s){-0.5f, 0.5f, 0.0f, 1.0f}; 566 | 567 | 568 | // Populating the textures array in the shader with texture ids 569 | int32_t tex_slots[MAX_TEX_COUNT_BATCH]; 570 | for(uint32_t i = 0; i < MAX_TEX_COUNT_BATCH; i++) 571 | tex_slots[i] = i; 572 | 573 | glUseProgram(state.render.shader.id); 574 | set_projection_matrix(); 575 | glUniform1iv(glGetUniformLocation(state.render.shader.id, "u_textures"), MAX_TEX_COUNT_BATCH, tex_slots); 576 | } 577 | 578 | void renderer_begin() { 579 | state.render.vert_count = 0; 580 | state.render.index_count = 0; 581 | state.render.tex_index = 0; 582 | state.render.tex_count = 0; 583 | state.drawcalls = 0; 584 | } 585 | 586 | void renderer_flush() { 587 | if(state.render.vert_count <= 0) return; 588 | 589 | // Bind the vertex buffer & shader set the vertex data, bind the textures & draw 590 | glUseProgram(state.render.shader.id); 591 | glBindBuffer(GL_ARRAY_BUFFER, state.render.vbo); 592 | glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertex) * state.render.vert_count, 593 | state.render.verts); 594 | 595 | for(uint32_t i = 0; i < state.render.tex_count; i++) { 596 | glBindTextureUnit(i, state.render.textures[i].id); 597 | state.drawcalls++; 598 | } 599 | 600 | vec2s renderSize = (vec2s){(float)state.dsp_w, (float)state.dsp_h}; 601 | glUniform2fv(glGetUniformLocation(state.render.shader.id, "u_screen_size"), 1, (float*)renderSize.raw); 602 | glBindVertexArray(state.render.vao); 603 | glDrawElements(GL_TRIANGLES, state.render.index_count, GL_UNSIGNED_INT, NULL); 604 | } 605 | 606 | LfTextProps text_render_simple(vec2s pos, const char* text, LfFont font, LfColor font_color, bool no_render) { 607 | return lf_text_render(pos, text, font, font_color, -1, (vec2s){-1, -1}, no_render, false, -1, -1); 608 | } 609 | LfTextProps text_render_simple_wide(vec2s pos, const wchar_t* text, LfFont font, LfColor font_color, bool no_render) { 610 | return lf_text_render_wchar(pos, text, font, font_color, -1, (vec2s){-1, -1}, no_render, false, -1, -1); 611 | } 612 | 613 | 614 | LfClickableItemState button(const char* file, int32_t line, vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color) { 615 | return button_ex(file, line, pos, size, props, color, border_width, click_color, hover_color, (vec2s){-1, -1}); 616 | } 617 | LfClickableItemState button_ex(const char* file, int32_t line, vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color, vec2s hitbox_override) { 618 | uint64_t id = DJB2_INIT; 619 | id = djb2_hash(id, file, strlen(file)); 620 | id = djb2_hash(id, &line, sizeof(line)); 621 | if(state.element_id_stack != -1) { 622 | id = djb2_hash(id, &state.element_id_stack, sizeof(state.element_id_stack)); 623 | } 624 | 625 | if(item_should_cull((LfAABB){.pos = pos, .size= size})) { 626 | return LF_IDLE; 627 | } 628 | 629 | LfColor hover_color_rgb = hover_color ? (props.hover_color.a == 0.0f ? lf_color_brightness(color, 1.2) : props.hover_color) : color; 630 | LfColor held_color_rgb = click_color ? lf_color_brightness(color, 1.3) : color; 631 | 632 | bool is_hovered = lf_hovered(pos, (vec2s){hitbox_override.x != -1 ? hitbox_override.x : size.x, 633 | hitbox_override.y != -1 ? hitbox_override.y : size.y}); 634 | if(state.active_element_id == 0) { 635 | if(is_hovered && lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT)) { 636 | state.active_element_id = id; 637 | } 638 | } else if(state.active_element_id == id) { 639 | if(is_hovered && lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 640 | lf_rect_render(pos, size, hover_color_rgb, props.border_color, border_width, props.corner_radius); 641 | state.active_element_id = 0; 642 | return LF_CLICKED; 643 | } 644 | } 645 | if(is_hovered && lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 646 | state.active_element_id = 0; 647 | } 648 | if(is_hovered && lf_mouse_button_is_down(GLFW_MOUSE_BUTTON_LEFT)) { 649 | lf_rect_render(pos, size, held_color_rgb, props.border_color, border_width, props.corner_radius); 650 | return LF_HELD; 651 | } 652 | if(is_hovered && (!lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT) && !lf_mouse_button_is_down(GLFW_MOUSE_BUTTON_LEFT))) { 653 | lf_rect_render(pos, size, hover_color ? hover_color_rgb : color, props.border_color, border_width, props.corner_radius); 654 | return LF_HOVERED; 655 | } 656 | lf_rect_render(pos, size, color, props.border_color, border_width, props.corner_radius); 657 | return LF_IDLE; 658 | 659 | } 660 | LfClickableItemState div_container(vec2s pos, vec2s size, LfUIElementProps props, LfColor color, float border_width, bool click_color, bool hover_color) { 661 | if(item_should_cull((LfAABB){.pos = pos, .size = size})) { 662 | return LF_IDLE; 663 | } 664 | 665 | LfColor hover_color_rgb = hover_color ? (props.hover_color.a == 0.0f ? lf_color_brightness(color, 1.5) : props.hover_color) : color; 666 | LfColor held_color_rgb = click_color ? lf_color_brightness(color, 1.8) : color; 667 | 668 | bool is_hovered = lf_hovered(pos, size); 669 | if(is_hovered && lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 670 | lf_rect_render(pos, size, hover_color_rgb, props.border_color, border_width, props.corner_radius); 671 | return LF_CLICKED; 672 | } 673 | if(is_hovered && lf_mouse_button_is_down(GLFW_MOUSE_BUTTON_LEFT)) { 674 | lf_rect_render(pos, size, held_color_rgb, props.border_color, border_width, props.corner_radius); 675 | return LF_HELD; 676 | } 677 | if(is_hovered && (!lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT) && !lf_mouse_button_is_down(GLFW_MOUSE_BUTTON_LEFT))) { 678 | lf_rect_render(pos, size, hover_color ? hover_color_rgb : color, props.border_color, border_width, props.corner_radius); 679 | return LF_HOVERED; 680 | } 681 | lf_rect_render(pos, size, color, props.border_color, border_width, props.corner_radius); 682 | return LF_IDLE; 683 | 684 | } 685 | 686 | void next_line_on_overflow(vec2s size, float xoffset) { 687 | if(!state.line_overflow) return; 688 | 689 | // If the object does not fit in the area of the current div, advance to the next line 690 | if(state.pos_ptr.x - state.current_div.aabb.pos.x + size.x > state.current_div.aabb.size.x) { 691 | state.pos_ptr.y += state.current_line_height; 692 | state.pos_ptr.x = state.current_div.aabb.pos.x + xoffset; 693 | state.current_line_height = 0; 694 | } 695 | if(size.y > state.current_line_height) { 696 | state.current_line_height = size.y; 697 | } 698 | } 699 | 700 | bool item_should_cull(LfAABB item) { 701 | bool intersect = true; 702 | LfAABB window = (LfAABB){.pos = (vec2s){0, 0}, .size = (vec2s){state.dsp_w, state.dsp_h}}; 703 | if(item.size.x == -1 || item.size.y == -1) { 704 | item.size.x = state.dsp_w; 705 | item.size.y = get_current_font().font_size; 706 | } 707 | if (item.pos.x + item.size.x <= window.pos.x || item.pos.x >= window.pos.x + window.size.x) 708 | intersect = false; 709 | 710 | if (item.pos.y + item.size.y <= window.pos.y || item.pos.y >= window.pos.y + window.size.y) 711 | intersect = false; 712 | 713 | return !intersect && state.current_div.id == state.scrollbar_div.id; 714 | 715 | return false; 716 | } 717 | 718 | 719 | void draw_scrollbar_on(LfDiv* div) { 720 | lf_next_line(); 721 | if (state.current_div.id == div->id) { 722 | state.scrollbar_div = *div; 723 | LfDiv* selected = div; 724 | float scroll = *state.scroll_ptr; 725 | LfUIElementProps props = get_props_for(state.theme.scrollbar_props); 726 | 727 | selected->total_area.x = state.pos_ptr.x; 728 | selected->total_area.y = state.pos_ptr.y + state.div_props.corner_radius; 729 | 730 | if (*state.scroll_ptr < -((div->total_area.y - *state.scroll_ptr) - div->aabb.pos.y - div->aabb.size.y) && *state.scroll_velocity_ptr < 0 && state.theme.div_smooth_scroll) { 731 | *state.scroll_velocity_ptr = 0; 732 | *state.scroll_ptr = -((div->total_area.y - *state.scroll_ptr) - div->aabb.pos.y - div->aabb.size.y); 733 | } 734 | 735 | float total_area = selected->total_area.y - scroll; 736 | float visible_area = selected->aabb.size.y + selected->aabb.pos.y; 737 | if (total_area > visible_area) { 738 | const float min_scrollbar_height = 20; 739 | 740 | float area_mapped = visible_area / total_area; 741 | float scroll_mapped = (-1 * scroll) / total_area; 742 | float scrollbar_height = MAX((selected->aabb.size.y * area_mapped - props.margin_top * 2), min_scrollbar_height); 743 | 744 | LfAABB scrollbar_area = (LfAABB){ 745 | .pos = (vec2s){ 746 | selected->aabb.pos.x + selected->aabb.size.x - state.theme.scrollbar_width - props.margin_right - state.div_props.padding - state.div_props.border_width, 747 | MIN((selected->aabb.pos.y + selected->aabb.size.y * scroll_mapped + props.margin_top + state.div_props.padding + state.div_props.border_width + state.div_props.corner_radius), 748 | visible_area - scrollbar_height)}, 749 | .size = (vec2s){ 750 | state.theme.scrollbar_width, 751 | scrollbar_height - state.div_props.border_width * 2 - state.div_props.corner_radius * 2}, 752 | }; 753 | 754 | vec2s cursorpos = (vec2s){lf_get_mouse_x(), lf_get_mouse_y()}; 755 | if (lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT) && lf_hovered(scrollbar_area.pos, scrollbar_area.size)) { 756 | state.drag_state.is_dragging = true; 757 | state.drag_state.start_cursor_pos = cursorpos; 758 | state.drag_state.start_scroll = *state.scroll_ptr; 759 | } 760 | if(state.drag_state.is_dragging) { 761 | float cursor_delta = (cursorpos.y - state.drag_state.start_cursor_pos.y); 762 | float new_scroll = state.drag_state.start_scroll - cursor_delta * (total_area / visible_area); 763 | *state.scroll_ptr = new_scroll; 764 | 765 | if (*state.scroll_ptr > 0) { 766 | *state.scroll_ptr = 0; 767 | } else if (*state.scroll_ptr < -(total_area - visible_area)) { 768 | *state.scroll_ptr = -(total_area - visible_area); 769 | } 770 | } 771 | if (lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 772 | state.drag_state.is_dragging = false; 773 | } 774 | 775 | lf_rect_render(scrollbar_area.pos, scrollbar_area.size, 776 | props.color, props.border_color, props.border_width, props.corner_radius); 777 | } 778 | } 779 | } 780 | 781 | void input_field(LfInputField* input, InputFieldType type, const char* file, int32_t line) { 782 | if(!input->buf) return; 783 | 784 | if(!input->_init) { 785 | lf_input_field_unselect_all(input); 786 | input->_init = true; 787 | } 788 | 789 | LfUIElementProps props = get_props_for(state.theme.inputfield_props); 790 | LfFont font = get_current_font(); 791 | 792 | state.pos_ptr.x += props.margin_left; 793 | state.pos_ptr.y += props.margin_top; 794 | 795 | float wrap_point = state.pos_ptr.x + input->width - props.padding; 796 | 797 | if(input->selected) { 798 | if(lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT) && (lf_get_mouse_x_delta() == 0 && lf_get_mouse_y_delta() == 0)) { 799 | LfTextProps selected_props = lf_text_render((vec2s){ 800 | state.pos_ptr.x + props.padding, 801 | state.pos_ptr.y + props.padding 802 | }, input->buf, font, LF_NO_COLOR, 803 | wrap_point, (vec2s){lf_get_mouse_x(), lf_get_mouse_y()}, true, false, -1, -1); 804 | input->cursor_index = selected_props.rendered_count; 805 | lf_input_field_unselect_all(input); 806 | input->mouse_selection_end = input->cursor_index; 807 | input->mouse_selection_start = input->cursor_index; 808 | } else if(lf_mouse_button_is_down(GLFW_MOUSE_BUTTON_LEFT) && (lf_get_mouse_x_delta() != 0 || lf_get_mouse_y_delta() != 0)) { 809 | if(input->mouse_dir == 0) { 810 | input->mouse_dir = (lf_get_mouse_x_delta() < 0) ? -1 : 1; 811 | input->mouse_selection_end = input->cursor_index; 812 | input->mouse_selection_start = input->cursor_index; 813 | } 814 | LfTextProps selected_props = lf_text_render((vec2s){ 815 | state.pos_ptr.x + props.padding, 816 | state.pos_ptr.y + props.padding 817 | }, input->buf, font, LF_NO_COLOR, wrap_point, (vec2s){lf_get_mouse_x(), lf_get_mouse_y()}, true, false, -1, -1); 818 | 819 | input->cursor_index = selected_props.rendered_count; 820 | 821 | if(input->mouse_dir == -1) 822 | input->mouse_selection_start = input->cursor_index; 823 | else if(input->mouse_dir == 1) 824 | input->mouse_selection_end = input->cursor_index; 825 | 826 | input->selection_start = input->mouse_selection_start; 827 | input->selection_end = input->mouse_selection_end; 828 | 829 | if(input->mouse_selection_start == input->mouse_selection_end) { 830 | input->mouse_dir = (lf_get_mouse_x_delta() < 0) ? -1 : 1; 831 | } 832 | } else if(lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)){ 833 | input->mouse_dir = 0; 834 | } 835 | if(lf_char_event().happened && lf_char_event().charcode >= 0 && lf_char_event().charcode <= 127 && 836 | strlen(input->buf) + 1 <= input->buf_size && (input->max_chars ? strlen(input->buf) + 1 <= input->max_chars : true)) { 837 | if(input->insert_override_callback) { 838 | input->insert_override_callback(input); 839 | } else { 840 | if(input->selection_start != -1) { 841 | int start = input->selection_dir != 0 ? input->selection_start : input->selection_start - 1; 842 | int end = input->selection_end; 843 | 844 | remove_substr_str(input->buf, start, end); 845 | 846 | input->cursor_index = input->selection_start; 847 | lf_input_field_unselect_all(input); 848 | } 849 | lf_input_insert_char_idx(input, lf_char_event().charcode, input->cursor_index++); 850 | } 851 | } 852 | 853 | if(lf_key_event().happened && lf_key_event().pressed) { 854 | switch(lf_key_event().keycode) { 855 | case GLFW_KEY_BACKSPACE: { 856 | if(input->selection_start != -1) { 857 | int start = input->selection_dir != 0 ? input->selection_start : input->selection_start - 1; 858 | int end = input->selection_end; 859 | 860 | remove_substr_str(input->buf, start, end); 861 | 862 | input->cursor_index = input->selection_start; 863 | lf_input_field_unselect_all(input); 864 | } 865 | else { 866 | if(input->cursor_index - 1 < 0) break; 867 | remove_i_str(input->buf, input->cursor_index - 1); 868 | input->cursor_index--; 869 | } 870 | break; 871 | } 872 | case GLFW_KEY_LEFT: { 873 | if(input->cursor_index - 1 < 0 ) { 874 | if(!lf_key_is_down(GLFW_KEY_LEFT_SHIFT)) 875 | lf_input_field_unselect_all(input); 876 | break; 877 | } 878 | if(lf_key_is_down(GLFW_KEY_LEFT_SHIFT)) { 879 | if(input->selection_end == -1) { 880 | input->selection_end = input->cursor_index - 1; 881 | input->selection_dir = -1; 882 | } 883 | input->cursor_index--; 884 | if(input->selection_dir == 1) { 885 | if(input->cursor_index != input->selection_start) { 886 | input->selection_end = input->cursor_index - 1; 887 | } else { 888 | lf_input_field_unselect_all(input); 889 | } 890 | } else { 891 | input->selection_start = input->cursor_index; 892 | } 893 | } else { 894 | if(input->selection_end == -1) 895 | input->cursor_index--; 896 | lf_input_field_unselect_all(input); 897 | } 898 | break; 899 | } 900 | case GLFW_KEY_RIGHT: { 901 | if(input->cursor_index + 1 > strlen(input->buf)){ 902 | if(!lf_key_is_down(GLFW_KEY_LEFT_SHIFT)) 903 | lf_input_field_unselect_all(input); 904 | break; 905 | } 906 | if(lf_key_is_down(GLFW_KEY_LEFT_SHIFT)) { 907 | if(input->selection_start == -1) { 908 | input->selection_start = input->cursor_index; 909 | input->selection_dir = 1; 910 | } 911 | if(input->selection_dir == -1) { 912 | input->cursor_index++; 913 | if(input->cursor_index - 1 != input->selection_end) { 914 | input->selection_start = input->cursor_index; 915 | } else { 916 | lf_input_field_unselect_all(input); 917 | } 918 | } else { 919 | input->selection_end = input->cursor_index; 920 | input->cursor_index++; 921 | } 922 | } else { 923 | if(input->selection_end == -1) 924 | input->cursor_index++; 925 | lf_input_field_unselect_all(input); 926 | } 927 | break; 928 | } 929 | case GLFW_KEY_ENTER: { 930 | // TODO: There is a bug with the input cursor when inserting new line characters 931 | /* 932 | insert_i_str(input->buf, '\n', input->cursor_index++); 933 | */ 934 | break; 935 | } 936 | case GLFW_KEY_TAB: { 937 | if(strlen(input->buf) + 1 <= input->buf_size && (input->max_chars ? strlen(input->buf) + 1 <= input->max_chars : true)) { 938 | for(uint32_t i = 0; i < 2; i++) { 939 | insert_i_str(input->buf, ' ', input->cursor_index++); 940 | } 941 | } 942 | break; 943 | } 944 | case GLFW_KEY_A: { 945 | if(!lf_key_is_down(GLFW_KEY_LEFT_CONTROL)) break; 946 | bool selected_all = input->selection_start == 0 && input->selection_end == strlen(input->buf); 947 | if(selected_all) { 948 | lf_input_field_unselect_all(input); 949 | } else { 950 | input->selection_start = 0; 951 | input->selection_end = strlen(input->buf); 952 | } 953 | break; 954 | } 955 | case GLFW_KEY_C: { 956 | if(!lf_key_is_down(GLFW_KEY_LEFT_CONTROL)) break; 957 | char selection[strlen(input->buf)]; 958 | memset(selection, 0, strlen(input->buf)); 959 | substr_str(input->buf, input->selection_start, input->selection_end, selection); 960 | 961 | clipboard_set_text(state.clipboard, selection); 962 | break; 963 | } 964 | case GLFW_KEY_V: { 965 | if(!lf_key_is_down(GLFW_KEY_LEFT_CONTROL)) break; 966 | int32_t length; 967 | const char* clipboard_content = clipboard_text_ex(state.clipboard, &length, LCB_CLIPBOARD); 968 | if(strlen(input->buf) + length > input->buf_size || (input->max_chars ? strlen(input->buf) + length > input->max_chars : false)) break; 969 | 970 | lf_input_insert_str_idx(input, clipboard_content, length, input->cursor_index); 971 | input->cursor_index += strlen(clipboard_content); 972 | break; 973 | 974 | } 975 | case GLFW_KEY_X: { 976 | if (!lf_key_is_down(GLFW_KEY_LEFT_CONTROL)) break; 977 | char selection[strlen(input->buf)]; 978 | memset(selection, 0, strlen(input->buf)); 979 | substr_str(input->buf, input->selection_start, input->selection_end, selection); 980 | 981 | clipboard_set_text(state.clipboard, selection); 982 | 983 | int start = input->selection_dir != 0 ? input->selection_start : input->selection_start - 1; 984 | remove_substr_str(input->buf, start, input->selection_end); 985 | input->cursor_index = input->selection_start; 986 | lf_input_field_unselect_all(input); 987 | break; 988 | } 989 | default: { 990 | break; 991 | } 992 | } 993 | } 994 | if(input->key_callback) { 995 | input->key_callback(input); 996 | } 997 | } 998 | 999 | LfTextProps textprops = lf_text_render((vec2s){state.pos_ptr.x + props.padding, state.pos_ptr.y + props.padding}, input->buf, 1000 | font, LF_NO_COLOR, 1001 | wrap_point, (vec2s){-1, -1}, true, false, -1, -1); 1002 | 1003 | if(!input->retain_height) { 1004 | input->height = (input->start_height) ? MAX(input->start_height, textprops.height) : (textprops.height ? textprops.height : get_max_char_height_font(font)); 1005 | } else { 1006 | input->height = (input->start_height) ? input->start_height : get_max_char_height_font(font); 1007 | } 1008 | 1009 | next_line_on_overflow( 1010 | (vec2s){input->width + props.padding * 2.0f + props.margin_right + props.margin_left, 1011 | input->height + props.padding * 2.0f + props.margin_bottom + props.margin_top}, state.div_props.border_width); 1012 | 1013 | LfAABB input_aabb = (LfAABB){ 1014 | .pos = state.pos_ptr, 1015 | .size = (vec2s){input->width + props.padding * 2.0f, input->height + props.padding * 2.0f} 1016 | }; 1017 | 1018 | LfClickableItemState inputfield = button(file, line, input_aabb.pos, input_aabb.size, props, props.color, props.border_width, false, false); 1019 | 1020 | if(lf_mouse_button_went_down(GLFW_MOUSE_BUTTON_LEFT) && input->selected && inputfield == LF_IDLE) { 1021 | input->selected = false; 1022 | state.input_grabbed = false; 1023 | lf_input_field_unselect_all(input); 1024 | } else if(inputfield == LF_CLICKED) { 1025 | input->selected = true; 1026 | state.input_grabbed = true; 1027 | LfTextProps selected_props = lf_text_render((vec2s){ 1028 | state.pos_ptr.x + props.padding, 1029 | state.pos_ptr.y + props.padding 1030 | }, input->buf, font, LF_NO_COLOR, 1031 | wrap_point, (vec2s){lf_get_mouse_x(), lf_get_mouse_y()}, true, false, -1, -1); 1032 | input->cursor_index = selected_props.rendered_count; 1033 | } 1034 | 1035 | if(input->selected) { 1036 | char selected_buf[strlen(input->buf)]; 1037 | strncpy(selected_buf, input->buf, input->cursor_index); 1038 | selected_buf[input->cursor_index] = '\0'; 1039 | 1040 | LfTextProps selected_props = lf_text_render((vec2s){state.pos_ptr.x + props.padding, lf_get_mouse_y() + props.padding}, selected_buf, 1041 | font, LF_NO_COLOR, wrap_point, (vec2s){-1, -1}, true, false, -1, -1); 1042 | 1043 | vec2s cursor_pos = 1044 | { 1045 | (strlen(input->buf) > 0) ? selected_props.end_x : state.pos_ptr.x + props.padding, 1046 | state.pos_ptr.y + props.padding + (selected_props.height - get_max_char_height_font(font)) 1047 | }; 1048 | if(input->selection_start == -1 || input->selection_end == -1) { 1049 | lf_rect_render(cursor_pos, (vec2s){1, get_max_char_height_font(font)}, props.text_color, 1050 | LF_NO_COLOR, 0.0f, 0.0f); 1051 | } else { 1052 | lf_text_render((vec2s){state.pos_ptr.x + props.padding, 1053 | state.pos_ptr.y + props.padding}, input->buf, font, (LfColor){255, 255, 255, 80}, wrap_point, (vec2s){-1, -1}, 1054 | false, true, input->selection_start, input->selection_end); 1055 | } 1056 | } 1057 | 1058 | lf_text_render((vec2s){state.pos_ptr.x + props.padding, state.pos_ptr.y + props.padding}, 1059 | (!strlen(input->buf) && !input->selected) ? input->placeholder : input->buf, font, 1060 | !strlen(input->buf) ? lf_color_brightness(props.text_color, 0.75f) : props.text_color, 1061 | wrap_point, (vec2s){-1, -1}, false, false, -1, -1); 1062 | 1063 | 1064 | state.pos_ptr.x += input->width + props.margin_right + props.padding * 2.0f; 1065 | state.pos_ptr.y -= props.margin_top; 1066 | } 1067 | 1068 | LfFont load_font(const char* filepath, uint32_t pixelsize, uint32_t tex_width, uint32_t tex_height, uint32_t line_gap_add) { 1069 | LfFont font = {0}; 1070 | /* Opening the file, reading the content to a buffer and parsing the loaded data with stb_truetype */ 1071 | FILE* file = fopen(filepath, "rb"); 1072 | if (file == NULL) { 1073 | LF_ERROR("Failed to open font file '%s'\n", filepath); 1074 | } 1075 | 1076 | // Loading the content 1077 | fseek(file, 0, SEEK_END); 1078 | long fileSize = ftell(file); 1079 | fseek(file, 0, SEEK_SET); 1080 | uint8_t* buffer = (uint8_t*)malloc(fileSize); 1081 | size_t bytesRead = fread(buffer, 1, fileSize, file); 1082 | fclose(file); 1083 | if (bytesRead != fileSize) { 1084 | LF_ERROR("Failed to read font file '%s'\n", filepath); 1085 | // Handle the error (e.g., free memory and return from the function) 1086 | free(buffer); 1087 | LfFont emptyFont = {0}; // Or whatever initialization you need 1088 | return emptyFont; 1089 | } 1090 | font.font_info = malloc(sizeof(stbtt_fontinfo)); 1091 | 1092 | // Initializing the font with stb_truetype 1093 | stbtt_InitFont((stbtt_fontinfo*)font.font_info, buffer, stbtt_GetFontOffsetForIndex(buffer, 0)); 1094 | 1095 | stbtt_fontinfo* fontinfo = (stbtt_fontinfo*)font.font_info; 1096 | int numglyphs = fontinfo->numGlyphs; 1097 | 1098 | // Loading the font bitmap to memory by using stbtt_BakeFontBitmap 1099 | uint8_t* bitmap = (uint8_t*)malloc(tex_width * tex_height * sizeof(uint32_t)); 1100 | uint8_t* bitmap_4bpp = (uint8_t*)malloc(tex_width * tex_height * 4 * sizeof(uint32_t)); 1101 | font.cdata = malloc(sizeof(stbtt_bakedchar) * numglyphs); 1102 | font.tex_width = tex_width; 1103 | font.tex_height = tex_height; 1104 | font.line_gap_add = line_gap_add; 1105 | font.font_size = pixelsize; 1106 | font.num_glyphs = numglyphs; 1107 | stbtt_BakeFontBitmap(buffer, 0, pixelsize, bitmap, tex_width, tex_height, 32, numglyphs, (stbtt_bakedchar*)font.cdata); 1108 | 1109 | uint32_t bitmap_index = 0; 1110 | for(uint32_t i = 0; i < (uint32_t)(tex_width * tex_height * 4); i++) { 1111 | bitmap_4bpp[i] = bitmap[bitmap_index]; 1112 | if((i + 1) % 4 == 0) { 1113 | bitmap_index++; 1114 | } 1115 | } 1116 | 1117 | // Creating an opengl texture (texture atlas) for the font 1118 | glGenTextures(1, &font.bitmap.id); 1119 | glBindTexture(GL_TEXTURE_2D, font.bitmap.id); 1120 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 1121 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 1122 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 1123 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 1124 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, tex_width, tex_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bitmap_4bpp); 1125 | glGenerateMipmap(GL_TEXTURE_2D); 1126 | 1127 | // Deallocating the bitmap data 1128 | free(bitmap); 1129 | free(bitmap_4bpp); 1130 | return font; 1131 | } 1132 | 1133 | LfFont get_current_font() { 1134 | return state.font_stack ? *state.font_stack : state.theme.font; 1135 | } 1136 | 1137 | LfClickableItemState button_element_loc(void* text, const char* file, int32_t line, bool wide) { 1138 | // Retrieving the property data of the button 1139 | LfUIElementProps props = get_props_for(state.theme.button_props); 1140 | float padding = props.padding; 1141 | float margin_left = props.margin_left, margin_right = props.margin_right, 1142 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 1143 | 1144 | // Advancing the position pointer by the margins 1145 | state.pos_ptr.x += margin_left; 1146 | state.pos_ptr.y += margin_top; 1147 | LfFont font = get_current_font(); 1148 | 1149 | LfTextProps text_props; 1150 | if(wide) 1151 | text_props = text_render_simple_wide(state.pos_ptr, (const wchar_t*)text, font, LF_NO_COLOR, true); 1152 | else 1153 | text_props = text_render_simple(state.pos_ptr, (const char*)text, font, LF_NO_COLOR, true); 1154 | 1155 | LfColor color = props.color; 1156 | LfColor text_color = lf_hovered(state.pos_ptr, (vec2s){text_props.width, text_props.height}) && props.hover_text_color.a != 0.0f ? props.hover_text_color : props.text_color; 1157 | 1158 | // If the button does not fit onto the current div, advance to the next line 1159 | next_line_on_overflow( 1160 | (vec2s){text_props.width + padding * 2.0f + margin_right + margin_left, 1161 | text_props.height + padding * 2.0f + margin_bottom + margin_top}, state.div_props.border_width); 1162 | 1163 | 1164 | // Rendering the button 1165 | LfClickableItemState ret = button(file, line, state.pos_ptr, (vec2s){text_props.width + padding * 2, text_props.height + padding * 2}, 1166 | props, color, props.border_width, true, true); 1167 | // Rendering the text of the button 1168 | if(wide) 1169 | text_render_simple_wide((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const wchar_t*)text, font, text_color, false); 1170 | else 1171 | text_render_simple((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const char*)text, font, text_color, false); 1172 | 1173 | // Advancing the position pointer by the width of the button 1174 | state.pos_ptr.x += text_props.width + margin_right + padding * 2.0f; 1175 | state.pos_ptr.y -= margin_top; 1176 | 1177 | return ret; 1178 | } 1179 | LfClickableItemState button_fixed_element_loc(void* text, float width, float height, const char* file, int32_t line, bool wide) { 1180 | // Retrieving the property data of the button 1181 | LfUIElementProps props = get_props_for(state.theme.button_props); 1182 | float padding = props.padding; 1183 | float margin_left = props.margin_left, margin_right = props.margin_right, 1184 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 1185 | 1186 | LfFont font = state.font_stack ? *state.font_stack : state.theme.font; 1187 | LfTextProps text_props; 1188 | if(wide) 1189 | text_props = text_render_simple_wide(state.pos_ptr, (const wchar_t*)text, font, LF_NO_COLOR, true); 1190 | else 1191 | text_props = text_render_simple(state.pos_ptr, (const char*)text, font, LF_NO_COLOR, true); 1192 | 1193 | LfColor color = props.color; 1194 | LfColor text_color = lf_hovered(state.pos_ptr, (vec2s){text_props.width, text_props.height}) && props.hover_text_color.a != 0.0f ? props.hover_text_color : props.text_color; 1195 | 1196 | // If the button does not fit onto the current div, advance to the next line 1197 | int32_t render_width = ((width == -1) ? text_props.width : width); 1198 | int32_t render_height = ((height == -1) ? text_props.height : height); 1199 | 1200 | next_line_on_overflow( 1201 | (vec2s){render_width + padding * 2.0f + margin_right + margin_left, 1202 | render_height + padding * 2.0f + margin_bottom + margin_top}, 1203 | state.div_props.border_width); 1204 | 1205 | // Advancing the position pointer by the margins 1206 | state.pos_ptr.x += margin_left; 1207 | state.pos_ptr.y += margin_top; 1208 | 1209 | // Rendering the button 1210 | LfClickableItemState ret = button(file, line, state.pos_ptr, 1211 | (vec2s){render_width + padding * 2.0f, render_height + padding * 2.0f}, props, 1212 | color, props.border_width, false, true); 1213 | 1214 | // Rendering the text of the button 1215 | 1216 | lf_set_cull_end_x(state.pos_ptr.x + render_width + padding); 1217 | if(wide) { 1218 | text_render_simple_wide((vec2s) 1219 | {state.pos_ptr.x + padding + ((width != -1) ? (width - text_props.width) / 2.0f : 0), 1220 | state.pos_ptr.y + padding + ((height != -1) ? (height - text_props.height) / 2.0f : 0)}, (const wchar_t*)text, font, text_color, false); 1221 | } else { 1222 | text_render_simple((vec2s) 1223 | {state.pos_ptr.x + padding + ((width != -1) ? (width - text_props.width) / 2.0f : 0), 1224 | state.pos_ptr.y + padding + ((height != -1) ? (height - text_props.height) / 2.0f : 0)}, (const char*)text, font, text_color, false); 1225 | } 1226 | lf_unset_cull_end_x(); 1227 | 1228 | // Advancing the position pointer by the width of the button 1229 | state.pos_ptr.x += render_width + margin_right + padding * 2.0f; 1230 | state.pos_ptr.y -= margin_top; 1231 | return ret; 1232 | 1233 | } 1234 | LfClickableItemState checkbox_element_loc(void* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line, bool wide) { 1235 | // Retrieving the property values of the checkbox 1236 | LfFont font = get_current_font(); 1237 | LfUIElementProps props = get_props_for(state.theme.checkbox_props); 1238 | float margin_left = props.margin_left; 1239 | float margin_right = props.margin_right; 1240 | float margin_top = props.margin_top; 1241 | float margin_bottom = props.margin_bottom; 1242 | 1243 | float checkbox_size; 1244 | if(wide) 1245 | checkbox_size = lf_text_dimension_wide((const wchar_t*)text).y; 1246 | else 1247 | checkbox_size = lf_text_dimension((const char*)text).y; 1248 | 1249 | // Advance to next line if the object does not fit on the div 1250 | next_line_on_overflow((vec2s){checkbox_size + margin_left + margin_right + props.padding * 2.0f, checkbox_size + margin_top + margin_bottom + props.padding * 2.0f}, 1251 | state.div_props.border_width); 1252 | 1253 | state.pos_ptr.x += margin_left; 1254 | state.pos_ptr.y += margin_top; 1255 | 1256 | // Render the box 1257 | LfColor checkbox_color = (*val) ? ((tick_color.a == 0) ? props.color : tick_color) : props.color; 1258 | LfClickableItemState checkbox = button(file, line, state.pos_ptr, (vec2s){checkbox_size + props.padding * 2.0f, checkbox_size + props.padding * 2.0f}, 1259 | props, checkbox_color, props.border_width, false, false); 1260 | 1261 | 1262 | if(wide) 1263 | text_render_simple_wide((vec2s){state.pos_ptr.x + checkbox_size + props.padding * 2.0f + margin_right, state.pos_ptr.y + props.padding}, (const wchar_t*)text, font, props.text_color, false); 1264 | else 1265 | text_render_simple((vec2s){state.pos_ptr.x + checkbox_size + props.padding * 2.0f + margin_right, state.pos_ptr.y + props.padding}, (const char*)text, font, props.text_color, false); 1266 | 1267 | // Change the value if the checkbox is clicked 1268 | if(checkbox == LF_CLICKED) { 1269 | *val = !*val; 1270 | } 1271 | if(*val) { 1272 | // Render the image 1273 | lf_image_render( 1274 | (vec2s){state.pos_ptr.x + props.padding, 1275 | state.pos_ptr.y + props.padding}, 1276 | tex_color, 1277 | (LfTexture){ 1278 | .id = state.tex_tick.id, 1279 | .width = (uint32_t)(checkbox_size), 1280 | .height = (uint32_t)(checkbox_size)}, 1281 | (LfColor){0.0f, 0.0f, 0.0f, 0.0f}, 0, props.corner_radius); 1282 | } 1283 | state.pos_ptr.x += checkbox_size + props.padding * 2.0f + margin_right + 1284 | ((wide) ? lf_text_dimension_wide((const wchar_t*)text).x : lf_text_dimension((const char*)text).x) + margin_right; 1285 | state.pos_ptr.y -= margin_top; 1286 | 1287 | return checkbox; 1288 | 1289 | } 1290 | void dropdown_menu_item_loc(void** items, void* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line, bool wide) { 1291 | LfUIElementProps props = get_props_for(state.theme.button_props); 1292 | float margin_left = props.margin_left, margin_right = props.margin_right, 1293 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 1294 | float padding = props.padding; 1295 | LfFont font = get_current_font(); 1296 | 1297 | int32_t placeholder_strlen = (wide) ? wcslen((const wchar_t*)placeholder) : strlen((const char*)placeholder); 1298 | void* button_text = (void*)((*selected_index != -1) ? items[*selected_index] : (placeholder_strlen != 0) ? placeholder : "Select"); 1299 | 1300 | LfTextProps text_props; 1301 | if(wide) 1302 | text_props = text_render_simple_wide((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const wchar_t*)button_text, font, props.text_color, true); 1303 | else 1304 | text_props = text_render_simple((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const char*)button_text, font, props.text_color, true); 1305 | 1306 | float item_height = get_max_char_height_font(font) + ((*opened) ? height + padding * 4.0f + margin_top : padding * 2.0f); 1307 | next_line_on_overflow( 1308 | (vec2s){width + padding * 2.0f + margin_right, 1309 | item_height + margin_top + margin_bottom}, 0.0f); 1310 | 1311 | state.pos_ptr.x += margin_left; 1312 | state.pos_ptr.y += margin_top; 1313 | 1314 | vec2s button_pos = state.pos_ptr; 1315 | LfClickableItemState dropdown_button = button(file, line, state.pos_ptr, (vec2s){(float)width + padding * 2.0f, (float)text_props.height + padding * 2.0f}, props, props.color, props.border_width, false, true); 1316 | 1317 | if(wide) 1318 | text_render_simple_wide((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const wchar_t*)button_text, font, props.text_color, false); 1319 | else 1320 | text_render_simple((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, (const char*)button_text, font, props.text_color, false); 1321 | 1322 | // Render dropdown arrow 1323 | { 1324 | vec2s image_size = (vec2s){20, 10}; 1325 | lf_image_render((vec2s){state.pos_ptr.x + width + padding - image_size.x, state.pos_ptr.y + ((text_props.height + padding * 2) - image_size.y) / 2.0f}, props.text_color, 1326 | (LfTexture){.id = state.tex_arrow_down.id, .width = (uint32_t)image_size.x, .height = (uint32_t)image_size.y}, LF_NO_COLOR, 0.0f, 0.0f); 1327 | } 1328 | 1329 | if(dropdown_button == LF_CLICKED) { 1330 | *opened = !*opened; 1331 | } 1332 | 1333 | if(*opened) { 1334 | if((lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT) && dropdown_button != LF_CLICKED) || 1335 | (!lf_input_grabbed() && lf_key_went_down(GLFW_KEY_ESCAPE))) { 1336 | *opened = false; 1337 | } 1338 | 1339 | LfUIElementProps div_props = lf_get_theme().div_props; 1340 | div_props.corner_radius = props.corner_radius; 1341 | div_props.border_color = props.border_color; 1342 | div_props.border_width = props.border_width; 1343 | div_props.color = props.color; 1344 | lf_push_style_props(div_props); 1345 | lf_div_begin(((vec2s){state.pos_ptr.x, state.pos_ptr.y + text_props.height + padding * 2.0f}), 1346 | ((vec2s){width + props.padding * 2.0f, height + props.padding * 2.0f}), false); 1347 | lf_pop_style_props(); 1348 | 1349 | for(uint32_t i = 0; i < item_count; i++) { 1350 | LfUIElementProps text_props = lf_get_theme().text_props; 1351 | text_props.text_color = props.text_color; 1352 | bool hovered = lf_hovered((vec2s){state.pos_ptr.x + text_props.margin_left, state.pos_ptr.y + text_props.margin_top}, 1353 | (vec2s){width + props.padding * 2.0f, lf_get_theme().font.font_size}); 1354 | if(hovered) { 1355 | lf_rect_render(((vec2s){state.pos_ptr.x, state.pos_ptr.y}), 1356 | (vec2s){width + props.padding * 2.0f, lf_get_theme().font.font_size + props.margin_top}, lf_color_brightness(div_props.color, 1.2f), 1357 | LF_NO_COLOR, 0.0f, 0.0f); 1358 | } 1359 | 1360 | if(hovered && lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 1361 | *selected_index = i; 1362 | } 1363 | lf_push_style_props(text_props); 1364 | lf_text(items[i]); 1365 | lf_pop_style_props(); 1366 | lf_next_line(); 1367 | } 1368 | lf_div_end(); 1369 | } 1370 | 1371 | state.pos_ptr.x += width + padding * 2.0f + margin_right; 1372 | state.pos_ptr.y -= margin_top; 1373 | 1374 | lf_push_style_props(props); 1375 | 1376 | } 1377 | int32_t menu_item_list_item_loc(void** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line, bool wide) { 1378 | LfUIElementProps props = get_props_for(state.theme.button_props); 1379 | float padding = props.padding; 1380 | float margin_left = props.margin_left, margin_right = props.margin_right, 1381 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 1382 | LfColor color = props.color; 1383 | LfFont font = get_current_font(); 1384 | 1385 | LfTextProps text_props[item_count]; 1386 | float width = 0; 1387 | for(uint32_t i = 0; i < item_count; i++) { 1388 | if(wide) 1389 | text_props[i] = text_render_simple_wide((vec2s){state.pos_ptr.x, state.pos_ptr.y + margin_top}, (const wchar_t*)items[i], font, props.text_color, true); 1390 | else 1391 | text_props[i] = text_render_simple((vec2s){state.pos_ptr.x, state.pos_ptr.y + margin_top}, (const char*)items[i], font, props.text_color, true); 1392 | width += text_props[i].width + padding * 2.0f; 1393 | } 1394 | next_line_on_overflow( 1395 | (vec2s){width + padding * 2.0f + margin_right + margin_left, 1396 | font.font_size + padding * 2.0f + margin_bottom + margin_top}, 1397 | state.div_props.border_width); 1398 | 1399 | 1400 | state.pos_ptr.y += margin_top; 1401 | state.pos_ptr.x += margin_left; 1402 | 1403 | uint32_t element_width = 0; 1404 | uint32_t clicked_item = -1; 1405 | for(uint32_t i = 0; i < item_count; i++) { 1406 | LfUIElementProps props = state.theme.button_props; 1407 | props.margin_left = 0; 1408 | props.margin_right = 0; 1409 | LfUIElementProps button_props = state.theme.button_props; 1410 | lf_push_style_props(props); 1411 | if(i == selected_index) { 1412 | props.color = lf_color_brightness(props.color, 1.2); 1413 | } 1414 | lf_push_style_props(props); 1415 | if(wide) { 1416 | if(_lf_button_wide_loc((const wchar_t*)items[i], file, line) == LF_CLICKED) { 1417 | clicked_item = i; 1418 | } 1419 | } else { 1420 | if(_lf_button_loc((const char*)items[i], file, line) == LF_CLICKED) { 1421 | clicked_item = i; 1422 | } 1423 | } 1424 | lf_pop_style_props(); 1425 | per_cb(&i); 1426 | } 1427 | next_line_on_overflow((vec2s){element_width + margin_right, font.font_size + margin_top + margin_bottom}, state.div_props.border_width); 1428 | 1429 | 1430 | state.pos_ptr.y -= margin_top; 1431 | return clicked_item; 1432 | } 1433 | 1434 | static int32_t get_max_char_height_font(LfFont font) { 1435 | float fontScale = stbtt_ScaleForPixelHeight((stbtt_fontinfo*)font.font_info, font.font_size); 1436 | int32_t xmin, ymin, xmax, ymax; 1437 | int32_t codepoint = 'p'; 1438 | stbtt_GetCodepointBitmapBox((stbtt_fontinfo*)font.font_info, codepoint, fontScale, fontScale, &xmin, &ymin, &xmax, &ymax); 1439 | return ymax - ymin; 1440 | } 1441 | void remove_i_str(char *str, int32_t index) { 1442 | int32_t len = strlen(str); 1443 | if (index >= 0 && index < len) { 1444 | for (int32_t i = index; i < len - 1; i++) { 1445 | str[i] = str[i + 1]; 1446 | } 1447 | str[len - 1] = '\0'; 1448 | } 1449 | } 1450 | 1451 | void remove_substr_str(char *str, int start_index, int end_index) { 1452 | int len = strlen(str); 1453 | 1454 | memmove(str + start_index, str + end_index + 1, len - end_index); 1455 | str[len - (end_index - start_index) + 1] = '\0'; 1456 | } 1457 | 1458 | void insert_i_str(char *str, char ch, int32_t index) { 1459 | int len = strlen(str); 1460 | 1461 | if (index < 0 || index > len) { 1462 | LF_ERROR("Invalid string index for inserting.\n"); 1463 | return; 1464 | } 1465 | 1466 | for (int i = len; i > index; i--) { 1467 | str[i] = str[i - 1]; 1468 | } 1469 | 1470 | str[index] = ch; 1471 | str[len + 1] = '\0'; 1472 | } 1473 | 1474 | void insert_str_str(char* source, const char* insert, int32_t index) { 1475 | int source_len = strlen(source); 1476 | int insert_len = strlen(insert); 1477 | 1478 | if (index < 0 || index > source_len) { 1479 | LF_ERROR("Index for inserting out of bounds\n"); 1480 | return; 1481 | } 1482 | 1483 | memmove(source + index + insert_len, source + index, source_len - index + 1); 1484 | 1485 | memcpy(source + index, insert, insert_len); 1486 | } 1487 | 1488 | void substr_str(const char* str, int start_index, int end_index, char* substring) { 1489 | int substring_length = end_index - start_index + 1; 1490 | strncpy(substring, str + start_index, substring_length); 1491 | substring[substring_length] = '\0'; 1492 | } 1493 | 1494 | int map_vals(int value, int from_min, int from_max, int to_min, int to_max) { 1495 | return (value - from_min) * (to_max - to_min) / (from_max - from_min) + to_min; 1496 | } 1497 | 1498 | #ifdef LF_GLFW 1499 | void glfw_key_callback(GLFWwindow* window, int32_t key, int scancode, int action, int mods) { 1500 | (void)window; 1501 | (void)mods; 1502 | (void)scancode; 1503 | // Changing the the keys array to resamble the state of the keyboard 1504 | if(action != GLFW_RELEASE) { 1505 | if(!state.input.keyboard.keys[key]) 1506 | state.input.keyboard.keys[key] = true; 1507 | } else { 1508 | state.input.keyboard.keys[key] = false; 1509 | } 1510 | state.input.keyboard.keys_changed[key] = (action != GLFW_REPEAT); 1511 | 1512 | // Calling user defined callbacks 1513 | for(uint32_t i = 0; i < state.input.key_cb_count; i++) { 1514 | state.input.key_cbs[i](window, key, scancode, action, mods); 1515 | } 1516 | 1517 | // Populating the key event 1518 | state.key_ev.happened = true; 1519 | state.key_ev.pressed = action != GLFW_RELEASE; 1520 | state.key_ev.keycode = key; 1521 | } 1522 | void glfw_mouse_button_callback(GLFWwindow* window, int32_t button, int action, int mods) { 1523 | (void)window; 1524 | (void)mods; 1525 | // Changing the buttons array to resamble the state of the mouse 1526 | if(action != GLFW_RELEASE) { 1527 | if(!state.input.mouse.buttons_current[button]) 1528 | state.input.mouse.buttons_current[button] = true; 1529 | } else { 1530 | state.input.mouse.buttons_current[button] = false; 1531 | } 1532 | // Calling user defined callbacks 1533 | for(uint32_t i = 0; i < state.input.mouse_button_cb_count; i++) { 1534 | state.input.mouse_button_cbs[i](window, button, action, mods); 1535 | } 1536 | // Populating the mouse button event 1537 | state.mb_ev.happened = true; 1538 | state.mb_ev.pressed = action != GLFW_RELEASE; 1539 | state.mb_ev.button_code = button; 1540 | } 1541 | void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { 1542 | (void)window; 1543 | // Setting the scroll values 1544 | state.input.mouse.xscroll_delta = xoffset; 1545 | state.input.mouse.yscroll_delta = yoffset; 1546 | 1547 | // Calling user defined callbacks 1548 | for(uint32_t i = 0; i< state.input.scroll_cb_count; i++) { 1549 | state.input.scroll_cbs[i](window, xoffset, yoffset); 1550 | } 1551 | // Populating the scroll event 1552 | state.scr_ev.happened = true; 1553 | state.scr_ev.xoffset = xoffset; 1554 | state.scr_ev.yoffset = yoffset; 1555 | 1556 | 1557 | // Scrolling the current div 1558 | LfDiv* selected_div = &state.selected_div; 1559 | if(!selected_div->scrollable) return; 1560 | if((state.grabbed_div.id != -1 && selected_div->id != state.grabbed_div.id)) return; 1561 | 1562 | if(yoffset < 0.0f) { 1563 | if(selected_div->total_area.y > (selected_div->aabb.size.y + selected_div->aabb.pos.y)) { 1564 | if(state.theme.div_smooth_scroll) { 1565 | *state.scroll_velocity_ptr -= state.theme.div_scroll_acceleration; 1566 | state.div_velocity_accelerating = true; 1567 | } else { 1568 | *state.scroll_ptr -= state.theme.div_scroll_amount_px; 1569 | } 1570 | } 1571 | } else if (yoffset > 0.0f) { 1572 | if(*state.scroll_ptr) { 1573 | if(state.theme.div_smooth_scroll) { 1574 | *state.scroll_velocity_ptr += state.theme.div_scroll_acceleration; 1575 | state.div_velocity_accelerating = false; 1576 | } else { 1577 | *state.scroll_ptr += state.theme.div_scroll_amount_px; 1578 | } 1579 | } 1580 | } 1581 | if(state.theme.div_smooth_scroll) 1582 | *state.scroll_velocity_ptr = MIN(MAX(*state.scroll_velocity_ptr, -state.theme.div_scroll_max_velocity), state.theme.div_scroll_max_velocity); 1583 | } 1584 | void glfw_cursor_callback(GLFWwindow* window, double xpos, double ypos) { 1585 | (void)window; 1586 | LfMouse* mouse = &state.input.mouse; 1587 | // Setting the position values 1588 | mouse->xpos = xpos; 1589 | mouse->ypos = ypos; 1590 | 1591 | if(mouse->first_mouse_press) { 1592 | mouse->xpos_last = xpos; 1593 | mouse->ypos_last = ypos; 1594 | mouse->first_mouse_press = false; 1595 | } 1596 | // Delta mouse positions 1597 | mouse->xpos_delta = mouse->xpos - mouse->xpos_last; 1598 | mouse->ypos_delta = mouse->ypos - mouse->ypos_last; 1599 | mouse->xpos_last = xpos; 1600 | mouse->ypos_last = ypos; 1601 | 1602 | // Calling User defined callbacks 1603 | for(uint32_t i = 0; i < state.input.cursor_pos_cb_count; i++) { 1604 | state.input.cursor_pos_cbs[i](window, xpos, ypos); 1605 | } 1606 | 1607 | // Populating the cursor event 1608 | state.cp_ev.happened = true; 1609 | state.cp_ev.x = xpos; 1610 | state.cp_ev.y = ypos; 1611 | } 1612 | void glfw_char_callback(GLFWwindow* window, uint32_t charcode) { 1613 | (void)window; 1614 | state.ch_ev.charcode = charcode; 1615 | state.ch_ev.happened = true; 1616 | } 1617 | #endif 1618 | 1619 | void update_input() { 1620 | memcpy(state.input.mouse.buttons_last, state.input.mouse.buttons_current, sizeof(bool) * MAX_MOUSE_BUTTONS); 1621 | } 1622 | 1623 | void clear_events() { 1624 | state.key_ev.happened = false; 1625 | state.mb_ev.happened = false; 1626 | state.cp_ev.happened = false; 1627 | state.scr_ev.happened = false; 1628 | state.ch_ev.happened = false; 1629 | state.input.mouse.xpos_delta = 0; 1630 | state.input.mouse.ypos_delta = 0; 1631 | } 1632 | 1633 | uint64_t djb2_hash(uint64_t hash, const void* buf, size_t size) { 1634 | uint8_t* bytes = (uint8_t*)buf; 1635 | int c; 1636 | 1637 | while ((c = *bytes++)) { 1638 | hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ 1639 | } 1640 | 1641 | return hash; 1642 | } 1643 | 1644 | void props_stack_create(PropsStack* stack) { 1645 | stack->data = (LfUIElementProps*)malloc(LF_STACK_INIT_CAP * sizeof(LfUIElementProps)); 1646 | if(!stack->data) { 1647 | LF_ERROR("Failed to allocate memory for stack data structure.\n"); 1648 | } 1649 | stack->count = 0; 1650 | stack->cap = LF_STACK_INIT_CAP; 1651 | } 1652 | 1653 | void props_stack_resize(PropsStack* stack, uint32_t newcap) { 1654 | LfUIElementProps* newdata = (LfUIElementProps*)realloc(stack->data, newcap * sizeof(LfUIElementProps)); 1655 | if(!newdata) { 1656 | LF_ERROR("Failed to reallocate memory for stack datastructure."); 1657 | } 1658 | stack->data = newdata; 1659 | stack->cap = newcap; 1660 | } 1661 | 1662 | void props_stack_push(PropsStack* stack, LfUIElementProps props) { 1663 | if(stack->count == stack->cap) { 1664 | props_stack_resize(stack, stack->cap * 2); 1665 | } 1666 | stack->data[stack->count++] = props; 1667 | } 1668 | 1669 | LfUIElementProps props_stack_pop(PropsStack* stack) { 1670 | LF_ASSERT(stack.count != 0, "Stack underflow on stack data structure!"); 1671 | LfUIElementProps val = stack->data[--stack->count]; 1672 | if(stack->count > 0 && stack->count == stack->cap / 4) { 1673 | props_stack_resize(stack, stack->cap / 2); 1674 | } 1675 | return val; 1676 | } 1677 | 1678 | LfUIElementProps props_stack_peak(PropsStack* stack) { 1679 | LF_ASSERT(stack.count != 0, "Stack is empty on stack data structure!"); 1680 | return stack->data[stack->count - 1]; 1681 | } 1682 | 1683 | bool props_stack_empty(PropsStack* stack) { 1684 | return stack->count == 0; 1685 | } 1686 | 1687 | LfUIElementProps get_props_for(LfUIElementProps props) { 1688 | return (!props_stack_empty(&state.props_stack)) ? props_stack_peak(&state.props_stack) : props; 1689 | } 1690 | 1691 | // =========================================================== 1692 | // ----------------Public API Functions ---------------------- 1693 | // =========================================================== 1694 | void lf_init_glfw(uint32_t display_width, uint32_t display_height, void* glfw_window) { 1695 | setlocale(LC_ALL, ""); 1696 | #ifndef LF_GLFW 1697 | LF_ERROR("Trying to initialize Leif with GLFW without defining 'LF_GLFW'"); 1698 | return; 1699 | #else 1700 | if(!glfwInit()) { 1701 | LF_ERROR("Trying to initialize Leif with GLFW without initializing GLFW first."); 1702 | return; 1703 | } 1704 | 1705 | if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { 1706 | LF_ERROR("Failed to initialize Glad."); 1707 | return; 1708 | } 1709 | memset(&state, 0, sizeof(state)); 1710 | 1711 | // Default state 1712 | state.init = true; 1713 | state.dsp_w = display_width; 1714 | state.dsp_h = display_height; 1715 | state.window_handle = glfw_window; 1716 | state.input.mouse.first_mouse_press = true; 1717 | state.render.tex_count = 0; 1718 | state.pos_ptr = (vec2s){0, 0}; 1719 | state.image_color_stack = LF_NO_COLOR; 1720 | state.active_element_id = 0; 1721 | state.text_wrap = false; 1722 | state.line_overflow = true; 1723 | state.theme = lf_default_theme(); 1724 | state.renderer_render = true; 1725 | state.drag_state = (DragState){ false, {0, 0}, 0 }; 1726 | 1727 | props_stack_create(&state.props_stack); 1728 | 1729 | memset(&state.grabbed_div, 0, sizeof(LfDiv)); 1730 | state.grabbed_div.id = -1; 1731 | 1732 | state.clipboard = clipboard_new(NULL); 1733 | 1734 | state.drawcalls = 0; 1735 | 1736 | // Setting glfw callbacks 1737 | glfwSetKeyCallback((GLFWwindow*)state.window_handle, glfw_key_callback); 1738 | glfwSetMouseButtonCallback((GLFWwindow*)state.window_handle, glfw_mouse_button_callback); 1739 | glfwSetScrollCallback((GLFWwindow*)state.window_handle, glfw_scroll_callback); 1740 | glfwSetCursorPosCallback((GLFWwindow*)state.window_handle, glfw_cursor_callback); 1741 | glfwSetCharCallback((GLFWwindow*)state.window_handle, glfw_char_callback); 1742 | renderer_init(); 1743 | 1744 | state.tex_arrow_down = lf_load_texture_asset("arrow-down", "png"); 1745 | state.tex_tick = lf_load_texture_asset("tick", "png"); 1746 | #endif 1747 | } 1748 | 1749 | void lf_terminate() { 1750 | lf_free_font(&state.theme.font); 1751 | } 1752 | 1753 | LfTheme lf_default_theme() { 1754 | // The default theme of Leif 1755 | LfTheme theme = {0}; 1756 | theme.div_props = (LfUIElementProps){ 1757 | .color = (LfColor){45, 45, 45, 255}, 1758 | .border_color = (LfColor){0, 0, 0, 0}, 1759 | .border_width = 0.0f, 1760 | .corner_radius = 0.0f, 1761 | .hover_color = LF_NO_COLOR, 1762 | }; 1763 | float global_padding = 10; 1764 | float global_margin = 5; 1765 | theme.text_props = (LfUIElementProps){ 1766 | .text_color = LF_WHITE, 1767 | .border_color = LF_NO_COLOR, 1768 | .padding = 0, 1769 | .margin_left = global_margin, 1770 | .margin_right = global_margin, 1771 | .margin_top = global_margin, 1772 | .margin_bottom = global_margin, 1773 | .border_width = global_margin, 1774 | .corner_radius = 0, 1775 | .hover_color = LF_NO_COLOR, 1776 | .hover_text_color = LF_NO_COLOR 1777 | }; 1778 | theme.button_props = (LfUIElementProps){ 1779 | .color = LF_PRIMARY_ITEM_COLOR, 1780 | .text_color = LF_BLACK, 1781 | .border_color = LF_SECONDARY_ITEM_COLOR, 1782 | .padding = global_padding, 1783 | .margin_left = global_margin, 1784 | .margin_right = global_margin, 1785 | .margin_top = global_margin, 1786 | .margin_bottom = global_margin, 1787 | .border_width = 4, 1788 | .corner_radius = 0, 1789 | .hover_color = LF_NO_COLOR, 1790 | .hover_text_color = LF_NO_COLOR 1791 | }; 1792 | theme.image_props = (LfUIElementProps){ 1793 | .color = LF_WHITE, 1794 | .margin_left = global_margin, 1795 | .margin_right = global_margin, 1796 | .margin_top = global_margin, 1797 | .margin_bottom = global_margin, 1798 | .corner_radius = 0, 1799 | .hover_color = LF_NO_COLOR, 1800 | .hover_text_color = LF_NO_COLOR 1801 | }; 1802 | theme.inputfield_props = (LfUIElementProps){ 1803 | .color = LF_PRIMARY_ITEM_COLOR, 1804 | .text_color = LF_BLACK, 1805 | .border_color = LF_SECONDARY_ITEM_COLOR, 1806 | .padding = global_padding, 1807 | .margin_left = global_margin, 1808 | .margin_right = global_margin, 1809 | .margin_top = global_margin, 1810 | .margin_bottom = global_margin, 1811 | .border_width = 4, 1812 | .corner_radius = 0, 1813 | .hover_color = LF_NO_COLOR, 1814 | .hover_text_color = LF_NO_COLOR 1815 | }; 1816 | theme.checkbox_props = (LfUIElementProps){ 1817 | .color = LF_PRIMARY_ITEM_COLOR, 1818 | .text_color = LF_WHITE, 1819 | .border_color = LF_SECONDARY_ITEM_COLOR, 1820 | .padding = global_padding, 1821 | .margin_left = global_margin, 1822 | .margin_right = global_margin, 1823 | .margin_top = global_margin, 1824 | .margin_bottom = global_margin, 1825 | .border_width = 4, 1826 | .corner_radius = 0, 1827 | .hover_color = LF_NO_COLOR, 1828 | .hover_text_color = LF_NO_COLOR 1829 | }; 1830 | theme.slider_props = (LfUIElementProps){ 1831 | .color = LF_PRIMARY_ITEM_COLOR, 1832 | .text_color = LF_SECONDARY_ITEM_COLOR, 1833 | .border_color = LF_SECONDARY_ITEM_COLOR, 1834 | .padding = global_padding, 1835 | .margin_left = global_margin, 1836 | .margin_right = global_margin, 1837 | .margin_top = global_margin, 1838 | .margin_bottom = global_margin, 1839 | .border_width = 4, 1840 | .corner_radius = 0, 1841 | .hover_color = LF_NO_COLOR, 1842 | .hover_text_color = LF_NO_COLOR 1843 | }; 1844 | theme.scrollbar_props = (LfUIElementProps){ 1845 | .color = LF_SECONDARY_ITEM_COLOR, 1846 | .border_color = LF_BLACK, 1847 | .padding = 0, 1848 | .margin_left = 0, 1849 | .margin_right = 5, 1850 | .margin_top = 5, 1851 | .margin_bottom = 0, 1852 | .border_width = 0, 1853 | .corner_radius = 0, 1854 | .hover_color = LF_NO_COLOR, 1855 | .hover_text_color = LF_NO_COLOR 1856 | }; 1857 | theme.font = lf_load_font_asset("inter", "ttf", 24); 1858 | 1859 | theme.div_scroll_max_velocity = 100.0f; 1860 | theme.div_scroll_velocity_deceleration = 0.92; 1861 | theme.div_scroll_acceleration = 2.5f; 1862 | theme.div_scroll_amount_px = 20.0f; 1863 | theme.div_smooth_scroll = true; 1864 | 1865 | theme.scrollbar_width = 8; 1866 | 1867 | return theme; 1868 | } 1869 | 1870 | LfTheme lf_get_theme() { 1871 | return state.theme; 1872 | } 1873 | 1874 | void lf_set_theme(LfTheme theme) { 1875 | state.theme = theme; 1876 | } 1877 | 1878 | void lf_resize_display(uint32_t display_width, uint32_t display_height) { 1879 | // Setting the display height internally 1880 | state.dsp_w = display_width; 1881 | state.dsp_h = display_height; 1882 | 1883 | set_projection_matrix(); 1884 | 1885 | state.current_div.aabb.size.x = state.dsp_w; 1886 | state.current_div.aabb.size.y = state.dsp_h; 1887 | } 1888 | 1889 | LfFont lf_load_font(const char* filepath, uint32_t size) { 1890 | return load_font(filepath, size, 1024, 1024, 0); 1891 | } 1892 | 1893 | LfFont lf_load_font_ex(const char* filepath, uint32_t size, uint32_t bitmap_w, uint32_t bitmap_h) { 1894 | return load_font(filepath, size, bitmap_w, bitmap_h, 0); 1895 | } 1896 | 1897 | LfTexture lf_load_texture(const char* filepath, bool flip, LfTextureFiltering filter) { 1898 | LfTexture tex; 1899 | int width, height, channels; 1900 | unsigned char* image = stbi_load(filepath, &width, &height, &channels, STBI_rgb_alpha); 1901 | if (!image) { 1902 | LF_ERROR("Failed to load texture at '%s'.", filepath); 1903 | return tex; 1904 | } 1905 | 1906 | glGenTextures(1, &tex.id); 1907 | glBindTexture(GL_TEXTURE_2D, tex.id); 1908 | 1909 | // Set texture parameters 1910 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 1911 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 1912 | switch(filter) { 1913 | case LF_TEX_FILTER_LINEAR: 1914 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 1915 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 1916 | break; 1917 | case LF_TEX_FILTER_NEAREST: 1918 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 1919 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 1920 | break; 1921 | } 1922 | 1923 | // Load texture data 1924 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); 1925 | glGenerateMipmap(GL_TEXTURE_2D); 1926 | stbi_image_free(image); // Free image data 1927 | // 1928 | tex.width = width; 1929 | tex.height = height; 1930 | return tex; 1931 | } 1932 | 1933 | LfTexture lf_load_texture_resized(const char* filepath, bool flip, LfTextureFiltering filter, uint32_t w, uint32_t h) { 1934 | LfTexture tex; 1935 | int32_t width, height, channels; 1936 | stbi_uc* image_data = stbi_load(filepath, &width, &height, &channels, 0); 1937 | 1938 | unsigned char* downscaled_image = (unsigned char*)malloc(sizeof(unsigned char) * w * h * channels); 1939 | 1940 | // Resize the original image to the downscaled size 1941 | stbir_resize_uint8_linear(image_data, width, height, 0, downscaled_image, w, h, 0,(stbir_pixel_layout)channels); 1942 | 1943 | glCreateTextures(GL_TEXTURE_2D, 1, &tex.id); 1944 | glBindTexture(GL_TEXTURE_2D, tex.id); 1945 | 1946 | switch(filter) { 1947 | case LF_TEX_FILTER_LINEAR: 1948 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 1949 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 1950 | break; 1951 | case LF_TEX_FILTER_NEAREST: 1952 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 1953 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 1954 | break; 1955 | } 1956 | 1957 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 1958 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 1959 | 1960 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, downscaled_image); 1961 | glGenerateMipmap(GL_TEXTURE_2D); 1962 | 1963 | stbi_image_free(image_data); 1964 | free(downscaled_image); 1965 | 1966 | tex.width = width; 1967 | tex.height = height; 1968 | 1969 | return tex; 1970 | 1971 | } 1972 | 1973 | LfTexture lf_load_texture_resized_factor(const char* filepath, bool flip, LfTextureFiltering filter, float wfactor, float hfactor) { 1974 | // Loading the texture into memory with stb_image 1975 | LfTexture tex; 1976 | int32_t width, height, channels; 1977 | stbi_uc* data = lf_load_texture_data_resized_factor(filepath, wfactor, hfactor, &width, &height, &channels, flip); 1978 | 1979 | if(!data) { 1980 | LF_ERROR("Failed to load texture file at '%s'.", filepath); 1981 | return tex; 1982 | } 1983 | 1984 | int32_t w = width * wfactor; 1985 | int32_t h = height * hfactor; 1986 | lf_create_texture_from_image_data(filter, &tex.id, w, h, channels, data); 1987 | 1988 | free(data); 1989 | 1990 | tex.width = w; 1991 | tex.height = h; 1992 | 1993 | return tex; 1994 | } 1995 | 1996 | LfTexture lf_load_texture_from_memory(const void* data, size_t size, bool flip, LfTextureFiltering filter) { 1997 | LfTexture tex; 1998 | int width, height, channels; 1999 | unsigned char* image = stbi_load_from_memory(data, size, &width, &height, &channels, STBI_rgb_alpha); 2000 | if (!image) { 2001 | return tex; 2002 | } 2003 | 2004 | glGenTextures(1, &tex.id); 2005 | glBindTexture(GL_TEXTURE_2D, tex.id); 2006 | 2007 | // Set texture parameters 2008 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 2009 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 2010 | switch(filter) { 2011 | case LF_TEX_FILTER_LINEAR: 2012 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 2013 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 2014 | break; 2015 | case LF_TEX_FILTER_NEAREST: 2016 | glTextureParameteri(tex.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 2017 | glTextureParameteri(tex.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 2018 | break; 2019 | } 2020 | 2021 | // Load texture data 2022 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); 2023 | glGenerateMipmap(GL_TEXTURE_2D); 2024 | stbi_image_free(image); // Free image data 2025 | tex.width = width; 2026 | tex.height = height; 2027 | return tex; 2028 | 2029 | } 2030 | LfTexture lf_load_texture_from_memory_resized(const void* data, size_t size, bool flip, LfTextureFiltering filter, uint32_t w, uint32_t h) { 2031 | LfTexture tex; 2032 | 2033 | int32_t channels; 2034 | unsigned char* resized = lf_load_texture_data_from_memory_resized(data, size, &channels, NULL, NULL, flip, w, h); 2035 | lf_create_texture_from_image_data(LF_TEX_FILTER_LINEAR, &tex.id, w, h, channels, resized); 2036 | 2037 | tex.width = w; 2038 | tex.height = h; 2039 | 2040 | return tex; 2041 | } 2042 | 2043 | 2044 | LfTexture lf_load_texture_from_memory_resized_factor(const void* data, size_t size, bool flip, LfTextureFiltering filter, float wfactor, float hfactor) { 2045 | LfTexture tex; 2046 | 2047 | int32_t width, height, channels; 2048 | stbi_uc* image_data = stbi_load_from_memory((const stbi_uc*)data, size, &width, &height, &channels, 0); 2049 | 2050 | int w = width * wfactor; 2051 | int h = height * hfactor; 2052 | 2053 | unsigned char* resized_data = (unsigned char*)malloc(sizeof(unsigned char) * w * h * channels); 2054 | stbir_resize_uint8_linear(image_data, width, height, 0, resized_data, w, h, 0,(stbir_pixel_layout)channels); 2055 | stbi_image_free(image_data); 2056 | 2057 | lf_create_texture_from_image_data(LF_TEX_FILTER_LINEAR, &tex.id, w, h, channels, resized_data); 2058 | 2059 | tex.width = w; 2060 | tex.height = h; 2061 | 2062 | return tex; 2063 | } 2064 | 2065 | LfTexture lf_load_texture_from_memory_resized_to_fit(const void* data, size_t size, bool flip, LfTextureFiltering filter, int32_t container_w, int32_t container_h) { 2066 | LfTexture tex; 2067 | 2068 | int32_t image_width, image_height, channels; 2069 | stbi_uc* image_data = lf_load_texture_data_from_memory((const stbi_uc*)data, size, &image_width, &image_height, &channels, flip); 2070 | 2071 | int32_t new_width, new_height; 2072 | unsigned char* resized_data = lf_load_texture_data_from_memory_resized_to_fit_ex(image_data, size, &new_width, &new_height, 2073 | channels, image_width, image_height, flip, container_w, container_h); 2074 | stbi_image_free(image_data); 2075 | 2076 | lf_create_texture_from_image_data(LF_TEX_FILTER_LINEAR, &tex.id, new_width, new_height, channels, resized_data); 2077 | 2078 | tex.width = new_width; 2079 | tex.height = new_height; 2080 | 2081 | return tex; 2082 | } 2083 | unsigned char* lf_load_texture_data(const char* filepath, int32_t* width, int32_t* height, int32_t* channels, bool flip) { 2084 | stbi_set_flip_vertically_on_load(!flip); 2085 | stbi_uc* data = stbi_load(filepath, width, height, channels, STBI_rgb_alpha); 2086 | return data; 2087 | } 2088 | 2089 | unsigned char* lf_load_texture_data_resized(const char* filepath, int32_t w, int32_t h, int32_t* channels, bool flip) { 2090 | int32_t width, height; 2091 | stbi_uc* data = lf_load_texture_data(filepath, &width, &height, channels, flip); 2092 | unsigned char* downscaled_image = (unsigned char*)malloc(sizeof(unsigned char) * w * h * *channels); 2093 | stbir_resize_uint8_linear(data, width, height, *channels, downscaled_image, w, h, 0,(stbir_pixel_layout)*channels); 2094 | stbi_image_free(data); 2095 | return downscaled_image; 2096 | } 2097 | 2098 | unsigned char* lf_load_texture_data_resized_factor(const char* filepath, int32_t wfactor, int32_t hfactor, int32_t* width, int32_t* height, int32_t* channels, bool flip) { 2099 | unsigned char* image = stbi_load(filepath, width, height, channels, STBI_rgb_alpha); 2100 | if (!image) { 2101 | return NULL; 2102 | } 2103 | 2104 | float w = (wfactor * (*width)); 2105 | float h = (hfactor * (*height)); 2106 | 2107 | size_t new_size = w * h * (*channels); 2108 | unsigned char* resized_data = (unsigned char*)malloc(new_size); 2109 | if (resized_data == NULL) { 2110 | return NULL; 2111 | } 2112 | 2113 | stbir_resize_uint8_linear(image, *width, *height, *channels, resized_data, w, h, 0,(stbir_pixel_layout)*channels); 2114 | free(image); 2115 | return resized_data; 2116 | 2117 | } 2118 | 2119 | unsigned char* lf_load_texture_data_from_memory(const void* data, size_t size, int32_t* width, int32_t* height, int32_t* channels, bool flip) { 2120 | stbi_set_flip_vertically_on_load(!flip); 2121 | unsigned char* image = stbi_load_from_memory(data, size, width, height, channels, 0); 2122 | if (!image) { 2123 | return NULL; 2124 | } 2125 | return image; 2126 | } 2127 | 2128 | unsigned char* lf_load_texture_data_from_memory_resized(const void* data, size_t size, int32_t* channels, int32_t* o_w, int32_t* o_h, bool flip, uint32_t w, uint32_t h) { 2129 | int32_t width, height; 2130 | stbi_uc* image_data = stbi_load_from_memory((const stbi_uc*)data, size, &width, &height, channels, 0); 2131 | unsigned char* resized_data = lf_load_texture_data_from_memory_resized_to_fit_ex(image_data, size, o_w, o_h, *channels, width, height, flip, 48, 48); 2132 | stbi_image_free(image_data); 2133 | return resized_data; 2134 | } 2135 | 2136 | unsigned char* lf_load_texture_data_from_memory_resized_to_fit_ex(const void* data, size_t size, int32_t* o_width, int32_t* o_height, int32_t i_channels, 2137 | int32_t i_width, int32_t i_height, bool flip, int32_t container_w, int32_t container_h) { 2138 | float container_aspect_ratio = (float)container_w / container_h; 2139 | float image_aspect_ratio = (float)i_width / i_height; 2140 | 2141 | int new_width, new_height; 2142 | 2143 | if (image_aspect_ratio > container_aspect_ratio) { 2144 | new_width = container_w; 2145 | new_height = (int)((container_w / (float)i_width) * i_height); 2146 | } else { 2147 | new_height = container_h; 2148 | new_width = (int)((container_h / (float)i_height) * i_width); 2149 | } 2150 | 2151 | if(o_width) 2152 | *o_width = new_width; 2153 | if(o_height) 2154 | *o_height = new_height; 2155 | 2156 | unsigned char* resized_image = (unsigned char*)malloc(sizeof(unsigned char) * new_width * new_height * i_channels); 2157 | stbir_resize_uint8_linear(data, i_width, i_height, 0, resized_image, new_width, new_height, 0,(stbir_pixel_layout)i_channels); 2158 | return resized_image; 2159 | } 2160 | unsigned char* lf_load_texture_data_from_memory_resized_to_fit(const void* data, size_t size, int32_t* o_width, int32_t* o_height, 2161 | int32_t* o_channels, bool flip, int32_t container_w, int32_t container_h) { 2162 | 2163 | int32_t image_width, image_height, channels; 2164 | stbi_uc* image_data = lf_load_texture_data_from_memory((const stbi_uc*)data, size, &image_width, &image_height, &channels, flip); 2165 | 2166 | int32_t new_width, new_height; 2167 | unsigned char* resized_data = lf_load_texture_data_from_memory_resized_to_fit_ex(image_data, size, &new_width, &new_height, 2168 | channels, image_width, image_height, flip, container_w, container_h); 2169 | *o_width = new_width; 2170 | *o_height = new_height; 2171 | *o_channels = channels; 2172 | 2173 | stbi_image_free(image_data); 2174 | return image_data; 2175 | } 2176 | 2177 | unsigned char* lf_load_texture_data_from_memory_resized_factor(const void* data, size_t size, int32_t* width, int32_t* height, int32_t* channels, bool flip, float wfactor, float hfactor) { 2178 | stbi_uc* image_data = stbi_load_from_memory((const stbi_uc*)data, size, width, height, channels, 0); 2179 | 2180 | int w = (*width) * wfactor; 2181 | int h = (*height) * hfactor; 2182 | 2183 | unsigned char* resized_data = (unsigned char*)malloc(sizeof(unsigned char) * w * h * (*channels)); 2184 | stbir_resize_uint8_linear(image_data, *width, *height, 0, resized_data, w, h, 0,(stbir_pixel_layout)*channels); 2185 | stbi_image_free(image_data); 2186 | return resized_data; 2187 | } 2188 | 2189 | void lf_create_texture_from_image_data(LfTextureFiltering filter, uint32_t* id, int32_t width, int32_t height, int32_t channels, unsigned char* data) { 2190 | GLenum internal_format = (channels == 4) ? GL_RGBA8 : GL_RGB8; 2191 | GLenum data_format = (channels == 4) ? GL_RGBA : GL_RGB; 2192 | glGenTextures(1, id); 2193 | glBindTexture(GL_TEXTURE_2D, *id); 2194 | 2195 | // Set texture parameters 2196 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 2197 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 2198 | switch(filter) { 2199 | case LF_TEX_FILTER_LINEAR: 2200 | glTextureParameteri(*id, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 2201 | glTextureParameteri(*id, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); 2202 | break; 2203 | case LF_TEX_FILTER_NEAREST: 2204 | glTextureParameteri(*id, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 2205 | glTextureParameteri(*id, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 2206 | break; 2207 | } 2208 | 2209 | // Load texture data 2210 | glTexImage2D(GL_TEXTURE_2D, 0, data_format, width, height, 0, data_format, GL_UNSIGNED_BYTE, data); 2211 | glGenerateMipmap(GL_TEXTURE_2D); 2212 | } 2213 | 2214 | void lf_free_texture(LfTexture* tex) { 2215 | glDeleteTextures(1, &tex->id); 2216 | memset(tex, 0, sizeof(LfTexture)); 2217 | } 2218 | 2219 | void lf_free_font(LfFont* font) { 2220 | free(font->cdata); 2221 | free(font->font_info); 2222 | } 2223 | 2224 | LfFont lf_load_font_asset(const char* asset_name, const char* file_extension, uint32_t font_size) { 2225 | char leif_dir[strlen(getenv(HOMEDIR)) + strlen("/.leif") + 1]; 2226 | memset(leif_dir, 0, sizeof(leif_dir)); 2227 | strcat(leif_dir, getenv(HOMEDIR)); 2228 | strcat(leif_dir, "/.leif"); 2229 | 2230 | char path[strlen(leif_dir) + strlen("/assets/fonts/") + strlen(asset_name) + strlen(".") + strlen(file_extension) + 1]; 2231 | 2232 | memset(path, 0, sizeof(path)); 2233 | strcat(path, leif_dir); 2234 | strcat(path, "/assets/fonts/"); 2235 | strcat(path, asset_name); 2236 | strcat(path, "."); 2237 | strcat(path, file_extension); 2238 | 2239 | return lf_load_font(path, font_size); 2240 | } 2241 | 2242 | LfTexture lf_load_texture_asset(const char* asset_name, const char* file_extension) { 2243 | char leif_dir[strlen(getenv(HOMEDIR)) + strlen("/.leif") + 1]; 2244 | memset(leif_dir, 0, sizeof(leif_dir)); 2245 | strcat(leif_dir, getenv(HOMEDIR)); 2246 | strcat(leif_dir, "/.leif"); 2247 | 2248 | char path[strlen(leif_dir) + strlen("/assets/textures/") + strlen(asset_name) + strlen(".") + strlen(file_extension) + 1]; 2249 | memset(path, 0, sizeof(path)); 2250 | strcat(path, leif_dir); 2251 | strcat(path, "/assets/textures/"); 2252 | strcat(path, asset_name); 2253 | strcat(path, "."); 2254 | strcat(path, file_extension); 2255 | 2256 | return lf_load_texture(path, false, LF_TEX_FILTER_LINEAR); 2257 | } 2258 | 2259 | void lf_add_key_callback(void* cb) { 2260 | state.input.key_cbs[state.input.key_cb_count++] = (KEY_CALLBACK_t)cb; 2261 | } 2262 | void lf_add_mouse_button_callback(void* cb) { 2263 | state.input.mouse_button_cbs[state.input.mouse_button_cb_count++] = (MOUSE_BUTTON_CALLBACK_t)cb; 2264 | } 2265 | 2266 | void lf_add_scroll_callback(void* cb) { 2267 | state.input.scroll_cbs[state.input.scroll_cb_count++] = (SCROLL_CALLBACK_t)cb; 2268 | } 2269 | 2270 | void lf_add_cursor_pos_callback(void* cb) { 2271 | state.input.cursor_pos_cbs[state.input.cursor_pos_cb_count++] = (CURSOR_CALLBACK_t)cb; 2272 | } 2273 | 2274 | bool lf_key_went_down(uint32_t key) { 2275 | return lf_key_changed(key) && state.input.keyboard.keys[key]; 2276 | } 2277 | 2278 | bool lf_key_is_down(uint32_t key) { 2279 | return state.input.keyboard.keys[key]; 2280 | } 2281 | 2282 | bool lf_key_is_released(uint32_t key) { 2283 | return lf_key_changed(key) && !state.input.keyboard.keys[key]; 2284 | } 2285 | 2286 | bool lf_key_changed(uint32_t key) { 2287 | bool ret = state.input.keyboard.keys_changed[key]; 2288 | state.input.keyboard.keys_changed[key] = false; 2289 | return ret; 2290 | } 2291 | 2292 | bool lf_mouse_button_went_down(uint32_t button) { 2293 | return lf_mouse_button_changed(button) && state.input.mouse.buttons_current[button]; 2294 | } 2295 | 2296 | bool lf_mouse_button_is_down(uint32_t button) { 2297 | return state.input.mouse.buttons_current[button]; 2298 | } 2299 | 2300 | bool lf_mouse_button_is_released(uint32_t button) { 2301 | return lf_mouse_button_changed(button) && !state.input.mouse.buttons_current[button]; 2302 | } 2303 | 2304 | bool lf_mouse_button_changed(uint32_t button) { 2305 | return state.input.mouse.buttons_current[button] != state.input.mouse.buttons_last[button]; 2306 | } 2307 | 2308 | bool lf_mouse_button_went_down_on_div(uint32_t button) { 2309 | return lf_mouse_button_went_down(button) && state.scrollbar_div.id == state.current_div.id; 2310 | } 2311 | 2312 | bool lf_mouse_button_is_released_on_div(uint32_t button) { 2313 | return lf_mouse_button_is_released(button) && state.scrollbar_div.id == state.current_div.id; 2314 | } 2315 | 2316 | bool lf_mouse_button_changed_on_div(uint32_t button) { 2317 | return lf_mouse_button_changed(button) && state.scrollbar_div.id == state.current_div.id; 2318 | } 2319 | double lf_get_mouse_x() { 2320 | return state.input.mouse.xpos; 2321 | } 2322 | 2323 | double lf_get_mouse_y() { 2324 | return state.input.mouse.ypos; 2325 | } 2326 | 2327 | double lf_get_mouse_x_delta() { 2328 | return state.input.mouse.xpos_delta; 2329 | } 2330 | 2331 | double lf_get_mouse_y_delta() { 2332 | return state.input.mouse.ypos_delta; 2333 | } 2334 | 2335 | double lf_get_mouse_scroll_x() { 2336 | return state.input.mouse.xscroll_delta; 2337 | } 2338 | 2339 | double lf_get_mouse_scroll_y() { 2340 | return state.input.mouse.yscroll_delta; 2341 | } 2342 | 2343 | LfDiv* _lf_div_begin_loc(vec2s pos, vec2s size, bool scrollable, float* scroll, float* scroll_velocity, const char* file, int32_t line) { 2344 | bool hovered_div = lf_area_hovered(pos, size); 2345 | if(hovered_div) { 2346 | state.scroll_velocity_ptr = scroll_velocity; 2347 | state.scroll_ptr = scroll; 2348 | } 2349 | uint64_t id = DJB2_INIT; 2350 | id = djb2_hash(id, file, strlen(file)); 2351 | id = djb2_hash(id, &line, sizeof(line)); 2352 | if(state.element_id_stack != -1) { 2353 | id = djb2_hash(id, &state.element_id_stack, sizeof(state.element_id_stack)); 2354 | } 2355 | 2356 | state.prev_pos_ptr = state.pos_ptr; 2357 | state.prev_font_stack = state.font_stack; 2358 | state.prev_line_height = state.current_line_height; 2359 | state.prev_div = state.current_div; 2360 | 2361 | LfUIElementProps props = get_props_for(state.theme.div_props); 2362 | 2363 | state.div_props = props; 2364 | 2365 | LfDiv div; 2366 | div.aabb = (LfAABB){.pos = pos, .size = size}; 2367 | div.scrollable = scrollable; 2368 | div.id = id; 2369 | 2370 | if(div.scrollable) { 2371 | if(*scroll > 0) 2372 | *scroll = 0; 2373 | 2374 | if(state.theme.div_smooth_scroll) { 2375 | *scroll += *scroll_velocity; 2376 | *scroll_velocity *= state.theme.div_scroll_velocity_deceleration; 2377 | if(*scroll_velocity > -0.1 && state.div_velocity_accelerating) { 2378 | *scroll_velocity = 0.0f; 2379 | } 2380 | } 2381 | } 2382 | 2383 | state.pos_ptr = pos; 2384 | state.current_div = div; 2385 | 2386 | div.interact_state = div_container((vec2s){pos.x - props.padding, pos.y - props.padding}, 2387 | (vec2s){size.x + props.padding * 2.0f, size.y + props.padding * 2.0f}, 2388 | props, props.color, props.border_width, false, state.div_hoverable); 2389 | 2390 | if(hovered_div) { 2391 | state.selected_div_tmp = div; 2392 | } 2393 | 2394 | // Culling & Scrolling 2395 | if(div.scrollable) { 2396 | lf_set_ptr_y(*scroll + props.border_width + props.corner_radius); 2397 | } else { 2398 | lf_set_ptr_y(props.border_width + props.corner_radius); 2399 | } 2400 | state.cull_start = (vec2s){pos.x, pos.y + props.border_width}; 2401 | state.cull_end = (vec2s){pos.x + size.x - props.border_width, pos.y + size.y - props.border_width}; 2402 | 2403 | state.current_div = div; 2404 | 2405 | state.current_line_height = 0; 2406 | state.font_stack = NULL; 2407 | 2408 | return &state.current_div; 2409 | } 2410 | 2411 | void lf_div_end() { 2412 | if(state.current_div.scrollable) { 2413 | draw_scrollbar_on(&state.selected_div_tmp); 2414 | } 2415 | 2416 | state.pos_ptr = state.prev_pos_ptr; 2417 | state.font_stack = state.prev_font_stack; 2418 | state.current_line_height = state.prev_line_height; 2419 | state.current_div = state.prev_div; 2420 | state.cull_start = (vec2s){-1, -1}; 2421 | state.cull_end = (vec2s){-1, -1}; 2422 | } 2423 | 2424 | 2425 | LfClickableItemState _lf_item_loc(vec2s size, const char* file, int32_t line) { 2426 | LfUIElementProps props = get_props_for(state.theme.button_props); 2427 | 2428 | next_line_on_overflow( 2429 | (vec2s){size.x + props.padding * 2.0f + props.margin_right + props.margin_left, 2430 | size.y + props.padding * 2.0f + props.margin_bottom + props.margin_top}, 2431 | state.div_props.border_width); 2432 | 2433 | state.pos_ptr.x += props.margin_left; 2434 | state.pos_ptr.y += props.margin_top; 2435 | 2436 | LfClickableItemState item = button(file, line, state.pos_ptr, size, props, props.color, props.border_width, false, true); 2437 | 2438 | state.pos_ptr.x += size.x + props.margin_left + props.padding * 2.0f; 2439 | state.pos_ptr.y -= props.margin_top; 2440 | return item; 2441 | } 2442 | LfClickableItemState _lf_button_loc(const char* text, const char* file, int32_t line) { 2443 | return button_element_loc((void*)text, file, line, false); 2444 | } 2445 | 2446 | LfClickableItemState _lf_button_wide_loc(const wchar_t* text, const char* file, int32_t line) { 2447 | return button_element_loc((void*)text, file, line, true); 2448 | } 2449 | 2450 | LfClickableItemState _lf_image_button_loc(LfTexture img, const char* file, int32_t line) { 2451 | // Retrieving the property data of the button 2452 | LfUIElementProps props = get_props_for(state.theme.button_props); 2453 | float padding = props.padding; 2454 | float margin_left = props.margin_left, margin_right = props.margin_right, 2455 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2456 | 2457 | LfColor color = props.color; 2458 | LfColor text_color = state.theme.button_props.text_color; 2459 | LfFont font = get_current_font(); 2460 | 2461 | // If the button does not fit onto the current div, advance to the next line 2462 | next_line_on_overflow( 2463 | (vec2s){img.width + padding * 2.0f, 2464 | img.height + padding * 2.0f}, 2465 | state.div_props.border_width); 2466 | 2467 | // Advancing the position pointer by the margins 2468 | state.pos_ptr.x += margin_left; 2469 | state.pos_ptr.y += margin_top; 2470 | 2471 | // Rendering the button 2472 | LfClickableItemState ret = button(file, line, state.pos_ptr, (vec2s){img.width + padding * 2, img.height + padding * 2}, 2473 | props, color, props.border_width, true, true); 2474 | 2475 | LfColor imageColor = LF_WHITE; 2476 | lf_image_render((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, imageColor, img, LF_NO_COLOR, 0, props.corner_radius); 2477 | 2478 | // Advancing the position pointer by the width of the button 2479 | state.pos_ptr.x += img.width + margin_right + padding * 2.0f; 2480 | state.pos_ptr.y -= margin_top; 2481 | 2482 | return ret; 2483 | } 2484 | 2485 | LfClickableItemState _lf_image_button_fixed_loc(LfTexture img, float width, float height, const char* file, int32_t line) { 2486 | // Retrieving the property data of the button 2487 | LfUIElementProps props = get_props_for(state.theme.button_props); 2488 | float padding = props.padding; 2489 | float margin_left = props.margin_left, margin_right = props.margin_right, 2490 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2491 | 2492 | LfColor color = props.color; 2493 | LfColor text_color = state.theme.button_props.text_color; 2494 | LfFont font = get_current_font(); 2495 | 2496 | int32_t render_width = ((width == -1) ? img.width : width); 2497 | int32_t render_height = ((height == -1) ? img.height : height); 2498 | 2499 | // If the button does not fit onto the current div, advance to the next line 2500 | next_line_on_overflow( 2501 | (vec2s){render_width + padding * 2.0f, 2502 | render_height + padding * 2.0f}, 2503 | state.div_props.border_width); 2504 | 2505 | // Advancing the position pointer by the margins 2506 | state.pos_ptr.x += margin_left; 2507 | state.pos_ptr.y += margin_top; 2508 | 2509 | // Rendering the button 2510 | LfClickableItemState ret = button(file, line, state.pos_ptr, (vec2s){render_width + padding * 2, render_height + padding * 2}, 2511 | props, color, props.border_width, true, true); 2512 | LfColor imageColor = LF_WHITE; 2513 | lf_image_render((vec2s){state.pos_ptr.x + padding + (render_width - img.width) / 2.0f, state.pos_ptr.y + padding}, imageColor, img, LF_NO_COLOR, 0, props.corner_radius); 2514 | 2515 | // Advancing the position pointer by the width of the button 2516 | state.pos_ptr.x += render_width + margin_right + padding * 2.0f; 2517 | state.pos_ptr.y -= margin_top; 2518 | 2519 | return ret; 2520 | } 2521 | 2522 | LfClickableItemState _lf_button_fixed_loc(const char* text, float width, float height, const char* file, int32_t line) { 2523 | return button_fixed_element_loc((void*)text, width, height, file, line, false); 2524 | } 2525 | 2526 | LfClickableItemState _lf_button_fixed_loc_wide(const wchar_t* text, float width, float height, const char* file, int32_t line) { 2527 | return button_fixed_element_loc((void*)text, width, height, file, line, true); 2528 | } 2529 | 2530 | LfClickableItemState _lf_slider_int_loc(LfSlider* slider, const char* file, int32_t line) { 2531 | // Getting property data 2532 | LfUIElementProps props = get_props_for(state.theme.button_props); 2533 | float margin_left = props.margin_left, margin_right = props.margin_right, 2534 | margin_top = props.margin_top, margin_bottom =props.margin_bottom; 2535 | 2536 | float handle_size; 2537 | if(slider->handle_size != 0.0f) 2538 | handle_size = slider->handle_size; 2539 | else 2540 | handle_size = (slider->height != 0) ? slider->height * 4 : 20; 2541 | 2542 | if(slider->held) { 2543 | handle_size = (slider->height != 0) ? slider->height * 4.5 : 22.5; 2544 | } 2545 | float slider_width = (slider->width != 0) ? slider->width : 200; 2546 | float slider_height = (slider->height != 0) ? slider->height : handle_size / 2.0f; 2547 | 2548 | LfColor color = props.color; 2549 | 2550 | next_line_on_overflow( 2551 | (vec2s){slider_width + margin_right + margin_left, 2552 | handle_size + margin_bottom + margin_top}, 2553 | state.div_props.border_width); 2554 | 2555 | state.pos_ptr.x += margin_left; 2556 | state.pos_ptr.y += margin_top; 2557 | 2558 | 2559 | // Render the slider 2560 | LfUIElementProps slider_props = props; 2561 | slider_props.border_width /= 2.0f; 2562 | LfClickableItemState slider_state = button_ex(file, line, state.pos_ptr, (vec2s){(float)slider_width, (float)slider_height}, 2563 | slider_props, color, 0, 2564 | false, false, (vec2s){-1, handle_size}); 2565 | 2566 | slider->handle_pos = map_vals(*(int32_t*)slider->val, slider->min, slider->max, 2567 | handle_size / 2.0f, slider->width - handle_size / 2.0f) - (handle_size) / 2.0f; 2568 | 2569 | 2570 | lf_rect_render((vec2s){state.pos_ptr.x + slider->handle_pos, state.pos_ptr.y - (handle_size) / 2.0f + slider_height / 2.0f}, 2571 | (vec2s){handle_size, handle_size}, props.text_color, props.border_color, props.border_width, 2572 | slider->held ? props.corner_radius * 3.5f : props.corner_radius * 3.0f); 2573 | 2574 | 2575 | // Check if the slider bar is pressed 2576 | 2577 | if(slider_state == LF_HELD || slider_state == LF_CLICKED) { 2578 | slider->held = true; 2579 | } 2580 | if(slider->held && lf_mouse_button_is_released(GLFW_MOUSE_BUTTON_LEFT)) { 2581 | slider->held = false; 2582 | slider_state = LF_CLICKED; 2583 | } 2584 | if(slider->held) { 2585 | if(lf_get_mouse_x() >= state.pos_ptr.x && lf_get_mouse_x() <= state.pos_ptr.x + slider_width - handle_size) { 2586 | slider->handle_pos = lf_get_mouse_x() - state.pos_ptr.x; 2587 | *(int32_t*)slider->val = map_vals(state.pos_ptr.x + slider->handle_pos, state.pos_ptr.x, state.pos_ptr.x + slider_width - handle_size, 2588 | slider->min, slider->max); 2589 | } else if(lf_get_mouse_x() <= state.pos_ptr.x) { 2590 | *(int32_t*)slider->val = slider->min; 2591 | slider->handle_pos = 0; 2592 | } else if(lf_get_mouse_x() >= state.pos_ptr.x + slider_width - handle_size) { 2593 | *(int32_t*)slider->val = slider->max; 2594 | slider->handle_pos = slider_width - handle_size; 2595 | } 2596 | slider_state = LF_HELD; 2597 | } 2598 | state.pos_ptr.x += slider_width + margin_right; 2599 | state.pos_ptr.y -= margin_top; 2600 | 2601 | return slider_state; 2602 | } 2603 | 2604 | LfClickableItemState _lf_progress_bar_val_loc(float width, float height, int32_t min, int32_t max, int32_t val, const char* file, int32_t line) { 2605 | // Getting property data 2606 | LfUIElementProps props = get_props_for(state.theme.slider_props); 2607 | float margin_left = props.margin_left, margin_right = props.margin_right, 2608 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2609 | // constants 2610 | const float handle_size = (height == -1) ? 10 : height * 2; // px 2611 | const float slider_width = (width == -1) ? 200 : width; // px 2612 | const float slider_height = (height == -1) ? handle_size / 2.0f : height; // px 2613 | // Get the height of the element 2614 | 2615 | 2616 | next_line_on_overflow( 2617 | (vec2s){slider_width + margin_right + margin_left, 2618 | handle_size + margin_bottom + margin_top}, 2619 | state.div_props.border_width); 2620 | 2621 | state.pos_ptr.x += margin_left; 2622 | state.pos_ptr.y += margin_top + handle_size / 4.0f; 2623 | 2624 | // Render the slider 2625 | LfUIElementProps slider_props = props; 2626 | slider_props.corner_radius = props. corner_radius / 2.0f; 2627 | LfClickableItemState slider_state = button(file, line, state.pos_ptr, (vec2s){(float)slider_width, (float)slider_height}, 2628 | slider_props, props.color, 0, 2629 | false, false); 2630 | 2631 | // Render the handle 2632 | int32_t handle_pos = map_vals(val, min, max, 2633 | handle_size / 2.0f, slider_width - handle_size / 2.0f) - (handle_size) / 2.0f; 2634 | 2635 | lf_push_element_id(1); 2636 | LfClickableItemState handle = button(file, line, (vec2s){state.pos_ptr.x + handle_pos, state.pos_ptr.y - (handle_size) / 2.0f + slider_height / 2.0f}, 2637 | (vec2s){handle_size, handle_size}, props, props.text_color, props.border_width, false, false); 2638 | lf_pop_element_id(); 2639 | 2640 | state.pos_ptr.x += slider_width + margin_right; 2641 | state.pos_ptr.y -= margin_top + handle_size / 4.0f; 2642 | return handle; 2643 | 2644 | } 2645 | LfClickableItemState _lf_progress_bar_int_loc(float val, float min, float max, float width, float height, const char* file, int32_t line) { 2646 | // Getting property data 2647 | LfUIElementProps props = get_props_for(state.theme.slider_props); 2648 | float margin_left = props.margin_left, margin_right = props.margin_right, 2649 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2650 | LfColor color = props.color; 2651 | // constants 2652 | 2653 | 2654 | next_line_on_overflow( 2655 | (vec2s){width + margin_right + margin_left, 2656 | height + margin_bottom + margin_top }, 2657 | state.div_props.border_width); 2658 | 2659 | state.pos_ptr.x += margin_left; 2660 | state.pos_ptr.y += margin_top; 2661 | 2662 | // Render the slider 2663 | LfClickableItemState bar = button(file, line, state.pos_ptr, (vec2s){(float)width, (float)height},props, color, props.border_width, false, false); 2664 | 2665 | float pos_x = map_vals(val, min, max, 0, width); 2666 | 2667 | lf_push_element_id(1); 2668 | LfClickableItemState handle = button(file, line, state.pos_ptr, (vec2s){(float)pos_x, (float)height}, props, props.text_color, 0, false, false); 2669 | lf_pop_element_id(); 2670 | 2671 | state.pos_ptr.x += width + margin_right; 2672 | state.pos_ptr.y -= margin_top; 2673 | return bar; 2674 | } 2675 | 2676 | LfClickableItemState _lf_progress_stripe_int_loc(LfSlider* slider, const char* file, int32_t line) { 2677 | // Getting property data 2678 | LfUIElementProps props = get_props_for(state.theme.slider_props); 2679 | float margin_left = props.margin_left, margin_right = props.margin_right, 2680 | margin_top = props.margin_top, margin_bottom =props.margin_bottom; 2681 | 2682 | const float handle_size = 20; // px 2683 | const float height = (slider->height != 0) ? slider->height : handle_size / 2.0f; // px 2684 | 2685 | LfColor color = props.color; 2686 | 2687 | next_line_on_overflow( 2688 | (vec2s){slider->width + margin_right + margin_left, 2689 | slider->height + margin_bottom + margin_top}, 2690 | state.div_props.border_width); 2691 | 2692 | state.pos_ptr.x += margin_left; 2693 | state.pos_ptr.y += margin_top; 2694 | 2695 | // Render the slider 2696 | LfClickableItemState bar = button(file, line, state.pos_ptr, (vec2s){(float)slider->width, (float)height},props, color, props.border_width, false, false); 2697 | 2698 | // Check if the slider bar is pressed 2699 | slider->handle_pos = map_vals(*(int32_t*)slider->val, slider->min, slider->max, 2700 | 0, slider->width); 2701 | 2702 | 2703 | lf_push_element_id(1); 2704 | LfClickableItemState handle = button(file, line, state.pos_ptr, (vec2s){(float)slider->handle_pos, (float)height}, props, props.text_color, 0, false, false); 2705 | lf_pop_element_id(); 2706 | 2707 | lf_rect_render((vec2s){state.pos_ptr.x + slider->handle_pos, state.pos_ptr.y - (float)height / 2.0f}, (vec2s){(float)slider->height * 2, (float)slider->height * 2}, props.text_color, (LfColor){0.0f, 0.0f, 0.0f, 0.0f}, 0, props.corner_radius); 2708 | 2709 | state.pos_ptr.x += slider->width + margin_right; 2710 | state.pos_ptr.y -= margin_top; 2711 | return bar; 2712 | } 2713 | 2714 | LfClickableItemState _lf_checkbox_loc(const char* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line) { 2715 | return checkbox_element_loc((void*)text, val, tick_color, tex_color, file, line, false); 2716 | } 2717 | 2718 | LfClickableItemState _lf_checkbox_wide_loc(const wchar_t* text, bool* val, LfColor tick_color, LfColor tex_color, const char* file, int32_t line) { 2719 | return checkbox_element_loc((void*)text, val, tick_color, tex_color, file, line, true); 2720 | } 2721 | 2722 | int32_t _lf_menu_item_list_loc(const char** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line) { 2723 | return menu_item_list_item_loc((void**)items, item_count, selected_index, per_cb, vertical, file, line, false); 2724 | } 2725 | 2726 | int32_t _lf_menu_item_list_loc_wide(const wchar_t** items, uint32_t item_count, int32_t selected_index, LfMenuItemCallback per_cb, bool vertical, const char* file, int32_t line) { 2727 | return menu_item_list_item_loc((void**)items, item_count, selected_index, per_cb, vertical, file, line, true); 2728 | } 2729 | 2730 | void _lf_dropdown_menu_loc(const char** items, const char* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line) { 2731 | return dropdown_menu_item_loc((void**)items, (void*)placeholder, item_count, width, height, selected_index, opened, file, line, false); 2732 | } 2733 | 2734 | void _lf_dropdown_menu_loc_wide(const wchar_t** items, const wchar_t* placeholder, uint32_t item_count, float width, float height, int32_t* selected_index, bool* opened, const char* file, int32_t line) { 2735 | return dropdown_menu_item_loc((void**)items, (void*)placeholder, item_count, width, height, selected_index, opened, file, line, true); 2736 | } 2737 | 2738 | void _lf_input_text_loc(LfInputField* input, const char* file, int32_t line) { 2739 | input_field(input, INPUT_TEXT, file, line); 2740 | } 2741 | 2742 | void _lf_input_int_loc(LfInputField *input, const char* file, int32_t line) { 2743 | input_field(input, INPUT_INT, file, line); 2744 | } 2745 | void _lf_input_float_loc(LfInputField* input, const char* file, int32_t line) { 2746 | input_field(input, INPUT_FLOAT, file, line); 2747 | } 2748 | void lf_input_insert_char_idx(LfInputField* input, char c, uint32_t idx) { 2749 | lf_input_field_unselect_all(input); 2750 | insert_i_str(input->buf, c, idx); 2751 | } 2752 | 2753 | void lf_input_insert_str_idx(LfInputField* input, const char* insert, uint32_t len, uint32_t idx) { 2754 | if(len > input->buf_size || strlen(input->buf) + len > input->buf_size) return; 2755 | 2756 | insert_str_str(input->buf, insert, idx); 2757 | lf_input_field_unselect_all(input); 2758 | } 2759 | 2760 | void lf_input_field_unselect_all(LfInputField* input) { 2761 | input->selection_start = -1; 2762 | input->selection_end = -1; 2763 | input->selection_dir = 0; 2764 | } 2765 | 2766 | bool lf_input_grabbed() { 2767 | return state.input_grabbed; 2768 | } 2769 | 2770 | void lf_div_grab(LfDiv div) { 2771 | state.grabbed_div = div; 2772 | } 2773 | 2774 | void lf_div_ungrab() { 2775 | memset(&state.grabbed_div, 0, sizeof(LfDiv)); 2776 | state.grabbed_div.id = -1; 2777 | } 2778 | 2779 | bool lf_div_grabbed() { 2780 | return state.grabbed_div.id != -1; 2781 | } 2782 | 2783 | LfDiv lf_get_grabbed_div() { 2784 | return state.grabbed_div; 2785 | } 2786 | 2787 | void _lf_begin_loc(const char* file, int32_t line) { 2788 | state.pos_ptr = (vec2s){0, 0}; 2789 | renderer_begin(); 2790 | LfUIElementProps props = get_props_for(state.theme.div_props); 2791 | props.color = (LfColor){0, 0, 0, 0}; 2792 | lf_push_style_props(props); 2793 | 2794 | lf_div_begin(((vec2s){0, 0}), ((vec2s){(float)state.dsp_w, (float)state.dsp_h}), true); 2795 | 2796 | lf_pop_style_props(); 2797 | } 2798 | void lf_end() { 2799 | lf_div_end(); 2800 | 2801 | state.selected_div = state.selected_div_tmp; 2802 | 2803 | update_input(); 2804 | clear_events(); 2805 | renderer_flush(); 2806 | state.drawcalls = 0; 2807 | } 2808 | 2809 | void lf_next_line() { 2810 | state.pos_ptr.x = state.current_div.aabb.pos.x + state.div_props.border_width; 2811 | state.pos_ptr.y += state.current_line_height; 2812 | state.current_line_height = 0; 2813 | } 2814 | vec2s lf_text_dimension(const char* str) { 2815 | return lf_text_dimension_ex(str, -1); 2816 | } 2817 | 2818 | vec2s lf_text_dimension_ex(const char* str, float wrap_point) { 2819 | LfFont font = get_current_font(); 2820 | LfTextProps props = lf_text_render((vec2s){0.0f, 0.0f}, str, font, state.theme.text_props.text_color, wrap_point, 2821 | (vec2s){-1, -1}, true, false, -1, -1); 2822 | 2823 | return (vec2s){(float)props.width, (float)props.height}; 2824 | } 2825 | 2826 | vec2s lf_text_dimension_wide(const wchar_t* str) { 2827 | return lf_text_dimension_wide_ex(str, -1); 2828 | } 2829 | 2830 | vec2s lf_text_dimension_wide_ex(const wchar_t* str, float wrap_point) { 2831 | LfFont font = get_current_font(); 2832 | LfTextProps props = lf_text_render_wchar((vec2s){0.0f, 0.0f}, str, font, LF_NO_COLOR, wrap_point, (vec2s){-1, -1}, true, false, -1, -1); 2833 | 2834 | return (vec2s){(float)props.width, (float)props.height}; 2835 | } 2836 | 2837 | vec2s lf_button_dimension(const char* text) { 2838 | LfUIElementProps props = get_props_for(state.theme.button_props); 2839 | float padding = props.padding; 2840 | vec2s text_dimension = lf_text_dimension(text); 2841 | return (vec2s){text_dimension.x + padding * 2.0f, text_dimension.y + padding}; 2842 | } 2843 | 2844 | float lf_get_text_end(const char* str, float start_x) { 2845 | LfFont font = get_current_font(); 2846 | LfTextProps props = text_render_simple((vec2s){start_x, 0.0f}, str, font, state.theme.text_props.text_color, true); 2847 | return props.end_x; 2848 | } 2849 | 2850 | void lf_text(const char* text) { 2851 | // Retrieving the property data of the text 2852 | LfUIElementProps props = get_props_for(state.theme.text_props); 2853 | float padding = props.padding; 2854 | float margin_left = props.margin_left, margin_right = props.margin_right, 2855 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2856 | 2857 | LfColor text_color = props.text_color; 2858 | LfColor color = props.color; 2859 | LfFont font = get_current_font(); 2860 | 2861 | // Advancing to the next line if the the text does not fit on the current div 2862 | LfTextProps text_props = lf_text_render(state.pos_ptr, text, font, text_color, 2863 | state.text_wrap ? 2864 | (state.current_div.aabb.size.x + state.current_div.aabb.pos.x) - margin_right - margin_left 2865 | : 2866 | -1, 2867 | (vec2s){-1, -1}, true, false, -1, -1); 2868 | next_line_on_overflow( 2869 | (vec2s){text_props.width + padding * 2.0f + margin_left + margin_right, 2870 | text_props.height + padding * 2.0f + margin_top + margin_bottom}, 2871 | state.div_props.border_width); 2872 | 2873 | // Advancing the position pointer by the margins 2874 | state.pos_ptr.x += margin_left; 2875 | state.pos_ptr.y += margin_top; 2876 | 2877 | // Rendering a colored text box if a color is specified 2878 | // Rendering the text 2879 | lf_text_render((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, text, font, text_color, 2880 | state.text_wrap ? 2881 | (state.current_div.aabb.size.x + state.current_div.aabb.pos.x) - margin_right - margin_left 2882 | : 2883 | -1, (vec2s){-1, -1}, false, false, -1, -1); 2884 | 2885 | // Advancing the position pointer by the width of the text 2886 | state.pos_ptr.x += text_props.width + margin_right + padding; 2887 | state.pos_ptr.y -= margin_top; 2888 | } 2889 | 2890 | void lf_text_wide(const wchar_t* text) { 2891 | // Retrieving the property data of the text 2892 | LfUIElementProps props = get_props_for(state.theme.text_props); 2893 | float padding = props.padding; 2894 | float margin_left = props.margin_left, margin_right = props.margin_right, 2895 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 2896 | LfColor text_color = props.text_color; 2897 | LfColor color = props.color; 2898 | LfFont font = get_current_font(); 2899 | 2900 | // Advancing to the next line if the the text does not fit on the current div 2901 | LfTextProps text_props = lf_text_render_wchar(state.pos_ptr, text, font, text_color, 2902 | (state.text_wrap ? (state.current_div.aabb.size.x + state.current_div.aabb.pos.x) - margin_right - margin_left : -1), (vec2s){-1, -1}, true, false, -1, -1); 2903 | next_line_on_overflow( 2904 | (vec2s){text_props.width + padding * 2.0f + margin_left + margin_right, 2905 | text_props.height + padding * 2.0f + margin_top + margin_bottom}, 2906 | state.div_props.border_width); 2907 | 2908 | // Advancing the position pointer by the margins 2909 | 2910 | state.pos_ptr.x += margin_left; 2911 | state.pos_ptr.y += margin_top; 2912 | 2913 | lf_rect_render(state.pos_ptr, (vec2s){text_props.width + padding * 2.0f, text_props.height + padding * 2.0f}, props.color, props.border_color, props.border_width, props.corner_radius); 2914 | 2915 | // Rendering a colored text box if a color is specified 2916 | // Rendering the text 2917 | lf_text_render_wchar((vec2s){state.pos_ptr.x + padding, state.pos_ptr.y + padding}, text, font, text_color, 2918 | (state.text_wrap ? (state.current_div.aabb.size.x + state.current_div.aabb.pos.x) - margin_right - margin_left : -1), (vec2s){-1, -1}, false, false, -1, -1); 2919 | 2920 | // Advancing the position pointer by the width of the text 2921 | state.pos_ptr.x += text_props.width + padding * 2.0f + margin_right + padding; 2922 | state.pos_ptr.y -= margin_top; 2923 | } 2924 | 2925 | void lf_set_text_wrap(bool wrap) { 2926 | state.text_wrap = wrap; 2927 | } 2928 | 2929 | LfDiv lf_get_current_div() { 2930 | return state.current_div; 2931 | } 2932 | 2933 | LfDiv lf_get_selected_div() { 2934 | return state.selected_div; 2935 | } 2936 | 2937 | LfDiv* lf_get_current_div_ptr() { 2938 | return &state.current_div; 2939 | } 2940 | 2941 | LfDiv* lf_get_selected_div_ptr() { 2942 | return &state.selected_div; 2943 | } 2944 | 2945 | void lf_set_ptr_x(float x) { 2946 | state.pos_ptr.x = x + state.current_div.aabb.pos.x; 2947 | } 2948 | 2949 | void lf_set_ptr_y(float y) { 2950 | state.pos_ptr.y = y + state.current_div.aabb.pos.y; 2951 | } 2952 | void lf_set_ptr_x_absolute(float x) { 2953 | state.pos_ptr.x = x; 2954 | } 2955 | 2956 | void lf_set_ptr_y_absolute(float y) { 2957 | state.pos_ptr.y = y; 2958 | } 2959 | float lf_get_ptr_x() { 2960 | return state.pos_ptr.x; 2961 | } 2962 | 2963 | float lf_get_ptr_y() { 2964 | return state.pos_ptr.y; 2965 | } 2966 | 2967 | uint32_t lf_get_display_width() { 2968 | return state.dsp_w; 2969 | } 2970 | 2971 | uint32_t lf_get_display_height() { 2972 | return state.dsp_h; 2973 | } 2974 | 2975 | void lf_push_font(LfFont* font) { 2976 | state.font_stack = font; 2977 | } 2978 | 2979 | void lf_pop_font() { 2980 | state.font_stack = NULL; 2981 | } 2982 | 2983 | // Decode a UTF-8 character sequence to Unicode code point 2984 | uint32_t decode_utf8(const char *s, int *bytes_read) { 2985 | uint8_t c = s[0]; 2986 | if (c < 0x80) { 2987 | *bytes_read = 1; 2988 | return c; 2989 | } else if (c < 0xE0) { 2990 | *bytes_read = 2; 2991 | return ((s[0] & 0x1F) << 6) | (s[1] & 0x3F); 2992 | } else if (c < 0xF0) { 2993 | *bytes_read = 3; 2994 | return ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); 2995 | } else { 2996 | *bytes_read = 4; 2997 | return ((s[0] & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | (s[3] & 0x3F); 2998 | } 2999 | } 3000 | 3001 | static void renderer_add_glyph(stbtt_aligned_quad q, int32_t max_descended_char_height, LfColor color, uint32_t tex_index) { 3002 | vec2s texcoords[4] = { 3003 | q.s0, q.t0, 3004 | q.s1, q.t0, 3005 | q.s1, q.t1, 3006 | q.s0, q.t1 3007 | }; 3008 | vec2s verts[4] = { 3009 | (vec2s){q.x0, q.y0 + max_descended_char_height}, 3010 | (vec2s){q.x1, q.y0 + max_descended_char_height}, 3011 | (vec2s){q.x1, q.y1 + max_descended_char_height}, 3012 | (vec2s){q.x0, q.y1 + max_descended_char_height} 3013 | }; 3014 | for(uint32_t i = 0; i < 4; i++) { 3015 | if(state.render.vert_count >= MAX_RENDER_BATCH) { 3016 | renderer_flush(); 3017 | renderer_begin(); 3018 | } 3019 | const vec2 verts_arr = {verts[i].x, verts[i].y}; 3020 | memcpy(state.render.verts[state.render.vert_count].pos, verts_arr, sizeof(vec2)); 3021 | 3022 | const vec4 border_color = {0, 0, 0, 0}; 3023 | memcpy(state.render.verts[state.render.vert_count].border_color, border_color, sizeof(vec4)); 3024 | 3025 | state.render.verts[state.render.vert_count].border_width = 0; 3026 | 3027 | vec4s color_zto = lf_color_to_zto(color); 3028 | const vec4 color_arr = {color_zto.r, color_zto.g, color_zto.b, color_zto.a}; 3029 | memcpy(state.render.verts[state.render.vert_count].color, color_arr, sizeof(vec4)); 3030 | 3031 | const vec2 texcoord_arr = {texcoords[i].x, texcoords[i].y}; 3032 | memcpy(state.render.verts[state.render.vert_count].texcoord, texcoord_arr, sizeof(vec2)); 3033 | 3034 | state.render.verts[state.render.vert_count].tex_index = tex_index; 3035 | 3036 | const vec2 scale_arr = {0, 0}; 3037 | memcpy(state.render.verts[state.render.vert_count].scale, scale_arr, sizeof(vec2)); 3038 | 3039 | const vec2 pos_px_arr = {0, 0}; 3040 | memcpy(state.render.verts[state.render.vert_count].pos_px, pos_px_arr, sizeof(vec2)); 3041 | 3042 | state.render.verts[state.render.vert_count].corner_radius = 0; 3043 | 3044 | const vec2 cull_start_arr = {state.cull_start.x, state.cull_start.y}; 3045 | const vec2 cull_end_arr = {state.cull_end.x, state.cull_end.y}; 3046 | memcpy(state.render.verts[state.render.vert_count].min_coord, cull_start_arr, sizeof(vec2)); 3047 | memcpy(state.render.verts[state.render.vert_count].max_coord, cull_end_arr, sizeof(vec2)); 3048 | 3049 | state.render.vert_count++; 3050 | } 3051 | state.render.index_count += 6; 3052 | } 3053 | 3054 | 3055 | static wchar_t* str_to_wstr(const char* str) { 3056 | size_t len = strlen(str) + 1; 3057 | 3058 | wchar_t* wstr = (wchar_t*)malloc(len * sizeof(wchar_t)); 3059 | if (wstr == NULL) { 3060 | perror("Memory allocation failed"); 3061 | return NULL; 3062 | } 3063 | if (mbstowcs(wstr, str, len) == (size_t)-1) { 3064 | perror("Conversion failed"); 3065 | free(wstr); 3066 | return NULL; 3067 | } 3068 | 3069 | return wstr; 3070 | } 3071 | 3072 | LfTextProps lf_text_render(vec2s pos, const char* str, LfFont font, LfColor color, 3073 | int32_t wrap_point, vec2s stop_point, bool no_render, bool render_solid, int32_t start_index, int32_t end_index) { 3074 | wchar_t* wstr = str_to_wstr(str); 3075 | LfTextProps textprops = lf_text_render_wchar(pos, (const wchar_t*)wstr, font, color, wrap_point, stop_point, no_render, render_solid, start_index, end_index); 3076 | free(wstr); 3077 | return textprops; 3078 | } 3079 | 3080 | 3081 | LfTextProps lf_text_render_wchar(vec2s pos, const wchar_t* str, LfFont font, LfColor color, 3082 | int32_t wrap_point, vec2s stop_point, bool no_render, bool render_solid, int32_t start_index, int32_t end_index) { 3083 | bool culled = item_should_cull((LfAABB){.pos = (vec2s){pos.x, pos.y + get_current_font().font_size}, .size = (vec2s){-1, -1}}); 3084 | 3085 | // Retrieving the texture index 3086 | float tex_index = -1.0f; 3087 | if (!culled && !no_render) { 3088 | if (state.render.tex_count - 1 >= MAX_TEX_COUNT_BATCH - 1) { 3089 | renderer_flush(); 3090 | renderer_begin(); 3091 | } 3092 | for (uint32_t i = 0; i < state.render.tex_count; i++) { 3093 | if (state.render.textures[i].id == font.bitmap.id) { 3094 | tex_index = (float)i; 3095 | break; 3096 | } 3097 | } 3098 | if (tex_index == -1.0f) { 3099 | tex_index = (float)state.render.tex_index; 3100 | LfTexture tex = font.bitmap; 3101 | state.render.textures[state.render.tex_count++] = tex; 3102 | state.render.tex_index++; 3103 | } 3104 | } 3105 | 3106 | // Local variables needed for rendering 3107 | LfTextProps ret = {0}; 3108 | float x = pos.x; 3109 | float y = pos.y; 3110 | 3111 | int32_t max_descended_char_height = get_max_char_height_font(font); 3112 | 3113 | float last_x = x; 3114 | 3115 | float height = get_max_char_height_font(font); 3116 | float width = 0; 3117 | 3118 | uint32_t i = 0; 3119 | while (str[i] != L'\0') { 3120 | if (str[i] >= font.num_glyphs) { 3121 | i++; 3122 | continue; 3123 | } 3124 | if (stbtt_FindGlyphIndex((const stbtt_fontinfo*)font.font_info, str[i] - 32) == 0 && 3125 | str[i] != L' ' && str[i] != L'\n' && str[i] != L'\t' && !iswdigit(str[i]) && !iswpunct(str[i])) { 3126 | i++; 3127 | continue; 3128 | } 3129 | if (i >= end_index && end_index != -1) { 3130 | break; 3131 | } 3132 | 3133 | // Calculate the width of the next word 3134 | float word_width = 0; 3135 | uint32_t j = i; 3136 | while (str[j] != L' ' && str[j] != L'\n' && str[j] != L'\0') { 3137 | stbtt_aligned_quad q; 3138 | stbtt_GetBakedQuad((stbtt_bakedchar*)font.cdata, font.tex_width, font.tex_height, str[j] - 32, &word_width, &y, &q, 0); 3139 | j++; 3140 | } 3141 | 3142 | // If the next word exceeds the wrap point, move to the next line 3143 | if (x + word_width > wrap_point && wrap_point != -1) { 3144 | y += font.font_size; 3145 | height += font.font_size; 3146 | if (x - pos.x > width) { 3147 | width = x - pos.x; 3148 | } 3149 | x = pos.x; 3150 | last_x = x; 3151 | } 3152 | 3153 | // If the current character is a new line, advance to the next line 3154 | if (str[i] == L'\n') { 3155 | y += font.font_size; 3156 | height += font.font_size; 3157 | if (x - pos.x > width) { 3158 | width = x - pos.x; 3159 | } 3160 | x = pos.x; 3161 | last_x = x; 3162 | i++; 3163 | continue; 3164 | } 3165 | 3166 | // Retrieving the vertex data of the current character & submitting it to the batch 3167 | stbtt_aligned_quad q; 3168 | stbtt_GetBakedQuad((stbtt_bakedchar*)font.cdata, font.tex_width, font.tex_height, str[i] - 32, &x, &y, &q, 1); 3169 | if (i < start_index && start_index != -1) { 3170 | last_x = x; 3171 | ret.rendered_count++; 3172 | i++; 3173 | continue; 3174 | } 3175 | if (stop_point.x != -1 && stop_point.y != -1) { 3176 | if (x >= stop_point.x && stop_point.x != -1 && y + get_max_char_height_font(font) >= stop_point.y && stop_point.y != -1) { 3177 | break; 3178 | } 3179 | } else { 3180 | if (y + get_max_char_height_font(font) >= stop_point.y && stop_point.y != -1) { 3181 | break; 3182 | } 3183 | } 3184 | if (!culled && !no_render && state.renderer_render) { 3185 | if (render_solid) { 3186 | lf_rect_render((vec2s){x, y}, (vec2s){last_x - x, get_max_char_height_font(font)}, color, LF_NO_COLOR, 0.0f, 0.0f); 3187 | } else { 3188 | renderer_add_glyph(q, max_descended_char_height, color, tex_index); 3189 | } 3190 | last_x = x; 3191 | } 3192 | ret.rendered_count++; 3193 | i++; 3194 | } 3195 | 3196 | // Populating the return value 3197 | if (x - pos.x > width) { 3198 | width = x - pos.x; 3199 | } 3200 | ret.width = width; 3201 | ret.height = height; 3202 | ret.end_x = x; 3203 | ret.end_y = y; 3204 | return ret; 3205 | } 3206 | 3207 | void lf_rect_render(vec2s pos, vec2s size, LfColor color, LfColor border_color, float border_width, float corner_radius) { 3208 | if(!state.renderer_render) return; 3209 | if(item_should_cull((LfAABB){.pos = pos, .size = size})) { 3210 | return; 3211 | } 3212 | // Offsetting the postion, so that pos is the top left of the rendered object 3213 | vec2s pos_initial = pos; 3214 | pos = (vec2s){pos.x + size.x / 2.0f, pos.y + size.y / 2.0f}; 3215 | 3216 | // Initializing texture coords data 3217 | vec2s texcoords[4] = { 3218 | (vec2s){1.0f, 1.0f}, 3219 | (vec2s){1.0f, 0.0f}, 3220 | (vec2s){0.0f, 0.0f}, 3221 | (vec2s){0.0f, 1.0f}, 3222 | }; 3223 | // Calculating the transform matrix 3224 | mat4 translate; 3225 | mat4 scale; 3226 | mat4 transform; 3227 | vec3 pos_xyz = {(corner_radius != 0.0f ? (float)state.dsp_w / 2.0f : pos.x), (corner_radius != 0.0f ? (float)state.dsp_h / 2.0f : pos.y), 0.0f}; 3228 | vec3 size_xyz = {corner_radius != 0.0f ? state.dsp_w : size.x, corner_radius != 0.0f ? state.dsp_h : size.y, 0.0f}; 3229 | glm_translate_make(translate, pos_xyz); 3230 | glm_scale_make(scale, size_xyz); 3231 | glm_mat4_mul(translate,scale,transform); 3232 | 3233 | // Adding the vertices to the batch renderer 3234 | for(uint32_t i = 0; i < 4; i++) { 3235 | if(state.render.vert_count >= MAX_RENDER_BATCH) { 3236 | renderer_flush(); 3237 | renderer_begin(); 3238 | } 3239 | vec4 result; 3240 | glm_mat4_mulv(transform, state.render.vert_pos[i].raw, result); 3241 | state.render.verts[state.render.vert_count].pos[0] = result[0]; 3242 | state.render.verts[state.render.vert_count].pos[1] = result[1]; 3243 | 3244 | vec4s border_color_zto = lf_color_to_zto(border_color); 3245 | const vec4 border_color_arr = {border_color_zto.r, border_color_zto.g, border_color_zto.b, border_color_zto.a}; 3246 | memcpy(state.render.verts[state.render.vert_count].border_color, border_color_arr, sizeof(vec4)); 3247 | 3248 | state.render.verts[state.render.vert_count].border_width = border_width; 3249 | 3250 | vec4s color_zto = lf_color_to_zto(color); 3251 | const vec4 color_arr = {color_zto.r, color_zto.g, color_zto.b, color_zto.a}; 3252 | memcpy(state.render.verts[state.render.vert_count].color, color_arr, sizeof(vec4)); 3253 | 3254 | const vec2 texcoord_arr = {texcoords[i].x, texcoords[i].y}; 3255 | memcpy(state.render.verts[state.render.vert_count].texcoord, texcoord_arr, sizeof(vec2)); 3256 | 3257 | state.render.verts[state.render.vert_count].tex_index = -1; 3258 | 3259 | const vec2 scale_arr = {size.x, size.y}; 3260 | memcpy(state.render.verts[state.render.vert_count].scale, scale_arr, sizeof(vec2)); 3261 | 3262 | const vec2 pos_px_arr = {(float)pos_initial.x, (float)pos_initial.y}; 3263 | memcpy(state.render.verts[state.render.vert_count].pos_px, pos_px_arr, sizeof(vec2)); 3264 | 3265 | state.render.verts[state.render.vert_count].corner_radius = corner_radius; 3266 | 3267 | const vec2 cull_start_arr = {state.cull_start.x, state.cull_start.y}; 3268 | memcpy(state.render.verts[state.render.vert_count].min_coord, cull_start_arr, sizeof(vec2)); 3269 | 3270 | const vec2 cull_end_arr = {state.cull_end.x, state.cull_end.y}; 3271 | memcpy(state.render.verts[state.render.vert_count].max_coord, cull_end_arr, sizeof(vec2)); 3272 | 3273 | state.render.vert_count++; 3274 | } 3275 | state.render.index_count += 6; 3276 | } 3277 | 3278 | void lf_image_render(vec2s pos, LfColor color, LfTexture tex, LfColor border_color, float border_width, float corner_radius) { 3279 | if(!state.renderer_render) return; 3280 | if(item_should_cull((LfAABB){.pos = pos, .size = (vec2s){tex.width, tex.height}})) { 3281 | return; 3282 | } 3283 | if(state.render.tex_count - 1 >= MAX_TEX_COUNT_BATCH - 1) { 3284 | renderer_flush(); 3285 | renderer_begin(); 3286 | } 3287 | // Offsetting the postion, so that pos is the top left of the rendered object 3288 | vec2s pos_initial = pos; 3289 | pos = (vec2s){pos.x + tex.width / 2.0f, pos.y + tex.height / 2.0f}; 3290 | 3291 | if(state.image_color_stack.a != 0.0) { 3292 | color = state.image_color_stack; 3293 | } 3294 | // Initializing texture coords data 3295 | vec2s texcoords[4] = { 3296 | (vec2s){0.0f, 0.0f}, 3297 | (vec2s){1.0f, 0.0f}, 3298 | (vec2s){1.0f, 1.0f}, 3299 | (vec2s){0.0f, 1.0f}, 3300 | }; 3301 | // Retrieving the texture index of the rendered texture 3302 | float tex_index = -1.0f; 3303 | for(uint32_t i = 0; i < state.render.tex_count; i++) { 3304 | if(tex.id == state.render.textures[i].id) { 3305 | tex_index = i; 3306 | break; 3307 | } 3308 | } 3309 | if(tex_index == -1.0f) { 3310 | tex_index = (float)state.render.tex_index; 3311 | state.render.textures[state.render.tex_count++] = tex; 3312 | state.render.tex_index++; 3313 | } 3314 | // Calculating the transform 3315 | mat4 translate = GLM_MAT4_IDENTITY_INIT; 3316 | mat4 scale = GLM_MAT4_IDENTITY_INIT; 3317 | mat4 transform = GLM_MAT4_IDENTITY_INIT; 3318 | vec3s pos_xyz = (vec3s){pos.x, pos.y, 0.0f}; 3319 | vec3 tex_size; 3320 | tex_size[0] = tex.width; 3321 | tex_size[1] = tex.height; 3322 | tex_size[2] = 0; 3323 | glm_translate_make(translate, pos_xyz.raw); 3324 | glm_scale_make(scale, tex_size); 3325 | glm_mat4_mul(translate,scale,transform); 3326 | 3327 | // Adding the vertices to the batch renderer 3328 | for(uint32_t i = 0; i < 4; i++) { 3329 | if(state.render.vert_count >= MAX_RENDER_BATCH) { 3330 | renderer_flush(); 3331 | renderer_begin(); 3332 | } 3333 | vec4 result; 3334 | glm_mat4_mulv(transform, state.render.vert_pos[i].raw, result); 3335 | memcpy(state.render.verts[state.render.vert_count].pos, result, sizeof(vec2)); 3336 | 3337 | vec4s border_color_zto = lf_color_to_zto(border_color); 3338 | const vec4 border_color_arr = {border_color_zto.r, border_color_zto.g, border_color_zto.b, border_color_zto.a}; 3339 | memcpy(state.render.verts[state.render.vert_count].border_color, border_color_arr, sizeof(vec4)); 3340 | 3341 | state.render.verts[state.render.vert_count].border_width = border_width; 3342 | 3343 | vec4s color_zto = lf_color_to_zto(color); 3344 | const vec4 color_arr = {color_zto.r, color_zto.g, color_zto.b, color_zto.a}; 3345 | memcpy(state.render.verts[state.render.vert_count].color, color_arr, sizeof(vec4)); 3346 | 3347 | const vec2 texcoord_arr = {texcoords[i].x, texcoords[i].y}; 3348 | memcpy(state.render.verts[state.render.vert_count].texcoord, texcoord_arr, sizeof(vec2)); 3349 | 3350 | state.render.verts[state.render.vert_count].tex_index = tex_index; 3351 | 3352 | const vec2 scale_arr = {(float)tex.width, (float)tex.height}; 3353 | memcpy(state.render.verts[state.render.vert_count].scale, scale_arr, sizeof(vec2)); 3354 | 3355 | vec2 pos_px_arr = {(float)pos_initial.x, (float)pos_initial.y}; 3356 | memcpy(state.render.verts[state.render.vert_count].pos_px, pos_px_arr, sizeof(vec2)); 3357 | 3358 | state.render.verts[state.render.vert_count].corner_radius = corner_radius; 3359 | 3360 | const vec2 cull_start_arr = {state.cull_start.x, state.cull_start.y}; 3361 | memcpy(state.render.verts[state.render.vert_count].min_coord, cull_start_arr, sizeof(vec2)); 3362 | 3363 | const vec2 cull_end_arr = {state.cull_end.x, state.cull_end.y}; 3364 | memcpy(state.render.verts[state.render.vert_count].max_coord, cull_end_arr, sizeof(vec2)); 3365 | 3366 | state.render.vert_count++; 3367 | } 3368 | state.render.index_count += 6; 3369 | } 3370 | 3371 | bool lf_point_intersects_aabb(vec2s p, LfAABB aabb) { 3372 | vec2s min = {aabb.pos.x, aabb.pos.y}; 3373 | vec2s max = {aabb.pos.x + aabb.size.x, aabb.pos.y + aabb.size.y}; 3374 | 3375 | // Check if the p is within the AABB bounds 3376 | if (p.x >= min.x && p.x <= max.x && 3377 | p.y >= min.y && p.y <= max.y) { 3378 | return true; 3379 | } 3380 | return false; 3381 | } 3382 | 3383 | bool lf_aabb_intersects_aabb(LfAABB a, LfAABB b) { 3384 | vec2s minA = a.pos; 3385 | vec2s maxA = (vec2s){a.pos.x + a.size.x, a.pos.y + a.size.y}; 3386 | 3387 | vec2s minB = b.pos; 3388 | vec2s maxB = (vec2s){b.pos.x + b.size.x, b.pos.y + b.size.y}; 3389 | 3390 | return (minA.x >= minB.x && minA.x <= maxB.x && 3391 | minA.y >= minB.y && minA.y <= maxB.y); 3392 | 3393 | } 3394 | 3395 | void lf_push_style_props(LfUIElementProps props) { 3396 | props_stack_push(&state.props_stack, props); 3397 | } 3398 | 3399 | void lf_pop_style_props() { 3400 | props_stack_pop(&state.props_stack); 3401 | } 3402 | 3403 | bool lf_hovered(vec2s pos, vec2s size) { 3404 | bool hovered = lf_get_mouse_x() <= (pos.x + size.x) && lf_get_mouse_x() >= (pos.x) && 3405 | lf_get_mouse_y() <= (pos.y + size.y) && lf_get_mouse_y() >= (pos.y) && 3406 | ((state.selected_div.id == state.current_div.id && state.grabbed_div.id == -1) || (state.grabbed_div.id == state.current_div.id && state.grabbed_div.id != -1)); 3407 | return hovered; 3408 | } 3409 | 3410 | bool lf_area_hovered(vec2s pos, vec2s size) { 3411 | bool hovered = lf_get_mouse_x() <= (pos.x + size.x) && lf_get_mouse_x() >= (pos.x) && 3412 | lf_get_mouse_y() <= (pos.y + size.y) && lf_get_mouse_y() >= (pos.y); 3413 | return hovered; 3414 | } 3415 | 3416 | LfCursorPosEvent lf_mouse_move_event() { 3417 | return state.cp_ev; 3418 | } 3419 | 3420 | LfMouseButtonEvent lf_mouse_button_event() { 3421 | return state.mb_ev; 3422 | } 3423 | 3424 | LfScrollEvent lf_mouse_scroll_event() { 3425 | return state.scr_ev; 3426 | } 3427 | 3428 | LfKeyEvent lf_key_event() { 3429 | return state.key_ev; 3430 | } 3431 | 3432 | LfCharEvent lf_char_event() { 3433 | return state.ch_ev; 3434 | } 3435 | 3436 | void lf_set_cull_end_x(float x) { 3437 | state.cull_end.x = x; 3438 | } 3439 | 3440 | void lf_set_cull_end_y(float y) { 3441 | state.cull_end.y = y; 3442 | } 3443 | void lf_set_cull_start_x(float x) { 3444 | state.cull_start.x = x; 3445 | } 3446 | 3447 | void lf_set_cull_start_y(float y) { 3448 | state.cull_start.y = y; 3449 | } 3450 | 3451 | void lf_unset_cull_start_x() { 3452 | state.cull_start.x = -1; 3453 | } 3454 | 3455 | void lf_unset_cull_start_y() { 3456 | state.cull_start.y = -1; 3457 | } 3458 | 3459 | void lf_unset_cull_end_x() { 3460 | state.cull_end.x = -1; 3461 | } 3462 | 3463 | void lf_unset_cull_end_y() { 3464 | state.cull_end.y = -1; 3465 | } 3466 | 3467 | void lf_set_image_color(LfColor color) { 3468 | state.image_color_stack = color; 3469 | } 3470 | 3471 | void lf_unset_image_color() { 3472 | state.image_color_stack = LF_NO_COLOR; 3473 | } 3474 | 3475 | void lf_set_current_div_scroll(float scroll) { 3476 | *state.scroll_ptr = scroll; 3477 | } 3478 | float lf_get_current_div_scroll() { 3479 | return *state.scroll_ptr; 3480 | } 3481 | 3482 | void lf_set_current_div_scroll_velocity(float scroll_velocity) { 3483 | *state.scroll_velocity_ptr = scroll_velocity; 3484 | } 3485 | 3486 | float lf_get_current_div_scroll_velocity() { 3487 | return *state.scroll_ptr; 3488 | } 3489 | 3490 | void lf_set_line_height(uint32_t line_height) { 3491 | state.current_line_height = line_height; 3492 | } 3493 | 3494 | uint32_t lf_get_line_height() { 3495 | return state.current_line_height; 3496 | } 3497 | 3498 | void lf_set_line_should_overflow(bool overflow) { 3499 | state.line_overflow = overflow; 3500 | } 3501 | 3502 | void lf_set_div_hoverable(bool clickable) { 3503 | state.div_hoverable = clickable; 3504 | } 3505 | void lf_push_element_id(int64_t id) { 3506 | state.element_id_stack = id; 3507 | } 3508 | 3509 | void lf_pop_element_id() { 3510 | state.element_id_stack = -1; 3511 | } 3512 | 3513 | LfColor lf_color_brightness(LfColor color, float brightness) { 3514 | uint32_t adjustedR = (int)(color.r * brightness); 3515 | uint32_t adjustedG = (int)(color.g * brightness); 3516 | uint32_t adjustedB = (int)(color.b * brightness); 3517 | color.r = (unsigned char)(adjustedR > 255 ? 255 : adjustedR); 3518 | color.g = (unsigned char)(adjustedG > 255 ? 255 : adjustedG); 3519 | color.b = (unsigned char)(adjustedB > 255 ? 255 : adjustedB); 3520 | return color; 3521 | } 3522 | 3523 | LfColor lf_color_alpha(LfColor color, uint8_t a) { 3524 | return (LfColor){color.r, color.g, color.b, a}; 3525 | } 3526 | 3527 | vec4s lf_color_to_zto(LfColor color) { 3528 | return (vec4s){color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f}; 3529 | } 3530 | LfColor lf_color_from_hex(uint32_t hex) { 3531 | LfColor color; 3532 | color.r = (hex>> 16) & 0xFF; 3533 | color.g = (hex >> 8) & 0xFF; 3534 | color.b = hex& 0xFF; 3535 | color.a = 255; 3536 | return color; 3537 | } 3538 | 3539 | LfColor lf_color_from_zto(vec4s zto) { 3540 | return (LfColor){(uint8_t)(zto.r * 255.0f), (uint8_t)(zto.g * 255.0f), (uint8_t)(zto.b * 255.0f), (uint8_t)(zto.a * 255.0f)}; 3541 | } 3542 | 3543 | void lf_image(LfTexture tex) { 3544 | // Retrieving the property data of the image 3545 | LfUIElementProps props = get_props_for(state.theme.image_props); 3546 | float margin_left = props.margin_left, margin_right = props.margin_right, 3547 | margin_top = props.margin_top, margin_bottom = props.margin_bottom; 3548 | LfColor color = props.color; 3549 | 3550 | // Advancing to the next line if the image does not fit on the current div 3551 | next_line_on_overflow((vec2s){tex.width + margin_left + margin_right, tex.height + margin_top + margin_bottom}, 3552 | state.div_props.border_width); 3553 | 3554 | // Advancing the position pointer by the margins 3555 | state.pos_ptr.x += margin_left; 3556 | state.pos_ptr.y += margin_top; 3557 | 3558 | // Rendering the image 3559 | lf_image_render(state.pos_ptr, color, tex, props.border_color, props.border_width, props.corner_radius); 3560 | 3561 | // Advancing the position pointer by the width of the image 3562 | state.pos_ptr.x += tex.width + margin_right; 3563 | state.pos_ptr.y -= margin_top; 3564 | } 3565 | 3566 | void lf_rect(float width, float height, LfColor color, float corner_radius) { 3567 | // Rendering the rect 3568 | next_line_on_overflow((vec2s){(float)width, (float)height}, 3569 | state.div_props.border_width); 3570 | 3571 | lf_rect_render(state.pos_ptr, (vec2s){(float)width, (float)height}, color, (LfColor){0.0f, 0.0f, 0.0f, 0.0f}, 0, corner_radius); 3572 | 3573 | state.pos_ptr.x += width; 3574 | } 3575 | 3576 | void lf_seperator() { 3577 | lf_next_line(); 3578 | LfUIElementProps props = get_props_for(state.theme.button_props); 3579 | state.pos_ptr.x += props.margin_left; 3580 | state.pos_ptr.y += props.margin_top; 3581 | 3582 | const uint32_t seperator_height = 1; 3583 | lf_set_line_height(props.margin_top + seperator_height + props.margin_bottom); 3584 | 3585 | lf_rect_render(state.pos_ptr, (vec2s){state.current_div.aabb.size.x - props.margin_left * 2.0f, seperator_height}, 3586 | props.color, LF_NO_COLOR, 0, props.corner_radius); 3587 | 3588 | state.pos_ptr.y -= props.margin_top; 3589 | lf_next_line(); 3590 | } 3591 | 3592 | void lf_set_clipboard_text(const char* text) { 3593 | clipboard_set_text(state.clipboard, text); 3594 | } 3595 | char* lf_get_clipboard_text() { 3596 | return clipboard_text(state.clipboard); 3597 | } 3598 | 3599 | void lf_set_no_render(bool no_render) { 3600 | state.renderer_render = !no_render; 3601 | } 3602 | -------------------------------------------------------------------------------- /vendor/glad/include/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | /* 157 | * To support platform where unsigned long cannot be used interchangeably with 158 | * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. 159 | * Ideally, we could just use (u)intptr_t everywhere, but this could result in 160 | * ABI breakage if khronos_uintptr_t is changed from unsigned long to 161 | * unsigned long long or similar (this results in different C++ name mangling). 162 | * To avoid changes for existing platforms, we restrict usage of intptr_t to 163 | * platforms where the size of a pointer is larger than the size of long. 164 | */ 165 | #if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) 166 | #if __SIZEOF_POINTER__ > __SIZEOF_LONG__ 167 | #define KHRONOS_USE_INTPTR_T 168 | #endif 169 | #endif 170 | 171 | #elif defined(__VMS ) || defined(__sgi) 172 | 173 | /* 174 | * Using 175 | */ 176 | #include 177 | typedef int32_t khronos_int32_t; 178 | typedef uint32_t khronos_uint32_t; 179 | typedef int64_t khronos_int64_t; 180 | typedef uint64_t khronos_uint64_t; 181 | #define KHRONOS_SUPPORT_INT64 1 182 | #define KHRONOS_SUPPORT_FLOAT 1 183 | 184 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 185 | 186 | /* 187 | * Win32 188 | */ 189 | typedef __int32 khronos_int32_t; 190 | typedef unsigned __int32 khronos_uint32_t; 191 | typedef __int64 khronos_int64_t; 192 | typedef unsigned __int64 khronos_uint64_t; 193 | #define KHRONOS_SUPPORT_INT64 1 194 | #define KHRONOS_SUPPORT_FLOAT 1 195 | 196 | #elif defined(__sun__) || defined(__digital__) 197 | 198 | /* 199 | * Sun or Digital 200 | */ 201 | typedef int khronos_int32_t; 202 | typedef unsigned int khronos_uint32_t; 203 | #if defined(__arch64__) || defined(_LP64) 204 | typedef long int khronos_int64_t; 205 | typedef unsigned long int khronos_uint64_t; 206 | #else 207 | typedef long long int khronos_int64_t; 208 | typedef unsigned long long int khronos_uint64_t; 209 | #endif /* __arch64__ */ 210 | #define KHRONOS_SUPPORT_INT64 1 211 | #define KHRONOS_SUPPORT_FLOAT 1 212 | 213 | #elif 0 214 | 215 | /* 216 | * Hypothetical platform with no float or int64 support 217 | */ 218 | typedef int khronos_int32_t; 219 | typedef unsigned int khronos_uint32_t; 220 | #define KHRONOS_SUPPORT_INT64 0 221 | #define KHRONOS_SUPPORT_FLOAT 0 222 | 223 | #else 224 | 225 | /* 226 | * Generic fallback 227 | */ 228 | #include 229 | typedef int32_t khronos_int32_t; 230 | typedef uint32_t khronos_uint32_t; 231 | typedef int64_t khronos_int64_t; 232 | typedef uint64_t khronos_uint64_t; 233 | #define KHRONOS_SUPPORT_INT64 1 234 | #define KHRONOS_SUPPORT_FLOAT 1 235 | 236 | #endif 237 | 238 | 239 | /* 240 | * Types that are (so far) the same on all platforms 241 | */ 242 | typedef signed char khronos_int8_t; 243 | typedef unsigned char khronos_uint8_t; 244 | typedef signed short int khronos_int16_t; 245 | typedef unsigned short int khronos_uint16_t; 246 | 247 | /* 248 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 249 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 250 | * to be the only LLP64 architecture in current use. 251 | */ 252 | #ifdef KHRONOS_USE_INTPTR_T 253 | typedef intptr_t khronos_intptr_t; 254 | typedef uintptr_t khronos_uintptr_t; 255 | #elif defined(_WIN64) 256 | typedef signed long long int khronos_intptr_t; 257 | typedef unsigned long long int khronos_uintptr_t; 258 | #else 259 | typedef signed long int khronos_intptr_t; 260 | typedef unsigned long int khronos_uintptr_t; 261 | #endif 262 | 263 | #if defined(_WIN64) 264 | typedef signed long long int khronos_ssize_t; 265 | typedef unsigned long long int khronos_usize_t; 266 | #else 267 | typedef signed long int khronos_ssize_t; 268 | typedef unsigned long int khronos_usize_t; 269 | #endif 270 | 271 | #if KHRONOS_SUPPORT_FLOAT 272 | /* 273 | * Float type 274 | */ 275 | typedef float khronos_float_t; 276 | #endif 277 | 278 | #if KHRONOS_SUPPORT_INT64 279 | /* Time types 280 | * 281 | * These types can be used to represent a time interval in nanoseconds or 282 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 283 | * of nanoseconds since some arbitrary system event (e.g. since the last 284 | * time the system booted). The Unadjusted System Time is an unsigned 285 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 286 | * may be either signed or unsigned. 287 | */ 288 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 289 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 290 | #endif 291 | 292 | /* 293 | * Dummy value used to pad enum types to 32 bits. 294 | */ 295 | #ifndef KHRONOS_MAX_ENUM 296 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 297 | #endif 298 | 299 | /* 300 | * Enumerated boolean type 301 | * 302 | * Values other than zero should be considered to be true. Therefore 303 | * comparisons should not be made against KHRONOS_TRUE. 304 | */ 305 | typedef enum { 306 | KHRONOS_FALSE = 0, 307 | KHRONOS_TRUE = 1, 308 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 309 | } khronos_boolean_enum_t; 310 | 311 | #endif /* __khrplatform_h_ */ 312 | --------------------------------------------------------------------------------