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

a popup menu with submenus

a popup menu with submenus

Author
Castagnier Mickael
Version
1.0
Date
29/09/2026

Headless: a popup opens where it is asked and is pulled back inside the display; resting the pointer on a submenu's row opens it beside the row, on the left when there is no room on the right; another row closes it and a separator does not; a press on an item closes the popup and calls back with its tag; a disabled item does nothing; a hidden entry takes no room, nor does a submenu whose entries are all hidden, nor a separator left at an end; the heading takes a row; the hit test covers the open levels.

#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 picks = 0, picked_tag = 0;
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;
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_POPUP_FN
static void on_pick(N_GUI_POPUP* popup, int tag, void* user_data) {
(void)user_data;
picks++;
picked_tag = tag;
expect_true("the popup is closed before the callback", !n_gui_popup_is_open(popup));
}
static void feed(N_GUI_CTX* gui, int type, float x, float y, int button) {
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 ? 1 : 0;
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);
}
static const N_GUI_WINDOW* level_window(N_GUI_CTX* gui, const N_GUI_POPUP* popup, int level) {
int id = n_gui_popup_window(popup, level);
if (id < 0 || !n_gui_window_is_open(gui, id)) return NULL;
return n_gui_get_window(gui, id);
}
int main(int argc, char** argv) {
const N_GUI_WINDOW* root = NULL;
const N_GUI_WINDOW* sub = NULL;
float row_h = 0.0f, root_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);
/* a row is the font's line and four pixels above and below it */
row_h = (float)al_get_font_line_height(font) + 8.0f;
expect_true("a popup needs a callback", n_gui_popup_new(gui, 1.0f, NULL, NULL) == NULL);
N_GUI_POPUP* popup = n_gui_popup_new(gui, 1.0f, on_pick, NULL);
if (!popup) return 1;
int a = n_gui_popup_add_item(popup, N_GUI_POPUP_ROOT, "Alpha", 1, "the first");
int s = n_gui_popup_add_submenu(popup, N_GUI_POPUP_ROOT, "Some more", NULL);
int b = n_gui_popup_add_item(popup, s, "Bravo", 2, NULL);
int c = n_gui_popup_add_item(popup, s, "Charlie", 3, NULL);
int h = n_gui_popup_add_item(popup, N_GUI_POPUP_ROOT, "Hidden", 4, NULL);
int e = n_gui_popup_add_submenu(popup, N_GUI_POPUP_ROOT, "Empty", NULL);
int x = n_gui_popup_add_item(popup, e, "X-ray", 5, NULL);
expect_true("entries are made", a > 0 && s > 0 && b > 0 && c > 0 && h > 0 && e > 0 && x > 0 && trailing > 0);
expect_true("an item is not a level", n_gui_popup_add_item(popup, a, "no", 9, NULL) < 0);
n_gui_popup_set_shown(popup, h, 0);
n_gui_popup_set_shown(popup, x, 0);
expect_true("closed before it is opened", !n_gui_popup_is_open(popup) && n_gui_popup_window(popup, N_GUI_POPUP_ROOT) < 0);
expect_true("it opens", n_gui_popup_open(popup, 10.0f, 10.0f) == 0 && n_gui_popup_is_open(popup));
expect_true("where it was asked", root && root->x == 10.0f && root->y == 10.0f);
/* Alpha, the separator (7), Some more; not Hidden, not Empty whose one
* entry is hidden, not the trailing separator; 4 below the last row */
root_h = row_h + 7.0f + row_h + 4.0f;
expect_true("hidden entries, an empty submenu and a trailing separator take no room", root && root->h == root_h);
/* rest on Some more: its level opens beside it, level with the row */
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 20.0f, 10.0f + row_h + 7.0f + 4.0f, 0);
sub = level_window(gui, popup, s);
expect_true("resting on a submenu opens it", sub != NULL);
expect_true("to the right, overlapping by two", sub && root && sub->x == root->x + root->w - 2.0f);
expect_true("its first row level with the row that opened it", sub && sub->y == 10.0f + row_h + 7.0f);
expect_true("the hit test covers the submenu", sub && n_gui_popup_hit(popup, sub->x + 5.0f, sub->y + 5.0f));
expect_true("and nothing else", !n_gui_popup_hit(popup, 399.0f, 299.0f));
/* crossing the separator keeps it, another row closes it */
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 20.0f, 10.0f + row_h + 3.0f, 0);
expect_true("a separator keeps the submenu open", level_window(gui, popup, s) != NULL);
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 20.0f, 10.0f + 4.0f, 0);
expect_true("another row closes it", level_window(gui, popup, s) == NULL);
/* a disabled item and a hidden submenu do nothing */
feed(gui, ALLEGRO_EVENT_MOUSE_AXES, 20.0f, 10.0f + row_h + 7.0f + 4.0f, 0);
expect_true("a disabled item is not pressed", n_gui_popup_press(popup, c) == 0 && picks == 0);
expect_true("an entry that is not shown is not pressed", n_gui_popup_press(popup, e) == 0);
/* a press on Bravo picks it */
sub = level_window(gui, popup, s);
if (sub) feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, sub->x + 10.0f, sub->y + 4.0f, 1);
expect_true("a press on an item picks it", picks == 1 && picked_tag == 2);
expect_true("and closes every level", !n_gui_popup_is_open(popup) && level_window(gui, popup, s) == NULL);
feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, 0.0f, 0.0f, 1);
/* near the corner: pulled back inside, and the submenu goes left */
n_gui_popup_set_title(popup, "3 frames");
n_gui_popup_set_shown(popup, h, 1);
expect_true("it opens near the corner", n_gui_popup_open(popup, 395.0f, 295.0f) == 0);
expect_true("pulled back inside the display", root && root->x + root->w <= 400.0f && root->y + root->h <= 300.0f);
expect_true("the heading and the shown entry take their rows", root && root->h == root_h + row_h + 7.0f + row_h);
expect_true("a pressed submenu opens", n_gui_popup_press(popup, s) == 1);
sub = level_window(gui, popup, s);
expect_true("on the left when the right has no room", sub && root && sub->x < root->x);
expect_true("an item pressed through the API is picked", n_gui_popup_press(popup, a) == 1 && picks == 2 && picked_tag == 1);
n_gui_popup_open(popup, 10.0f, 10.0f);
expect_true("close closes", !n_gui_popup_is_open(popup));
expect_true("free clears the handle", popup == NULL);
al_destroy_font(font);
if (failures) {
fprintf(stderr, "ex_gui_popup: %d failure(s)\n", failures);
return 1;
}
printf("ex_gui_popup: 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_CTX * gui
static void draw(N_GUI_CTX *gui)
draw the whole context into a memory bitmap
static int picks
what the callback saw
static int picked_tag
static void on_pick(N_GUI_POPUP *popup, int tag, void *user_data)
the pick callback
static const N_GUI_WINDOW * level_window(N_GUI_CTX *gui, const N_GUI_POPUP *popup, int level)
a level's window, or NULL when it is not open
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()
float x
position x on screen
Definition n_gui.h:1312
float w
window width
Definition n_gui.h:1316
float h
window height
Definition n_gui.h:1318
float y
position y on screen
Definition n_gui.h:1314
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
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
void n_gui_draw(N_GUI_CTX *ctx)
draw all visible GUI windows and widgets
Definition n_gui.c:11735
int n_gui_window_is_open(N_GUI_CTX *ctx, int window_id)
Check if a window is currently visible.
Definition n_gui.c:2506
N_GUI_WINDOW * n_gui_get_window(N_GUI_CTX *ctx, int window_id)
Get a window pointer by id.
Definition n_gui.c:1397
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
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
A pseudo window that contains widgets.
Definition n_gui.h:1298
#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
int n_gui_popup_window(const N_GUI_POPUP *popup, int level)
The window of a level, for an application that wants to test it.
void n_gui_popup_set_title(N_GUI_POPUP *popup, const char *text)
Set the heading line over the top level.
void n_gui_popup_close(N_GUI_POPUP *popup)
Close every level without picking anything.
int n_gui_popup_press(N_GUI_POPUP *popup, int entry)
Press the row of an entry, as a click on it would.
int n_gui_popup_open(N_GUI_POPUP *popup, float x, float y)
Open the top level with its corner at a point.
int n_gui_popup_add_item(N_GUI_POPUP *popup, int parent, const char *text, int tag, const char *tip)
Add an item at the end of a level.
int n_gui_popup_add_separator(N_GUI_POPUP *popup, int parent)
Add a separator at the end of a level.
int n_gui_popup_hit(const N_GUI_POPUP *popup, float x, float y)
Is a point on any of its open levels.
#define N_GUI_POPUP_ROOT
the top level, as a parent for n_gui_popup_add_item() and the others
Definition n_gui_popup.h:68
void n_gui_popup_set_shown(N_GUI_POPUP *popup, int entry, int shown)
Show or hide an entry.
int n_gui_popup_add_submenu(N_GUI_POPUP *popup, int parent, const char *text, const char *tip)
Add a submenu at the end of a level.
N_GUI_POPUP * n_gui_popup_new(N_GUI_CTX *ctx, float scale, N_GUI_POPUP_FN on_pick, void *user_data)
Make an empty popup menu in ctx, closed.
int n_gui_popup_is_open(const N_GUI_POPUP *popup)
Is its top level on screen.
void n_gui_popup_set_enabled(N_GUI_POPUP *popup, int entry, int enabled)
Enable or grey out an entry.
void n_gui_popup_free(N_GUI_POPUP **popup)
Close the popup and release the handle.
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
the popup
A popup menu with submenus for N_GUI, the kind a right click opens.
Generic log system.