6 ex__gui__button__keys_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_gui_button_keys.c

Functions

Name
int process_args(int argc, char ** argv)
parse -V LOG_LEVEL
void on_click(int widget_id, void * user_data)
a button's click: counted
int main(int argc, char ** argv)
void key(N_GUI_CTX * gui, int keycode, int modifiers, int repeat)
one key as the keyboard sends it: a down, then a char
void expect_true(const char * what, int ok)
record one check

Attributes

Name
int[64] pressed
presses per button, indexed by widget id
int log_level
int failures

Defines

Name
ALLEGRO_UNSTABLE

Functions Documentation

function process_args

static int process_args(
    int argc,
    char ** argv
)

parse -V LOG_LEVEL

function on_click

static void on_click(
    int widget_id,
    void * user_data
)

a button's click: counted

function main

int main(
    int argc,
    char ** argv
)

function key

static void key(
    N_GUI_CTX * gui,
    int keycode,
    int modifiers,
    int repeat
)

one key as the keyboard sends it: a down, then a char

function expect_true

static void expect_true(
    const char * what,
    int ok
)

record one check

Attributes Documentation

variable pressed

static int[64] pressed;

presses per button, indexed by widget id

variable log_level

static int log_level = 3;

variable failures

static int failures = 0;

Macros Documentation

define ALLEGRO_UNSTABLE

#define ALLEGRO_UNSTABLE 1

Source code



#define ALLEGRO_UNSTABLE 1

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

#include "nilorea/n_common.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);
        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 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;
        n_gui_process_event(gui, ev);
    }
    ev.type = ALLEGRO_EVENT_KEY_CHAR;
    ev.keyboard.repeat = repeat ? true : false;
    n_gui_process_event(gui, ev);
}

int main(int argc, char** argv) {
    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, 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;
    }
    n_gui_set_widget_visible(gui, hidden, 0);
    /* 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. */
    n_gui_set_focus(gui, other);
    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. */
    n_gui_set_focus(gui, -1);
    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];

        n_gui_set_focus(gui, go);
        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);
            n_gui_process_event(gui, ev);
            ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
            ev.mouse.button = 1;
            n_gui_process_event(gui, ev);
            ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
            n_gui_process_event(gui, ev);
        }
        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;

        n_gui_set_default_theme(gui, theme);
        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;

            n_gui_raise_window(gui, win2);
            al_set_target_bitmap(target);
            /* not focused */
            n_gui_set_focus(gui, -1);
            al_clear_to_color(al_map_rgb(0, 0, 0));
            n_gui_draw(gui);
            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));
            n_gui_draw(gui);
            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_draw(gui);
            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);
    }

    n_gui_destroy_ctx(&gui);
    al_destroy_font(font);
    n_log(LOG_NOTICE, "ex_gui_button_keys: %s", failures ? "FAILED" : "ok");
    return failures ? 1 : 0;
}