diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-09-28 20:10:07 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2023-01-11 15:23:39 +0100 |
commit | a338adecd93b328816b4a3ff51bfb278377c7cf9 (patch) | |
tree | 6abd7791bae10213d34b7a2928f08f4f77190fc0 | |
parent | 44e987c8521a99519350a42292bcfcd28451dcbd (diff) |
-rw-r--r-- | TODO | 11 | ||||
m--------- | depends/mgl | 0 | ||||
-rw-r--r-- | include/async_image.h | 6 | ||||
-rw-r--r-- | include/mgui/mgui.h | 8 | ||||
-rw-r--r-- | include/mgui/richtext.h | 3 | ||||
-rw-r--r-- | include/mgui/scrollview.h | 6 | ||||
-rw-r--r-- | src/async_image.c | 16 | ||||
-rw-r--r-- | src/mgui/mgui.c | 8 | ||||
-rw-r--r-- | src/mgui/richtext.c | 203 | ||||
-rw-r--r-- | src/mgui/scrollview.c | 159 | ||||
-rw-r--r-- | tests/main.c | 2 |
11 files changed, 334 insertions, 88 deletions
@@ -9,4 +9,13 @@ Cut widget collision check by scissor. Handle scrolling when resizing scroll content and widget items change size (images downscaled, richtext wrapping, etc). Cut event and size calculation for widgets outside scissor. Calculate text (richtext and label) height better when its a single line. In such cases it should be the glyph height and not character size. -Move richtext vertices to global space and only add vertices for the visible richtext.
\ No newline at end of file +Move richtext vertices to global space and only add vertices for the visible richtext. +Create an effect widget. This would allow all children to have an effect applied to it (also support multiple effects at the same time), such as rounded corners. +Have a global effect loader for widgets that the end user can overwrite in their system mglgui theme folder to apply effects. Also have json files for themes. +Allow creating custom widgets, and perhaps allow the end user to create their own custom widget shared libraries to overwrite rendering of widgets. +Add checks in mgl that everything is done in the same thread as mgl_init. Especially in deinit functions. +Reduce number of font atlases by rounding the font size up to power of two and then downscaling the rendering to the font size (either here or in mgl). +Implement a sprite sheet that automatically creates a sprite sheet from images (almost like the font atlas). That reduces memory usage and improves performance (either here or in mgl). +Implement a method to load/unload widgets in a scrollview list to allow easy loading of items when visible on the screen and unload to save memory. +Overlay widget to implement context menu and watch progress in quickmedia. +Do not call event handler for widgets that are not visible, but call a widget specific handler when widget is set to visible/not visible to reset things (such as selecting text).
\ No newline at end of file diff --git a/depends/mgl b/depends/mgl -Subproject 347a6fa2795a6856c1ad3d623673b62f879227c +Subproject c6017539ea7caa1db88040f96b3352f15f7b02b diff --git a/include/async_image.h b/include/async_image.h index a108d5e..39e0bc6 100644 --- a/include/async_image.h +++ b/include/async_image.h @@ -27,11 +27,11 @@ struct mgui_async_image { }; /* Do not call this manually */ -void mgui_async_image_init(); +void mgui_async_image_init(void); /* Do not call this manually */ -void mgui_async_image_deinit(); +void mgui_async_image_deinit(void); /* Do not call this manually */ -void mgui_async_image_unload_unreferenced(); +void mgui_async_image_unload_unreferenced(void); /* Increases reference count by 1 every time this is called. diff --git a/include/mgui/mgui.h b/include/mgui/mgui.h index cb018d5..024a062 100644 --- a/include/mgui/mgui.h +++ b/include/mgui/mgui.h @@ -5,14 +5,14 @@ typedef struct mgui_widget mgui_widget; typedef struct mgl_event mgl_event; typedef struct mgl_window mgl_window; -void mgui_init(); -void mgui_deinit(); +void mgui_init(void); +void mgui_deinit(void); void mgui_on_event(mgui_widget *root_widget, mgl_window *window, mgl_event *event); /* This should only be called once every frame */ void mgui_draw(mgui_widget *root_widget, mgl_window *window); /* Clamped to 1.0 second */ -double mgui_get_seconds_since_last_update(); +double mgui_get_seconds_since_last_update(void); /* Clamped to 1.0 second */ -double mgui_get_frame_time_seconds(); +double mgui_get_frame_time_seconds(void); #endif /* MGUI_H */ diff --git a/include/mgui/richtext.h b/include/mgui/richtext.h index f3b07a5..98b46c6 100644 --- a/include/mgui/richtext.h +++ b/include/mgui/richtext.h @@ -21,7 +21,8 @@ typedef struct { mgl_vec2i position; mgl_vec2i render_size; int width; - mgui_richtext_vertex_data vertex_data[2]; + mgui_richtext_vertex_data vertex_data[2]; /* size should match NUM_VERTEX_DATA */ + /* TODO: Turn bools into flags */ bool dirty; bool vertices_dirty; } mgui_richtext; diff --git a/include/mgui/scrollview.h b/include/mgui/scrollview.h index dded990..76ae6d5 100644 --- a/include/mgui/scrollview.h +++ b/include/mgui/scrollview.h @@ -9,9 +9,13 @@ typedef struct { mgl_vec2i position; mgl_vec2i scroll; mgl_vec2f mouse_scroll; + mgl_vec2f scrollbar_pos; + mgl_vec2f scrollbar_size; + mgl_vec2f scrollbar_move_prev_pos; + bool moving_scrollbar; } mgui_scrollview; -mgui_scrollview* mgui_scrollview_create(); +mgui_scrollview* mgui_scrollview_create(void); void mgui_scrollview_destroy(mgui_scrollview *scrollview); mgui_widget* mgui_scrollview_to_widget(mgui_scrollview *list); mgui_scrollview* mgui_widget_to_scrollview(mgui_widget *widget); diff --git a/src/async_image.c b/src/async_image.c index 4e509dc..6a1a4c0 100644 --- a/src/async_image.c +++ b/src/async_image.c @@ -141,7 +141,7 @@ static void* images_thread_callback(void *arg) { return NULL; } -void mgui_async_image_init() { +void mgui_async_image_init(void) { if(!initialized) { initialized = true; mgui_hashmap_init(&images); @@ -174,7 +174,7 @@ static bool images_free(void *value, void *userdata) { return true; } -void mgui_async_image_deinit() { +void mgui_async_image_deinit(void) { if(initialized) { initialized = false; sem_post(&task_sem); @@ -193,15 +193,9 @@ void mgui_async_image_deinit() { static bool remove_unreferenced_tasks(async_image_task *task, void *userdata) { (void)userdata; - if(task->async_image) - return true; - - if(task->async_image->updated != update_counter) { + if(task->async_image && task->async_image->updated != update_counter) task->async_image = NULL; - return false; - } else { - return true; - } + return true; } static bool images_unload_unreferenced(void *value, void *userdata) { @@ -229,7 +223,7 @@ static bool images_unload_unreferenced(void *value, void *userdata) { } } -void mgui_async_image_unload_unreferenced() { +void mgui_async_image_unload_unreferenced(void) { pthread_mutex_lock(&mutex); async_image_task_fifo_for_each(&tasks, remove_unreferenced_tasks, NULL); pthread_mutex_unlock(&mutex); diff --git a/src/mgui/mgui.c b/src/mgui/mgui.c index 0d9f20d..fdb4cb4 100644 --- a/src/mgui/mgui.c +++ b/src/mgui/mgui.c @@ -8,13 +8,13 @@ static mgl_vec2i root_widget_size; static mgl_clock global_timer; static double frame_time; -void mgui_init() { +void mgui_init(void) { mgl_clock_init(&global_timer); mgui_async_image_init(); frame_time = 0.0; } -void mgui_deinit() { +void mgui_deinit(void) { mgui_async_image_deinit(); } @@ -35,13 +35,13 @@ void mgui_draw(mgui_widget *root_widget, mgl_window *window) { frame_time = 1.0; } -double mgui_get_seconds_since_last_update() { +double mgui_get_seconds_since_last_update(void) { double elapsed_time_sec = mgl_clock_get_elapsed_time_seconds(&global_timer); if(elapsed_time_sec > 1.0) elapsed_time_sec = 1.0; return elapsed_time_sec; } -double mgui_get_frame_time_seconds() { +double mgui_get_frame_time_seconds(void) { return frame_time; } diff --git a/src/mgui/richtext.c b/src/mgui/richtext.c index 77ee3c1..8de067f 100644 --- a/src/mgui/richtext.c +++ b/src/mgui/richtext.c @@ -4,21 +4,46 @@ #include "../../include/alloc.h" #include <mgl/mgl.h> #include <mgl/window/event.h> +#include <mgl/window/window.h> #include <mgl/graphics/vertex.h> #include <mgl/graphics/font.h> #include <mgl/graphics/texture.h> +#include <mgl/graphics/rectangle.h> #include <mgl/system/utf8.h> #include <string.h> #include <assert.h> #define TAB_WIDTH 4 + #define LATIN_INDEX 0 #define CJK_INDEX 1 #define NUM_VERTEX_DATA 2 +typedef enum { + MGUI_RICHTEXT_SIDE_LEFT, + MGUI_RICHTEXT_SIDE_RIGHT +} mgui_richtext_selection_side; + +typedef struct { + size_t start_vertex_data_index; + size_t start_vertex_index; + mgui_richtext_selection_side start_side; + + size_t current_vertex_data_index; + size_t current_vertex_index; + mgui_richtext_selection_side current_side; + + bool selecting_text; + mgui_richtext *selecting_widget; +} mgui_richtext_selection; + +static mgui_richtext_selection selection = {0}; + /* TODO: Scale richtext by scale setting */ /* TODO: Split multiple lines into multiple vertex draw calls and do not render the vertices outside the scissor */ +/* Maybe make the rows global and add the visible text into the rows */ /* TODO: Cleanup vertex when deleting mgui_richtext widget */ +/* TODO: Use global vertex buffers instead of mgl_vertices_draw. The global vertex buffers should only exist for the visible text items */ static int round_float(float value) { return value + 0.5f; @@ -32,6 +57,10 @@ static int min_int(int a, int b) { return a <= b ? a : b; } +static int abs_int(int value) { + return value >= 0 ? value : -value; +} + /* TODO: Is there a more efficient way to do this? maybe japanese characters have a specific bit-pattern? */ static bool is_japanese_codepoint(uint32_t codepoint) { return (codepoint >= 0x2E80 && codepoint <= 0x2FD5) /* Kanji radicals */ @@ -54,13 +83,13 @@ static bool is_korean_codepoint(uint32_t codepoint) { /* TODO: Is there a more efficient way to do this? maybe chinese characters have a specific bit-pattern? */ static bool is_chinese_codepoint(uint32_t codepoint) { - return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) /* CJK Unified Ideographs */ - || (codepoint >= 0x3400 && codepoint <= 0x4DBF) /* CJK Unified Ideographs Extension A */ + return (codepoint >= 0x4E00 && codepoint <= 0x9FFF) /* CJK Unified Ideographs */ + || (codepoint >= 0x3400 && codepoint <= 0x4DBF) /* CJK Unified Ideographs Extension A */ || (codepoint >= 0x20000 && codepoint <= 0x2A6DF) /* CJK Unified Ideographs Extension B */ || (codepoint >= 0x2A700 && codepoint <= 0x2B73F) /* CJK Unified Ideographs Extension C */ || (codepoint >= 0x2B740 && codepoint <= 0x2B81F) /* CJK Unified Ideographs Extension D */ || (codepoint >= 0x2B820 && codepoint <= 0x2CEAF) /* CJK Unified Ideographs Extension E */ - || (codepoint >= 0xF900 && codepoint <= 0xFAFF) /* CJK Compatibility Ideographs */ + || (codepoint >= 0xF900 && codepoint <= 0xFAFF) /* CJK Compatibility Ideographs */ || (codepoint >= 0x2F800 && codepoint <= 0x2FA1F); /* CJK Compatibility Ideographs Supplement */ } @@ -129,6 +158,11 @@ mgui_richtext* mgui_richtext_create(const char *str, size_t size, unsigned char } void mgui_richtext_destroy(mgui_richtext *richtext) { + if(selection.selecting_widget == richtext) { + selection.selecting_text = false; + selection.selecting_widget = NULL; + } + for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) { mgui_free(richtext->vertex_data[i].vertices); } @@ -271,11 +305,159 @@ void mgui_richtext_calculate_size(mgui_richtext *self, mgl_vec2i max_size) { } } +static bool get_first_vertex_index(mgui_richtext *self, size_t *vertex_data_index, size_t *vertex_index) { + for(size_t j = 0; j < NUM_VERTEX_DATA; ++j) { + mgui_richtext_vertex_data *vertex_data = &self->vertex_data[j]; + if(vertex_data->vertex_count > 0) { + *vertex_data_index = j; + *vertex_index = 0; + return true; + } + } + return false; +} + +static bool get_last_vertex_index(mgui_richtext *self, size_t *vertex_data_index, size_t *vertex_index) { + for(size_t j = 0; j < NUM_VERTEX_DATA; ++j) { + mgui_richtext_vertex_data *vertex_data = &self->vertex_data[j]; + if(vertex_data->vertex_count > 0) { + *vertex_data_index = j; + *vertex_index = vertex_data->vertex_count - 6; + return true; + } + } + return false; +} + +/* TODO: For the start record the coordinate. Then for the current take the row closest to the cursor and then the column */ +/* Clamp to text bounding box */ +static bool mgui_richtext_get_closest_vertex_to_point(mgui_richtext *self, mgl_vec2f pos, size_t *vertex_data_index, size_t *vertex_index, mgui_richtext_selection_side *side) { + if(pos.x < self->position.x) { + if(!get_first_vertex_index(self, vertex_data_index, vertex_index)) + return false; + *side = MGUI_RICHTEXT_SIDE_LEFT; + } + + // TODO: BLALBLA SIDES + + if(pos.x > self->position.x + self->render_size.x) { + if(!get_last_vertex_index(self, vertex_data_index, vertex_index)) + return false; + *side = MGUI_RICHTEXT_SIDE_LEFT; + } + + if(pos.x < self->position.x || pos.x > self->position.x + self->render_size.x) + return false; + + if(pos.y < self->position.y || pos.y > self->position.y + self->render_size.y) + return false; + + /* TODO: Binary search, and when moving cursor make binary search relative to the vertex that was closest to the cursor the last time */ + /* TODO: Get closest vertex instead of the vertex which the point is inside (make sure bounds are properly checked, for example the first row might be less than render_size.x in width) */ + for(size_t j = 0; j < NUM_VERTEX_DATA; ++j) { + mgui_richtext_vertex_data *vertex_data = &self->vertex_data[j]; + for(size_t i = 0; i < vertex_data->vertex_count; i += 6) { + mgl_vertex *top_left_vertex = &vertex_data->vertices[i + 1]; + mgl_vertex *bottom_right_vertex = &vertex_data->vertices[i + 4]; + mgl_vec2f vertex_pos = (mgl_vec2f){ self->position.x + top_left_vertex->position.x, self->position.y + top_left_vertex->position.y }; + mgl_vec2f vertex_size = (mgl_vec2f){ bottom_right_vertex->position.x - top_left_vertex->position.x, bottom_right_vertex->position.y - top_left_vertex->position.y }; + if(mgui_rectangle_contains(vertex_pos, vertex_size, pos)) { + *vertex_data_index = j; + *vertex_index = i; + *side = pos.x - vertex_pos.x < vertex_size.x * 0.5f ? MGUI_RICHTEXT_SIDE_LEFT : MGUI_RICHTEXT_SIDE_RIGHT; + return true; + } + } + } + + return false; +} + +// TODO: Text selection in draw + void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event) { - /* TODO: Implement */ - (void)self; (void)window; - (void)event; + + /* TODO: Reset text selection if text content is modified or if the text is modified in any other way (text size changed or anything else that affects text position or size) */ + if(event->type == MGL_EVENT_MOUSE_BUTTON_PRESSED && event->mouse_button.button == MGL_BUTTON_LEFT && !selection.selecting_text) { + mgl_vec2f mouse_pos = (mgl_vec2f){ event->mouse_button.x, event->mouse_button.y }; + if(!mgui_richtext_get_closest_vertex_to_point(self, mouse_pos, &selection.start_vertex_data_index, &selection.start_vertex_index, &selection.start_side)) + { + selection.selecting_text = false; + selection.selecting_widget = NULL; + return; + } + + selection.selecting_text = true; + selection.selecting_widget = self; + + selection.current_vertex_data_index = selection.start_vertex_data_index; + selection.current_vertex_index = selection.start_vertex_index; + selection.current_side = selection.start_side; + } else if(event->type == MGL_EVENT_MOUSE_BUTTON_RELEASED && event->mouse_button.button == MGL_BUTTON_LEFT && selection.selecting_widget == self) { + selection.selecting_text = false; + } else if(event->type == MGL_EVENT_MOUSE_MOVED && selection.selecting_text && selection.selecting_widget == self) { + /* TODO: */ + } else if(event->type == MGL_EVENT_KEY_PRESSED && event->key.code == MGL_KEY_C && event->key.control && selection.selecting_widget == self) { + /* TODO: Get selected vertices text and copy to clipboard */ + /*mgl_window_set_clipboard(window, "hello world", 11);*/ + } +} + +/* Returns -1 if invalid index */ +static int mgui_richtext_get_line_from_vertex(mgui_richtext *self, size_t vertex_data_index, size_t vertex_index) { + if(self->character_size == 0) + return -1; + + if(vertex_data_index > NUM_VERTEX_DATA) + return -1; + + if(vertex_index > self->vertex_data[vertex_data_index].vertex_count) + return -1; + + mgui_richtext_vertex_data *vertex_data = &self->vertex_data[vertex_data_index]; + mgl_vertex *top_left_vertex = &vertex_data->vertices[vertex_index + 1]; + return top_left_vertex->position.y / self->character_size; +} + +static mgl_vec2f selection_get_position(mgui_richtext *self, size_t vertex_data_index, size_t vertex_index, mgui_richtext_selection_side side) { + mgl_vertex *vertex = &self->vertex_data[vertex_data_index].vertices[vertex_index + (side == MGUI_RICHTEXT_SIDE_LEFT ? 1 : 0)]; + return vertex->position; +} + +static void mgui_richtext_draw_selection_background(mgui_richtext *self, mgl_window *window) { + (void)window; + const int start_row = mgui_richtext_get_line_from_vertex(self, selection.start_vertex_data_index, selection.start_vertex_index); + const int current_row = mgui_richtext_get_line_from_vertex(self, selection.current_vertex_data_index, selection.current_vertex_index); + if(start_row == -1 || current_row == -1) + return; + + mgl_rectangle rect = { + .position = { 0.0f, 0.0f }, + .size = { 0.0f, self->character_size }, + .color = { 60, 60, 180, 255 } + }; + + if(start_row == current_row) { + const mgl_vec2f start_pos = selection_get_position(self, selection.start_vertex_data_index, selection.start_vertex_index, selection.start_side); + const mgl_vec2f current_pos = selection_get_position(self, selection.current_vertex_data_index, selection.current_vertex_index, selection.current_side); + + rect.position.x = self->position.x + min_int(start_pos.x, current_pos.x); + rect.position.y = self->position.y + (start_row * self->character_size); + rect.size.x = abs_int(current_pos.x - start_pos.x); + mgl_rectangle_draw(mgl_get_context(), &rect); + return; + } + + const int y1 = min_int(start_row, current_row); + const int y2 = max_int(start_row, current_row); + /* TODO: Optimize rendering */ + for(int i = y1; i < y2; ++i) { + rect.position.x = self->position.x; + rect.position.y = self->position.y + (i * self->character_size); + rect.size.x = self->render_size.x; + mgl_rectangle_draw(mgl_get_context(), &rect); + } } void mgui_richtext_draw(mgui_richtext *self, mgl_window *window) { @@ -291,6 +473,15 @@ void mgui_richtext_draw(mgui_richtext *self, mgl_window *window) { MGUI_FONT_CJK }; + if(selection.selecting_widget == self) { + /* TODO: Only if moved */ + mgl_vec2f mouse_pos = (mgl_vec2f){ window->cursor_position.x, window->cursor_position.y }; + if(selection.selecting_text) { + mgui_richtext_get_closest_vertex_to_point(self, mouse_pos, &selection.current_vertex_data_index, &selection.current_vertex_index, &selection.current_side); + } + mgui_richtext_draw_selection_background(self, window); + } + for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) { if(self->vertex_data[i].vertex_count == 0) continue; diff --git a/src/mgui/scrollview.c b/src/mgui/scrollview.c index a11862f..5cb4773 100644 --- a/src/mgui/scrollview.c +++ b/src/mgui/scrollview.c @@ -1,12 +1,14 @@ #include "../../include/mgui/scrollview.h" #include "../../include/mgui/mgui.h" #include "../../include/alloc.h" +#include "../../include/common.h" #include <mgl/mgl.h> #include <mgl/window/window.h> #include <mgl/window/event.h> #include <mgl/graphics/rectangle.h> #include <limits.h> #include <assert.h> +#include <stdio.h> #define SCROLL_ACCEL 20.0f #define SCROLL_DEACCEL 10.0f @@ -29,13 +31,17 @@ static float abs_float(float value) { return value >= 0.0f ? value : -value; } -mgui_scrollview* mgui_scrollview_create() { +mgui_scrollview* mgui_scrollview_create(void) { mgui_scrollview *scrollview = mgui_alloc(sizeof(mgui_scrollview)); mgui_widget_init(&scrollview->widget, MGUI_WIDGET_SCROLLVIEW); scrollview->child = NULL; scrollview->position = (mgl_vec2i){ 0, 0 }; scrollview->scroll = (mgl_vec2i){ 0, 0 }; scrollview->mouse_scroll = (mgl_vec2f){ 0.0f, 0.0f }; + scrollview->scrollbar_pos = (mgl_vec2f){ 0.0f, 0.0f }; + scrollview->scrollbar_size = (mgl_vec2f){ 0.0f, 0.0f }; + scrollview->scrollbar_move_prev_pos = (mgl_vec2f){ 0.0f, 0.0f }; + scrollview->moving_scrollbar = false; return scrollview; } @@ -84,33 +90,77 @@ void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_eve /* TODO: Allow keyboard navigation if scrollview has focus */ if(event->type == MGL_EVENT_MOUSE_WHEEL_SCROLLED) self->mouse_scroll.y += (event->mouse_wheel_scroll.delta * SCROLL_ACCEL); + + if(event->type == MGL_EVENT_MOUSE_BUTTON_PRESSED + && event->mouse_button.button == MGL_BUTTON_LEFT + && mgui_rectangle_contains(self->scrollbar_pos, self->scrollbar_size, (mgl_vec2f){ event->mouse_button.x, event->mouse_button.y })) + { + self->moving_scrollbar = true; + self->scrollbar_move_prev_pos = (mgl_vec2f){ event->mouse_button.x, event->mouse_button.y }; + } else if(event->type == MGL_EVENT_MOUSE_BUTTON_RELEASED && event->mouse_button.button == MGL_BUTTON_LEFT) { + self->moving_scrollbar = false; + } if(self->child) mgui_widget_on_event(self->child, window, event); } -/* TODO: Check if visible in scissor */ +static void mgui_scrollview_handle_scrollbar_move(mgui_scrollview *self, mgl_window *window, double scrollbar_height_ratio) { + if(mgl_window_is_mouse_button_pressed(window, MGL_BUTTON_LEFT) && self->moving_scrollbar) { + mgl_vec2f mouse_move_diff = (mgl_vec2f){ + window->cursor_position.x - self->scrollbar_move_prev_pos.x, + window->cursor_position.y - self->scrollbar_move_prev_pos.y + }; + self->scrollbar_move_prev_pos.x = window->cursor_position.x; + self->scrollbar_move_prev_pos.y = window->cursor_position.y; -void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { - const double frame_time = mgui_get_frame_time_seconds(); - self->mouse_scroll.x *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); - self->mouse_scroll.y *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); + /* TODO: Handle vertical scrollbar once its added */ + /* TODO: Not perfect, improve */ + if(scrollbar_height_ratio != 0.0) + self->scroll.y -= (mouse_move_diff.y * (1.0/scrollbar_height_ratio)); + } +} - if(abs_float(self->mouse_scroll.x) < 0.0001f) - self->mouse_scroll.x = 0.0f; +static void mgui_scrollview_draw_child(mgui_scrollview *self, mgl_window *window, double margin_width, double margin_height, double height, double scrollbar_height, mgl_vec2i child_size) { + if(!self->child) + return; - if(abs_float(self->mouse_scroll.y) < 0.0001f) - self->mouse_scroll.y = 0.0f; + mgl_scissor prev_scissor; + mgl_window_get_scissor(window, &prev_scissor); - self->scroll.x += (int)self->mouse_scroll.x; - self->scroll.y += (int)self->mouse_scroll.y; + mgl_scissor new_scissor = { + .position = self->position, + .size = (mgl_vec2i){ self->widget.size.x - margin_width, self->widget.size.y - margin_height } + }; + mgl_window_set_scissor(window, &new_scissor); - mgl_vec2i child_size; - if(self->child) - child_size = self->child->size; - else - child_size = (mgl_vec2i){ 0, 0 }; + mgui_widget_set_position(self->child, + (mgl_vec2i){ self->position.x + self->scroll.x + self->child->margin.left, self->position.y + self->scroll.y + self->child->margin.top }); + mgui_widget_draw(self->child, window); + + mgl_window_set_scissor(window, &prev_scissor); + + { + const double scroll_ratio = child_size.y == 0 ? 0.0 : (double)-self->scroll.y / (double)child_size.y; + const int scrollbar_offset_y = height * scroll_ratio; + + const int scrollbar_width = 10; + const int right = self->widget.size.x - self->widget.margin.left - self->widget.margin.right; + + self->scrollbar_pos = (mgl_vec2f){ self->position.x + right - scrollbar_width, self->position.y + scrollbar_offset_y }; + self->scrollbar_size = (mgl_vec2f){ scrollbar_width, scrollbar_height }; + + mgl_rectangle rect = { + .position = self->scrollbar_pos, + .size = self->scrollbar_size, + .color = { 55, 60, 68, 255 } + }; + + mgl_rectangle_draw(mgl_get_context(), &rect); + } +} +static void mgui_scrollview_limit_scroll_to_child_size(mgui_scrollview *self, mgl_vec2i child_size) { if(child_size.x > self->widget.size.x) { self->scroll.x = min_int(self->scroll.x, 0); self->scroll.x = max_int(self->scroll.x, -child_size.x + self->widget.size.x); @@ -124,46 +174,43 @@ void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { } else { self->scroll.y = 0; } +} - if(self->child) { - mgl_scissor prev_scissor; - mgl_window_get_scissor(window, &prev_scissor); +static void mgui_scrollview_update_mouse_scroll(mgui_scrollview *self, double frame_time) { + self->mouse_scroll.x *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); + self->mouse_scroll.y *= max_float(0.0f, (1.0f - frame_time * SCROLL_DEACCEL)); + + if(abs_float(self->mouse_scroll.x) < 0.0001f) + self->mouse_scroll.x = 0.0f; - /* TODO: Fix all this margin crap, it should be invisible */ - const int margin_width = self->widget.margin.top + self->widget.margin.bottom; - const int margin_height = self->widget.margin.top + self->widget.margin.bottom; + if(abs_float(self->mouse_scroll.y) < 0.0001f) + self->mouse_scroll.y = 0.0f; - mgl_scissor new_scissor = { - .position = self->position, - .size = (mgl_vec2i){ self->widget.size.x - margin_width, self->widget.size.y - margin_height } - }; - mgl_window_set_scissor(window, &new_scissor); - - mgui_widget_set_position(self->child, - (mgl_vec2i){ self->position.x + self->scroll.x + self->child->margin.left, self->position.y + self->scroll.y + self->child->margin.top }); - mgui_widget_draw(self->child, window); - - mgl_window_set_scissor(window, &prev_scissor); - - { - /* TODO: Fix all this margin crap, it should be invisible */ - const int margin_height = self->widget.margin.top + self->widget.margin.bottom; - const int height = self->widget.size.y - margin_height; - const double scrollbar_height_ratio = child_size.y == 0 ? 0.0 : (double)height / (double)child_size.y; - const int scrollbar_height = scrollbar_height_ratio * height; - - const double scroll_ratio = child_size.y == 0 ? 0.0 : (double)-self->scroll.y / (double)child_size.y; - const int scrollbar_offset_y = height * scroll_ratio; - - const int scrollbar_width = 5; - const int right = self->widget.size.x - self->widget.margin.left - self->widget.margin.right; - mgl_rectangle rect = { - .position = { self->position.x + right - scrollbar_width, self->position.y + scrollbar_offset_y }, - .size = { scrollbar_width, scrollbar_height }, - .color = { 55, 60, 68, 255 } - }; - - mgl_rectangle_draw(mgl_get_context(), &rect); - } - } + self->scroll.x += (int)self->mouse_scroll.x; + self->scroll.y += (int)self->mouse_scroll.y; +} + +/* TODO: Check if visible in scissor */ +void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { + const double frame_time = mgui_get_frame_time_seconds(); + mgui_scrollview_update_mouse_scroll(self, frame_time); + + mgl_vec2i child_size; + if(self->child) + child_size = self->child->size; + else + child_size = (mgl_vec2i){ 0, 0 }; + + /* TODO: Fix all this margin crap, it should be invisible */ + const int margin_width = self->widget.margin.top + self->widget.margin.bottom; + const int margin_height = self->widget.margin.top + self->widget.margin.bottom; + + /* TODO: Fix all this margin crap, it should be invisible */ + const int height = self->widget.size.y - margin_height; + const double scrollbar_height_ratio = child_size.y == 0 ? 0.0 : (double)height / (double)child_size.y; + const int scrollbar_height = scrollbar_height_ratio * height; + + mgui_scrollview_handle_scrollbar_move(self, window, scrollbar_height_ratio); + mgui_scrollview_limit_scroll_to_child_size(self, child_size); + mgui_scrollview_draw_child(self, window, margin_width, margin_height, height, scrollbar_height, child_size); } diff --git a/tests/main.c b/tests/main.c index 00fcd1f..e1f49d4 100644 --- a/tests/main.c +++ b/tests/main.c @@ -47,7 +47,7 @@ int main() { //mgui_list_set_spacing(list, 30); for(int i = 0; i < 60; ++i) { char text[256]; - snprintf(text, sizeof(text), "hello world 東京 %d", i); + snprintf(text, sizeof(text), "hello world 東京\nNext line %d", i); mgui_list_append(list, mgui_list_to_widget(create_list_item(NULL, "User", text))); } |