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

Completions offered under a textarea while it is typed in (headless).

Completions offered under a textarea while it is typed in (headless). n_gui_textarea_set_suggest() hands the word under the cursor to a provider and shows what it answers in a panel under the field. The keys it takes while that panel is up - Up, Down, ENTER, TAB, ESCAPE - are keys the field would otherwise use, so they are answered by the panel and the field never hears them.

The separator set is what makes it usable in a field holding a list: with "," the word being completed is the one the cursor is in, and picking one replaces that word and leaves the rest of the line alone.

Author
Castagnier Mickael
Version
1.0
Date
22/09/2026
/*
* 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 "nilorea/n_gui.h"
#include "nilorea/n_log.h"
static int failures = 0;
static int asks = 0;
static char last_prefix[64] = "";
static void expect_true(const char* label, int cond) {
if (!cond) {
n_log(LOG_ERR, "%s: FAILED", label);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
static void expect_text(const char* label, const char* got, const char* want) {
if (!got || strcmp(got, want) != 0) {
n_log(LOG_ERR, "%s: FAILED, got \"%s\" wanted \"%s\"", label, got ? got : "(null)", want);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
static void vocabulary(const char* prefix, N_GUI_SUGGEST_SINK* sink, void* user_data) {
static const char* words[] = {"sunset", "sunsets", "sunrise", "sunflower",
"beach", "berlin", "bergen", NULL};
size_t n = strlen(prefix);
int i = 0;
(void)user_data;
asks++;
snprintf(last_prefix, sizeof(last_prefix), "%s", prefix);
for (i = 0; words[i]; i++) {
if (strncasecmp(words[i], prefix, n) == 0) n_gui_suggest_add(sink, words[i]);
}
}
static void nothing(const char* prefix, N_GUI_SUGGEST_SINK* sink, void* user_data) {
(void)prefix;
(void)sink;
(void)user_data;
}
static ALLEGRO_EVENT key_event(int keycode) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_KEY_CHAR;
ev.keyboard.keycode = keycode;
return ev;
}
static ALLEGRO_EVENT char_event(char c) {
ALLEGRO_EVENT ev = key_event(0);
ev.keyboard.unichar = (int)(unsigned char)c;
return ev;
}
static void type(N_GUI_CTX* gui, const char* s) {
while (*s) {
s++;
}
}
int main(void) {
ALLEGRO_FONT* font;
int win, field, plain;
if (!al_init() || !al_init_font_addon()) {
n_log(LOG_ERR, "allegro init failed");
return 1;
}
font = al_create_builtin_font(); /* no display needed */
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;
}
n_gui_set_display_size(gui, 800.0f, 600.0f);
win = n_gui_add_window(gui, "W", 0.0f, 0.0f, 400.0f, 300.0f);
field = n_gui_add_textarea(gui, win, 0.0f, 0.0f, 300.0f, 20.0f, 0, 200, NULL, NULL);
plain = n_gui_add_textarea(gui, win, 0.0f, 40.0f, 300.0f, 20.0f, 0, 200, NULL, NULL);
/* A field with no provider is a field that behaves exactly as it did. */
type(gui, "sun");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_DOWN));
expect_true("a field with no provider offers nothing",
/* Typing opens it as soon as something matches. */
type(gui, "sun");
expect_true("typing opens the panel", n_gui_textarea_suggest_is_open(gui, field));
expect_text("and the provider was given what was typed", last_prefix, "sun");
/* Asked when the word under the cursor changes, and not when a key leaves
it as it was. The cursor is at the end, so RIGHT moves nothing and asks
nothing; LEFT shortens the word to "su" and asks once. */
{
int before = asks;
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
expect_true("a key that does not change the word does not ask again",
asks == before);
n_gui_process_event(gui, key_event(ALLEGRO_KEY_LEFT));
expect_true("and one that does, asks once", asks == before + 1);
expect_text("with the shorter word", last_prefix, "su");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_END));
}
/* ENTER takes the highlighted one, which starts on the first. */
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ENTER));
expect_text("ENTER puts the first one in", n_gui_textarea_get_text(gui, field), "sunset");
expect_true("and closes the panel", !n_gui_textarea_suggest_is_open(gui, field));
/* Down moves the highlight, so the second entry is reachable. */
type(gui, "sun");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_DOWN));
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ENTER));
expect_text("Down then ENTER takes the second", n_gui_textarea_get_text(gui, field),
"sunsets");
/* TAB does what ENTER does, because a completion is what TAB means in a
field that has one. */
type(gui, "sunf");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_TAB));
expect_text("TAB takes it too", n_gui_textarea_get_text(gui, field), "sunflower");
/* ESCAPE dismisses without changing the text, and Down brings it back
without the word having to be retyped. */
type(gui, "sun");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ESCAPE));
expect_true("ESCAPE puts it away", !n_gui_textarea_suggest_is_open(gui, field));
expect_text("and leaves the text alone", n_gui_textarea_get_text(gui, field), "sun");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_DOWN));
expect_true("Down asks for it again", n_gui_textarea_suggest_is_open(gui, field));
/* A word nothing matches closes it rather than showing an empty panel. */
type(gui, "zzz");
expect_true("a word nothing matches shows nothing",
/* The separator is the whole point of the feature in a list field: the
word under the cursor is completed and the rest of the line stays. */
type(gui, "beach, sunr");
expect_text("the provider sees only the word under the cursor", last_prefix, "sunr");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ENTER));
expect_text("and only that word is replaced", n_gui_textarea_get_text(gui, field),
"beach, sunrise");
/* Nothing is added after it. A field that appended its own separator
would be guessing that another entry is coming. */
expect_true("and nothing was added after it",
n_gui_textarea_get_text_length(gui, field) == strlen("beach, sunrise"));
/* Completing in the middle of a line replaces that word and not the tail. */
type(gui, "berg, beach");
n_gui_process_event(gui, key_event(ALLEGRO_KEY_HOME));
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
expect_true("the panel followed the cursor back into the first word",
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ENTER));
expect_text("the first word is completed and the tail is kept",
n_gui_textarea_get_text(gui, field), "bergen, beach");
/* A provider that answers nothing is not an error, it is a field with no
completions today. */
n_gui_textarea_set_suggest(gui, field, nothing, ",", NULL);
type(gui, "sun");
expect_true("a provider that answers nothing shows nothing",
/* And setting NULL takes the feature off the field again. */
type(gui, "sun");
expect_true("it is on again", n_gui_textarea_suggest_is_open(gui, field));
n_gui_textarea_set_suggest(gui, field, NULL, NULL, NULL);
expect_true("setting NULL closes what was up",
type(gui, "s");
expect_true("and stops offering", !n_gui_textarea_suggest_is_open(gui, field));
/* Duplicates are refused, so a provider that walks two sources does not
show the same word twice. */
type(gui, "beach");
expect_true("an exact match still offers itself",
n_gui_process_event(gui, key_event(ALLEGRO_KEY_ENTER));
expect_text("and picking it changes nothing", n_gui_textarea_get_text(gui, field), "beach");
/* Asking a widget that is not a textarea is answered rather than obeyed. */
{
int label = n_gui_add_label(gui, win, "x", 0.0f, 80.0f, 50.0f, 20.0f,
expect_true("a non-textarea has no panel", !n_gui_textarea_suggest_is_open(gui, label));
expect_true("and setting one on it does nothing",
}
expect_true("an unknown widget has no panel", !n_gui_textarea_suggest_is_open(gui, 9999));
al_destroy_font(font);
if (failures) {
n_log(LOG_ERR, "ex_gui_suggest: %d failure(s)", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_gui_suggest: all checks passed");
return 0;
}
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 ALLEGRO_EVENT key_event(int keycode, unsigned int modifiers)
Build a key event for keycode with modifiers.
static N_GUI_CTX * gui
static void nothing(const char *prefix, N_GUI_SUGGEST_SINK *sink, void *user_data)
A provider that answers nothing, whatever it is asked.
static void vocabulary(const char *prefix, N_GUI_SUGGEST_SINK *sink, void *user_data)
A vocabulary to complete against, the way a catalog would answer.
static char last_prefix[64]
The last prefix the provider was given.
static void expect_text(const char *label, const char *got, const char *want)
Assert two strings are equal.
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
static ALLEGRO_EVENT char_event(char c)
Build a key event that types c.
static int asks
How many times the provider was asked.
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_suggest_add(N_GUI_SUGGEST_SINK *sink, const char *text)
add one completion to a suggestion sink
Definition n_gui.c:12705
#define N_GUI_ALIGN_LEFT
left aligned text
Definition n_gui.h:212
void n_gui_textarea_set_text(N_GUI_CTX *ctx, int widget_id, const char *text)
set the text content of a textarea widget
Definition n_gui.c:4210
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
int n_gui_add_label(N_GUI_CTX *ctx, int window_id, const char *text, float x, float y, float w, float h, int align)
Add a static text label.
Definition n_gui.c:3639
void n_gui_set_focus(N_GUI_CTX *ctx, int widget_id)
Set keyboard focus to a specific widget.
Definition n_gui.c:3908
#define N_GUI_WIN_FRAMELESS
frameless window: no title bar drawn, drag via window body unless N_GUI_WIN_FIXED_POSITION is also se...
Definition n_gui.h:276
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_textarea_set_suggest(N_GUI_CTX *ctx, int widget_id, N_GUI_SUGGEST_FN fn, const char *separators, void *user_data)
offer completions in a textarea
Definition n_gui.c:12852
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
const char * n_gui_textarea_get_text(N_GUI_CTX *ctx, int widget_id)
get the text content of a textarea widget
Definition n_gui.c:4196
size_t n_gui_textarea_get_text_length(N_GUI_CTX *ctx, int widget_id)
return the current text length in bytes
Definition n_gui.c:7389
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
Definition n_gui.c:1436
int n_gui_textarea_suggest_is_open(N_GUI_CTX *ctx, int widget_id)
is a textarea's completion panel up
Definition n_gui.c:12871
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
Definition n_gui.c:2515
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
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
#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.
handle textarea key input.
Definition n_gui.c:12700
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.