From 0417619b36dc7f4b004caa64a65570f1344d1c8d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 14 Dec 2021 23:48:34 +0100 Subject: Layout, expand, etc --- TODO | 3 +- depends/mgl | 2 +- include/mgui/button.h | 5 +- include/mgui/image.h | 5 +- include/mgui/label.h | 7 +-- include/mgui/list.h | 14 ++--- include/mgui/mgui.h | 2 + include/mgui/richtext.h | 5 +- include/mgui/scrollview.h | 7 +-- include/mgui/widget.h | 42 ++++++++++++--- src/mgui/button.c | 17 +++--- src/mgui/image.c | 61 +++++++++++++++++++--- src/mgui/label.c | 29 ++++------- src/mgui/list.c | 130 ++++++++++++++++++++++++++++++---------------- src/mgui/mgui.c | 9 ++++ src/mgui/richtext.c | 58 +++++++++++++++------ src/mgui/scrollview.c | 81 ++++++++++++++++------------- src/mgui/widget.c | 91 ++++++++++++++++++++++++-------- tests/main.c | 19 ++++--- 19 files changed, 394 insertions(+), 193 deletions(-) diff --git a/TODO b/TODO index 866aa89..a898137 100644 --- a/TODO +++ b/TODO @@ -2,4 +2,5 @@ Widget alignment. Widget horizontal/vertical expand. Homogeneous sizing for list items. Allow setting list direction. This is more efficient than prepending widgets to a list. -Stop rendering scroll view when reaching outside the scissor view. \ No newline at end of file +Stop rendering scroll view when reaching outside the scissor view. +Widgets such as richtext and label should unload their text when updated but not rendered, this allows the widget to save its render size without saving the render data. \ No newline at end of file diff --git a/depends/mgl b/depends/mgl index e52886f..08b27c7 160000 --- a/depends/mgl +++ b/depends/mgl @@ -1 +1 @@ -Subproject commit e52886f8bea55fb3c9ff973b16ed812549cd1f22 +Subproject commit 08b27c7854cf38d3f03b0607f06c0140d6dc7952 diff --git a/include/mgui/button.h b/include/mgui/button.h index 986cb9c..f8e223c 100644 --- a/include/mgui/button.h +++ b/include/mgui/button.h @@ -20,9 +20,8 @@ mgui_widget* mgui_button_to_widget(mgui_button *list); mgui_button* mgui_widget_to_button(mgui_widget *widget); void mgui_button_set_position(mgui_button *self, mgl_vec2i position); -void mgui_button_set_width(mgui_button *self, int width); +void mgui_button_calculate_size(mgui_button *self, mgl_vec2i max_size); void mgui_button_on_event(mgui_button *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_button_draw(mgui_button *self, mgl_window *window); +void mgui_button_draw(mgui_button *self, mgl_window *window); #endif /* MGUI_BUTTON_H */ diff --git a/include/mgui/image.h b/include/mgui/image.h index 8d34a7a..e6787f2 100644 --- a/include/mgui/image.h +++ b/include/mgui/image.h @@ -15,9 +15,8 @@ mgui_widget* mgui_image_to_widget(mgui_image *list); mgui_image* mgui_widget_to_image(mgui_widget *widget); void mgui_image_set_position(mgui_image *self, mgl_vec2i position); -void mgui_image_set_width(mgui_image *self, int width); +void mgui_image_calculate_size(mgui_image *self, mgl_vec2i max_size); void mgui_image_on_event(mgui_image *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_image_draw(mgui_image *self, mgl_window *window); +void mgui_image_draw(mgui_image *self, mgl_window *window); #endif /* MGUI_IMAGE_H */ diff --git a/include/mgui/label.h b/include/mgui/label.h index 4b66ef8..ef1369b 100644 --- a/include/mgui/label.h +++ b/include/mgui/label.h @@ -10,8 +10,6 @@ typedef struct { char *str; size_t str_size; mgl_text text; - mgl_vec2i position; - int width; } mgui_label; mgui_label* mgui_label_create(const char *str, size_t size, unsigned char character_size); @@ -20,9 +18,8 @@ mgui_widget* mgui_label_to_widget(mgui_label *list); mgui_label* mgui_widget_to_label(mgui_widget *widget); void mgui_label_set_position(mgui_label *self, mgl_vec2i position); -void mgui_label_set_width(mgui_label *self, int width); +void mgui_label_calculate_size(mgui_label *self, mgl_vec2i max_size); void mgui_label_on_event(mgui_label *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_label_draw(mgui_label *self, mgl_window *window); +void mgui_label_draw(mgui_label *self, mgl_window *window); #endif /* MGUI_LABEL_H */ diff --git a/include/mgui/list.h b/include/mgui/list.h index af1ceae..7769ab6 100644 --- a/include/mgui/list.h +++ b/include/mgui/list.h @@ -3,6 +3,7 @@ #include "widget.h" #include +#include #include typedef struct mgl_window mgl_window; @@ -13,12 +14,15 @@ typedef enum { MGUI_LIST_VERTICAL } mgui_list_direction; +typedef struct { + mgui_widget *widget; +} mgui_list_item; + typedef struct { mgui_widget widget; mgui_list_direction direction; mgl_vec2i position; - mgl_color background_color; - mgui_widget **items; + mgui_list_item *items; size_t items_capacity; size_t num_items; } mgui_list; @@ -29,11 +33,9 @@ mgui_widget* mgui_list_to_widget(mgui_list *list); mgui_list* mgui_widget_to_list(mgui_widget *widget); void mgui_list_set_position(mgui_list *self, mgl_vec2i position); -void mgui_list_set_width(mgui_list *self, int width); -void mgui_list_set_background_color(mgui_list *self, mgl_color color); +void mgui_list_calculate_size(mgui_list *self, mgl_vec2i max_size); void mgui_list_append(mgui_list *self, mgui_widget *widget); void mgui_list_on_event(mgui_list *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_list_draw(mgui_list *self, mgl_window *window); +void mgui_list_draw(mgui_list *self, mgl_window *window); #endif /* MGUI_LIST_H */ diff --git a/include/mgui/mgui.h b/include/mgui/mgui.h index d76226f..15a2f73 100644 --- a/include/mgui/mgui.h +++ b/include/mgui/mgui.h @@ -2,9 +2,11 @@ #define MGUI_H typedef struct mgui_widget mgui_widget; +typedef struct mgl_event mgl_event; typedef struct mgl_window mgl_window; void mgui_init(); +void mgui_on_event(mgui_widget *root_widget, mgl_window *window, mgl_event *event); void mgui_draw(mgui_widget *root_widget, mgl_window *window); /* Clamped to 1.0 second */ double mgui_get_seconds_since_last_update(); diff --git a/include/mgui/richtext.h b/include/mgui/richtext.h index c0604c9..ff57271 100644 --- a/include/mgui/richtext.h +++ b/include/mgui/richtext.h @@ -31,9 +31,8 @@ mgui_widget* mgui_richtext_to_widget(mgui_richtext *list); mgui_richtext* mgui_widget_to_richtext(mgui_widget *widget); void mgui_richtext_set_position(mgui_richtext *self, mgl_vec2i position); -void mgui_richtext_set_width(mgui_richtext *self, int width); +void mgui_richtext_calculate_size(mgui_richtext *self, mgl_vec2i max_size); void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_richtext_draw(mgui_richtext *self, mgl_window *window); +void mgui_richtext_draw(mgui_richtext *self, mgl_window *window); #endif /* MGUI_RICHTEXT_H */ diff --git a/include/mgui/scrollview.h b/include/mgui/scrollview.h index 1b3d109..dded990 100644 --- a/include/mgui/scrollview.h +++ b/include/mgui/scrollview.h @@ -6,9 +6,7 @@ typedef struct { mgui_widget widget; mgui_widget *child; - mgl_vec2i child_size; mgl_vec2i position; - mgl_vec2i size; mgl_vec2i scroll; mgl_vec2f mouse_scroll; } mgui_scrollview; @@ -20,10 +18,9 @@ mgui_scrollview* mgui_widget_to_scrollview(mgui_widget *widget); void mgui_scrollview_set_child(mgui_scrollview *self, mgui_widget *child); void mgui_scrollview_set_position(mgui_scrollview *self, mgl_vec2i position); +void mgui_scrollview_calculate_size(mgui_scrollview *self, mgl_vec2i max_size); void mgui_scrollview_set_size(mgui_scrollview *self, mgl_vec2i size); -void mgui_scrollview_set_width(mgui_scrollview *self, int width); void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window); +void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window); #endif /* MGUI_SCROLLVIEW_H */ diff --git a/include/mgui/widget.h b/include/mgui/widget.h index 679e4ad..d425c11 100644 --- a/include/mgui/widget.h +++ b/include/mgui/widget.h @@ -2,6 +2,8 @@ #define MGUI_WIDGET_H #include +#include +#include typedef struct mgl_window mgl_window; typedef struct mgl_event mgl_event; @@ -16,27 +18,55 @@ typedef enum { MGUI_WIDGET_IMAGE } mgui_widget_type; +typedef enum { + MGUI_WIDGET_ALIGN_TOP_LEFT, + MGUI_WIDGET_ALIGN_TOP_CENTER, + MGUI_WIDGET_ALIGN_TOP_RIGHT, + + MGUI_WIDGET_ALIGN_CENTER_LEFT, + MGUI_WIDGET_ALIGN_CENTER_CENTER, + MGUI_WIDGET_ALIGN_CENTER_RIGHT, + + MGUI_WIDGET_ALIGN_BOTTOM_LEFT, + MGUI_WIDGET_ALIGN_BOTTOM_CENTER, + MGUI_WIDGET_ALIGN_BOTTOM_RIGHT, +} mgui_alignment; + +typedef enum { + MGUI_WIDGET_VISIBLE = 1 << 0, + MGUI_WIDGET_EXPAND_HORIZONTAL = 1 << 1, + MGUI_WIDGET_EXPAND_VERTICAL = 1 << 2, + MGUI_WIDGET_DEBUG_HAS_PARENT = 1 << 3 +} mgui_widget_flags; + typedef struct { int left; - int top; int right; + int top; int bottom; } mgui_margin; struct mgui_widget { - mgui_widget_type type; + uint8_t type; /* mgui_widget_type */ + uint8_t flags; /* mgui_widget_flags */ + uint8_t alignment; /* mgui_alignment, MGUI_WIDGET_ALIGN_TOP_LEFT by default */ mgui_margin margin; + mgl_vec2i size; }; void mgui_widget_init(mgui_widget *self, mgui_widget_type type); void mgui_widget_destroy(mgui_widget *widget); -void mgui_widget_set_margin(mgui_widget *self, int left, int top, int right, int bottom); +void mgui_widget_set_visible(mgui_widget *self, bool visible); +void mgui_widget_set_expand(mgui_widget *self, bool horizontal, bool vertical); +void mgui_widget_set_alignment(mgui_widget *self, mgui_alignment alignment); +void mgui_widget_set_margin(mgui_widget *self, mgui_margin margin); +void mgui_widget_set_size(mgui_widget *self, mgl_vec2i size); void mgui_widget_set_position(mgui_widget *self, mgl_vec2i position); -void mgui_widget_set_width(mgui_widget *self, int width); +void mgui_widget_calculate_size(mgui_widget *self, mgl_vec2i max_size); +void mgui_widget_set_has_parent(mgui_widget *self); void mgui_widget_on_event(mgui_widget *self, mgl_window *window, mgl_event *event); -/* Returns the size of the widget */ -mgl_vec2i mgui_widget_draw(mgui_widget *self, mgl_window *window); +void mgui_widget_draw(mgui_widget *self, mgl_window *window); #endif /* MGUI_WIDGET_H */ diff --git a/src/mgui/button.c b/src/mgui/button.c index 02070c3..00db361 100644 --- a/src/mgui/button.c +++ b/src/mgui/button.c @@ -7,6 +7,10 @@ #include #include +static int min_int(int a, int b) { + return a <= b ? a : b; +} + mgui_button* mgui_button_create(const char *str, size_t size, unsigned char character_size) { mgui_button *button = mgui_alloc(sizeof(mgui_button)); mgui_widget_init(&button->widget, MGUI_WIDGET_BUTTON); @@ -45,14 +49,12 @@ void mgui_button_set_position(mgui_button *self, mgl_vec2i position) { }); } -static int max_int(int a, int b) { - return a >= b ? a : b; -} - -void mgui_button_set_width(mgui_button *self, int width) { +void mgui_button_calculate_size(mgui_button *self, mgl_vec2i max_size) { const mgl_vec2f text_bounds = mgl_text_get_bounds(&self->text); - self->background.size.x = max_int(text_bounds.x, width); + const mgl_vec2i text_bounds_int = (mgl_vec2i){ text_bounds.x, text_bounds.y }; + self->background.size = (mgl_vec2f){ min_int(text_bounds_int.x, max_size.x), min_int(text_bounds_int.y, max_size.y) }; mgui_button_set_position(self, (mgl_vec2i){ self->background.position.x, self->background.position.y }); + self->widget.size = (mgl_vec2i){ self->background.size.x, self->background.size.y }; } void mgui_button_on_event(mgui_button *self, mgl_window *window, mgl_event *event) { @@ -70,12 +72,11 @@ void mgui_button_on_event(mgui_button *self, mgl_window *window, mgl_event *even } } -mgl_vec2i mgui_button_draw(mgui_button *self, mgl_window *window) { +void mgui_button_draw(mgui_button *self, mgl_window *window) { const mgl_vec2i position = (mgl_vec2i){ self->background.position.x, self->background.position.y }; const mgl_vec2i size = (mgl_vec2i){ self->background.size.x, self->background.size.y }; if(mgui_rectangle_intersects_with_scissor(position, size, window)) { mgl_rectangle_draw(mgl_get_context(), &self->background); mgl_text_draw(mgl_get_context(), &self->text); } - return size; } diff --git a/src/mgui/image.c b/src/mgui/image.c index c2ff12f..1a0871f 100644 --- a/src/mgui/image.c +++ b/src/mgui/image.c @@ -9,6 +9,46 @@ /* TODO: Load image asynchronously and support network files */ +static mgl_vec2i wrap_to_size_x(mgl_vec2i size, int clamp_size) { + mgl_vec2i new_size; + if(size.x == 0) { + new_size.x = 0; + new_size.y = 0; + return new_size; + } + float size_ratio = (float)size.y / (float)size.x; + new_size.x = clamp_size; + new_size.y = new_size.x * size_ratio; + return new_size; +} + +static mgl_vec2i wrap_to_size_y(mgl_vec2i size, int clamp_size) { + mgl_vec2i new_size; + if(size.y == 0) { + new_size.x = 0; + new_size.y = 0; + return new_size; + } + float size_ratio = (float)size.x / (float)size.y; + new_size.y = clamp_size; + new_size.x = new_size.y * size_ratio; + return new_size; +} + +static mgl_vec2i wrap_to_size(const mgl_vec2i size, const mgl_vec2i clamp_size) { + mgl_vec2i new_size = wrap_to_size_x(size, clamp_size.x); + if(new_size.y > clamp_size.y) + new_size = wrap_to_size_y(size, clamp_size.y); + return new_size; +} + +static mgl_vec2i clamp_to_size(mgl_vec2i size, mgl_vec2i clamp_size) { + mgl_vec2i new_size = size; + if(size.x > clamp_size.x || size.y > clamp_size.y) + new_size = wrap_to_size(new_size, clamp_size); + return new_size; +} + mgui_image* mgui_image_create(const char *filepath) { mgui_image *image = mgui_alloc(sizeof(mgui_image)); mgui_widget_init(&image->widget, MGUI_WIDGET_IMAGE); @@ -35,10 +75,18 @@ void mgui_image_set_position(mgui_image *self, mgl_vec2i position) { mgl_sprite_set_position(&self->sprite, (mgl_vec2f){ position.x, position.y }); } -void mgui_image_set_width(mgui_image *self, int width) { - /* TODO: Implement */ - (void)self; - (void)width; +void mgui_image_calculate_size(mgui_image *self, mgl_vec2i max_size) { + if(self->sprite.texture) { + const mgl_vec2i texture_size = (mgl_vec2i){ self->sprite.texture->width, self->sprite.texture->height }; + const mgl_vec2i new_size = clamp_to_size(texture_size, max_size); + + self->sprite.scale.x = (float)new_size.x / (float)texture_size.x; + self->sprite.scale.y = (float)new_size.y / (float)texture_size.y; + + self->widget.size = new_size; + } else { + self->widget.size = (mgl_vec2i){ 0, 0 }; + } } void mgui_image_on_event(mgui_image *self, mgl_window *window, mgl_event *event) { @@ -48,13 +96,10 @@ void mgui_image_on_event(mgui_image *self, mgl_window *window, mgl_event *event) /* TODO: Implement */ } -mgl_vec2i mgui_image_draw(mgui_image *self, mgl_window *window) { +void mgui_image_draw(mgui_image *self, mgl_window *window) { if(self->sprite.texture) { const mgl_vec2i texture_size = (mgl_vec2i){ self->sprite.texture->width, self->sprite.texture->height }; if(mgui_rectangle_intersects_with_scissor((mgl_vec2i){ self->sprite.position.x, self->sprite.position.y }, texture_size, window)) mgl_sprite_draw(mgl_get_context(), &self->sprite); - return texture_size; - } else { - return (mgl_vec2i){ 0, 0 }; } } diff --git a/src/mgui/label.c b/src/mgui/label.c index 767297c..e380aac 100644 --- a/src/mgui/label.c +++ b/src/mgui/label.c @@ -9,6 +9,10 @@ /* TODO: Scale label by scale setting */ +static int min_int(int a, int b) { + return a <= b ? a : b; +} + mgui_label* mgui_label_create(const char *str, size_t size, unsigned char character_size) { mgui_label *label = mgui_alloc(sizeof(mgui_label)); mgui_widget_init(&label->widget, MGUI_WIDGET_LABEL); @@ -16,8 +20,6 @@ mgui_label* mgui_label_create(const char *str, size_t size, unsigned char charac label->str_size = size; memcpy(label->str, str, size); mgl_text_init(&label->text, mgui_get_font(MGUI_FONT_LATIN, character_size), label->str, label->str_size); - label->position = (mgl_vec2i){ 0, 0 }; - label->width = mgl_text_get_bounds(&label->text).x; return label; } @@ -37,22 +39,14 @@ mgui_label* mgui_widget_to_label(mgui_widget *widget) { } void mgui_label_set_position(mgui_label *self, mgl_vec2i position) { - const mgl_vec2f text_bounds = mgl_text_get_bounds(&self->text); - self->position = position; - mgl_text_set_position(&self->text, (mgl_vec2f){ - (int)(position.x + self->width * 0.5f - text_bounds.x * 0.5f), - position.y - }); + mgl_text_set_position(&self->text, (mgl_vec2f){ position.x, position.y }); } -static int max_int(int a, int b) { - return a >= b ? a : b; -} - -void mgui_label_set_width(mgui_label *self, int width) { +void mgui_label_calculate_size(mgui_label *self, mgl_vec2i max_size) { const mgl_vec2f text_bounds = mgl_text_get_bounds(&self->text); - self->width = max_int(text_bounds.x, width); - mgui_label_set_position(self, self->position); + const mgl_vec2i text_bounds_int = (mgl_vec2i){ text_bounds.x, text_bounds.y }; + self->widget.size.x = min_int(text_bounds_int.x, max_size.x); + self->widget.size.y = min_int(text_bounds_int.y, max_size.y); } void mgui_label_on_event(mgui_label *self, mgl_window *window, mgl_event *event) { @@ -62,10 +56,9 @@ void mgui_label_on_event(mgui_label *self, mgl_window *window, mgl_event *event) (void)event; } -mgl_vec2i mgui_label_draw(mgui_label *self, mgl_window *window) { +void mgui_label_draw(mgui_label *self, mgl_window *window) { const mgl_vec2f text_bounds = mgl_text_get_bounds(&self->text); const mgl_vec2i text_bounds_int = (mgl_vec2i){ text_bounds.x, text_bounds.y }; - if(mgui_rectangle_intersects_with_scissor(self->position, text_bounds_int, window)) + if(mgui_rectangle_intersects_with_scissor((mgl_vec2i){ self->text.position.x, self->text.position.y }, text_bounds_int, window)) mgl_text_draw(mgl_get_context(), &self->text); - return text_bounds_int; } diff --git a/src/mgui/list.c b/src/mgui/list.c index 58fc964..687906d 100644 --- a/src/mgui/list.c +++ b/src/mgui/list.c @@ -8,12 +8,15 @@ if num_items <= 2. */ +static int max_int(int a, int b) { + return a >= b ? a : b; +} + mgui_list* mgui_list_create(mgui_list_direction direction) { mgui_list *list = mgui_alloc(sizeof(mgui_list)); mgui_widget_init(&list->widget, MGUI_WIDGET_LIST); list->direction = direction; list->position = (mgl_vec2i){ 0, 0 }; - list->background_color = (mgl_color){ 0, 0, 0, 0 }; list->items = NULL; list->items_capacity = 0; list->num_items = 0; @@ -22,7 +25,7 @@ mgui_list* mgui_list_create(mgui_list_direction direction) { void mgui_list_destroy(mgui_list *list) { for(size_t i = 0; i < list->num_items; ++i) { - mgui_widget_destroy(list->items[i]); + mgui_widget_destroy(list->items[i].widget); } mgui_free(list->items); mgui_free(list); @@ -41,19 +44,71 @@ void mgui_list_set_position(mgui_list *self, mgl_vec2i position) { self->position = position; } -void mgui_list_set_width(mgui_list *self, int width) { - /* TODO: if direction is horizontal then put the widget in the center of its area */ - /* TODO: put the widget in the center for vertical as well, for items with a max size */ - /* TODO: support max size for widgets */ - if(self->direction == MGUI_LIST_VERTICAL) { - for(size_t i = 0; i < self->num_items; ++i) { - mgui_widget_set_width(self->items[i], width); +void mgui_list_calculate_size(mgui_list *self, mgl_vec2i max_size) { + mgl_vec2i size = (mgl_vec2i){ 0, 0 }; + switch(self->direction) { + case MGUI_LIST_HORIZONITAL: { + int num_expanded_widgets = 0; + for(size_t i = 0; i < self->num_items; ++i) { + mgui_widget *widget = self->items[i].widget; + if(widget->flags & MGUI_WIDGET_EXPAND_HORIZONTAL) { + ++num_expanded_widgets; + } else { + mgui_widget_calculate_size(widget, max_size); + size.x += widget->size.x; + size.y = max_int(size.y, widget->size.y); + max_size.x -= widget->size.x; + } + } + + if(num_expanded_widgets == 0) + break; + + max_size.x = max_size.x / num_expanded_widgets; + + for(size_t i = 0; i < self->num_items; ++i) { + mgui_widget *widget = self->items[i].widget; + if(widget->flags & MGUI_WIDGET_EXPAND_HORIZONTAL) { + mgui_widget_calculate_size(widget, max_size); + widget->size = max_size; + size.x += widget->size.x; + size.y = max_int(size.y, widget->size.y); + } + } + break; } - } -} + case MGUI_LIST_VERTICAL: { + int num_expanded_widgets = 0; + for(size_t i = 0; i < self->num_items; ++i) { + mgui_widget *widget = self->items[i].widget; + if(widget->flags & MGUI_WIDGET_EXPAND_VERTICAL) { + ++num_expanded_widgets; + } else { + mgui_widget_calculate_size(widget, max_size); + size.x = max_int(size.x, widget->size.x); + size.y += widget->size.y; + max_size.y -= widget->size.y; + } + } + + if(num_expanded_widgets == 0) + break; -void mgui_list_set_background_color(mgui_list *self, mgl_color color) { - self->background_color = color; + max_size.y = max_size.y / num_expanded_widgets; + + for(size_t i = 0; i < self->num_items; ++i) { + mgui_widget *widget = self->items[i].widget; + if(widget->flags & MGUI_WIDGET_EXPAND_VERTICAL) { + mgui_widget_calculate_size(widget, max_size); + widget->size = max_size; + size.x = max_int(size.x, widget->size.x); + size.y += widget->size.y; + } + } + break; + } + } + self->widget.size = size; } static void mgui_list_ensure_capacity(mgui_list *self, size_t new_capacity) { @@ -73,63 +128,46 @@ static void mgui_list_ensure_capacity(mgui_list *self, size_t new_capacity) { } void mgui_list_append(mgui_list *self, mgui_widget *widget) { - mgui_list_ensure_capacity(self, (self->num_items + 1) * sizeof(mgui_widget*)); - self->items[self->num_items] = widget; + mgui_widget_set_has_parent(widget); + mgui_list_ensure_capacity(self, (self->num_items + 1) * sizeof(mgui_list_item)); + self->items[self->num_items].widget = widget; ++self->num_items; } void mgui_list_on_event(mgui_list *self, mgl_window *window, mgl_event *event) { for(size_t i = 0; i < self->num_items; ++i) { - mgui_widget_on_event(self->items[i], window, event); + mgui_widget_on_event(self->items[i].widget, window, event); } } -static int max_int(int a, int b) { - return a >= b ? a : b; -} - /* TODO: Check if visible in scissor */ -mgl_vec2i mgui_list_draw(mgui_list *self, mgl_window *window) { +void mgui_list_draw(mgui_list *self, mgl_window *window) { mgl_vec2i position = self->position; - /* TODO: */ - mgl_vec2i size = (mgl_vec2i){ 0, 0 }; - - /* TODO: - if(self->background_color.a > 0) { - mgl_rectangle rect = { - .position = { self->position.x, self->position.y }, - .size = { self->size.x, self->size.y }, - .color = self->background_color - }; - mgl_rectangle_draw(mgl_get_context(), &rect); - } - */ /* TODO: Set scissor for each draw widget */ + /* TODO: Only do this when a direct child widget is dirty */ + //mgui_list_calculate_size(self); + switch(self->direction) { case MGUI_LIST_HORIZONITAL: { for(size_t i = 0; i < self->num_items; ++i) { - mgui_widget_set_position(self->items[i], position); - const mgl_vec2i widget_size = mgui_widget_draw(self->items[i], window); - size.x += widget_size.x; - size.y = max_int(size.y, widget_size.y); - position.x += widget_size.x; + mgui_widget *widget = self->items[i].widget; + mgui_widget_set_position(widget, position); + mgui_widget_draw(widget, window); + position.x += widget->size.x; } break; } case MGUI_LIST_VERTICAL: { for(size_t i = 0; i < self->num_items; ++i) { - mgui_widget_set_position(self->items[i], position); - const mgl_vec2i widget_size = mgui_widget_draw(self->items[i], window); - size.x = max_int(size.x, widget_size.x); - size.y += widget_size.y; - position.y += widget_size.y; + mgui_widget *widget = self->items[i].widget; + mgui_widget_set_position(widget, position); + mgui_widget_draw(widget, window); + position.y += widget->size.y; } break; } } - - return size; } diff --git a/src/mgui/mgui.c b/src/mgui/mgui.c index e2fa0be..3c1d955 100644 --- a/src/mgui/mgui.c +++ b/src/mgui/mgui.c @@ -1,5 +1,6 @@ #include "../../include/mgui/mgui.h" #include "../../include/mgui/widget.h" +#include #include static mgl_clock global_timer; @@ -8,7 +9,15 @@ void mgui_init() { mgl_clock_init(&global_timer); } +void mgui_on_event(mgui_widget *root_widget, mgl_window *window, mgl_event *event) { + if(event->type == MGL_EVENT_RESIZED) + mgui_widget_set_size(root_widget, (mgl_vec2i){ event->size.width, event->size.height }); + mgui_widget_on_event(root_widget, window, event); +} + void mgui_draw(mgui_widget *root_widget, mgl_window *window) { + /* TODO: Only do this if widget is dirty */ + mgui_widget_calculate_size(root_widget, root_widget->size); mgui_widget_draw(root_widget, window); } diff --git a/src/mgui/richtext.c b/src/mgui/richtext.c index c70967f..9b1acfc 100644 --- a/src/mgui/richtext.c +++ b/src/mgui/richtext.c @@ -28,6 +28,10 @@ static int max_int(int a, int b) { return a >= b ? a : b; } +static int min_int(int a, int b) { + return a <= b ? a : b; +} + /* 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 */ @@ -93,6 +97,13 @@ static void mgui_richtext_vertices_clear(mgui_richtext *self, size_t vertex_inde self->vertex_data[vertex_index].vertex_count = 0; } +static void mgui_richtext_vertices_free(mgui_richtext *self, size_t vertex_index) { + mgui_free(self->vertex_data[vertex_index].vertices); + self->vertex_data[vertex_index].vertices = NULL; + self->vertex_data[vertex_index].vertices_capacity = 0; + self->vertex_data[vertex_index].vertex_count = 0; +} + mgui_richtext* mgui_richtext_create(const char *str, size_t size, unsigned char character_size) { mgui_richtext *richtext = mgui_alloc(sizeof(mgui_richtext)); mgui_widget_init(&richtext->widget, MGUI_WIDGET_RICHTEXT); @@ -133,17 +144,6 @@ void mgui_richtext_set_position(mgui_richtext *self, mgl_vec2i position) { self->position = position; } -void mgui_richtext_set_width(mgui_richtext *self, int width) { - self->width = width; -} - -void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event) { - /* TODO: Implement */ - (void)self; - (void)window; - (void)event; -} - static void mgui_richtext_append_glyph(mgui_richtext *self, size_t vertex_index, mgl_vec2f position, mgl_color color, mgl_font_glyph *glyph) { const mgl_vertex top_left_vertex = { .position = (mgl_vec2f){ round_float(position.x + glyph->position.x), round_float(position.y + glyph->position.y) }, @@ -229,8 +229,8 @@ static void mgui_richtext_update(mgui_richtext *self) { } else { if(mgl_font_get_glyph(font, codepoint, &glyph) == 0) { if(position.x + glyph.size.x > self->width) { - //position.x = 0; - //position.y += self->character_size; + position.x = 0; + position.y += self->character_size; } mgui_richtext_append_glyph(self, vertex_index, position, color, &glyph); @@ -245,14 +245,37 @@ static void mgui_richtext_update(mgui_richtext *self) { } } -mgl_vec2i mgui_richtext_draw(mgui_richtext *self, mgl_window *window) { +void mgui_richtext_calculate_size(mgui_richtext *self, mgl_vec2i max_size) { /* TODO: Do not update if not visible on screen? */ + if(max_size.x != self->width) { + self->width = max_size.x; + self->dirty = true; + } + + /* TODO: Instead of updating richtext vertices, calculcate the richtext bounds only and update the vertices in the draw function if dirty */ if(self->dirty) { self->dirty = false; mgui_richtext_update(self); + self->widget.size.x = self->render_size.x; + self->widget.size.y = min_int(self->render_size.y, max_size.y); } +} + +void mgui_richtext_on_event(mgui_richtext *self, mgl_window *window, mgl_event *event) { + /* TODO: Implement */ + (void)self; + (void)window; + (void)event; +} +void mgui_richtext_draw(mgui_richtext *self, mgl_window *window) { if(mgui_rectangle_intersects_with_scissor(self->position, self->render_size, window)) { + /* This can happen when the item is first not visible in its scissor and then becomes visible */ + if(self->dirty) { + self->dirty = false; + mgui_richtext_update(self); + } + const mgui_font_type font_types[NUM_VERTEX_DATA] = { MGUI_FONT_LATIN, MGUI_FONT_CJK @@ -268,7 +291,10 @@ mgl_vec2i mgui_richtext_draw(mgui_richtext *self, mgl_window *window) { } mgl_texture_use(NULL); + } else { + for(size_t i = 0; i < NUM_VERTEX_DATA; ++i) { + mgui_richtext_vertices_free(self, i); + } + self->dirty = true; } - - return self->render_size; } diff --git a/src/mgui/scrollview.c b/src/mgui/scrollview.c index 3c5850c..c7a4075 100644 --- a/src/mgui/scrollview.c +++ b/src/mgui/scrollview.c @@ -4,18 +4,35 @@ #include #include #include +#include #include #define SCROLL_ACCEL 20.0f #define SCROLL_DEACCEL 10.0f +#define WIDGET_NATURAL_SIZE INT_MAX + +static float max_float(float a, float b) { + return a >= b ? a : b; +} + +static int max_int(int a, int b) { + return a >= b ? a : b; +} + +static int min_int(int a, int b) { + return a <= b ? a : b; +} + +static float abs_float(float value) { + return value >= 0.0f ? value : -value; +} + mgui_scrollview* mgui_scrollview_create() { mgui_scrollview *scrollview = mgui_alloc(sizeof(mgui_scrollview)); mgui_widget_init(&scrollview->widget, MGUI_WIDGET_SCROLLVIEW); scrollview->child = NULL; - scrollview->child_size = (mgl_vec2i){ 0, 0 }; scrollview->position = (mgl_vec2i){ 0, 0 }; - scrollview->size = (mgl_vec2i){ 0, 0 }; scrollview->scroll = (mgl_vec2i){ 0, 0 }; scrollview->mouse_scroll = (mgl_vec2f){ 0.0f, 0.0f }; return scrollview; @@ -39,22 +56,26 @@ mgui_scrollview* mgui_widget_to_scrollview(mgui_widget *widget) { void mgui_scrollview_set_child(mgui_scrollview *self, mgui_widget *child) { assert(child != mgui_scrollview_to_widget(self)); self->child = child; - mgui_scrollview_set_width(self, self->size.x); + if(self->child) { + mgui_widget_set_has_parent(self->child); + mgui_widget_calculate_size(self->child, self->widget.size); + } } void mgui_scrollview_set_position(mgui_scrollview *self, mgl_vec2i position) { self->position = position; } -void mgui_scrollview_set_size(mgui_scrollview *self, mgl_vec2i size) { - self->size = size; - mgui_scrollview_set_width(self, self->size.x); -} +void mgui_scrollview_calculate_size(mgui_scrollview *self, mgl_vec2i max_size) { + self->widget.size = max_size; + if(self->widget.size.x == WIDGET_NATURAL_SIZE) + self->widget.size.x = 500; + + if(self->widget.size.y == WIDGET_NATURAL_SIZE) + self->widget.size.y = 600; -void mgui_scrollview_set_width(mgui_scrollview *self, int width) { - /* TODO: Call for child so text can wordwrap? but only if wordwrap is enabled in this scrollview */ if(self->child) - mgui_widget_set_width(self->child, width); + mgui_widget_calculate_size(self->child, (mgl_vec2i){ WIDGET_NATURAL_SIZE, WIDGET_NATURAL_SIZE }); } void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_event *event) { @@ -66,25 +87,9 @@ void mgui_scrollview_on_event(mgui_scrollview *self, mgl_window *window, mgl_eve mgui_widget_on_event(self->child, window, event); } -static float max_float(float a, float b) { - return a >= b ? a : b; -} - -static int max_int(int a, int b) { - return a >= b ? a : b; -} - -static int min_int(int a, int b) { - return a <= b ? a : b; -} - -static float abs_float(float value) { - return value >= 0.0f ? value : -value; -} - /* TODO: Check if visible in scissor */ -mgl_vec2i mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { +void mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { const double frame_time = mgui_get_seconds_since_last_update(); 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)); @@ -98,16 +103,22 @@ mgl_vec2i mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { self->scroll.x += (int)self->mouse_scroll.x; self->scroll.y += (int)self->mouse_scroll.y; - if(self->child_size.x > self->size.x) { + mgl_vec2i child_size; + if(self->child) + child_size = self->child->size; + else + child_size = (mgl_vec2i){ 0, 0 }; + + 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, -self->child_size.x + self->size.x); + self->scroll.x = max_int(self->scroll.x, -child_size.x + self->widget.size.x); } else { self->scroll.x = 0; } - if(self->child_size.y > self->size.y) { + if(child_size.y > self->widget.size.y) { self->scroll.y = min_int(self->scroll.y, 0); - self->scroll.y = max_int(self->scroll.y, -self->child_size.y + self->size.y); + self->scroll.y = max_int(self->scroll.y, -child_size.y + self->widget.size.y); } else { self->scroll.y = 0; } @@ -118,17 +129,13 @@ mgl_vec2i mgui_scrollview_draw(mgui_scrollview *self, mgl_window *window) { mgl_scissor new_scissor = { .position = self->position, - .size = self->size + .size = self->widget.size }; mgl_window_set_scissor(window, &new_scissor); mgui_widget_set_position(self->child, (mgl_vec2i){ self->position.x + self->scroll.x, self->position.y + self->scroll.y }); - self->child_size = mgui_widget_draw(self->child, window); + mgui_widget_draw(self->child, window); mgl_window_set_scissor(window, &prev_scissor); - } else { - self->child_size = (mgl_vec2i){ 0, 0 }; } - - return self->child_size; } diff --git a/src/mgui/widget.c b/src/mgui/widget.c index b9865ca..ed50154 100644 --- a/src/mgui/widget.c +++ b/src/mgui/widget.c @@ -6,12 +6,34 @@ #include "../../include/mgui/richtext.h" #include "../../include/mgui/image.h" #include "../../include/alloc.h" +#include +#include /* TODO: Use margin */ +/* TODO: Use alignment */ +/* TODO: Use visible flag */ + +static void mgui_widget_set_flag(mgui_widget *self, uint8_t flag) { + self->flags |= flag; +} + +static void mgui_widget_unset_flag(mgui_widget *self, uint8_t flag) { + self->flags &= ~flag; +} + +static void mgui_widget_set_flag_state(mgui_widget *self, uint8_t flag, bool set) { + if(set) + mgui_widget_set_flag(self, flag); + else + mgui_widget_unset_flag(self, flag); +} void mgui_widget_init(mgui_widget *self, mgui_widget_type type) { self->type = type; - mgui_widget_set_margin(self, 0, 0, 0, 0); + self->flags = MGUI_WIDGET_VISIBLE; + self->alignment = MGUI_WIDGET_ALIGN_TOP_LEFT; + mgui_widget_set_margin(self, (mgui_margin){ 0, 0, 0, 0 }); + self->size = (mgl_vec2i){ 0, 0 }; } void mgui_widget_destroy(mgui_widget *widget) { @@ -37,11 +59,25 @@ void mgui_widget_destroy(mgui_widget *widget) { } } -void mgui_widget_set_margin(mgui_widget *self, int left, int top, int right, int bottom) { - self->margin.left = left; - self->margin.top = top; - self->margin.right = right; - self->margin.bottom = bottom; +void mgui_widget_set_visible(mgui_widget *self, bool visible) { + mgui_widget_set_flag_state(self, MGUI_WIDGET_VISIBLE, visible); +} + +void mgui_widget_set_expand(mgui_widget *self, bool horizontal, bool vertical) { + mgui_widget_set_flag_state(self, MGUI_WIDGET_EXPAND_HORIZONTAL, horizontal); + mgui_widget_set_flag_state(self, MGUI_WIDGET_EXPAND_VERTICAL, vertical); +} + +void mgui_widget_set_alignment(mgui_widget *self, mgui_alignment alignment) { + self->alignment = alignment; +} + +void mgui_widget_set_margin(mgui_widget *self, mgui_margin margin) { + self->margin = margin; +} + +void mgui_widget_set_size(mgui_widget *self, mgl_vec2i size) { + self->size = size; } void mgui_widget_set_position(mgui_widget *self, mgl_vec2i position) { @@ -67,29 +103,37 @@ void mgui_widget_set_position(mgui_widget *self, mgl_vec2i position) { } } -void mgui_widget_set_width(mgui_widget *self, int width) { +void mgui_widget_calculate_size(mgui_widget *self, mgl_vec2i max_size) { switch(self->type) { case MGUI_WIDGET_LIST: - mgui_list_set_width(mgui_widget_to_list(self), width); + mgui_list_calculate_size(mgui_widget_to_list(self), max_size); break; case MGUI_WIDGET_SCROLLVIEW: - mgui_scrollview_set_width(mgui_widget_to_scrollview(self), width); + mgui_scrollview_calculate_size(mgui_widget_to_scrollview(self), max_size); break; case MGUI_WIDGET_BUTTON: - mgui_button_set_width(mgui_widget_to_button(self), width); + mgui_button_calculate_size(mgui_widget_to_button(self), max_size); break; case MGUI_WIDGET_LABEL: - mgui_label_set_width(mgui_widget_to_label(self), width); + mgui_label_calculate_size(mgui_widget_to_label(self), max_size); break; case MGUI_WIDGET_RICHTEXT: - mgui_richtext_set_width(mgui_widget_to_richtext(self), width); + mgui_richtext_calculate_size(mgui_widget_to_richtext(self), max_size); break; case MGUI_WIDGET_IMAGE: - mgui_image_set_width(mgui_widget_to_image(self), width); + mgui_image_calculate_size(mgui_widget_to_image(self), max_size); break; } } +void mgui_widget_set_has_parent(mgui_widget *self) { + if(self->flags & MGUI_WIDGET_DEBUG_HAS_PARENT) { + fprintf(stderr, "mglgui error: widget %p was added to a list/scrollview twice. One widget can only belong to one list/scrollview.\n", (void*)self); + abort(); + } + self->flags |= MGUI_WIDGET_DEBUG_HAS_PARENT; +} + void mgui_widget_on_event(mgui_widget *self, mgl_window *window, mgl_event *event) { switch(self->type) { case MGUI_WIDGET_LIST: @@ -113,20 +157,25 @@ void mgui_widget_on_event(mgui_widget *self, mgl_window *window, mgl_event *even } } -mgl_vec2i mgui_widget_draw(mgui_widget *self, mgl_window *window) { +void mgui_widget_draw(mgui_widget *self, mgl_window *window) { switch(self->type) { case MGUI_WIDGET_LIST: - return mgui_list_draw(mgui_widget_to_list(self), window); + mgui_list_draw(mgui_widget_to_list(self), window); + break; case MGUI_WIDGET_SCROLLVIEW: - return mgui_scrollview_draw(mgui_widget_to_scrollview(self), window); + mgui_scrollview_draw(mgui_widget_to_scrollview(self), window); + break; case MGUI_WIDGET_BUTTON: - return mgui_button_draw(mgui_widget_to_button(self), window); + mgui_button_draw(mgui_widget_to_button(self), window); + break; case MGUI_WIDGET_LABEL: - return mgui_label_draw(mgui_widget_to_label(self), window); + mgui_label_draw(mgui_widget_to_label(self), window); + break; case MGUI_WIDGET_RICHTEXT: - return mgui_richtext_draw(mgui_widget_to_richtext(self), window); + mgui_richtext_draw(mgui_widget_to_richtext(self), window); + break; case MGUI_WIDGET_IMAGE: - return mgui_image_draw(mgui_widget_to_image(self), window); + mgui_image_draw(mgui_widget_to_image(self), window); + break; } - return (mgl_vec2i){ 0, 0 }; } diff --git a/tests/main.c b/tests/main.c index e02d4bb..6261bcd 100644 --- a/tests/main.c +++ b/tests/main.c @@ -12,7 +12,6 @@ static mgui_list* create_list_item(const char *title, const char *description) { mgui_list *container = mgui_list_create(MGUI_LIST_HORIZONITAL); - mgui_list_set_background_color(container, (mgl_color){ 255, 0, 0, 255 }); mgui_list_append(container, mgui_button_to_widget(mgui_button_create("button", 6, 30))); mgui_list *list = mgui_list_create(MGUI_LIST_VERTICAL); @@ -32,7 +31,14 @@ int main() { if(mgl_window_create(&window, "mgl", &(mgl_window_create_params){ .size = { 1280, 720 } }) != 0) return 1; + mgui_list *root = mgui_list_create(MGUI_LIST_VERTICAL); + mgui_widget *root_widget = mgui_list_to_widget(root); + mgui_list_append(root, mgui_list_to_widget(create_list_item("Title", "Description"))); + mgui_scrollview *scrollview = mgui_scrollview_create(); + mgui_widget_set_expand(mgui_scrollview_to_widget(scrollview), false, true); + mgui_list_append(root, mgui_scrollview_to_widget(scrollview)); + mgui_list *list = mgui_list_create(MGUI_LIST_VERTICAL); mgui_scrollview_set_child(scrollview, mgui_list_to_widget(list)); for(int i = 0; i < 30; ++i) { @@ -41,20 +47,21 @@ int main() { mgui_list_append(list, mgui_list_to_widget(create_list_item("User", text))); } + mgui_list_append(root, mgui_list_to_widget(create_list_item("Bottom", "bottom"))); + mgl_event event; while(mgl_window_is_open(&window)) { while(mgl_window_poll_event(&window, &event)) { - if(event.type == MGL_EVENT_RESIZED) - mgui_scrollview_set_size(scrollview, (mgl_vec2i){ event.size.width, event.size.height }); - mgui_scrollview_on_event(scrollview, &window, &event); + mgui_on_event(root_widget, &window, &event); } mgl_window_clear(&window, (mgl_color){ 0, 0, 0, 0 }); - mgui_draw(mgui_scrollview_to_widget(scrollview), &window); + mgui_draw(root_widget, &window); mgl_window_display(&window); } - mgui_widget_destroy(mgui_scrollview_to_widget(scrollview)); + mgui_widget_destroy(root_widget); + mgl_window_deinit(&window); mgl_deinit(); return 0; } -- cgit v1.2.3