6 ex_gui_grid_8c example
gullradriel edited this page 2026-10-12 07:59:13 +02:00

ex_gui_grid.c

a virtualised, vertically scrolled grid of cells 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. ```c

#define ALLEGRO_UNSTABLE 1

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>

#include <allegro5/allegro_primitives.h>

#include "nilorea/n_common.h" #include "nilorea/n_log.h" #include "nilorea/n_gui.h" #include "nilorea/n_gui_grid.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); failures++; } 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; cells_drawn++; 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; backgrounds++; 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; last_seen = last; }

static void press(int widget_id, size_t index, int button, float x, float y, void* user_data) { (void)widget_id; (void)user_data; presses++; 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;
n_gui_process_event(gui, ev);

}

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;
n_gui_process_event(gui, ev);

}

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));
cells_drawn = 0;
n_gui_draw(gui);
al_set_target_bitmap(NULL);
al_destroy_bitmap(target);

}

int main(int argc, char** argv) { N_GUI_GRID_CALLBACKS cb; size_t at = 0; float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;

set_log_level(LOG_ERR);
if (process_args(argc, argv) != 0) {
    fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
    return 2;
}
set_log_level(log_level);
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;
N_GUI_CTX* gui = n_gui_new_ctx(font);
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);
n_gui_window_set_flags(gui, win, N_GUI_WIN_FRAMELESS);
n_gui_open_window(gui, win);

memset(&cb, 0, sizeof(cb));
expect_true("a grid without draw_cell is refused", n_gui_grid_new(gui, win, &cb, NULL) < 0);
cb.draw_cell = draw_cell;
cb.draw_background = draw_background;
cb.visible = visible;
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 */
draw(gui);
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 */
n_gui_grid_set_count(gui, grid, 20);
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);
draw(gui);
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);
n_gui_grid_scroll_to(gui, grid, 0);
expect_true("scrolling to the first cell goes back to the top", n_gui_grid_get_scroll(gui, grid) == 0.0f);
n_gui_grid_scroll_to(gui, grid, 19);
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);
n_gui_grid_set_count(gui, grid, 18);
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 245.0f, 190.0f, 0, 0);
n_gui_grid_scroll_to(gui, grid, 17);
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 */
n_gui_grid_set_count(gui, grid, 3);
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);
n_gui_grid_set_visible(gui, grid, 0);
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.draw_cell = draw_cell;
cb.wheel = wheel;
zoom_gui = gui;
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);

n_gui_destroy_ctx(&gui);
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;

}


_Filename: ex_gui_grid.c_