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

N_GUI_KVTABLE adaptive-resize regression test.

N_GUI_KVTABLE adaptive-resize regression test. Headless test (no display, builtin font) that exercises the kvtable layout across N_GUI_WIN_RESIZE_SCALE display resizes and clear/add cycles. Asserts:

Author
Castagnier Mickael
Version
1.0
Date
20/05/26
/*
* 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
*/
#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nilorea/n_log.h"
#include "nilorea/n_gui.h"
static int log_level = LOG_ERR;
static int process_args(int argc, char** argv) {
int opt;
while ((opt = getopt(argc, argv, "V:")) != -1) {
switch (opt) {
case 'V':
if (!strcmp("LOG_NULL", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_ERR", optarg))
else if (!strcmp("LOG_DEBUG", optarg))
else
return -1;
break;
default:
return -1;
}
}
return 0;
}
static int failures = 0;
static void expect_close(const char* label, float got, float want, float tol) {
float d = got - want;
if (d < 0) d = -d;
if (d > tol) {
n_log(LOG_ERR, "%s: got %.3f, expected %.3f (tol %.3f)",
label, got, want, tol);
} else {
n_log(LOG_INFO, "%s: %.3f ~= %.3f OK", label, got, want);
}
}
static void expect_true(const char* label, int cond) {
if (!cond) {
n_log(LOG_ERR, "%s: condition false", label);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
int main(int argc, char** argv) {
if (process_args(argc, argv) != 0) {
fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 2;
}
if (!al_init()) {
n_log(LOG_ERR, "al_init failed");
return 1;
}
if (!al_init_font_addon()) {
n_log(LOG_ERR, "al_init_font_addon failed");
return 1;
}
/* Builtin font does not need a display. */
ALLEGRO_FONT* font = al_create_builtin_font();
if (!font) {
n_log(LOG_ERR, "al_create_builtin_font failed");
return 1;
}
if (!gui) {
n_log(LOG_ERR, "n_gui_new_ctx failed");
al_destroy_font(font);
return 1;
}
/* Reference display matches the parent panel size used in Hook's
request panel (params/headers tab). Set display size BEFORE
switching to adaptive mode so ref_display_w/h is captured. */
n_gui_set_display_size(gui, 800.0f, 258.0f);
int win = n_gui_add_window(gui, "Panel", 0.0f, 0.0f, 800.0f, 258.0f);
expect_true("window created", win >= 0);
N_GUI_KVTABLE* kv = n_gui_kvtable_create(gui, win, 28.0f, 4.0f, NULL, NULL);
expect_true("kvtable created", kv != NULL);
/* Initial: one empty row, like Hook does. */
n_gui_kvtable_add_row(kv, "", "", "", 1);
expect_true("add button widget exists", add_btn != NULL);
expect_true("row 0 key widget exists", row0_key != NULL);
/* At initial size, "+" button must sit strictly below row 0. */
expect_true("+ below row 0 (initial)",
add_btn->y >= row0_key->y + row0_key->h - 0.5f);
/* Resize the display: window scales by 2x in both dimensions. */
n_gui_set_display_size(gui, 1600.0f, 516.0f);
/* After resize via apply_adaptive_resize, fetch fresh pointers
(same widgets, but their x/y/w/h have moved). */
add_btn = n_gui_get_widget(gui, kv->btn_add);
row0_key = n_gui_get_widget(gui, kv->rows[0].key_id);
expect_true("+ still exists after resize", add_btn != NULL);
/* The bug was: after resize, "+" reverted to the same y as row 0.
It should remain at or below row 0's bottom edge. */
expect_true("+ below row 0 (after resize)",
add_btn->y >= row0_key->y + row0_key->h - 0.5f);
/* Record row 0's scaled height for later comparison. */
float scaled_row_h = row0_key->h;
n_log(LOG_INFO, "row 0 scaled height after resize: %.3f", scaled_row_h);
/* Clear all rows then add new ones, mimicking Hook's "delete all
lines then add new lines" path on a maximized window. */
for (int i = kv->nb_rows - 1; i >= 0; i--) {
}
n_gui_kvtable_add_row(kv, "k", "v", "d", 1);
n_gui_kvtable_add_row(kv, "k2", "v2", "d2", 1);
add_btn = n_gui_get_widget(gui, kv->btn_add);
/* "+" must NOT overlap the "Key" header label. */
expect_true("+ button below Key header (after clear+add)",
add_btn->y >= lbl_key->y + lbl_key->h - 0.5f);
/* New rows must match the scaled row height (within rounding tolerance). */
int last_active = -1;
for (int i = 0; i < kv->nb_rows; i++) {
if (kv->rows[i].active) last_active = i;
}
expect_true("at least one active row exists", last_active >= 0);
if (last_active >= 0) {
N_GUI_WIDGET* new_key = n_gui_get_widget(gui, kv->rows[last_active].key_id);
expect_close("new row height matches scaled row height",
new_key->h, scaled_row_h, 0.5f);
/* "+" sits immediately below the last active row. */
expect_true("+ directly below last active row",
add_btn->y >= new_key->y + new_key->h - 0.5f);
}
/* Public relayout entry-point smoke test. */
add_btn = n_gui_get_widget(gui, kv->btn_add);
expect_true("+ still placed after manual relayout",
add_btn != NULL && add_btn->y > 0.0f);
/* Reload-safe clear: repopulating the same row count reuses the freed
slots instead of growing the widget array toward N_GUI_KV_MAX. */
expect_true("clear -> 0 active rows", n_gui_kvtable_get_count(kv) == 0);
n_gui_kvtable_add_row(kv, "a", "1", "", 1);
n_gui_kvtable_add_row(kv, "b", "2", "", 1);
n_gui_kvtable_add_row(kv, "c", "3", "", 1);
int rows_after_first_fill = kv->nb_rows;
expect_true("3 active rows after fill", n_gui_kvtable_get_count(kv) == 3);
n_gui_kvtable_add_row(kv, "a", "1", "", 1);
n_gui_kvtable_add_row(kv, "b", "2", "", 1);
n_gui_kvtable_add_row(kv, "c", "3", "", 1);
expect_true("3 active rows after reload", n_gui_kvtable_get_count(kv) == 3);
expect_true("reload reuses slots (nb_rows does not grow)",
kv->nb_rows == rows_after_first_fill);
/* Single-column mode: only the key column and the remove button are shown;
the value/description/enabled widgets (and their headers) are hidden, and
the key column widens to fill the row. */
n_gui_kvtable_set_placeholders(kv, "host, .suffix, * or re:regex", NULL);
n_gui_kvtable_add_row(kv, ".example.com", "", "", 1);
{
int active = -1;
for (int i = 0; i < kv->nb_rows; i++) {
if (kv->rows[i].active) {
active = i;
break;
}
}
expect_true("single-column: one active row", active >= 0);
if (active >= 0) {
const N_GUI_WINDOW* wwin = n_gui_get_window(gui, win);
expect_true("single-column: key visible", wk && wk->visible);
expect_true("single-column: remove visible", wr && wr->visible);
expect_true("single-column: value hidden", wv && !wv->visible);
expect_true("single-column: description hidden", wd && !wd->visible);
expect_true("single-column: enabled hidden", we && !we->visible);
expect_true("single-column: value header hidden",
expect_true("single-column: key column widened",
wk && wwin && wk->w > wwin->w * 0.5f);
}
}
/* Top offset reserves space above the header row (for a toolbar/heading in
a shared window); the header (and rows) shift down by the scaled offset. */
{
float y_before = lk_before ? lk_before->y : 0.0f;
expect_true("top_offset pushes the header row down",
lk_after && lk_after->y > y_before + 10.0f);
}
/* Row heights follow the window only when the window scales its widgets. The
table above lives in a SCALE-policy window, so its rows scaled with the
display resize (asserted earlier). A MOVE-policy window instead keeps the
widgets' pixel layout, so the rows must keep their design height and a taller
window simply shows more of them, rather than the same rows drawn fatter. */
{
int win2 = n_gui_add_window(gui, "MovePanel", 0.0f, 0.0f, 800.0f, 258.0f);
N_GUI_KVTABLE* kv2 = n_gui_kvtable_create(gui, win2, 24.0f, 8.0f, NULL, NULL);
expect_true("move-policy table created", kv2 != NULL);
if (kv2) {
n_gui_kvtable_add_row(kv2, "k", "v", "d", 1);
float h_before = r0 ? r0->h : 0.0f;
expect_true("move-policy row has a height", h_before > 0.0f);
/* grow the window vertically only, then relayout */
if (w2) w2->h *= 2.0f;
r0 = n_gui_get_widget(gui, kv2->rows[0].key_id);
expect_close("move-policy row keeps its height", r0 ? r0->h : -1.0f, h_before, 0.5f);
/* a row added after the growth matches the existing ones */
n_gui_kvtable_add_row(kv2, "k2", "v2", "d2", 1);
expect_close("move-policy new row matches", r1 ? r1->h : -1.0f, h_before, 0.5f);
/* widths still follow the window: widen it and the key column grows */
N_GUI_WIDGET* k_before = n_gui_get_widget(gui, kv2->rows[0].key_id);
float w_before = k_before ? k_before->w : 0.0f;
if (w2) w2->w *= 2.0f;
expect_true("move-policy key column still widens",
k_after && k_after->w > w_before + 1.0f);
}
}
al_destroy_font(font);
if (failures > 0) {
n_log(LOG_ERR, "ex_gui_kvtable: %d check(s) failed", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_gui_kvtable: all checks passed");
return 0;
}
static int failures
int main(void)
void process_args(int argc, char **argv)
Definition ex_common.c:48
int log_level
Definition ex_fluid.c:60
static void expect_true(const char *label, int cond)
static N_GUI_CTX * gui
static void expect_close(const char *label, float got, float want, float tol)
float h
height
Definition n_gui.h:894
int visible
visibility flag
Definition n_gui.h:898
int nb_rows
total rows (including removed)
Definition n_gui.h:2566
int btn_add
button widget ID for "+" add row
Definition n_gui.h:2577
float w
width
Definition n_gui.h:892
int remove_id
button widget ID for the remove action
Definition n_gui.h:2557
float w
window width
Definition n_gui.h:1009
N_GUI_KV_ROW rows[128]
row storage
Definition n_gui.h:2565
int key_id
textarea widget ID for the key
Definition n_gui.h:2553
int desc_id
textarea widget ID for the description
Definition n_gui.h:2555
int lbl_key
label widget ID for "Key" header
Definition n_gui.h:2573
float h
window height
Definition n_gui.h:1011
float y
position y relative to parent window
Definition n_gui.h:890
int active
1 if active, 0 if removed
Definition n_gui.h:2558
int lbl_value
label widget ID for "Value" header
Definition n_gui.h:2574
int enabled_id
checkbox widget ID for the enabled toggle
Definition n_gui.h:2556
int value_id
textarea widget ID for the value
Definition n_gui.h:2554
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
Set the display (viewport) size for global scrollbar computation.
Definition n_gui.c:9034
void n_gui_kvtable_set_top_offset(N_GUI_KVTABLE *table, float top_offset)
reserve top_offset unscaled pixels above the header row so other widgets (a toolbar,...
Definition n_gui.c:14166
void n_gui_kvtable_relayout(N_GUI_KVTABLE *table)
re-apply layout to all KV table widgets (positions, sizes, normalized coords).
Definition n_gui.c:14176
int n_gui_kvtable_get_count(const N_GUI_KVTABLE *table)
get the number of active rows
Definition n_gui.c:14172
N_GUI_KVTABLE * n_gui_kvtable_create(N_GUI_CTX *ctx, int window_id, float row_height, float padding, void(*on_remove)(int, void *), void *user_data)
create a KV table in an existing window
Definition n_gui.c:14004
#define N_GUI_WIN_RESIZE_SCALE
reposition AND resize proportionally, child widgets scale too
Definition n_gui.h:290
void n_gui_kvtable_free(N_GUI_KVTABLE **table)
free a KV table (does not destroy the N_GUI widgets)
Definition n_gui.c:14180
void n_gui_window_set_resize_policy(N_GUI_CTX *ctx, int window_id, int policy)
Set per-window resize policy (N_GUI_WIN_RESIZE_NONE / _MOVE / _SCALE).
Definition n_gui.c:8936
void n_gui_kvtable_clear(N_GUI_KVTABLE *table)
remove every row at once.
Definition n_gui.c:14126
void n_gui_kvtable_set_placeholders(N_GUI_KVTABLE *table, const char *key_hint, const char *value_hint)
set the placeholder/hint text shown in each row's key (and value) textarea while it is empty.
Definition n_gui.c:14152
void n_gui_kvtable_remove_row(N_GUI_KVTABLE *table, int row_index)
remove a row by index (hides widgets, marks inactive)
Definition n_gui.c:14112
N_GUI_WIDGET * n_gui_get_widget(N_GUI_CTX *ctx, int widget_id)
Get a widget pointer by id.
Definition n_gui.c:3028
N_GUI_WINDOW * n_gui_get_window(N_GUI_CTX *ctx, int window_id)
Get a window pointer by id.
Definition n_gui.c:1182
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:1063
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:903
void n_gui_set_resize_mode(N_GUI_CTX *ctx, int mode)
Set context-level resize mode: N_GUI_RESIZE_VIRTUAL (default) or N_GUI_RESIZE_ADAPTIVE.
Definition n_gui.c:8897
void n_gui_kvtable_set_columns(N_GUI_KVTABLE *table, int show_value, int show_desc, int show_enabled)
choose which optional columns are shown: Value, Description, and the enabled checkbox.
Definition n_gui.c:14144
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:995
int n_gui_kvtable_add_row(N_GUI_KVTABLE *table, const char *key, const char *value, const char *description, int enabled)
add a row to the KV table
Definition n_gui.c:14053
#define N_GUI_WIN_RESIZE_MOVE
reposition proportionally, keep pixel size
Definition n_gui.h:288
#define N_GUI_RESIZE_ADAPTIVE
virtual size tracks display; windows adapt per their resize_policy
Definition n_gui.h:282
The top-level GUI context that holds all windows.
Definition n_gui.h:1354
key-value table built from textareas, checkboxes, and buttons
Definition n_gui.h:2562
A single GUI widget.
Definition n_gui.h:882
A pseudo window that contains widgets.
Definition n_gui.h:999
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_DEBUG
debug-level messages
Definition n_log.h:84
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:80
#define LOG_NULL
no log output
Definition n_log.h:46
#define LOG_INFO
informational
Definition n_log.h:82
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.