Table of Contents
- src/n_gui_grid.c
- Structures
- Functions
- Detailed Description
- Functions Documentation
- function _grid_sync_bar
- function _grid_step
- function _grid_rows_for
- function _grid_place
- function _grid_on_bar
- function _grid_of
- function _grid_mouse
- function _grid_index_local
- function _grid_draw
- function _grid_destroy
- function _grid_content_h_for
- function _grid_columns_for
- function _grid_clamp
- Source code
src/n_gui_grid.c
A virtualised grid of cells for N_GUI, scrolled vertically. More...
Structures
| Name | |
|---|---|
| struct | _N_GUI_GRID a grid's state, owned by its custom widget |
Functions
| Name | |
|---|---|
| void | _grid_sync_bar(_N_GUI_GRID * g) helper: tell the scrollbar where the view is |
| float | _grid_step(const _N_GUI_GRID * g) helper: one row and the gap below it |
| size_t | _grid_rows_for(const _N_GUI_GRID * g, int columns) helper: rows the cells make at a column count |
| void | _grid_place(_N_GUI_GRID * g) helper: decide whether the bar shows, and place the grid and the bar |
| void | _grid_on_bar(int widget_id, double pos, void * user_data) the scrollbar's callback: the view follows a drag of the bar |
| _N_GUI_GRID * | _grid_of(N_GUI_CTX * ctx, int widget_id) helper: find a grid's state behind a widget id, or NULL |
| int | _grid_mouse(int widget_id, const N_GUI_MOUSE_EVENT * ev, void * user_data) custom mouse callback |
| int | _grid_index_local(const _N_GUI_GRID * g, float w, float lx, float ly, size_t * index) helper: the cell at a widget-local point, the rules of n_gui_grid_index_at |
| void | _grid_draw(N_GUI_WIDGET * widget, float x, float y, ALLEGRO_FONT * font, void * user_data) custom draw callback |
| void | _grid_destroy(void * user_data) the widget's destroy callback. |
| float | _grid_content_h_for(const _N_GUI_GRID * g, float w) helper: content height at a width: the gap above the first row, then each row with its gap |
| int | _grid_columns_for(const _N_GUI_GRID * g, float w) helper: cells across a width |
| void | _grid_clamp(_N_GUI_GRID * g) helper: keep the view inside the content |
Detailed Description
A virtualised grid of cells for N_GUI, scrolled vertically.
Author: Castagnier Mickael
Version: 1.0
Date: 24/09/2026
Functions Documentation
function _grid_sync_bar
static void _grid_sync_bar(
_N_GUI_GRID * g
)
helper: tell the scrollbar where the view is
function _grid_step
static float _grid_step(
const _N_GUI_GRID * g
)
helper: one row and the gap below it
function _grid_rows_for
static size_t _grid_rows_for(
const _N_GUI_GRID * g,
int columns
)
helper: rows the cells make at a column count
function _grid_place
static void _grid_place(
_N_GUI_GRID * g
)
helper: decide whether the bar shows, and place the grid and the bar
function _grid_on_bar
static void _grid_on_bar(
int widget_id,
double pos,
void * user_data
)
the scrollbar's callback: the view follows a drag of the bar
function _grid_of
static _N_GUI_GRID * _grid_of(
N_GUI_CTX * ctx,
int widget_id
)
helper: find a grid's state behind a widget id, or NULL
function _grid_mouse
static int _grid_mouse(
int widget_id,
const N_GUI_MOUSE_EVENT * ev,
void * user_data
)
custom mouse callback
function _grid_index_local
static int _grid_index_local(
const _N_GUI_GRID * g,
float w,
float lx,
float ly,
size_t * index
)
helper: the cell at a widget-local point, the rules of n_gui_grid_index_at
function _grid_draw
static void _grid_draw(
N_GUI_WIDGET * widget,
float x,
float y,
ALLEGRO_FONT * font,
void * user_data
)
custom draw callback
function _grid_destroy
static void _grid_destroy(
void * user_data
)
the widget's destroy callback.
The scrollbar is a widget of the same window and is only ever destroyed with it, by n_gui_destroy_ctx(), which calls no scrollbar callback, so nothing reaches the state once it is freed.
function _grid_content_h_for
static float _grid_content_h_for(
const _N_GUI_GRID * g,
float w
)
helper: content height at a width: the gap above the first row, then each row with its gap
function _grid_columns_for
static int _grid_columns_for(
const _N_GUI_GRID * g,
float w
)
helper: cells across a width
function _grid_clamp
static void _grid_clamp(
_N_GUI_GRID * g
)
helper: keep the view inside the content
Source code
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef HAVE_ALLEGRO
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_gui_grid.h"
typedef struct {
N_GUI_CTX* ctx;
int id;
int bar;
N_GUI_GRID_CALLBACKS cb;
void* user_data;
size_t count;
float cell_w;
float cell_h;
float gap;
float scroll;
float outer_x, outer_y, outer_w, outer_h;
float bar_w;
float inner_w;
int visible;
int bar_showing;
int placing;
} _N_GUI_GRID;
static int _grid_columns_for(const _N_GUI_GRID* g, float w) {
int columns = (int)((w + g->gap) / (g->cell_w + g->gap));
return columns > 0 ? columns : 1;
}
static size_t _grid_rows_for(const _N_GUI_GRID* g, int columns) {
return (g->count + (size_t)columns - 1) / (size_t)columns;
}
static float _grid_step(const _N_GUI_GRID* g) {
return g->cell_h + g->gap;
}
static float _grid_content_h_for(const _N_GUI_GRID* g, float w) {
return g->gap + (float)_grid_rows_for(g, _grid_columns_for(g, w)) * _grid_step(g);
}
static void _grid_clamp(_N_GUI_GRID* g) {
float most = _grid_content_h_for(g, g->inner_w) - g->outer_h;
if (most < 0.0f) most = 0.0f;
if (g->scroll > most) g->scroll = most;
if (g->scroll < 0.0f) g->scroll = 0.0f;
}
static void _grid_sync_bar(_N_GUI_GRID* g) {
if (!g->bar_showing) return;
g->placing = 1;
n_gui_scrollbar_set_sizes(g->ctx, g->bar, _grid_content_h_for(g, g->inner_w), g->outer_h);
n_gui_scrollbar_set_pos(g->ctx, g->bar, g->scroll);
g->placing = 0;
}
static void _grid_place(_N_GUI_GRID* g) {
/* Asked of the whole width first. A grid that needs a bar still needs
* one once the bar has its strip, and one that does not cannot be made
* to by narrowing it: narrowing a grid of fixed cells only adds rows.
* So one pass settles it. */
int want = g->count > 0 && _grid_content_h_for(g, g->outer_w) > g->outer_h + 0.5f;
g->inner_w = g->outer_w;
if (want) {
g->inner_w = g->outer_w - g->bar_w;
if (g->inner_w < 1.0f) g->inner_w = 1.0f;
}
n_gui_widget_set_rect(g->ctx, g->id, g->outer_x, g->outer_y, g->inner_w, g->outer_h);
n_gui_widget_set_rect(g->ctx, g->bar, g->outer_x + g->inner_w, g->outer_y, g->bar_w > 0.0f ? g->bar_w : 1.0f, g->outer_h);
/* hidden with the grid too: a bar left over a hidden grid is drawn on
* whatever took its place */
n_gui_set_widget_visible(g->ctx, g->bar, want && g->visible);
g->bar_showing = want;
if (!want) g->scroll = 0.0f;
_grid_clamp(g);
_grid_sync_bar(g);
n_gui_mark_dirty(g->ctx);
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_DRAW
// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
static void _grid_draw(N_GUI_WIDGET* widget, float x, float y, ALLEGRO_FONT* font, void* user_data) {
const _N_GUI_GRID* g = (const _N_GUI_GRID*)user_data;
int cx = 0, cy = 0, cw = 0, ch = 0;
int columns = 0;
size_t rows = 0, first_row = 0, last_row = 0, row = 0;
float step = 0.0f;
(void)font;
if (!widget || !g) return;
if (g->cb.draw_background) g->cb.draw_background(g->id, x, y, widget->w, widget->h, g->user_data);
if (!g->count || !g->cb.draw_cell) return;
columns = _grid_columns_for(g, widget->w);
rows = _grid_rows_for(g, columns);
step = _grid_step(g);
/* The rows the view touches, one either side of the arithmetic: the
* clipping rectangle cuts the overhang, and an off by one here would
* show as a strip of empty grid. */
first_row = g->scroll > g->gap ? (size_t)((g->scroll - g->gap) / step) : 0;
last_row = first_row + (size_t)(widget->h / step) + 1;
if (last_row >= rows) last_row = rows - 1;
/* the cells are cut at the grid, inside whatever the window cuts at */
al_get_clipping_rectangle(&cx, &cy, &cw, &ch);
{
int x0 = (int)x > cx ? (int)x : cx;
int y0 = (int)y > cy ? (int)y : cy;
int x1 = (int)(x + widget->w) < cx + cw ? (int)(x + widget->w) : cx + cw;
int y1 = (int)(y + widget->h) < cy + ch ? (int)(y + widget->h) : cy + ch;
if (x1 <= x0 || y1 <= y0) return;
al_set_clipping_rectangle(x0, y0, x1 - x0, y1 - y0);
}
for (row = first_row; row <= last_row; row++) {
int column = 0;
for (column = 0; column < columns; column++) {
size_t index = row * (size_t)columns + (size_t)column;
if (index >= g->count) break;
g->cb.draw_cell(g->id, index, x + g->gap + (float)column * (g->cell_w + g->gap), y + g->gap + (float)row * step - g->scroll, g->cell_w, g->cell_h, g->user_data);
}
}
al_set_clipping_rectangle(cx, cy, cw, ch);
if (g->cb.visible) {
/* one row beyond the bottom, so a steady scroll does not outrun
* whatever prepares the cells */
size_t first = first_row * (size_t)columns;
size_t last = (last_row + 2) * (size_t)columns;
if (last > g->count) last = g->count;
g->cb.visible(g->id, first, last, g->user_data);
}
}
static int _grid_index_local(const _N_GUI_GRID* g, float w, float lx, float ly, size_t* index) {
int columns = _grid_columns_for(g, w);
long column = (long)floorf((lx - g->gap) / (g->cell_w + g->gap));
float content_y = ly - g->gap + g->scroll;
long row = content_y < 0.0f ? 0 : (long)floorf(content_y / _grid_step(g));
size_t at = 0;
if (column < 0) column = 0;
if (column >= columns) column = columns - 1;
at = (size_t)row * (size_t)columns + (size_t)column;
if (at >= g->count) return FALSE;
*index = at;
return TRUE;
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_MOUSE
// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
static int _grid_mouse(int widget_id, const N_GUI_MOUSE_EVENT* ev, void* user_data) {
_N_GUI_GRID* g = (_N_GUI_GRID*)user_data;
const N_GUI_WIDGET* wgt = g ? n_gui_get_widget(g->ctx, widget_id) : NULL;
if (!g || !wgt) return 0;
if (ev->type == N_GUI_MOUSE_WHEEL) {
/* the caller first: a turn it takes is not a scroll */
if (g->cb.wheel && g->cb.wheel(widget_id, ev->dz, ev->modifiers, g->user_data)) {
n_gui_mark_dirty(g->ctx);
return 1;
}
/* one notch, one row: the unit the grid is read in */
g->scroll -= (float)ev->dz * _grid_step(g);
_grid_clamp(g);
_grid_sync_bar(g);
n_gui_mark_dirty(g->ctx);
return 1;
}
if (ev->type == N_GUI_MOUSE_DOWN) {
size_t index = 0;
float sx = 0.0f, sy = 0.0f;
if (!_grid_index_local(g, wgt->w, ev->x, ev->y, &index)) return 0;
if (!g->cb.press) return 1;
n_gui_widget_screen_rect(g->ctx, widget_id, &sx, &sy, NULL, NULL);
g->cb.press(widget_id, index, ev->button, sx + ev->x, sy + ev->y, g->user_data);
return 1;
}
return 0;
}
static void _grid_on_bar(int widget_id, double pos, void* user_data) {
_N_GUI_GRID* g = (_N_GUI_GRID*)user_data;
(void)widget_id;
/* a bar being told where it is reports it as a move; following that
* would place the bar again, and the grid would walk under the hand */
if (!g || g->placing) return;
g->scroll = (float)pos;
_grid_clamp(g);
n_gui_mark_dirty(g->ctx);
}
static void _grid_destroy(void* user_data) {
free(user_data);
}
static _N_GUI_GRID* _grid_of(N_GUI_CTX* ctx, int widget_id) {
const N_GUI_WIDGET* wgt = ctx ? n_gui_get_widget(ctx, widget_id) : NULL;
const N_GUI_CUSTOM_DATA* cd = NULL;
if (!wgt || wgt->type != N_GUI_TYPE_CUSTOM || !wgt->data) return NULL;
cd = (const N_GUI_CUSTOM_DATA*)wgt->data;
/* the paint callback says what the widget is */
if (cd->draw != _grid_draw) return NULL;
return (_N_GUI_GRID*)cd->user_data;
}
int n_gui_grid_new(N_GUI_CTX* ctx, int window_id, const N_GUI_GRID_CALLBACKS* callbacks, void* user_data) {
_N_GUI_GRID* g = NULL;
__n_assert(ctx, return -1);
__n_assert(callbacks, return -1);
if (!callbacks->draw_cell) {
n_log(LOG_ERR, "grid: draw_cell is required");
return -1;
}
g = (_N_GUI_GRID*)calloc(1, sizeof(*g));
if (!g) return -1;
g->ctx = ctx;
g->cb = *callbacks;
g->user_data = user_data;
g->cell_w = 64.0f;
g->cell_h = 64.0f;
g->gap = 4.0f;
g->outer_w = 16.0f;
g->outer_h = 16.0f;
g->inner_w = 16.0f;
g->bar_w = 12.0f;
g->visible = 1;
g->id = n_gui_add_custom(ctx, window_id, 0.0f, 0.0f, 16.0f, 16.0f, _grid_draw, g);
if (g->id < 0) {
free(g);
return -1;
}
n_gui_custom_set_destroy(ctx, g->id, _grid_destroy);
n_gui_custom_set_mouse(ctx, g->id, _grid_mouse, g);
g->bar = n_gui_add_scrollbar(ctx, window_id, 0.0f, 0.0f, 16.0f, 16.0f, N_GUI_SCROLLBAR_V, N_GUI_SHAPE_ROUNDED, 1.0, 1.0, _grid_on_bar, g);
if (g->bar < 0) {
n_log(LOG_ERR, "grid: cannot create its scrollbar");
/* the widget owns the state now; it goes when the window does */
n_gui_set_widget_visible(ctx, g->id, 0);
return -1;
}
n_gui_set_widget_visible(ctx, g->bar, 0);
return g->id;
}
void n_gui_grid_set_rect(N_GUI_CTX* ctx, int widget_id, float x, float y, float w, float h, float bar_w) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
if (!g) return;
g->outer_x = x;
g->outer_y = y;
g->outer_w = w > 1.0f ? w : 1.0f;
g->outer_h = h > 1.0f ? h : 1.0f;
g->bar_w = bar_w > 0.0f ? bar_w : 0.0f;
_grid_place(g);
}
void n_gui_grid_set_cell_size(N_GUI_CTX* ctx, int widget_id, float cell_w, float cell_h, float gap) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
if (!g || cell_w <= 0.0f || cell_h <= 0.0f) return;
g->cell_w = cell_w;
g->cell_h = cell_h;
g->gap = gap > 0.0f ? gap : 0.0f;
_grid_place(g);
}
void n_gui_grid_set_count(N_GUI_CTX* ctx, int widget_id, size_t count) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
if (!g) return;
g->count = count;
_grid_place(g);
}
void n_gui_grid_set_visible(N_GUI_CTX* ctx, int widget_id, int visible) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
if (!g) return;
g->visible = visible ? 1 : 0;
n_gui_set_widget_visible(ctx, g->id, g->visible);
n_gui_set_widget_visible(ctx, g->bar, g->visible && g->bar_showing);
n_gui_mark_dirty(ctx);
}
int n_gui_grid_columns(N_GUI_CTX* ctx, int widget_id) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
return g ? _grid_columns_for(g, g->inner_w) : 0;
}
float n_gui_grid_get_scroll(N_GUI_CTX* ctx, int widget_id) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
return g ? g->scroll : 0.0f;
}
void n_gui_grid_set_scroll(N_GUI_CTX* ctx, int widget_id, float scroll) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
if (!g) return;
g->scroll = scroll;
_grid_clamp(g);
_grid_sync_bar(g);
n_gui_mark_dirty(ctx);
}
void n_gui_grid_scroll_to(N_GUI_CTX* ctx, int widget_id, size_t index) {
_N_GUI_GRID* g = _grid_of(ctx, widget_id);
float top = 0.0f, bottom = 0.0f;
size_t row = 0;
if (!g || index >= g->count) return;
row = index / (size_t)_grid_columns_for(g, g->inner_w);
/* the first row counts from the very top, so the gap above it shows */
top = row ? g->gap + (float)row * _grid_step(g) : 0.0f;
bottom = g->gap + (float)(row + 1) * _grid_step(g);
if (top < g->scroll) g->scroll = top;
if (bottom > g->scroll + g->outer_h) g->scroll = bottom - g->outer_h;
_grid_clamp(g);
_grid_sync_bar(g);
n_gui_mark_dirty(ctx);
}
int n_gui_grid_index_at(N_GUI_CTX* ctx, int widget_id, float x, float y, size_t* index) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
float gx = 0.0f, gy = 0.0f, gw = 0.0f, gh = 0.0f;
__n_assert(index, return FALSE);
if (!g || !g->visible || !g->count) return FALSE;
if (!n_gui_widget_screen_rect(ctx, widget_id, &gx, &gy, &gw, &gh)) return FALSE;
if (x < gx || x >= gx + gw || y < gy || y >= gy + gh) return FALSE;
return _grid_index_local(g, gw, x - gx, y - gy, index);
}
int n_gui_grid_cell_rect(N_GUI_CTX* ctx, int widget_id, size_t index, float* x, float* y, float* w, float* h) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
float gx = 0.0f, gy = 0.0f, gw = 0.0f, gh = 0.0f, cx = 0.0f, cy = 0.0f;
int columns = 0;
if (!g || index >= g->count) return FALSE;
n_gui_widget_screen_rect(ctx, widget_id, &gx, &gy, &gw, &gh);
columns = _grid_columns_for(g, g->inner_w);
cx = gx + g->gap + (float)(index % (size_t)columns) * (g->cell_w + g->gap);
cy = gy + g->gap + (float)(index / (size_t)columns) * _grid_step(g) - g->scroll;
if (x) *x = cx;
if (y) *y = cy;
if (w) *w = g->cell_w;
if (h) *h = g->cell_h;
return (cy + g->cell_h > gy && cy < gy + gh) ? TRUE : FALSE;
}
int n_gui_grid_scrollbar(N_GUI_CTX* ctx, int widget_id) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
return g ? g->bar : -1;
}
int n_gui_grid_scrollbar_showing(N_GUI_CTX* ctx, int widget_id) {
const _N_GUI_GRID* g = _grid_of(ctx, widget_id);
return g ? g->bar_showing : 0;
}
#endif /* HAVE_ALLEGRO */
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB