Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_grid.c

a virtualised, vertically scrolled grid of cells

a virtualised, vertically scrolled grid of cells

Author
Castagnier Mickael
Version
1.0
Date
24/09/2026

Headless: the grid lays twenty cells out in the columns its width holds, shows its scrollbar only when the rows overflow and narrows itself for it, draws only the cells on screen and says which range to prepare, scrolls one row a wheel notch, scrolls a cell into view, answers which cell is at a point, gaps included, and hands a press on a cell to its caller. A wheel callback sees Ctrl held, from the key events the context was given, and takes Ctrl+wheel to change the cell size instead of scrolling.

#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <allegro5/allegro_primitives.h>
#include "nilorea/n_log.h"
#include "nilorea/n_gui.h"
static int log_level = LOG_ERR;
static int failures = 0;
static int cells_drawn = 0, backgrounds = 0, presses = 0, pressed_button = 0;
static size_t first_seen = 0, last_seen = 0, pressed_index = 0;
static float pressed_x = 0.0f, pressed_y = 0.0f;
static int wheels = 0, wheel_mods = 0;
static float zoom_cell = 50.0f;
static N_GUI_CTX* zoom_gui = NULL;
static void expect_true(const char* what, int ok) {
if (!ok) {
n_log(LOG_ERR, "FAIL: %s", what);
} else {
n_log(LOG_INFO, "ok: %s", what);
}
}
static int process_args(int argc, char** argv) {
int opt = 0;
while ((opt = getopt(argc, argv, "V:")) != -1) {
if (opt == 'V') {
log_level = atoi(optarg);
} else {
return -1;
}
}
return 0;
}
static void draw_cell(int widget_id, size_t index, float x, float y, float w, float h, void* user_data) {
(void)widget_id;
(void)index;
(void)user_data;
al_draw_filled_rectangle(x, y, x + w, y + h, al_map_rgb(90, 90, 90));
}
static void draw_background(int widget_id, float x, float y, float w, float h, void* user_data) {
(void)widget_id;
(void)user_data;
al_draw_filled_rectangle(x, y, x + w, y + h, al_map_rgb(20, 20, 20));
}
static void visible(int widget_id, size_t first, size_t last, void* user_data) {
(void)widget_id;
(void)user_data;
first_seen = first;
}
static void press(int widget_id, size_t index, int button, float x, float y, void* user_data) {
(void)widget_id;
(void)user_data;
pressed_index = index;
pressed_button = button;
pressed_x = x;
pressed_y = y;
}
static int wheel(int widget_id, int dz, int modifiers, void* user_data) {
(void)user_data;
wheels++;
wheel_mods = modifiers;
if (!(modifiers & ALLEGRO_KEYMOD_CTRL)) return 0;
zoom_cell += 10.0f * (float)dz;
n_gui_grid_set_cell_size(zoom_gui, widget_id, zoom_cell, zoom_cell + 10.0f, 10.0f);
return 1;
}
static void feed_key(N_GUI_CTX* gui, int type, int keycode) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = (ALLEGRO_EVENT_TYPE)type;
ev.keyboard.keycode = keycode;
}
static void feed(N_GUI_CTX* gui, int type, float x, float y, int button, int dz) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = (ALLEGRO_EVENT_TYPE)type;
ev.mouse.x = (int)x;
ev.mouse.y = (int)y;
ev.mouse.dx = (type == ALLEGRO_EVENT_MOUSE_AXES && dz == 0) ? 1 : 0;
ev.mouse.dz = dz;
ev.mouse.button = (unsigned int)button;
}
static void draw(N_GUI_CTX* gui) {
ALLEGRO_BITMAP* target = NULL;
int old = al_get_new_bitmap_flags();
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
target = al_create_bitmap(400, 300);
al_set_new_bitmap_flags(old);
al_set_target_bitmap(target);
al_clear_to_color(al_map_rgb(0, 0, 0));
al_set_target_bitmap(NULL);
al_destroy_bitmap(target);
}
int main(int argc, char** argv) {
size_t at = 0;
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
if (process_args(argc, argv) != 0) {
fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 2;
}
if (!al_init() || !al_init_font_addon() || !al_init_primitives_addon()) {
n_log(LOG_ERR, "allegro init failed");
return 1;
}
ALLEGRO_FONT* font = al_create_builtin_font();
if (!font) return 1;
if (!gui) {
al_destroy_font(font);
return 1;
}
n_gui_set_display_size(gui, 400.0f, 300.0f);
int win = n_gui_add_window(gui, "W", 0.0f, 0.0f, 400.0f, 300.0f);
memset(&cb, 0, sizeof(cb));
expect_true("a grid without draw_cell is refused", n_gui_grid_new(gui, win, &cb, NULL) < 0);
cb.press = press;
int grid = n_gui_grid_new(gui, win, &cb, NULL);
expect_true("a grid is made", grid >= 0);
n_gui_grid_set_cell_size(gui, grid, 50.0f, 60.0f, 10.0f);
n_gui_grid_set_rect(gui, grid, 10.0f, 10.0f, 300.0f, 200.0f, 12.0f);
/* empty */
expect_true("an empty grid draws its background and no cell", backgrounds == 1 && cells_drawn == 0);
expect_true("and has no scrollbar", n_gui_grid_scrollbar_showing(gui, grid) == 0);
expect_true("and no cell anywhere", n_gui_grid_index_at(gui, grid, 30.0f, 30.0f, &at) == FALSE);
/* twenty cells: five across at 300 wide would be 290 tall, over 200, so
the bar shows; 288 wide then holds four across */
expect_true("rows that overflow show the scrollbar", n_gui_grid_scrollbar_showing(gui, grid) == 1);
expect_true("and the grid narrows for it", n_gui_grid_columns(gui, grid) == 4);
expect_true("only the rows on screen are drawn", cells_drawn == 16);
expect_true("and the range to prepare is one row further", first_seen == 0 && last_seen == 20);
/* scrolling */
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 50.0f, 50.0f, 0, -1);
expect_true("a wheel notch scrolls one row", n_gui_grid_get_scroll(gui, grid) == 70.0f);
expect_true("and the bar follows", n_gui_scrollbar_get_pos(gui, n_gui_grid_scrollbar(gui, grid)) == 70.0);
expect_true("scrolling to the first cell goes back to the top", n_gui_grid_get_scroll(gui, grid) == 0.0f);
expect_true("scrolling to the last shows its whole row", n_gui_grid_get_scroll(gui, grid) == 160.0f);
n_gui_grid_set_scroll(gui, grid, 1000.0f);
expect_true("the view never goes past the end", n_gui_grid_get_scroll(gui, grid) == 160.0f);
n_gui_grid_set_scroll(gui, grid, 0.0f);
/* hit test: the grid is at 10,10, cells start one gap in */
expect_true("a point on a cell is that cell", n_gui_grid_index_at(gui, grid, 25.0f, 25.0f, &at) && at == 0);
expect_true("the gap after a cell is that cell", n_gui_grid_index_at(gui, grid, 75.0f, 25.0f, &at) && at == 0);
expect_true("the gap below a row is that row", n_gui_grid_index_at(gui, grid, 25.0f, 85.0f, &at) && at == 0);
expect_true("the right edge is the last column", n_gui_grid_index_at(gui, grid, 295.0f, 25.0f, &at) && at == 3);
expect_true("outside the grid is no cell", n_gui_grid_index_at(gui, grid, 5.0f, 25.0f, &at) == FALSE);
expect_true("cell 5 is where the layout says", n_gui_grid_cell_rect(gui, grid, 5, &x, &y, &w, &h) && x == 80.0f && y == 90.0f && w == 50.0f && h == 60.0f);
expect_true("a cell scrolled out of view says so", n_gui_grid_cell_rect(gui, grid, 19, NULL, NULL, NULL, NULL) == FALSE);
/* presses */
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 105.0f, 120.0f, 0, 0);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 105.0f, 120.0f, 1, 0);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, 105.0f, 120.0f, 1, 0);
expect_true("a press on a cell is handed on with its index", presses == 1 && pressed_index == 5 && pressed_button == 1);
expect_true("and where it was, on screen", pressed_x == 105.0f && pressed_y == 120.0f);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 105.0f, 120.0f, 2, 0);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, 105.0f, 120.0f, 2, 0);
expect_true("a right press too, as button 2", presses == 2 && pressed_button == 2);
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 245.0f, 190.0f, 0, 0);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 245.0f, 170.0f, 1, 0);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, 245.0f, 170.0f, 1, 0);
expect_true("a press past the last cell is not", presses == 2);
/* fewer cells, then hidden */
expect_true("rows that fit hide the scrollbar, go back to the top and widen again",
n_gui_grid_scrollbar_showing(gui, grid) == 0 && n_gui_grid_get_scroll(gui, grid) == 0.0f && n_gui_grid_columns(gui, grid) == 5);
expect_true("a hidden grid has no cell anywhere", n_gui_grid_index_at(gui, grid, 25.0f, 25.0f, &at) == FALSE);
expect_true("an id that is not a grid answers nothing", n_gui_grid_columns(gui, win + 1000) == 0);
/* the wheel callback, and the modifiers the context follows */
memset(&cb, 0, sizeof(cb));
cb.wheel = wheel;
int zoomed = n_gui_grid_new(gui, win, &cb, NULL);
expect_true("a grid with a wheel callback is made", zoomed >= 0);
n_gui_grid_set_cell_size(gui, zoomed, zoom_cell, zoom_cell + 10.0f, 10.0f);
n_gui_grid_set_rect(gui, zoomed, 10.0f, 10.0f, 300.0f, 200.0f, 12.0f);
n_gui_grid_set_count(gui, zoomed, 40);
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 50.0f, 50.0f, 0, -1);
expect_true("the plain wheel is offered to the callback with no modifier", wheels == 1 && wheel_mods == 0);
expect_true("and, refused, scrolls", n_gui_grid_get_scroll(gui, zoomed) == 70.0f);
feed_key(gui, ALLEGRO_EVENT_KEY_DOWN, ALLEGRO_KEY_RCTRL);
expect_true("a Ctrl pressed is held", n_gui_get_key_modifiers(gui) == ALLEGRO_KEYMOD_CTRL);
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 50.0f, 50.0f, 0, 2);
expect_true("Ctrl+wheel reaches the callback with Ctrl", wheels == 2 && (wheel_mods & ALLEGRO_KEYMOD_CTRL));
expect_true("which takes it: the cells grow and nothing scrolls", zoom_cell == 70.0f && n_gui_grid_get_scroll(gui, zoomed) == 70.0f);
feed_key(gui, ALLEGRO_EVENT_KEY_DOWN, ALLEGRO_KEY_LCTRL);
feed_key(gui, ALLEGRO_EVENT_KEY_UP, ALLEGRO_KEY_RCTRL);
expect_true("one Ctrl let go while the other is held keeps Ctrl", n_gui_get_key_modifiers(gui) == ALLEGRO_KEYMOD_CTRL);
feed_key(gui, ALLEGRO_EVENT_DISPLAY_SWITCH_OUT, 0);
expect_true("losing the focus forgets every key", n_gui_get_key_modifiers(gui) == 0);
al_destroy_font(font);
if (failures) {
fprintf(stderr, "ex_gui_grid: %d failure(s)\n", failures);
return 1;
}
printf("ex_gui_grid: ok\n");
return 0;
}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static int log_level
static void feed(N_GUI_CTX *gui, int type, int x, int y, int button)
feed one mouse event at display coordinates
static N_GUI_MOUSE_EVENT last
the last event the widget was told of, and how many
static N_GUI_CTX * gui
static void wheel(N_GUI_CTX *gui, int dz)
turn the mouse wheel over the display
static size_t last_seen
Definition ex_gui_grid.c:36
static void press(int widget_id, size_t index, int button, float x, float y, void *user_data)
a press on a cell
Definition ex_gui_grid.c:94
static int backgrounds
Definition ex_gui_grid.c:35
static float pressed_x
Definition ex_gui_grid.c:37
static int wheel_mods
Definition ex_gui_grid.c:39
static size_t pressed_index
Definition ex_gui_grid.c:36
static int cells_drawn
what the callbacks saw
Definition ex_gui_grid.c:35
static void draw(N_GUI_CTX *gui)
draw the whole context into a memory bitmap
static size_t first_seen
Definition ex_gui_grid.c:36
static float pressed_y
Definition ex_gui_grid.c:37
static int presses
Definition ex_gui_grid.c:35
static int pressed_button
Definition ex_gui_grid.c:35
static N_GUI_CTX * zoom_gui
the context the wheel callback resizes the grid in
Definition ex_gui_grid.c:42
static float zoom_cell
Definition ex_gui_grid.c:40
static void visible(int widget_id, size_t first, size_t last, void *user_data)
the range to prepare
Definition ex_gui_grid.c:86
static int wheels
what the wheel callback saw, and the cell size it keeps
Definition ex_gui_grid.c:39
static void draw_background(int widget_id, float x, float y, float w, float h, void *user_data)
draw behind
Definition ex_gui_grid.c:78
static void draw_cell(int widget_id, size_t index, float x, float y, float w, float h, void *user_data)
draw one cell
Definition ex_gui_grid.c:69
static void feed_key(N_GUI_CTX *gui, int type, int keycode)
feed one key event
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
FONT * font
the default 8x8 font, valid after allegro_init()
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
set the display (viewport) size so global scrollbars can be computed.
Definition n_gui.c:12348
double n_gui_scrollbar_get_pos(N_GUI_CTX *ctx, int widget_id)
get the current scroll position of a scrollbar widget
Definition n_gui.c:4293
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
Definition n_gui.c:13611
#define N_GUI_WIN_FRAMELESS
frameless window: no title bar drawn, drag via window body unless N_GUI_WIN_FIXED_POSITION is also se...
Definition n_gui.h:276
void n_gui_draw(N_GUI_CTX *ctx)
draw all visible GUI windows and widgets
Definition n_gui.c:11735
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
Definition n_gui.c:1277
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:1103
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
Definition n_gui.c:1436
int n_gui_get_key_modifiers(const N_GUI_CTX *ctx)
the modifier keys held down now, as the GUI has seen them
Definition n_gui.c:13281
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
Definition n_gui.c:2515
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_INFO
informational
Definition n_log.h:106
void(* draw_background)(int widget_id, float x, float y, float w, float h, void *user_data)
Draw behind the cells, over the whole grid, before any cell; with no cells, the only thing drawn.
Definition n_gui_grid.h:75
void(* press)(int widget_id, size_t index, int button, float x, float y, void *user_data)
A press of button (1 or 2) on cell index, at screen x, y.
Definition n_gui_grid.h:81
void(* draw_cell)(int widget_id, size_t index, float x, float y, float w, float h, void *user_data)
Draw cell index in the box at x, y, w by h, on screen.
Definition n_gui_grid.h:72
int(* wheel)(int widget_id, int dz, int modifiers, void *user_data)
A wheel turn of dz notches over the grid, positive away from the user, with the modifier keys held (A...
Definition n_gui_grid.h:87
void(* visible)(int widget_id, size_t first, size_t last, void *user_data)
Called after each draw with the cells drawn and one row beyond, first included and last excluded: wha...
Definition n_gui_grid.h:78
void n_gui_grid_set_rect(N_GUI_CTX *ctx, int widget_id, float x, float y, float w, float h, float bar_w)
Place the grid and its scrollbar in a rectangle of the window.
Definition n_gui_grid.c:321
void n_gui_grid_set_scroll(N_GUI_CTX *ctx, int widget_id, float scroll)
Set the scroll offset, clamped to the content.
Definition n_gui_grid.c:373
int n_gui_grid_new(N_GUI_CTX *ctx, int window_id, const N_GUI_GRID_CALLBACKS *callbacks, void *user_data)
Add a grid, and its scrollbar, to a window.
Definition n_gui_grid.c:281
int n_gui_grid_index_at(N_GUI_CTX *ctx, int widget_id, float x, float y, size_t *index)
Which cell is at a point on screen.
Definition n_gui_grid.c:400
void n_gui_grid_set_count(N_GUI_CTX *ctx, int widget_id, size_t count)
Set how many cells there are.
Definition n_gui_grid.c:343
int n_gui_grid_columns(N_GUI_CTX *ctx, int widget_id)
How many cells a row holds at the grid's width.
Definition n_gui_grid.c:361
int n_gui_grid_cell_rect(N_GUI_CTX *ctx, int widget_id, size_t index, float *x, float *y, float *w, float *h)
Where a cell is on screen, whether or not it is scrolled into view.
Definition n_gui_grid.c:411
void n_gui_grid_scroll_to(N_GUI_CTX *ctx, int widget_id, size_t index)
Scroll the least that brings a cell's whole row into view.
Definition n_gui_grid.c:383
int n_gui_grid_scrollbar_showing(N_GUI_CTX *ctx, int widget_id)
Whether the scrollbar is shown, which is whether the rows overflow.
Definition n_gui_grid.c:434
float n_gui_grid_get_scroll(N_GUI_CTX *ctx, int widget_id)
The scroll offset, in pixels from the top of the content.
Definition n_gui_grid.c:367
void n_gui_grid_set_cell_size(N_GUI_CTX *ctx, int widget_id, float cell_w, float cell_h, float gap)
Set the size of a cell and the gap around cells.
Definition n_gui_grid.c:333
void n_gui_grid_set_visible(N_GUI_CTX *ctx, int widget_id, int visible)
Show or hide the grid and its scrollbar together.
Definition n_gui_grid.c:351
int n_gui_grid_scrollbar(N_GUI_CTX *ctx, int widget_id)
The grid's scrollbar widget.
Definition n_gui_grid.c:428
What the grid asks of its caller.
Definition n_gui_grid.h:69
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
A virtualised grid of cells for N_GUI, scrolled vertically.
Generic log system.