diff --git a/Readme.md b/Readme.md index 5b5224b..73f7d68 100644 --- a/Readme.md +++ b/Readme.md @@ -18,48 +18,12 @@ possible with fast streamlined development speed in mind. - Configurable - UTF-8 support -## Functionality -+ Label -+ Buttons -+ Slider -+ Progressbar -+ Checkbox -+ Radiobutton -+ Input -+ Shell -+ Spinner -+ Selector -+ Linegraph -+ Histogram -+ Table -+ Panel -+ Tab -+ Group -+ Shelf - ## Limitations - Does NOT provide os window/input management - Does NOT provide a renderer backend - Does NOT implement a font library Summary: It is only responsible for the actual user interface -## IMGUIs -Immediate mode in contrast to classical retained mode GUIs store as little state as possible -by using procedural function calls as "widgets" instead of storing objects. -Each "widget" function call takes hereby all its necessary data and immediately returns -the through the user modified state back to the caller. Immediate mode graphical -user interfaces therefore combine drawing and input handling into one unit -instead of separating them like retain mode GUIs. - -Since there is no to minimal internal state in immediate mode user interfaces, -updates have to occur every frame which on one hand is more drawing expensive than classic -retained GUI implementations but on the other hand grants a lot more flexibility and -support for overall layout changes. In addition without any state there is no -duplicated state between your program, the gui and the user which greatly -simplifies code. Further traits of immediate mode graphic user interfaces are a -code driven style, centralized flow control, easy extensibility and -understandability. - ## Example ```c struct gui_input input = {0}; @@ -111,14 +75,22 @@ while (1) { ``` ![gui screenshot](/screen.png?raw=true) -## API -The API for this gui toolkit is divided into two different layers. There -is the widget layer and the panel layer. The widget layer provides a number of -classical widgets in functional immediate mode form without any kind of internal -state. Each widget can be placed anywhere on the screen but there is no direct -way provided to group widgets together. For this to change there is the panel -layer which is build on top of the widget layer and uses most of the widget API -internally to form groups of widgets into a layout. +## IMGUIs +Immediate mode in contrast to classical retained mode GUIs store as little state as possible +by using procedural function calls as "widgets" instead of storing objects. +Each "widget" function call takes hereby all its necessary data and immediately returns +the through the user modified state back to the caller. Immediate mode graphical +user interfaces therefore combine drawing and input handling into one unit +instead of separating them like retain mode GUIs. + +Since there is no to minimal internal state in immediate mode user interfaces, +updates have to occur every frame which on one hand is more drawing expensive than classic +retained GUI implementations but on the other hand grants a lot more flexibility and +support for overall layout changes. In addition without any state there is no +duplicated state between your program, the gui and the user which greatly +simplifies code. Further traits of immediate mode graphic user interfaces are a +code driven style, centralized flow control, easy extensibility and +understandability. ### Input The `gui_input` struct holds the user input over the course of the frame and @@ -198,9 +170,9 @@ while (1) { } ``` -For the purpose of implementing overlapping panels sub buffers were implemented. +For the purpose of implementing multible panels, sub buffers were implemented. With sub buffers you can create one global buffer which owns the allocated memory -and sub buffers which directly reference the global buffer. Biggest +and sub buffers which directly reference the global buffer. The biggest advantage is that you do not have to allocate a buffer for each panel and boil down the memory management to a single buffer. diff --git a/demo/xlib.c b/demo/xlib.c index 4df0bf0..73df234 100644 --- a/demo/xlib.c +++ b/demo/xlib.c @@ -12,9 +12,6 @@ #include "../gui.h" /* macros */ -#define MAX_BUFFER 64 -#define MAX_MEMORY (8 * 1024) -#define MAX_PANELS 4 #define WIN_WIDTH 800 #define WIN_HEIGHT 600 #define DTIME 16 @@ -25,6 +22,8 @@ #define LEN(a) (sizeof(a)/sizeof(a)[0]) #define UNUSED(a) ((void)(a)) +#include "demo.c" + typedef struct XFont XFont; typedef struct XSurface XSurface; typedef struct XWindow XWindow; @@ -62,27 +61,6 @@ struct XWindow { unsigned int height; }; -struct demo { - gui_char in_buf[MAX_BUFFER]; - gui_size in_len; - gui_bool in_act; - gui_char cmd_buf[MAX_BUFFER]; - gui_size cmd_len; - gui_bool cmd_act; - gui_bool check; - gui_int option; - gui_float slider; - gui_size prog; - gui_int spinner; - gui_bool spin_act; - gui_size item_cur; - gui_size cur; - gui_bool tab_min; - gui_float group_off; - gui_float shelf_off; - gui_bool toggle; -}; - static void die(const char *fmt, ...) { @@ -320,7 +298,7 @@ surface_del(XSurface *surf) } static void -draw(XSurface *surf, struct gui_command_list *list) +execute(XSurface *surf, struct gui_command_list *list) { const struct gui_command *cmd; if (!list->count) return; @@ -355,12 +333,26 @@ draw(XSurface *surf, struct gui_command_list *list) surface_draw_text(surf, t->x, t->y, t->w, t->h, (const char*)t->string, t->length, t->font, t->bg, t->fg); } break; + case GUI_COMMAND_IMAGE: + case GUI_COMMAND_MAX: default: break; } cmd = gui_list_next(list, cmd); } } +static void +draw(XSurface *surf, struct gui_panel_stack *stack) +{ + struct gui_panel *iter = stack->begin; + if (!stack->count) return; + while (iter) { + struct gui_window *win = (void*)iter; + execute(surf, &win->list); + iter = iter->next; + } +} + static void key(struct XWindow *xw, struct gui_input *in, XEvent *evt, gui_bool down) { @@ -412,55 +404,6 @@ resize(struct XWindow *xw, XSurface *surf) surface_resize(surf, xw->width, xw->height); } -static void -demo_panel(struct gui_panel_layout *panel, struct demo *demo) -{ - gui_int i = 0; - enum {HISTO, PLOT}; - const char *shelfs[] = {"Histogram", "Lines"}; - const gui_float values[] = {8.0f, 15.0f, 20.0f, 12.0f, 30.0f}; - const char *items[] = {"Fist", "Pistol", "Shotgun", "Railgun", "BFG"}; - const char *options[] = {"easy", "normal", "hard", "hell", "doom", "godlike"}; - struct gui_panel_layout tab; - - /* Tabs */ - demo->tab_min = gui_panel_tab_begin(panel, &tab, "Difficulty", demo->tab_min); - gui_panel_row(&tab, 30, 3); - for (i = 0; i < (gui_int)LEN(options); i++) { - if (gui_panel_option(&tab, options[i], demo->option == i)) - demo->option = i; - } - gui_panel_tab_end(panel, &tab); - - /* Shelf */ - gui_panel_row(panel, 200, 2); - demo->cur = gui_panel_shelf_begin(panel,&tab,shelfs,LEN(shelfs),demo->cur,demo->shelf_off); - gui_panel_row(&tab, 100, 1); - if (demo->cur == HISTO) { - gui_panel_graph(&tab, GUI_GRAPH_HISTO, values, LEN(values)); - } else { - gui_panel_graph(&tab, GUI_GRAPH_LINES, values, LEN(values)); - } - demo->shelf_off = gui_panel_shelf_end(panel, &tab); - - /* Group */ - gui_panel_group_begin(panel, &tab, "Options", demo->group_off); - gui_panel_row(&tab, 30, 1); - if (gui_panel_button_text(&tab, "button", GUI_BUTTON_DEFAULT)) - fprintf(stdout, "button pressed!\n"); - demo->toggle = gui_panel_button_toggle(&tab, "toggle", demo->toggle); - demo->check = gui_panel_check(&tab, "advanced", demo->check); - demo->slider = gui_panel_slider(&tab, 0, demo->slider, 10, 1.0f); - demo->prog = gui_panel_progress(&tab, demo->prog, 100, gui_true); - demo->item_cur = gui_panel_selector(&tab, items, LEN(items), demo->item_cur); - demo->spinner = gui_panel_spinner(&tab, 0, demo->spinner, 250, 10, &demo->spin_act); - if (gui_panel_shell(&tab, demo->cmd_buf, &demo->cmd_len, MAX_BUFFER, &demo->cmd_act)) - demo->cmd_len = 0; - demo->in_len = gui_panel_edit(&tab, demo->in_buf, demo->in_len, - MAX_BUFFER, &demo->in_act, GUI_INPUT_DEFAULT); - demo->group_off = gui_panel_group_end(panel, &tab); -} - int main(int argc, char *argv[]) { @@ -469,24 +412,16 @@ main(int argc, char *argv[]) long started; XWindow xw; gui_bool running = gui_true; - struct demo demo; /* GUI */ struct gui_input in; struct gui_font font; struct gui_memory memory; - struct gui_memory_status status; struct gui_config config; - struct gui_canvas canvas; struct gui_command_buffer buffer; - struct gui_command_list list; - struct gui_panel_layout layout; - struct gui_panel panel; - - gui_size option = 0; - gui_char buf[256]; - gui_size len = 0; - gui_bool active; + struct gui_panel_stack stack; + struct show_window show; + struct control_window control; /* Window */ UNUSED(argc); UNUSED(argv); @@ -521,18 +456,8 @@ main(int argc, char *argv[]) font.height = (gui_float)xw.font->height; font.width = font_get_text_width; gui_default_config(&config); - gui_panel_init(&panel, 50, 50, 400, 320, - GUI_PANEL_BORDER|GUI_PANEL_MOVEABLE| - GUI_PANEL_CLOSEABLE|GUI_PANEL_SCALEABLE| - GUI_PANEL_MINIMIZABLE, &config, &font); - - /* Demo */ - memset(&demo, 0, sizeof(demo)); - demo.tab_min = gui_true; - demo.spinner = 100; - demo.slider = 2.0f; - demo.prog = 60; - demo.cur = 1; + gui_stack_clear(&stack); + init_demo(&show, &control, &stack, &config, &font); while (running) { /* Input */ @@ -551,16 +476,12 @@ main(int argc, char *argv[]) gui_input_end(&in); /* GUI */ - gui_buffer_begin(&canvas, &buffer, xw.width, xw.height); - running = gui_panel_begin(&layout, &panel, "Demo", &canvas, &in); - demo_panel(&layout, &demo); - gui_panel_end(&layout, &panel); - gui_buffer_end(&list, &buffer, &canvas, &status); + running = run_demo(&show, &control, &stack, &config, &in, &buffer, xw.width, xw.height); /* Draw */ XClearWindow(xw.dpy, xw.win); surface_clear(xw.surf, 0x00646464); - draw(xw.surf, &list); + draw(xw.surf, &stack); surface_blit(xw.win, xw.surf, xw.width, xw.height); XFlush(xw.dpy); diff --git a/gui.c b/gui.c index 6fac37a..a9b5097 100644 --- a/gui.c +++ b/gui.c @@ -843,6 +843,7 @@ gui_buffer_push(struct gui_command_buffer* buffer, cap = (gui_size)((gui_float)buffer->capacity * buffer->grow_factor); buffer->memory = buffer->allocator.realloc(buffer->allocator.userdata,buffer->memory, cap); if (!buffer->memory) return NULL; + buffer->capacity = cap; buffer->begin = buffer->memory; buffer->end = (void*)((gui_byte*)buffer->begin + buffer->allocated); @@ -1006,7 +1007,8 @@ gui_buffer_push_image(struct gui_command_buffer *buffer, gui_float x, gui_float return; } } - cmd = gui_buffer_push(buffer, GUI_COMMAND_RECT, sizeof(*cmd)); + + cmd = gui_buffer_push(buffer, GUI_COMMAND_IMAGE, sizeof(*cmd)); if (!cmd) return; cmd->x = (gui_short)x; cmd->y = (gui_short)y; @@ -1035,6 +1037,7 @@ gui_buffer_push_text(struct gui_command_buffer *buffer, gui_float x, gui_float y return; } } + cmd = gui_buffer_push(buffer, GUI_COMMAND_TEXT, sizeof(*cmd) + length + 1); if (!cmd) return; cmd->x = (gui_short)x; @@ -1285,7 +1288,6 @@ gui_panel_init(struct gui_panel *panel, gui_float x, gui_float y, gui_float w, panel->y = y; panel->w = w; panel->h = h; - panel->flags = 0; panel->flags = flags; panel->config = config; panel->font = *font; @@ -1312,8 +1314,13 @@ gui_panel_begin(struct gui_panel_layout *layout, struct gui_panel *panel, if (!panel || !canvas || !layout) return gui_false; - if (panel->flags & GUI_PANEL_HIDDEN) + if (panel->flags & GUI_PANEL_HIDDEN) { + zero(layout, sizeof(*layout)); + layout->valid = gui_false; + layout->config = panel->config; + layout->canvas = canvas; return gui_false; + } config = panel->config; layout->header_height = panel->font.height + 3 * config->item_padding.y; @@ -1323,7 +1330,6 @@ gui_panel_begin(struct gui_panel_layout *layout, struct gui_panel *panel, mouse_y = (in) ? in->mouse_pos.y : -1; prev_x = (in) ? in->mouse_prev.x : -1; prev_y = (in) ? in->mouse_prev.y : -1; - clicked_x = (in) ? in->mouse_clicked_pos.x : -1; clicked_y = (in) ? in->mouse_clicked_pos.y : -1; @@ -1345,8 +1351,8 @@ gui_panel_begin(struct gui_panel_layout *layout, struct gui_panel *panel, gui_bool incursor; gui_float scaler_w = MAX(0, config->scaler_size.x - config->item_padding.x); gui_float scaler_h = MAX(0, config->scaler_size.y - config->item_padding.y); - gui_float scaler_x = (layout->x + layout->w) - (config->item_padding.x + scaler_w); - gui_float scaler_y = layout->y + layout->h - config->scaler_size.y; + gui_float scaler_x = (panel->x + panel->w) - (config->item_padding.x + scaler_w); + gui_float scaler_y = panel->y + panel->h - config->scaler_size.y; incursor = in && INBOX(prev_x, prev_y, scaler_x, scaler_y, scaler_w, scaler_h); if (in && in->mouse_down && incursor) { @@ -1423,7 +1429,7 @@ gui_panel_begin(struct gui_panel_layout *layout, struct gui_panel *panel, if (INBOX(mouse_x, mouse_y, close_x, close_y, close_w, close_h)) { if (INBOX(clicked_x, clicked_y, close_x, close_y, close_w, close_h)) { ret = !(in->mouse_down && in->mouse_clicked); - if (ret) panel->flags |= GUI_PANEL_HIDDEN; + if (!ret) panel->flags |= GUI_PANEL_HIDDEN; } } } @@ -1509,9 +1515,8 @@ gui_panel_begin_stacked(struct gui_panel_layout *layout, struct gui_panel *panel if (in->mouse_down && in->mouse_clicked && inpanel && panel != stack->end) { struct gui_panel *iter = panel->next; while (iter) { - if (!iter->minimized) - if (INBOX(in->mouse_prev.x, in->mouse_prev.y, iter->x, iter->y, - iter->w, iter->h)) break; + if (INBOX(in->mouse_prev.x, in->mouse_prev.y, iter->x, iter->y, iter->w, iter->h) && + !iter->minimized) break; iter = iter->next; } if (!iter) { @@ -1574,6 +1579,8 @@ gui_panel_alloc_space(struct gui_rect *bounds, struct gui_panel_layout *layout) ASSERT(layout); ASSERT(layout->config); ASSERT(bounds); + if (!layout || !layout->config || !bounds) + return; config = layout->config; if (layout->index >= layout->row_columns) { @@ -1644,7 +1651,6 @@ gui_panel_label(struct gui_panel_layout *layout, const char *text, enum gui_text gui_panel_text(layout, text, len, align); } - gui_bool gui_panel_button_text(struct gui_panel_layout *layout, const char *str, enum gui_button_behavior behavior) @@ -1704,7 +1710,7 @@ gui_bool gui_panel_button_color(struct gui_panel_layout *layout, button.background = color; button.foreground = color; button.highlight = color; - button.highlight_content = config->colors[GUI_COLOR_BUTTON_HOVER_FONT]; + button.highlight_content = color; return gui_do_button(layout->canvas, bounds.x, bounds.y, bounds.w, bounds.h, &button, layout->input, behavior); } @@ -2054,18 +2060,18 @@ gui_panel_spinner(struct gui_panel_layout *layout, gui_int min, gui_int value, ASSERT(layout->config); ASSERT(layout->canvas); - if (!layout || !layout->config || !layout->canvas) return 0; - if (!layout->valid) return 0; + if (!layout || !layout->config || !layout->canvas) return value; + if (!layout->valid) return value; gui_panel_alloc_space(&bounds, layout); config = layout->config; canvas = layout->canvas; c = &layout->clip; if (!INTERSECT(c->x, c->y, c->w, c->h, bounds.x, bounds.y, bounds.w, bounds.h)) - return 0; + return value; value = CLAMP(min, value, max); len = itos(string, value); - is_active = *active; + is_active = (active) ? *active : gui_false; old_len = len; button.border = 1; @@ -2105,7 +2111,7 @@ gui_panel_spinner(struct gui_panel_layout *layout, gui_int min, gui_int value, len, MAX_NUMBER_BUFFER, &is_active, &field, layout->input, &layout->font); if (old_len != len) strtoi(&value, string, len); - *active = is_active; + if (active) *active = is_active; return value; } @@ -2160,11 +2166,11 @@ gui_panel_selector(struct gui_panel_layout *layout, const char *items[], button.content = config->colors[GUI_COLOR_TEXT]; button.highlight = config->colors[GUI_COLOR_BUTTON]; button.highlight_content = config->colors[GUI_COLOR_TEXT]; - button_up_clicked = gui_button_triangle(canvas, button_x, button_y, button_w, + button_down_clicked = gui_button_triangle(canvas, button_x, button_y, button_w, button_h, GUI_UP, GUI_BUTTON_DEFAULT, &button, layout->input); button_y = bounds.y + button_h; - button_down_clicked = gui_button_triangle(canvas, button_x, button_y, button_w, + button_up_clicked = gui_button_triangle(canvas, button_x, button_y, button_w, button_h, GUI_DOWN, GUI_BUTTON_DEFAULT, &button, layout->input); item_current = (button_down_clicked && item_current < item_count-1) ? item_current+1 : (button_up_clicked && item_current > 0) ? item_current-1 : item_current; @@ -2342,14 +2348,13 @@ gui_panel_graph(struct gui_panel_layout *layout, enum gui_graph_type type, struct gui_rect bounds; gui_float min_value; gui_float max_value; - const struct gui_config *config; struct gui_graph graph; ASSERT(layout); - ASSERT(layout->config); - ASSERT(layout->canvas); ASSERT(values); ASSERT(count); + if (!layout || !values || !count) + return -1; max_value = values[0]; min_value = values[0]; @@ -2378,13 +2383,13 @@ gui_panel_graph_ex(struct gui_panel_layout *layout, enum gui_graph_type type, struct gui_rect bounds; gui_float min_value; gui_float max_value; - const struct gui_config *config; struct gui_graph graph; ASSERT(layout); - ASSERT(layout->config); - ASSERT(layout->canvas); ASSERT(get_value); + ASSERT(count); + if (!layout || !get_value || !count) + return -1; max_value = get_value(userdata, 0); min_value = max_value; diff --git a/gui.h b/gui.h index 1862c86..b764c31 100644 --- a/gui.h +++ b/gui.h @@ -228,7 +228,9 @@ enum gui_command_type { GUI_COMMAND_RECT, GUI_COMMAND_CIRCLE, GUI_COMMAND_TRIANGLE, - GUI_COMMAND_TEXT + GUI_COMMAND_TEXT, + GUI_COMMAND_IMAGE, + GUI_COMMAND_MAX }; struct gui_command { @@ -399,11 +401,6 @@ struct gui_panel { struct gui_panel *prev; }; -struct gui_window { - struct gui_panel panel; - struct gui_command_list list; -}; - struct gui_panel_layout { gui_float x, y, w, h; gui_float offset; @@ -430,6 +427,11 @@ struct gui_panel_stack { struct gui_panel *end; }; +struct gui_window { + struct gui_panel panel; + struct gui_command_list list; +}; + /* Input */ gui_size gui_utf_decode(const gui_char*, gui_long*, gui_size); gui_size gui_utf_encode(gui_long, gui_char*, gui_size);