This commit is contained in:
vurtun 2015-05-03 11:45:29 +02:00
parent 75e9719730
commit 732d165ea2
3 changed files with 154 additions and 21 deletions

117
Readme.md
View File

@ -8,7 +8,7 @@ possible with fast streamlined development speed in mind.
## Features
- Immediate mode graphical user interface toolkit
- Written in C89 (ANSI C)
- Small codebase (~2.5kLOC)
- Small codebase (~3kLOC)
- Focus on portability and minimal internal state
- Suited for embedding into graphical applications
- No global hidden state
@ -119,11 +119,11 @@ 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.
### Input
The input structure holds the user input over the course of the frame and
The `gui_input` struct holds the user input over the course of the frame and
manages the complete modification of widget and panel state. Like the panel and
buffering the input is an immediate mode API and consist of an begin sequence
buffering, input is an immediate mode API and consist of an begin sequence
point with `gui_input_begin` and a end sequence point with `gui_input_end`.
All modifications to the input struct can only occur between both of these
All modifications can only occur between both of these
sequence points while all outside modifcation provoke undefined behavior.
```c
@ -135,13 +135,6 @@ while (1) {
}
```
### Canvas
The Canvas is the abstract drawing interface between the GUI toolkit
and the user and contains drawing callbacks for the primitives
scissor, line, rectangle, circle, triangle, bitmap and text which need to be
provided by the user. Therefore the canvas is probably the biggest chunk of work
to be done by the user.
### Font
Since there is no direct font implementation in the toolkit but font handling is
still an aspect of a gui implemenatation the gui struct was introduced. It only
@ -159,22 +152,56 @@ filled by the user or can be setup with some default values by the function
`gui_default_config`. Modification on the fly to the `gui_config` struct is in
true immedate mode fashion possible and supported.
### Canvas
The Canvas is the abstract drawing interface between the GUI toolkit
and the user and contains drawing callbacks for the primitives
scissor, line, rectangle, circle, triangle, bitmap and text which need to be
provided by the user. Main advantage of using the raw canvas instead of using
buffering is that no memory to buffer all draw command is needed. Instead you
can directly draw each requested primitive. The downside is setting up the canvas
structure and the fact that you have to draw each primitive immediatly.
Internally the canvas is used to implement the buffering of primitve draw
commands, but can be used to implement a different buffering scheme like
buffering vertexes instead of primitives.
### Buffering
For the purpose of defered drawing or the implementation of overlapping panels
the command buffering API was added. The command buffer hereby holds a queue of
drawing commands for a number of primitives eg.: line, rectangle, circle,
triangle and text. The command buffer memory is provided by the user
triangle and text. The memory for the command buffer is provided by the user
in three possible ways. First by providing a fixed size memory block which
will be filled up until no memory is left.
The second way is extending the fixed size memory block by reallocating at the
end of the frame if the provided memory size was not sufficient.
The final way of memory management is by providing allocator callbacks with alloc,
realloc and free. In true immediate mode fashion the buffering API is based around sequence
points with an begin sequence point `gui_buffer_begin` and a end sequence
The final and most complex way of memory management is by providing allocator
callbacks with alloc, realloc and free.
In true immediate mode fashion the buffering API is based around sequence
points with a begin sequence point `gui_buffer_begin` and a end sequence
point `gui_buffer_end` and modification of state between both points. Just
like the input API the buffer modification before the beginning or after the end
sequence point is undefined behavior.
```c
struct gui_allocator allocator = {...};
struct gui_memory_status status;
struct gui_command_list list;
struct gui_command_buffer buffer;
gui_buffer_init(buffer, &allocator, 2.0f, INITAL_SIZE, 0);
while (1) {
struct gui_canvas canvas;
gui_buffer_begin(&canvas, &buffer, window_width, window_height);
/* add commands by using the canvas */
gui_buffer_end(&list, buffer, &status);
}
```
For the purpose of implementing overlapping panels sub buffers were implemented.
With sub buffers you can create one global buffer which owns the allocated memory
and a number of sub buffers which directly reference the global buffer. 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.
```c
struct gui_memory memory = {...};
struct gui_memory_status status;
@ -184,11 +211,14 @@ gui_buffer_init_fixed(buffer, &memory);
while (1) {
struct gui_canvas canvas;
struct gui_command_buffer sub;
gui_buffer_lock(&sub, &buffer);
gui_buffer_begin(&canvas, &buffer, window_width, window_height);
/* add commands by using the canvas */
gui_buffer_end(&list, buffer, &status);
gui_buffer_unlock(&buffer, &sub);
}
```
### Widgets
@ -247,6 +277,61 @@ while (1) {
}
```
### Stack
While using basic panels is fine for a single moveable panel or a big number of
static panels, it has rather limited support for overlapping movable panels. For
that to change the panel stack was introduced. The panel stack holds the basic
drawing order of each panel so instead of drawing each panel indiviually they
have to be drawn in a certain order. The biggest problem while creating the API
was that the buffer has to saved with the panel, but the type of the buffer is
not known beforhand since it is possible to create your own buffer type.
Therefore just the sequence of panels is managed and you either have to cast
from the panel to your own type, use inheritance in C++ or use the `container_of`
macro from the Linux kernel. For the standard buffer there is already a type
`gui_window` which contains the panel and the buffer output `gui_command_list`,
which can be used to implement overlapping panels.
```c
struct gui_window window;
struct gui_memory memory = {...};
struct gui_memory_status status;
struct gui_command_buffer buffer;
struct gui_config config;
struct gui_font font = {...}
struct gui_input input = {0};
struct gui_stack stack;
gui_buffer_init_fixed(buffer, &memory);
gui_default_config(&config);
gui_panel_init(&win.panel, 50, 50, 300, 200, 0, &config, &font);
gui_stack_clear(&stack);
gui_stack_push(&stack, &win.panel);
while (1) {
struct gui_panel_layout layout;
struct gui_canvas canvas;
gui_buffer_begin(&canvas, &buffer, window_width, window_height);
gui_panel_begin_stacked(&layout, &win.panel, &stack, "Demo", &canvas, &input);
gui_panel_row(&layout, 30, 1);
if (gui_panel_button_text(&layout, "button", GUI_BUTTON_DEFAULT))
fprintf(stdout, "button pressed!\n");
gui_panel_end(&layout, &win.panel);
gui_buffer_end(&win.list, buffer, &status);
/* draw each panel */
struct gui_panel *iter = stack.begin;
while (iter) {
struct gui_command *cmd = list.begin;
while (cmd != list.end) {
/* execute command */
cmd = cmd->next;
}
iter = iter->next;
}
}
```
## FAQ
#### Where is the demo/example code?
The demo and example code can be found in the demo folder.

56
gui.c
View File

@ -1158,11 +1158,13 @@ gui_buffer_end(struct gui_command_list *list, struct gui_command_buffer *buffer,
status->clipped_commands = buffer->clipped_cmds;
status->clipped_memory = buffer->clipped_memory;
}
if (list) {
list->count = buffer->count;
list->begin = buffer->begin;
list->end = buffer->end;
}
buffer->begin = buffer->memory;
buffer->end = buffer->begin;
buffer->needed = 0;
@ -1455,6 +1457,8 @@ gui_panel_begin_stacked(struct gui_panel_layout *layout, struct gui_panel *panel
assert(panel);
assert(stack);
assert(canvas);
if (!layout || !panel || !stack || !canvas)
return gui_false;
inpanel = INBOX(in->mouse_prev.x, in->mouse_prev.y, panel->x, panel->y, panel->w, panel->h);
if (in->mouse_down && in->mouse_clicked && inpanel && panel != stack->end) {
@ -2033,10 +2037,10 @@ gui_panel_spinner(struct gui_panel_layout *layout, gui_int min, gui_int value,
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_h,
GUI_UP, GUI_BUTTON_DEFAULT, &button, layout->input);
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_h,
GUI_DOWN, GUI_BUTTON_DEFAULT, &button, layout->input);
GUI_DOWN, GUI_BUTTON_DEFAULT, &button, layout->input);
if (button_up_clicked || button_down_clicked) {
value += (button_up_clicked) ? step : -step;
value = CLAMP(min, value, max);
@ -2243,7 +2247,9 @@ gui_panel_graph_push_histo(struct gui_panel_layout *layout,
}
ratio = ABS(value) / graph->max;
color = (value < 0) ? config->colors[GUI_COLOR_HISTO_NEGATIVE]: config->colors[GUI_COLOR_HISTO_BARS];
color = (value < 0) ? config->colors[GUI_COLOR_HISTO_NEGATIVE]:
config->colors[GUI_COLOR_HISTO_BARS];
item_h = graph->h * ratio;
item_y = (graph->y + graph->h) - item_h;
item_x = graph->x + ((gui_float)graph->index * item_w);
@ -2318,6 +2324,43 @@ gui_panel_graph(struct gui_panel_layout *layout, enum gui_graph_type type,
return index;
}
gui_int
gui_panel_graph_ex(struct gui_panel_layout *layout, enum gui_graph_type type,
gui_size count, gui_float(*get_value)(void*, gui_size), void *userdata)
{
gui_size i;
gui_int index = -1;
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);
max_value = get_value(userdata, 0);
min_value = max_value;
for (i = 1; i < count; ++i) {
gui_float value = get_value(userdata, i);
if (value > max_value)
max_value = value;
if (value < min_value)
min_value = value;
}
gui_panel_graph_begin(layout, &graph, type, count, min_value, max_value);
for (i = 0; i < count; ++i) {
gui_float value = get_value(userdata, i);
if (gui_panel_graph_push(layout, &graph, value))
index = (gui_int)i;
}
gui_panel_graph_end(layout, &graph);
return index;
}
static void
gui_panel_table_hline(struct gui_panel_layout *layout, gui_size row_height)
{
@ -2517,12 +2560,14 @@ gui_panel_shelf_begin(struct gui_panel_layout *parent, struct gui_panel_layout *
{
const struct gui_config *config;
const struct gui_canvas *canvas;
gui_float header_x, header_y;
gui_float header_w, header_h;
struct gui_rect bounds;
struct gui_rect clip;
struct gui_rect *c;
struct gui_panel panel;
gui_float header_x, header_y;
gui_float header_w, header_h;
gui_float item_width;
gui_flags flags;
gui_size i;
@ -2710,6 +2755,7 @@ gui_stack_push(struct gui_panel_stack *stack, struct gui_panel *panel)
stack->count = 1;
return;
}
stack->end->next = panel;
panel->prev = stack->end;
panel->next = NULL;

2
gui.h
View File

@ -547,6 +547,8 @@ gui_bool gui_panel_graph_push(struct gui_panel_layout *layout, struct gui_graph*
void gui_panel_graph_end(struct gui_panel_layout *layout, struct gui_graph*);
gui_int gui_panel_graph(struct gui_panel_layout*, enum gui_graph_type,
const gui_float *values, gui_size count);
gui_int gui_panel_graph_ex(struct gui_panel_layout*, enum gui_graph_type, gui_size count,
gui_float(*get_value)(void*, gui_size), void *userdata);
void gui_panel_table_begin(struct gui_panel_layout*, gui_flags flags,
gui_size row_height, gui_size cols);
void gui_panel_table_label(struct gui_panel_layout*, const char*);