Table of Contents
- src/n_gui_popup.c
- Structures
- Functions
- Defines
- Detailed Description
- Functions Documentation
- function _popup_valid
- function _popup_show_level
- function _popup_row_shows
- function _popup_row_h
- function _popup_row_at
- function _popup_open_child
- function _popup_mouse
- function _popup_make_window
- function _popup_layout
- function _popup_is_level
- function _popup_has_rows
- function _popup_font
- function _popup_draw
- function _popup_close_level
- function _popup_append
- function _popup_activate
- Macros Documentation
- define _POPUP_KIND_SUB
- define _POPUP_KIND_SEP
- define _POPUP_KIND_ROOT
- define _POPUP_KIND_ITEM
- define N_GUI_POPUP_SEP_H
- define N_GUI_POPUP_PAD_Y
- define N_GUI_POPUP_PAD_X
- define N_GUI_POPUP_OVERLAP
- define N_GUI_POPUP_MIN_W
- define N_GUI_POPUP_ARROW_W
- Source code
src/n_gui_popup.c
A popup menu with submenus for N_GUI, the kind a right click opens. More...
Structures
| Name | |
|---|---|
| struct | _N_GUI_POPUP_LEVEL what a level's widget is handed: stable while the entries array moves |
| struct | _N_GUI_POPUP_ENTRY one entry. |
| struct | N_GUI_POPUP the popup |
Functions
| Name | |
|---|---|
| int | _popup_valid(const N_GUI_POPUP * popup, int id) helper: is this id an entry |
| int | _popup_show_level(N_GUI_POPUP * popup, int level, float x, float y) helper: lay a level out, place it with its corner at a point pulled back inside the display, and open it on top |
| int | _popup_row_shows(const N_GUI_POPUP * popup, int id) helper: is an entry a row that shows, separators aside |
| float | _popup_row_h(const N_GUI_POPUP * popup) helper: height of an item row |
| int | _popup_row_at(const N_GUI_POPUP * popup, int level, float ly) helper: the row index at a level-local y, or -1 |
| void | _popup_open_child(N_GUI_POPUP * popup, int level, int row) helper: open a submenu beside its row. |
| int | _popup_mouse(int widget_id, const N_GUI_MOUSE_EVENT * ev, void * user_data) custom mouse callback: hover opens submenus, a press acts |
| int | _popup_make_window(N_GUI_POPUP * popup, int level) helper: make a level's window the first time it opens |
| int | _popup_layout(N_GUI_POPUP * popup, int level) helper: the rows of a level, and its size. |
| int | _popup_is_level(const N_GUI_POPUP * popup, int id) helper: is this id a level (the root or a submenu) |
| int | _popup_has_rows(const N_GUI_POPUP * popup, int level) helper: does a level have at least one row that would be shown. |
| ALLEGRO_FONT * | _popup_font(const N_GUI_POPUP * popup) helper: the font rows are measured with |
| void | _popup_draw(N_GUI_WIDGET * widget, float x, float y, ALLEGRO_FONT * font, void * user_data) custom draw callback: the rows of one level |
| void | _popup_close_level(N_GUI_POPUP * popup, int level) helper: close a level and everything open beside it |
| int | _popup_append(N_GUI_POPUP * popup, int kind, int parent, const char * text, const char * tip, int tag) helper: append an entry, returning its id or -1 |
| int | _popup_activate(N_GUI_POPUP * popup, int level, int row) helper: act on a row: pick an item, open a submenu. |
Defines
| Name | |
|---|---|
| _POPUP_KIND_SUB a submenu, which is also a level |
|
| _POPUP_KIND_SEP a separator |
|
| _POPUP_KIND_ROOT the top level |
|
| _POPUP_KIND_ITEM an item, picked with a tag |
|
| N_GUI_POPUP_SEP_H height of a separator row, at scale 1 |
|
| N_GUI_POPUP_PAD_Y padding above and below a row's text, at scale 1 |
|
| N_GUI_POPUP_PAD_X padding left and right of a row's text, at scale 1 |
|
| N_GUI_POPUP_OVERLAP how far a submenu overlaps its parent, at scale 1, so the pointer does not cross a gap on its way in |
|
| N_GUI_POPUP_MIN_W narrowest a level is, at scale 1 |
|
| N_GUI_POPUP_ARROW_W room kept at the right of a level for the submenu arrow, at scale 1 |
Detailed Description
A popup menu with submenus for N_GUI, the kind a right click opens.
Author: Castagnier Mickael
Version: 1.0
Date: 29/09/2026
Functions Documentation
function _popup_valid
static int _popup_valid(
const N_GUI_POPUP * popup,
int id
)
helper: is this id an entry
function _popup_show_level
static int _popup_show_level(
N_GUI_POPUP * popup,
int level,
float x,
float y
)
helper: lay a level out, place it with its corner at a point pulled back inside the display, and open it on top
function _popup_row_shows
static int _popup_row_shows(
const N_GUI_POPUP * popup,
int id
)
helper: is an entry a row that shows, separators aside
function _popup_row_h
static float _popup_row_h(
const N_GUI_POPUP * popup
)
helper: height of an item row
function _popup_row_at
static int _popup_row_at(
const N_GUI_POPUP * popup,
int level,
float ly
)
helper: the row index at a level-local y, or -1
function _popup_open_child
static void _popup_open_child(
N_GUI_POPUP * popup,
int level,
int row
)
helper: open a submenu beside its row.
To the right of the parent, unless it would leave the display there, then to the left: a submenu pulled back over its parent would hide the row it came from.
function _popup_mouse
static int _popup_mouse(
int widget_id,
const N_GUI_MOUSE_EVENT * ev,
void * user_data
)
custom mouse callback: hover opens submenus, a press acts
function _popup_make_window
static int _popup_make_window(
N_GUI_POPUP * popup,
int level
)
helper: make a level's window the first time it opens
function _popup_layout
static int _popup_layout(
N_GUI_POPUP * popup,
int level
)
helper: the rows of a level, and its size.
Separators are kept only between two shown rows, once, so that hiding entries never leaves a line at an end or two lines together.
function _popup_is_level
static int _popup_is_level(
const N_GUI_POPUP * popup,
int id
)
helper: is this id a level (the root or a submenu)
function _popup_has_rows
static int _popup_has_rows(
const N_GUI_POPUP * popup,
int level
)
helper: does a level have at least one row that would be shown.
A submenu without one is not shown either: an arrow to an empty panel is a row that does nothing.
function _popup_font
static ALLEGRO_FONT * _popup_font(
const N_GUI_POPUP * popup
)
helper: the font rows are measured with
function _popup_draw
static void _popup_draw(
N_GUI_WIDGET * widget,
float x,
float y,
ALLEGRO_FONT * font,
void * user_data
)
custom draw callback: the rows of one level
function _popup_close_level
static void _popup_close_level(
N_GUI_POPUP * popup,
int level
)
helper: close a level and everything open beside it
function _popup_append
static int _popup_append(
N_GUI_POPUP * popup,
int kind,
int parent,
const char * text,
const char * tip,
int tag
)
helper: append an entry, returning its id or -1
function _popup_activate
static int _popup_activate(
N_GUI_POPUP * popup,
int level,
int row
)
helper: act on a row: pick an item, open a submenu.
1 when it did.
Macros Documentation
define _POPUP_KIND_SUB
#define _POPUP_KIND_SUB 2
a submenu, which is also a level
define _POPUP_KIND_SEP
#define _POPUP_KIND_SEP 3
a separator
define _POPUP_KIND_ROOT
#define _POPUP_KIND_ROOT 0
the top level
define _POPUP_KIND_ITEM
#define _POPUP_KIND_ITEM 1
an item, picked with a tag
define N_GUI_POPUP_SEP_H
#define N_GUI_POPUP_SEP_H 7.0f
height of a separator row, at scale 1
define N_GUI_POPUP_PAD_Y
#define N_GUI_POPUP_PAD_Y 4.0f
padding above and below a row's text, at scale 1
define N_GUI_POPUP_PAD_X
#define N_GUI_POPUP_PAD_X 10.0f
padding left and right of a row's text, at scale 1
define N_GUI_POPUP_OVERLAP
#define N_GUI_POPUP_OVERLAP 2.0f
how far a submenu overlaps its parent, at scale 1, so the pointer does not cross a gap on its way in
define N_GUI_POPUP_MIN_W
#define N_GUI_POPUP_MIN_W 120.0f
narrowest a level is, at scale 1
define N_GUI_POPUP_ARROW_W
#define N_GUI_POPUP_ARROW_W 16.0f
room kept at the right of a level for the submenu arrow, at scale 1
Source code
/*
* 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
*/
#ifdef HAVE_ALLEGRO
#include <stdlib.h>
#include <string.h>
#include <allegro5/allegro_primitives.h>
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_gui_popup.h"
#define N_GUI_POPUP_PAD_X 10.0f
#define N_GUI_POPUP_PAD_Y 4.0f
#define N_GUI_POPUP_SEP_H 7.0f
#define N_GUI_POPUP_ARROW_W 16.0f
#define N_GUI_POPUP_MIN_W 120.0f
#define N_GUI_POPUP_OVERLAP 2.0f
#define _POPUP_KIND_ROOT 0
#define _POPUP_KIND_ITEM 1
#define _POPUP_KIND_SUB 2
#define _POPUP_KIND_SEP 3
typedef struct {
N_GUI_POPUP* popup;
int entry;
} _N_GUI_POPUP_LEVEL;
typedef struct {
int kind;
int parent;
char* text;
char* tip;
int tag;
int shown;
int enabled;
int window;
int widget;
_N_GUI_POPUP_LEVEL* hook;
int hover;
int child;
int* rows;
int nb_rows;
float* row_y;
float w, h;
float x, y;
} _N_GUI_POPUP_ENTRY;
struct N_GUI_POPUP {
N_GUI_CTX* ctx;
float scale;
N_GUI_POPUP_FN on_pick;
void* user_data;
_N_GUI_POPUP_ENTRY* entries;
int nb_entries;
int capacity;
};
static int _popup_valid(const N_GUI_POPUP* popup, int id) {
return popup && id >= 0 && id < popup->nb_entries;
}
static int _popup_is_level(const N_GUI_POPUP* popup, int id) {
return _popup_valid(popup, id) && (popup->entries[id].kind == _POPUP_KIND_ROOT || popup->entries[id].kind == _POPUP_KIND_SUB);
}
static ALLEGRO_FONT* _popup_font(const N_GUI_POPUP* popup) {
return popup->ctx ? popup->ctx->default_font : NULL;
}
static float _popup_row_h(const N_GUI_POPUP* popup) {
ALLEGRO_FONT* font = _popup_font(popup);
float line = font ? (float)al_get_font_line_height(font) : 12.0f;
return line + 2.0f * N_GUI_POPUP_PAD_Y * popup->scale;
}
static int _popup_append(N_GUI_POPUP* popup, int kind, int parent, const char* text, const char* tip, int tag) {
_N_GUI_POPUP_ENTRY* e = NULL;
if (popup->nb_entries == popup->capacity) {
int wanted = popup->capacity ? popup->capacity * 2 : 16;
_N_GUI_POPUP_ENTRY* grown = (_N_GUI_POPUP_ENTRY*)realloc(popup->entries, (size_t)wanted * sizeof(*grown));
if (!grown) {
n_log(LOG_ERR, "popup: out of memory for an entry");
return -1;
}
popup->entries = grown;
popup->capacity = wanted;
}
e = &popup->entries[popup->nb_entries];
memset(e, 0, sizeof(*e));
e->kind = kind;
e->parent = parent;
e->tag = tag;
e->shown = 1;
e->enabled = 1;
e->window = -1;
e->widget = -1;
e->hover = -1;
e->child = -1;
if (text && text[0]) {
e->text = strdup(text);
if (!e->text) return -1;
}
if (tip && tip[0]) {
e->tip = strdup(tip);
if (!e->tip) {
FreeNoLog(e->text);
return -1;
}
}
return popup->nb_entries++;
}
static int _popup_has_rows(const N_GUI_POPUP* popup, int level) {
int i = 0;
for (i = 1; i < popup->nb_entries; i++) {
const _N_GUI_POPUP_ENTRY* e = &popup->entries[i];
if (e->parent != level || !e->shown || e->kind == _POPUP_KIND_SEP) continue;
if (e->kind == _POPUP_KIND_SUB && !_popup_has_rows(popup, i)) continue;
return 1;
}
return 0;
}
static int _popup_row_shows(const N_GUI_POPUP* popup, int id) {
const _N_GUI_POPUP_ENTRY* e = &popup->entries[id];
if (!e->shown) return 0;
if (e->kind == _POPUP_KIND_SUB) return _popup_has_rows(popup, id);
return 1;
}
static int _popup_layout(N_GUI_POPUP* popup, int level) {
_N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
ALLEGRO_FONT* font = _popup_font(popup);
float s = popup->scale, row_h = _popup_row_h(popup), y = 0.0f, widest = 0.0f;
int i = 0, n = 0, pending_sep = -1, has_sub = 0;
FreeNoLog(lv->rows);
FreeNoLog(lv->row_y);
lv->nb_rows = 0;
lv->rows = (int*)calloc((size_t)popup->nb_entries + 1, sizeof(int));
lv->row_y = (float*)calloc((size_t)popup->nb_entries + 1, sizeof(float));
if (!lv->rows || !lv->row_y) {
FreeNoLog(lv->rows);
FreeNoLog(lv->row_y);
return -1;
}
/* the heading, on the top level only, takes a row and a separator */
if (level == N_GUI_POPUP_ROOT && lv->text) {
y += row_h + N_GUI_POPUP_SEP_H * s;
if (font) widest = (float)al_get_text_width(font, lv->text);
}
for (i = 1; i < popup->nb_entries; i++) {
const _N_GUI_POPUP_ENTRY* e = &popup->entries[i];
if (e->parent != level) continue;
if (e->kind == _POPUP_KIND_SEP) {
if (e->shown && n > 0) pending_sep = i;
continue;
}
if (!_popup_row_shows(popup, i)) continue;
if (pending_sep >= 0) {
lv->rows[n] = pending_sep;
lv->row_y[n] = y;
y += N_GUI_POPUP_SEP_H * s;
n++;
pending_sep = -1;
}
lv->rows[n] = i;
lv->row_y[n] = y;
y += row_h;
n++;
if (e->kind == _POPUP_KIND_SUB) has_sub = 1;
if (font && e->text) {
float w = (float)al_get_text_width(font, e->text);
if (w > widest) widest = w;
}
}
lv->nb_rows = n;
lv->w = widest + 2.0f * N_GUI_POPUP_PAD_X * s + (has_sub ? N_GUI_POPUP_ARROW_W * s : 0.0f);
if (lv->w < N_GUI_POPUP_MIN_W * s) lv->w = N_GUI_POPUP_MIN_W * s;
/* a little room under the last row */
lv->h = y + N_GUI_POPUP_PAD_Y * s;
return n > 0 ? 0 : -1;
}
static int _popup_row_at(const N_GUI_POPUP* popup, int level, float ly) {
const _N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
float row_h = _popup_row_h(popup), sep_h = N_GUI_POPUP_SEP_H * popup->scale;
int r = 0;
for (r = 0; r < lv->nb_rows; r++) {
float h = popup->entries[lv->rows[r]].kind == _POPUP_KIND_SEP ? sep_h : row_h;
if (ly >= lv->row_y[r] && ly < lv->row_y[r] + h) return r;
}
return -1;
}
static void _popup_close_level(N_GUI_POPUP* popup, int level) {
_N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
if (lv->child >= 0) _popup_close_level(popup, lv->child);
lv->child = -1;
lv->hover = -1;
if (lv->window >= 0) n_gui_close_window(popup->ctx, lv->window);
}
static void _popup_draw(N_GUI_WIDGET* widget, float x, float y, ALLEGRO_FONT* font, void* user_data);
static int _popup_mouse(int widget_id, const N_GUI_MOUSE_EVENT* ev, void* user_data);
static int _popup_make_window(N_GUI_POPUP* popup, int level) {
_N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
if (lv->window >= 0) return 0;
if (!lv->hook) {
lv->hook = (_N_GUI_POPUP_LEVEL*)calloc(1, sizeof(*lv->hook));
if (!lv->hook) return -1;
lv->hook->popup = popup;
lv->hook->entry = level;
}
lv->window = n_gui_add_window(popup->ctx, "", 0.0f, 0.0f, lv->w, lv->h);
if (lv->window < 0) return -1;
/* Frameless and fixed: a menu with a title bar is a dialog, and one that
* can be dragged away from what it is about is worse than one that
* cannot. */
n_gui_window_set_flags(popup->ctx, lv->window, N_GUI_WIN_FRAMELESS | N_GUI_WIN_FIXED_POSITION);
n_gui_window_set_zorder(popup->ctx, lv->window, N_GUI_ZORDER_POPUP, 0);
lv->widget = n_gui_add_custom(popup->ctx, lv->window, 0.0f, 0.0f, lv->w, lv->h, _popup_draw, lv->hook);
if (lv->widget < 0) {
n_gui_close_window(popup->ctx, lv->window);
return -1;
}
n_gui_custom_set_mouse(popup->ctx, lv->widget, _popup_mouse, lv->hook);
n_gui_close_window(popup->ctx, lv->window);
return 0;
}
static int _popup_show_level(N_GUI_POPUP* popup, int level, float x, float y) {
_N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
float dw = popup->ctx->display_w, dh = popup->ctx->display_h;
if (_popup_layout(popup, level) != 0) return -1;
if (_popup_make_window(popup, level) != 0) return -1;
if (dw > 0.0f && x + lv->w > dw) x = dw - lv->w;
if (dh > 0.0f && y + lv->h > dh) y = dh - lv->h;
if (x < 0.0f) x = 0.0f;
if (y < 0.0f) y = 0.0f;
lv->x = x;
lv->y = y;
lv->hover = -1;
lv->child = -1;
n_gui_window_set_rect(popup->ctx, lv->window, x, y, lv->w, lv->h);
n_gui_widget_set_rect(popup->ctx, lv->widget, 0.0f, 0.0f, lv->w, lv->h);
n_gui_set_widget_tooltip(popup->ctx, lv->widget, NULL);
n_gui_open_window(popup->ctx, lv->window);
n_gui_raise_window(popup->ctx, lv->window);
n_gui_mark_dirty(popup->ctx);
return 0;
}
static void _popup_open_child(N_GUI_POPUP* popup, int level, int row) {
_N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
int sub = lv->rows[row];
float s = popup->scale, x = 0.0f, y = 0.0f, dw = popup->ctx->display_w;
if (lv->child == sub) return;
if (lv->child >= 0) _popup_close_level(popup, lv->child);
lv->child = -1;
if (_popup_layout(popup, sub) != 0) return;
x = lv->x + lv->w - N_GUI_POPUP_OVERLAP * s;
if (dw > 0.0f && x + popup->entries[sub].w > dw) x = lv->x - popup->entries[sub].w + N_GUI_POPUP_OVERLAP * s;
/* its first row level with the row that opened it */
y = lv->y + lv->row_y[row];
if (_popup_show_level(popup, sub, x, y) == 0) lv->child = sub;
}
static int _popup_activate(N_GUI_POPUP* popup, int level, int row) {
const _N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
const _N_GUI_POPUP_ENTRY* e = NULL;
if (row < 0 || row >= lv->nb_rows) return 0;
e = &popup->entries[lv->rows[row]];
if (!e->enabled) return 0;
if (e->kind == _POPUP_KIND_SUB) {
_popup_open_child(popup, level, row);
return 1;
}
if (e->kind == _POPUP_KIND_ITEM) {
int tag = e->tag;
/* closed first, so a dialog the callback opens is not under it */
n_gui_popup_close(popup);
if (popup->on_pick) popup->on_pick(popup, tag, popup->user_data);
return 1;
}
return 0;
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_DRAW
// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
static void _popup_draw(N_GUI_WIDGET* widget, float x, float y, ALLEGRO_FONT* font, void* user_data) {
const _N_GUI_POPUP_LEVEL* hook = (const _N_GUI_POPUP_LEVEL*)user_data;
const N_GUI_POPUP* popup = NULL;
const _N_GUI_POPUP_ENTRY* lv = NULL;
const N_GUI_THEME* th = NULL;
float s = 1.0f, row_h = 0.0f, pad_x = 0.0f, pad_y = 0.0f;
ALLEGRO_COLOR dim;
int r = 0;
if (!widget || !hook || !hook->popup || !font) return;
popup = hook->popup;
lv = &popup->entries[hook->entry];
th = &widget->theme;
s = popup->scale;
row_h = _popup_row_h(popup);
pad_x = N_GUI_POPUP_PAD_X * s;
pad_y = N_GUI_POPUP_PAD_Y * s;
/* greyed text: the text colour halfway to the background */
{
float tr = 0, tg = 0, tb = 0, ta = 0, br = 0, bg = 0, bb = 0, ba = 0;
al_unmap_rgba_f(th->text_normal, &tr, &tg, &tb, &ta);
al_unmap_rgba_f(th->bg_normal, &br, &bg, &bb, &ba);
dim = al_map_rgba_f((tr + br) * 0.5f, (tg + bg) * 0.5f, (tb + bb) * 0.5f, ta);
}
al_draw_filled_rectangle(x, y, x + widget->w, y + widget->h, th->bg_normal);
al_draw_rectangle(x + 0.5f, y + 0.5f, x + widget->w - 0.5f, y + widget->h - 0.5f, th->border_normal, 1.0f);
if (hook->entry == N_GUI_POPUP_ROOT && lv->text) {
float my = y + row_h + N_GUI_POPUP_SEP_H * s * 0.5f;
al_draw_text(font, dim, x + pad_x, y + pad_y, 0, lv->text);
al_draw_line(x + pad_x * 0.5f, my, x + widget->w - pad_x * 0.5f, my, th->border_normal, 1.0f);
}
for (r = 0; r < lv->nb_rows; r++) {
const _N_GUI_POPUP_ENTRY* e = &popup->entries[lv->rows[r]];
float ry = y + lv->row_y[r];
int lit = 0;
ALLEGRO_COLOR ink;
if (e->kind == _POPUP_KIND_SEP) {
float my = ry + N_GUI_POPUP_SEP_H * s * 0.5f;
al_draw_line(x + pad_x * 0.5f, my, x + widget->w - pad_x * 0.5f, my, th->border_normal, 1.0f);
continue;
}
/* lit under the pointer, and while its submenu is open, so the way
* back to it shows */
lit = e->enabled && (r == lv->hover || lv->rows[r] == lv->child);
if (lit) al_draw_filled_rectangle(x + 1.0f, ry, x + widget->w - 1.0f, ry + row_h, th->bg_hover);
ink = !e->enabled ? dim : (lit ? th->text_hover : th->text_normal);
if (e->text) al_draw_text(font, ink, x + pad_x, ry + pad_y, 0, e->text);
if (e->kind == _POPUP_KIND_SUB) {
/* a small triangle pointing right, drawn rather than typed */
float ax = x + widget->w - pad_x - 4.0f * s, ay = ry + row_h * 0.5f, a = 3.5f * s;
al_draw_filled_triangle(ax - a, ay - a, ax - a, ay + a, ax + a * 0.5f, ay, ink);
}
}
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_MOUSE
static int _popup_mouse(int widget_id, const N_GUI_MOUSE_EVENT* ev, void* user_data) {
const _N_GUI_POPUP_LEVEL* hook = (const _N_GUI_POPUP_LEVEL*)user_data;
N_GUI_POPUP* popup = NULL;
_N_GUI_POPUP_ENTRY* lv = NULL;
int level = 0, row = -1;
if (!hook || !hook->popup || !ev) return 0;
popup = hook->popup;
level = hook->entry;
lv = &popup->entries[level];
row = _popup_row_at(popup, level, ev->y);
if (ev->type == N_GUI_MOUSE_MOVE) {
if (row == lv->hover) return 1;
lv->hover = row;
n_gui_set_widget_tooltip(popup->ctx, widget_id, row >= 0 ? popup->entries[lv->rows[row]].tip : NULL);
if (row >= 0) {
const _N_GUI_POPUP_ENTRY* e = &popup->entries[lv->rows[row]];
if (e->kind == _POPUP_KIND_SUB && e->enabled) {
_popup_open_child(popup, level, row);
} else if (e->kind != _POPUP_KIND_SEP && lv->child >= 0) {
/* another row of this level: whatever was open beside it
* goes. A separator keeps it, so crossing one on the way to
* the submenu does not shut it. */
_popup_close_level(popup, lv->child);
lv->child = -1;
}
}
n_gui_mark_dirty(popup->ctx);
return 1;
}
if (ev->type == N_GUI_MOUSE_DOWN) {
/* Either button: a menu opened with the right one is often picked
* with it too. A press anywhere on a level is the popup's, even on a
* separator, so it never falls through to what is under it. */
_popup_activate(popup, level, row);
return 1;
}
return ev->type == N_GUI_MOUSE_UP;
}
N_GUI_POPUP* n_gui_popup_new(N_GUI_CTX* ctx, float scale, N_GUI_POPUP_FN on_pick, void* user_data) {
N_GUI_POPUP* popup = NULL;
__n_assert(ctx, return NULL);
__n_assert(on_pick, return NULL);
popup = (N_GUI_POPUP*)calloc(1, sizeof(*popup));
__n_assert(popup, return NULL);
popup->ctx = ctx;
popup->scale = scale > 0.0f ? scale : 1.0f;
popup->on_pick = on_pick;
popup->user_data = user_data;
if (_popup_append(popup, _POPUP_KIND_ROOT, -1, NULL, NULL, 0) != N_GUI_POPUP_ROOT) {
free(popup->entries);
free(popup);
return NULL;
}
return popup;
}
int n_gui_popup_add_item(N_GUI_POPUP* popup, int parent, const char* text, int tag, const char* tip) {
if (!_popup_is_level(popup, parent)) return -1;
return _popup_append(popup, _POPUP_KIND_ITEM, parent, text, tip, tag);
}
int n_gui_popup_add_submenu(N_GUI_POPUP* popup, int parent, const char* text, const char* tip) {
if (!_popup_is_level(popup, parent)) return -1;
return _popup_append(popup, _POPUP_KIND_SUB, parent, text, tip, 0);
}
int n_gui_popup_add_separator(N_GUI_POPUP* popup, int parent) {
if (!_popup_is_level(popup, parent)) return -1;
return _popup_append(popup, _POPUP_KIND_SEP, parent, NULL, NULL, 0);
}
void n_gui_popup_set_title(N_GUI_POPUP* popup, const char* text) {
_N_GUI_POPUP_ENTRY* root = NULL;
if (!popup || !popup->entries) return;
root = &popup->entries[N_GUI_POPUP_ROOT];
FreeNoLog(root->text);
/* a failed copy leaves no heading, which is the only harm it can do */
if (text && text[0]) root->text = strdup(text);
}
void n_gui_popup_set_shown(N_GUI_POPUP* popup, int entry, int shown) {
if (!_popup_valid(popup, entry) || entry == N_GUI_POPUP_ROOT) return;
popup->entries[entry].shown = shown ? 1 : 0;
}
void n_gui_popup_set_enabled(N_GUI_POPUP* popup, int entry, int enabled) {
if (!_popup_valid(popup, entry) || entry == N_GUI_POPUP_ROOT) return;
popup->entries[entry].enabled = enabled ? 1 : 0;
}
int n_gui_popup_open(N_GUI_POPUP* popup, float x, float y) {
if (!popup) return -1;
n_gui_popup_close(popup);
return _popup_show_level(popup, N_GUI_POPUP_ROOT, x, y);
}
void n_gui_popup_close(N_GUI_POPUP* popup) {
if (!popup || !popup->entries) return;
_popup_close_level(popup, N_GUI_POPUP_ROOT);
n_gui_mark_dirty(popup->ctx);
}
int n_gui_popup_is_open(const N_GUI_POPUP* popup) {
if (!popup || !popup->entries || popup->entries[N_GUI_POPUP_ROOT].window < 0) return 0;
return n_gui_window_is_open(popup->ctx, popup->entries[N_GUI_POPUP_ROOT].window);
}
int n_gui_popup_hit(const N_GUI_POPUP* popup, float x, float y) {
int level = N_GUI_POPUP_ROOT;
if (!n_gui_popup_is_open(popup)) return 0;
/* the open levels are a chain from the root, each one's child */
while (level >= 0) {
const _N_GUI_POPUP_ENTRY* lv = &popup->entries[level];
if (x >= lv->x && x < lv->x + lv->w && y >= lv->y && y < lv->y + lv->h) return 1;
level = lv->child;
}
return 0;
}
int n_gui_popup_window(const N_GUI_POPUP* popup, int level) {
if (!_popup_is_level(popup, level)) return -1;
return popup->entries[level].window;
}
int n_gui_popup_press(N_GUI_POPUP* popup, int entry) {
const _N_GUI_POPUP_ENTRY* lv = NULL;
int level = 0, r = 0;
if (!_popup_valid(popup, entry) || entry == N_GUI_POPUP_ROOT) return 0;
level = popup->entries[entry].parent;
lv = &popup->entries[level];
if (lv->window < 0 || !n_gui_window_is_open(popup->ctx, lv->window)) return 0;
for (r = 0; r < lv->nb_rows; r++) {
if (lv->rows[r] == entry) return _popup_activate(popup, level, r);
}
return 0;
}
void n_gui_popup_free(N_GUI_POPUP** popup) {
int i = 0;
if (!popup || !*popup) return;
n_gui_popup_close(*popup);
for (i = 0; i < (*popup)->nb_entries; i++) {
_N_GUI_POPUP_ENTRY* e = &(*popup)->entries[i];
/* the widgets outlive the handle: hidden, and deaf, so nothing in
* them reaches the freed state */
if (e->widget >= 0) {
n_gui_custom_set_mouse((*popup)->ctx, e->widget, NULL, NULL);
n_gui_set_widget_visible((*popup)->ctx, e->widget, 0);
}
FreeNoLog(e->text);
FreeNoLog(e->tip);
FreeNoLog(e->rows);
FreeNoLog(e->row_y);
FreeNoLog(e->hook);
}
free((*popup)->entries);
free(*popup);
*popup = NULL;
}
#endif /* HAVE_ALLEGRO */
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB