N_GUI native (detached) window regression: a pop-up promoted to a real OS window keeps working, and the resources shared with the main display (font glyphs, bitmaps, clipboard) stay valid on both.
N_GUI native (detached) window regression: a pop-up promoted to a real OS window keeps working, and the resources shared with the main display (font glyphs, bitmaps, clipboard) stay valid on both. Needs a real display to create the second window, so it skips itself with a success exit code when none can be opened (headless CI without an X server).
#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAIN_W 640
#define MAIN_H 480
#define POPUP_W 240
#define POPUP_H 160
int opt;
while ((opt = getopt(argc, argv, "V:")) != -1) {
switch (opt) {
case 'V':
if (!strcmp("LOG_NULL", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_ERR", optarg))
else if (!strcmp("LOG_DEBUG", optarg))
else
return -1;
break;
default:
return -1;
}
}
return 0;
}
if (!cond) {
} else {
}
}
static void expect_eq_int(
const char* label,
int got,
int want) {
if (got != want) {
n_log(
LOG_ERR,
"FAIL %s: got %d, want %d", label, got, want);
} else {
}
}
float d = got - want;
if (d < 0.0f) d = -d;
if (d > 0.5f) {
n_log(
LOG_ERR,
"FAIL %s: got %f, want %f", label, (
double)got, (
double)want);
} else {
}
}
(void)widget_id;
(void)user_data;
}
(void)widget_id;
(void)user_data;
}
(void)user_data;
}
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.mouse.display = from;
ev.mouse.x = x;
ev.mouse.y = y;
ev.mouse.button = 1;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
}
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_KEY_CHAR;
ev.keyboard.display = from;
ev.keyboard.keycode = keycode;
ev.keyboard.modifiers = ALLEGRO_KEYMOD_CTRL;
ev.keyboard.unichar = 0;
}
int differing = 0;
unsigned char rr, rg, rb;
al_unmap_rgb(ref, &rr, &rg, &rb);
for (int py = y; py < y + h; py++) {
for (int px = x; px < x + w; px++) {
unsigned char cr, cg, cb;
al_unmap_rgb(al_get_pixel(bmp, px, py), &cr, &cg, &cb);
if (abs((int)cr - (int)rr) > 24 || abs((int)cg - (int)rg) > 24 || abs((int)cb - (int)rb) > 24)
differing++;
}
}
return differing;
}
ALLEGRO_BITMAP* target = al_get_target_bitmap();
if (!target || !win) return;
}
static void check_shared_resources(
const char* who, ALLEGRO_DISPLAY* d, ALLEGRO_FONT* font, ALLEGRO_BITMAP* bmp) {
char label[128];
ALLEGRO_COLOR black = al_map_rgb(0, 0, 0);
ALLEGRO_BITMAP* back;
int lit;
al_set_target_backbuffer(d);
al_clear_to_color(black);
al_draw_text(font, al_map_rgb(255, 255, 255), 4.0f, 4.0f, 0, "NILOREA");
al_draw_bitmap(bmp, 4.0f, 40.0f, 0);
back = al_get_backbuffer(d);
snprintf(label, sizeof(label), "%s: shared font rasterizes (%d lit pixels)", who, lit);
snprintf(label, sizeof(label), "%s: shared bitmap draws (%d lit pixels)", who, lit);
}
int main(
int argc,
char** argv) {
fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 2;
}
if (!al_init() || !al_init_font_addon() || !al_init_primitives_addon()) {
return 1;
}
al_install_keyboard();
al_install_mouse();
al_set_new_display_flags(ALLEGRO_WINDOWED);
ALLEGRO_DISPLAY* main_display = al_create_display(
MAIN_W,
MAIN_H);
if (!main_display) {
return 0;
}
ALLEGRO_EVENT_QUEUE* queue = al_create_event_queue();
ALLEGRO_FONT* font = al_create_builtin_font();
if (!queue || !font) {
if (queue) al_destroy_event_queue(queue);
if (font) al_destroy_font(font);
al_destroy_display(main_display);
return 1;
}
al_register_event_source(queue, al_get_display_event_source(main_display));
ALLEGRO_BITMAP* shared_bmp = al_create_bitmap(32, 32);
if (!shared_bmp) {
al_destroy_event_queue(queue);
al_destroy_font(font);
al_destroy_display(main_display);
return 1;
}
al_set_target_bitmap(shared_bmp);
al_clear_to_color(al_map_rgb(255, 0, 255));
al_set_target_backbuffer(main_display);
al_destroy_bitmap(shared_bmp);
al_destroy_event_queue(queue);
al_destroy_font(font);
al_destroy_display(main_display);
return 1;
}
expect_true(
"windows created", main_win >= 0 && popup_win >= 0);
expect_true(
"widgets created", main_btn >= 0 && popup_btn >= 0 && popup_txt >= 0);
expect_true(
"native display created", popup_display != NULL);
expect_true(
"native display differs from the main one", popup_display != main_display);
if (!popup_display) {
al_destroy_bitmap(shared_bmp);
al_destroy_event_queue(queue);
al_destroy_font(font);
al_destroy_display(main_display);
return 1;
}
{
expect_true(
"detached window sits at the origin", w && w->
x == 0.0f && w->
y == 0.0f);
expect_eq_float(
"detached window width matches the display", w ? w->
w : -1.0f, (float)al_get_display_width(popup_display));
expect_eq_float(
"detached window height matches the display", w ? w->
h : -1.0f, (float)al_get_display_height(popup_display));
}
{
const char* payload = "nilorea-multiwin-clipboard";
char* got = NULL;
expect_true(
"clipboard copy from the detached window round-trips",
got != NULL && strcmp(got, payload) == 0);
if (got) {
if (strcmp(got, payload) != 0)
n_log(
LOG_ERR,
"clipboard: got '%s', want '%s'", got, payload);
free(got);
}
} else {
char* al_got = al_get_clipboard_text(popup_display);
expect_true(
"clipboard copy from the detached window round-trips",
al_got != NULL && strcmp(al_got, payload) == 0);
if (al_got) {
if (strcmp(al_got, payload) != 0)
n_log(
LOG_ERR,
"clipboard: got '%s', want '%s'", al_got, payload);
al_free(al_got);
}
}
}
al_set_target_backbuffer(main_display);
al_clear_to_color(al_map_rgb(0, 0, 0));
{
ALLEGRO_BITMAP* back = al_get_backbuffer(main_display);
expect_eq_int(
"main pass leaves the far corner untouched", lit, 0);
}
al_set_target_backbuffer(main_display);
expect_true(
"draw_detached restores the caller's target", al_get_target_bitmap() == al_get_backbuffer(main_display));
{
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_DISPLAY_CLOSE;
ev.display.source = popup_display;
}
expect_true(
"a fresh native display was created", popup_display != NULL);
{
}
{
expect_true(
"own-chrome window created", chrome_win >= 0);
expect_true(
"own-chrome native display created", cd != NULL);
if (cd && !(al_get_display_flags(cd) & ALLEGRO_FRAMELESS))
n_log(
LOG_NOTICE,
"platform did not honour ALLEGRO_FRAMELESS (no window manager?), check skipped");
expect_true(
"own-chrome native window is resizable (needed by resize/maximize)",
cd && (al_get_display_flags(cd) & ALLEGRO_RESIZABLE));
cd ? (float)al_get_display_height(cd) : -1.0f, 180.0f);
expect_true(
"native window title was set before creation", cd != NULL);
if (cd && cw) {
int px0 = 0, py0 = 0, px1 = 0, py1 = 0;
ALLEGRO_EVENT ev;
al_get_window_position(cd, &px0, &py0);
memset(&ev, 0, sizeof(ev));
ev.mouse.display = cd;
ev.mouse.button = 1;
ev.mouse.x = 60;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
int cbx = 0, cby = 0, cax = 0, cay = 0;
int warped = al_get_mouse_cursor_position(&cbx, &cby) &&
al_set_mouse_xy(cd, 90, (
int)(cw->
titlebar_h / 2.0f)) &&
al_get_mouse_cursor_position(&cax, &cay);
ev.type = ALLEGRO_EVENT_MOUSE_AXES;
ev.mouse.x = 90;
al_get_window_position(cd, &px1, &py1);
if (warped && cax != cbx)
expect_eq_int(
"title bar drag moved the OS window", px1 - px0, cax - cbx);
else
n_log(
LOG_NOTICE,
"ex_gui_multiwin: pointer cannot be warped here, skipping the OS-window drag check");
expect_eq_float(
"title bar drag left the window pinned at the origin", cw->
x, 0.0f);
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
}
if (cd && cw) {
(
float)al_get_display_height(cd), cw->
titlebar_h);
expect_eq_float(
"minimise kept the full window height for the restore", cw->
h, full_h);
(float)al_get_display_height(cd), full_h);
}
if (cd && cw) {
ALLEGRO_EVENT ev;
float full_h2;
float sz, mid_x;
if (al_set_window_constraints(cd, 200, 150, 0, 0))
al_apply_window_constraints(cd, true);
memset(&ev, 0, sizeof(ev));
ev.mouse.display = cd;
ev.mouse.button = 1;
ev.mouse.x = (int)mid_x;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
expect_true(
"title bar minimise button set the minimised flag",
expect_eq_float(
"title bar minimise shrank the OS window to the title bar",
(
float)al_get_display_height(cd), cw->
titlebar_h);
(float)al_get_display_height(cd), full_h2);
}
if (cd && cw) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.mouse.display = cd;
ev.mouse.button = 1;
ev.mouse.x = (int)(cw->
w - grip / 2.0f);
ev.mouse.y = (int)(cw->
h - grip / 2.0f);
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_DOWN;
ev.type = ALLEGRO_EVENT_MOUSE_AXES;
ev.mouse.x += 40;
ev.mouse.y += 20;
expect_eq_float(
"grip resized the OS window (width)", (
float)al_get_display_width(cd), 300.0f);
expect_eq_float(
"grip resized the OS window (height)", (
float)al_get_display_height(cd), 200.0f);
ev.type = ALLEGRO_EVENT_MOUSE_BUTTON_UP;
}
if (cd && cw) {
int cmin_w = 0, cmin_h = 0, cmax_w = 0, cmax_h = 0;
if (al_get_window_constraints(cd, &cmin_w, &cmin_h, &cmax_w, &cmax_h)) {
expect_eq_int(
"min width constraint pushed to the window manager", cmin_w, (
int)(cw->
min_w + 0.5f));
expect_eq_int(
"min height constraint pushed to the window manager", cmin_h, (
int)(cw->
min_h + 0.5f));
} else {
n_log(
LOG_NOTICE,
"window constraints unsupported on this platform, check skipped");
}
}
{
ALLEGRO_BITMAP* icons[1];
icons[0] = shared_bmp;
int refused;
}
if (cd) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_DISPLAY_HALT_DRAWING;
ev.display.source = cd;
ev.type = ALLEGRO_EVENT_DISPLAY_RESUME_DRAWING;
}
if (cd && cw) {
}
if (cw) {
}
{
}
}
#ifdef HAVE_CJSON
{
const char* path = "ex_gui_multiwin_layout.json";
float want_w = 0.0f, want_h = 0.0f;
{
want_w = (float)al_get_display_width(d);
want_h = (float)al_get_display_height(d);
}
al_set_target_backbuffer(main_display);
expect_true(
"rebuilt windows", w2_main >= 0 && w2_popup >= 0);
expect_true(
"restored native display exists", d2 != NULL);
if (d2) {
expect_eq_float(
"restored native width", (
float)al_get_display_width(d2), want_w);
expect_eq_float(
"restored native height", (
float)al_get_display_height(d2), want_h);
}
}
remove(path);
}
#endif
al_set_target_backbuffer(main_display);
al_destroy_bitmap(shared_bmp);
al_destroy_event_queue(queue);
al_destroy_font(font);
al_destroy_display(main_display);
return 1;
}
return 0;
}
void process_args(int argc, char **argv)
static void expect_true(const char *label, int cond)
static void on_main_click(int widget_id, void *user_data)
static int g_chrome_titlebar_lit
static void expect_eq_float(const char *label, float got, float want)
static void on_popup_click(int widget_id, void *user_data)
static void ctrl_key(N_GUI_CTX *gui, ALLEGRO_DISPLAY *from, int keycode)
static int count_pixels_differing(ALLEGRO_BITMAP *bmp, int x, int y, int w, int h, ALLEGRO_COLOR ref)
static void expect_eq_int(const char *label, int got, int want)
static void check_shared_resources(const char *who, ALLEGRO_DISPLAY *d, ALLEGRO_FONT *font, ALLEGRO_BITMAP *bmp)
static int g_popup_clicks
static void sample_chrome_titlebar(int window_id, void *user_data)
static void on_popup_close(int window_id, void *user_data)
static void click_on(N_GUI_CTX *gui, ALLEGRO_DISPLAY *from, int x, int y)
float x
position x on screen
float titlebar_h
title bar height
float tb_btn_size
titlebar button size (0 = auto: titlebar_h - 4)
N_GUI_THEME theme
color theme for this window chrome
float saved_w
embedded width saved by n_gui_window_detach, restored by n_gui_window_attach
float saved_h
embedded height saved by n_gui_window_detach, restored by n_gui_window_attach
float grip_size
grip area size
int state
state flags (N_GUI_WIN_*)
int flags
feature flags (N_GUI_WIN_AUTO_SCROLLBAR, N_GUI_WIN_RESIZABLE, etc.)
N_GUI_STYLE style
configurable style (sizes, colours, paddings)
float min_h
minimum height
float tb_btn_spacing
gap between titlebar buttons
ALLEGRO_COLOR bg_normal
background normal
float tb_btn_right_margin
right margin from window edge to rightmost button
float y
position y on screen
ALLEGRO_DISPLAY * active_display
display that last received input, used to pick the display for clipboard and mouse-cursor calls.
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
Set the display (viewport) size for global scrollbar computation.
void n_gui_textarea_set_text(N_GUI_CTX *ctx, int widget_id, const char *text)
set the text content of a textarea widget
int n_gui_save_layout_json(N_GUI_CTX *ctx, const char *filepath)
save the geometry + persistent state of every window to a JSON file
int n_gui_window_detach(N_GUI_CTX *ctx, int window_id, int detach_flags)
Promote a pop-up window to a native OS window.
void n_gui_set_display(N_GUI_CTX *ctx, ALLEGRO_DISPLAY *display)
Set the display pointer for clipboard operations (copy/paste).
int n_gui_wants_mouse(N_GUI_CTX *ctx)
Check if the mouse is currently over any open GUI window.
int n_gui_window_native_is_hidden(N_GUI_CTX *ctx, int window_id)
Check whether a detached window is currently not drawable.
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
int n_gui_window_get_flags(N_GUI_CTX *ctx, int window_id)
Get feature flags of a window.
void n_gui_minimize_window(N_GUI_CTX *ctx, int window_id)
Minimise a window (show title bar only)
void n_gui_close_window(N_GUI_CTX *ctx, int window_id)
Close (hide) a window.
void n_gui_maximize_window(N_GUI_CTX *ctx, int window_id)
Toggle maximised state (full display or restore to previous size).
ALLEGRO_EVENT_QUEUE * n_gui_get_event_queue(N_GUI_CTX *ctx)
Get the event queue previously given to n_gui_set_event_queue.
void n_gui_set_focus(N_GUI_CTX *ctx, int widget_id)
Set keyboard focus to a specific widget.
#define N_GUI_WIN_FRAMELESS
frameless window: no title bar drawn, drag via window body unless N_GUI_WIN_FIXED_POSITION is also se...
#define N_GUI_DETACH_RESIZABLE
the native window can be resized by the user through the OS window manager
int n_gui_window_is_detached(N_GUI_CTX *ctx, int window_id)
Check whether a window currently lives in a native OS window.
void n_gui_draw(N_GUI_CTX *ctx)
Draw all visible windows and their widgets.
#define N_GUI_WIN_MINIMISED
window is minimised (title bar only)
int n_gui_window_is_open(N_GUI_CTX *ctx, int window_id)
Check if a window is currently visible.
N_GUI_WINDOW * n_gui_get_window(N_GUI_CTX *ctx, int window_id)
Get a window pointer by id.
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.
int n_gui_window_attach(N_GUI_CTX *ctx, int window_id)
Return a detached window to being an in-display pop-up.
void n_gui_draw_detached(N_GUI_CTX *ctx)
Render and flip every detached window, and release the ones whose native window was closed.
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
#define N_GUI_WIN_MAXIMISED
window is maximised (full display size)
void n_gui_window_set_close_callback(N_GUI_CTX *ctx, int window_id, void(*on_close)(int, void *), void *user_data)
Set the close button callback for a window.
#define N_GUI_DETACH_OWN_CHROME
keep N_GUI's own window chrome instead of the window manager's: the native window is created frameles...
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
void n_gui_restore_window(N_GUI_CTX *ctx, int window_id)
Restore a minimised window to its full size.
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
ALLEGRO_DISPLAY * n_gui_window_get_display(N_GUI_CTX *ctx, int window_id)
Get the native display backing a window, or NULL when it is a pop-up.
void n_gui_window_set_content_draw_callback(N_GUI_CTX *ctx, int window_id, void(*on_content_draw)(int, void *), void *user_data)
Set the content-draw callback for a window.
int n_gui_window_set_native_icons(N_GUI_CTX *ctx, int window_id, ALLEGRO_BITMAP **icons, int num_icons)
Give a detached window's OS window a taskbar / title bar icon.
int n_gui_count_detached(N_GUI_CTX *ctx)
Count the windows currently detached into native OS windows.
int n_gui_load_layout_json(N_GUI_CTX *ctx, const char *filepath)
load a JSON window layout file and apply it to matching windows
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
#define N_GUI_WIN_BTN_ALL
enable all three title bar buttons (minimize + maximize + close)
#define N_GUI_WIN_RESIZING
window is being resized
int n_gui_window_from_display(N_GUI_CTX *ctx, ALLEGRO_DISPLAY *display)
Find the window detached into a given display.
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.
#define N_GUI_SHAPE_RECT
rectangle shape (default)
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.
void n_gui_set_event_queue(N_GUI_CTX *ctx, ALLEGRO_EVENT_QUEUE *queue)
Set the event queue used to register native window event sources.
#define N_GUI_WIN_RESIZABLE
enable user-resizable window with a drag handle at bottom-right
#define N_GUI_WIN_DRAGGING
window is being dragged
The top-level GUI context that holds all windows.
A pseudo window that contains widgets.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
int n_clipboard_available(void)
Whether a real clipboard backend (X11 or Win32) is active on this platform.
char * n_clipboard_get(int which)
Fetch the current text of which selection from its owner.
System clipboard: full X11 selection support on Linux, Win32 clipboard on Windows/MinGW.
#define N_CLIPBOARD_CLIPBOARD
the Ctrl+C / Ctrl+V clipboard selection
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.