From a338adecd93b328816b4a3ff51bfb278377c7cf9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 28 Sep 2022 20:10:07 +0200 Subject: margin, scroll, richtext --- src/mgui/mgui.c | 8 +- src/mgui/richtext.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++-- src/mgui/scrollview.c | 159 +++++++++++++++++++++++++-------------- 3 files changed, 304 insertions(+), 66 deletions(-) (limited to 'src/mgui') 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 #include +#include #include #include #include +#include #include #include #include #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 #include #include #include #include #include +#include #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); } -- cgit v1.2.3