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

n_gui buttons from the keyboard

n_gui buttons from the keyboard

Author
Castagnier Mickael
Version
1.0
Date
30/09/2026

A dialog has to be answerable without the mouse. Tab and Shift+Tab reach the buttons as well as the fields; on a focused button Enter or Space presses it and the arrows move to the next or previous button of its window in reading order, going round. Another button bound to Enter does not fire while a button has the keyboard, a mouse press elsewhere takes the keyboard back from a button, and n_gui_focus_takes_key() says which keys a focused button keeps, so an application's own shortcuts can leave them alone.

Whatever has the keyboard, a button, a slider or a list, and whether Tab or a click gave it, is drawn with an outline in the theme's active border colour; the last check draws the window into a memory bitmap and reads it.

Headless: the builtin font needs no display, the keys are events handed to n_gui_process_event(), and the drawing goes to a memory bitmap.

#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"
#include <allegro5/allegro_primitives.h>
static int log_level = LOG_ERR;
static int failures = 0;
static int pressed[64];
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 on_click(int widget_id, void* user_data) {
(void)user_data;
if (widget_id >= 0 && widget_id < 64) pressed[widget_id]++;
}
static void key(N_GUI_CTX* gui, int keycode, int modifiers, int repeat) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.keyboard.keycode = keycode;
ev.keyboard.modifiers = (unsigned)modifiers;
if (!repeat) {
ev.type = ALLEGRO_EVENT_KEY_DOWN;
}
ev.type = ALLEGRO_EVENT_KEY_CHAR;
ev.keyboard.repeat = repeat ? true : false;
}
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() || !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, 800.0f, 600.0f);
int win = n_gui_add_window(gui, "Question", 10, 10, 400, 200);
int field = n_gui_add_textarea(gui, win, 10, 10, 300, 24, 0, 64, NULL, NULL);
/* added out of order on purpose: the arrows follow the screen, not the list */
int go = n_gui_add_button(gui, win, "Delete", 250, 50, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
int other = n_gui_add_button(gui, win, "Other", 10, 50, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
int cancel = n_gui_add_button(gui, win, "Cancel", 130, 51, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
int below = n_gui_add_button(gui, win, "Help", 10, 90, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
int hidden = n_gui_add_button(gui, win, "Hidden", 10, 130, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
if (field < 0 || go < 0 || other < 0 || cancel < 0 || below < 0 || hidden < 0 || below >= 64) {
n_log(LOG_ERR, "cannot build the window");
return 1;
}
/* Help answers Enter globally, as a default button would. */
n_gui_button_set_keycode(gui, below, ALLEGRO_KEY_ENTER, 0);
/* Tab reaches the field, then the buttons, in the window's order. */
key(gui, ALLEGRO_KEY_TAB, 0, 0);
expect_true("Tab focuses the field first", n_gui_get_focus(gui) == field);
key(gui, ALLEGRO_KEY_TAB, 0, 0);
expect_true("Tab goes on to a button", n_gui_get_focus(gui) == go);
key(gui, ALLEGRO_KEY_TAB, ALLEGRO_KEYMOD_SHIFT, 0);
expect_true("Shift+Tab comes back to the field", n_gui_get_focus(gui) == field);
/* The arrows, in reading order: Other, Cancel (a pixel lower, same row),
* Delete, then Help on the next row, then round; the hidden one never. */
key(gui, ALLEGRO_KEY_RIGHT, 0, 0);
expect_true("Right: Other to Cancel", n_gui_get_focus(gui) == cancel);
key(gui, ALLEGRO_KEY_RIGHT, 0, 0);
expect_true("Right: Cancel to Delete", n_gui_get_focus(gui) == go);
key(gui, ALLEGRO_KEY_DOWN, 0, 0);
expect_true("Down: Delete to Help, on the next row", n_gui_get_focus(gui) == below);
key(gui, ALLEGRO_KEY_RIGHT, 0, 0);
expect_true("Right from the last goes round to the first", n_gui_get_focus(gui) == other);
key(gui, ALLEGRO_KEY_LEFT, 0, 0);
expect_true("Left from the first goes round to the last", n_gui_get_focus(gui) == below);
key(gui, ALLEGRO_KEY_UP, 0, 0);
expect_true("Up: Help back to Delete", n_gui_get_focus(gui) == go);
/* Enter and Space press the focused button, and only it. */
key(gui, ALLEGRO_KEY_ENTER, 0, 0);
expect_true("Enter presses the focused button", pressed[go] == 1);
expect_true("and not the one bound to Enter", pressed[below] == 0);
key(gui, ALLEGRO_KEY_SPACE, 0, 0);
expect_true("Space presses it too", pressed[go] == 2);
key(gui, ALLEGRO_KEY_ENTER, 0, 1);
expect_true("a held key's repeats are not more presses", pressed[go] == 2);
key(gui, ALLEGRO_KEY_PAD_ENTER, 0, 0);
expect_true("the keypad's Enter presses it", pressed[go] == 3);
expect_true("a focused button keeps Enter", n_gui_focus_takes_key(gui, ALLEGRO_KEY_ENTER));
expect_true("and the arrows", n_gui_focus_takes_key(gui, ALLEGRO_KEY_LEFT));
expect_true("but not a letter", !n_gui_focus_takes_key(gui, ALLEGRO_KEY_A));
/* With nothing focused the binding answers Enter again. */
key(gui, ALLEGRO_KEY_ENTER, 0, 0);
expect_true("with no focus, Enter is the bound button's", pressed[below] == 1 && pressed[go] == 3);
/* A mouse press on another button takes the keyboard back, as a press
* anywhere else does: a button takes no focus from a press, and the
* outline must not stay on a button nobody is using. */
{
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
ALLEGRO_EVENT ev;
int before = pressed[other];
if (n_gui_widget_screen_rect(gui, other, &x, &y, &w, &h)) {
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_MOUSE_AXES;
ev.mouse.x = (int)(x + w * 0.5f);
ev.mouse.y = (int)(y + h * 0.5f);
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
ev.mouse.button = 1;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
}
expect_true("the clicked button is pressed", pressed[other] == before + 1);
expect_true("and the keyboard leaves the button it was on", n_gui_get_focus(gui) == -1);
}
/* The focus drawn: a slider reached by Tab, and a button, show an
* outline in the active border colour just inside their edge; without
* the focus that pixel is something else. */
{
ALLEGRO_COLOR mark = al_map_rgb(255, 0, 255);
N_GUI_THEME theme = n_gui_make_theme(al_map_rgb(40, 40, 40), al_map_rgb(50, 50, 50), al_map_rgb(60, 60, 60),
al_map_rgb(90, 90, 90), al_map_rgb(100, 100, 100), mark,
al_map_rgb(220, 220, 220), al_map_rgb(230, 230, 230),
al_map_rgb(240, 240, 240), 1.0f, 0.0f, 0.0f);
int win2 = n_gui_add_window(gui, "Focus", 420, 10, 300, 160);
int slider = 0, button = 0;
ALLEGRO_BITMAP* target = NULL;
int old = al_get_new_bitmap_flags();
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
unsigned char r = 0, g = 0, b = 0;
slider = n_gui_add_slider(gui, win2, 10, 10, 200, 24, 0.0, 100.0, 50.0, N_GUI_SLIDER_VALUE, NULL, NULL);
button = n_gui_add_button(gui, win2, "Go", 10, 50, 80, 24, N_GUI_SHAPE_RECT, on_click, NULL);
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
target = al_create_bitmap(800, 600);
al_set_new_bitmap_flags(old);
if (!target || slider < 0 || button < 0) {
expect_true("a bitmap to draw into", 0);
} else {
int s_on = 0, s_off = 0, b_on = 0;
al_set_target_bitmap(target);
/* not focused */
al_clear_to_color(al_map_rgb(0, 0, 0));
n_gui_widget_screen_rect(gui, slider, &x, &y, &w, &h);
al_unmap_rgb(al_get_pixel(target, (int)x + 1, (int)(y + h * 0.5f)), &r, &g, &b);
s_off = (r == 255 && g == 0 && b == 255);
/* the slider, by Tab from the window's start */
n_gui_set_focus(gui, slider);
al_clear_to_color(al_map_rgb(0, 0, 0));
al_unmap_rgb(al_get_pixel(target, (int)x + 1, (int)(y + h * 0.5f)), &r, &g, &b);
s_on = (r == 255 && g == 0 && b == 255);
n_gui_set_focus(gui, button);
al_clear_to_color(al_map_rgb(0, 0, 0));
n_gui_widget_screen_rect(gui, button, &x, &y, &w, &h);
al_unmap_rgb(al_get_pixel(target, (int)x + 1, (int)(y + h * 0.5f)), &r, &g, &b);
b_on = (r == 255 && g == 0 && b == 255);
al_set_target_bitmap(NULL);
expect_true("a slider without the focus has no outline", !s_off);
expect_true("a focused slider is outlined in the active border colour", s_on);
expect_true("and so is a focused button", b_on);
}
if (target) al_destroy_bitmap(target);
}
al_destroy_font(font);
n_log(LOG_NOTICE, "ex_gui_button_keys: %s", failures ? "FAILED" : "ok");
return failures ? 1 : 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 on_click(int widget_id, void *user_data)
a button's click: counted
static int pressed[64]
presses per button, indexed by widget id
static N_GUI_CTX * gui
volatile char key[227]
TRUE while the key is held.
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
int n_gui_widget_screen_rect(N_GUI_CTX *ctx, int widget_id, float *x, float *y, float *w, float *h)
Where a widget is on screen, rather than inside its window.
Definition n_gui.c:2091
void n_gui_raise_window(N_GUI_CTX *ctx, int window_id)
Bring a window to the front (top of draw order).
Definition n_gui.c:2058
int n_gui_add_slider(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, double min_val, double max_val, double initial, int mode, void(*on_change)(int, double, void *), void *user_data)
Add a slider widget.
Definition n_gui.c:3106
int n_gui_focus_takes_key(const N_GUI_CTX *ctx, int keycode)
Would the focused widget do something with this particular key.
Definition n_gui.c:2284
#define N_GUI_SLIDER_VALUE
slider uses raw start/end values
Definition n_gui.h:178
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_set_focus(N_GUI_CTX *ctx, int widget_id)
Set keyboard focus to a specific widget.
Definition n_gui.c:3908
void n_gui_draw(N_GUI_CTX *ctx)
draw all visible GUI windows and widgets
Definition n_gui.c:11735
void n_gui_button_set_keycode(N_GUI_CTX *ctx, int widget_id, int keycode, int modifiers)
Bind a keyboard key with optional modifier requirements to a button.
Definition n_gui.c:3075
int n_gui_get_focus(const N_GUI_CTX *ctx)
Which widget has the keyboard, or -1 when none has.
Definition n_gui.c:2168
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_set_default_theme(N_GUI_CTX *ctx, N_GUI_THEME theme)
Set the default theme on a context and sync scrollbar style colors.
Definition n_gui.c:919
void n_gui_set_widget_visible(N_GUI_CTX *ctx, int widget_id, int visible)
Show or hide a widget.
Definition n_gui.c:3828
int n_gui_add_textarea(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, int multiline, size_t char_limit, void(*on_change)(int, const char *, void *), void *user_data)
Add a text area widget.
Definition n_gui.c:3162
#define N_GUI_SHAPE_RECT
rectangle shape (default)
Definition n_gui.h:170
N_GUI_THEME n_gui_make_theme(ALLEGRO_COLOR bg, ALLEGRO_COLOR bg_hover, ALLEGRO_COLOR bg_active, ALLEGRO_COLOR border, ALLEGRO_COLOR border_hover, ALLEGRO_COLOR border_active, ALLEGRO_COLOR text, ALLEGRO_COLOR text_hover, ALLEGRO_COLOR text_active, float border_thickness, float corner_rx, float corner_ry)
Create a custom theme with explicit colours.
Definition n_gui.c:825
int n_gui_add_button(N_GUI_CTX *ctx, int window_id, const char *label, float x, float y, float w, float h, int shape, void(*on_click)(int, void *), void *user_data)
Add a button widget to a window.
Definition n_gui.c:2720
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
Color theme for a widget.
Definition n_gui.h:384
#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_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_INFO
informational
Definition n_log.h:106
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.