Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_layout_persist.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
30#define ALLEGRO_UNSTABLE 1
31
32#include <math.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <unistd.h>
37
38#include "nilorea/n_common.h"
39#include "nilorea/n_log.h"
40#include "nilorea/n_gui.h"
41
42static int failures = 0;
43
44static void expect_true(const char* label, int cond) {
45 if (!cond) {
46 n_log(LOG_ERR, "%s: condition false", label);
47 failures++;
48 }
49}
50
51static int feq(float a, float b) {
52 return fabsf(a - b) < 0.01f;
53}
54
55/* Build a window "W1" with a keyed splitpane and a keyed 3-column datagrid. */
56static void build_window(N_GUI_CTX* gui) {
57 int win = n_gui_add_window(gui, "W1", 0.0f, 0.0f, 400.0f, 300.0f);
58 int sp = n_gui_add_splitpane(gui, win, 0, 0, 400, 300, N_GUI_SPLIT_VERTICAL, 0.5f, NULL, NULL);
59 int dg = n_gui_add_datagrid(gui, win, 0, 0, 400, 200, NULL, NULL);
60 n_gui_datagrid_add_column(gui, dg, "A", 80.0f);
61 n_gui_datagrid_add_column(gui, dg, "B", 90.0f);
62 n_gui_datagrid_add_column(gui, dg, "C", 100.0f);
63 /* keying AFTER a load applies any pending state; before a load it is a no-op */
64 n_gui_widget_set_persist_key(gui, sp, "split.main");
65 n_gui_widget_set_persist_key(gui, dg, "grid.main");
66}
67
68/* Find a widget id in a window by walking the context's id space is awkward; instead
69 * we rebuild deterministically and re-fetch by known creation order via the getters. */
70int main(int argc, char** argv) {
71 (void)argc;
72 (void)argv;
74
75 if (!al_init() || !al_init_font_addon()) {
76 n_log(LOG_ERR, "allegro init failed");
77 return 1;
78 }
79 ALLEGRO_FONT* font = al_create_builtin_font();
80 if (!font) {
81 n_log(LOG_ERR, "al_create_builtin_font failed");
82 return 1;
83 }
84
85 char path[128];
86 snprintf(path, sizeof(path), "ex_gui_layout_persist_%d.json", (int)getpid());
87
88 /* ---- session 1: build, adjust, save ---- */
89 int sp_id = -1, dg_id = -1;
90 {
92 expect_true("ctx 1 created", gui != NULL);
93 n_gui_set_display_size(gui, 1000.0f, 800.0f);
94
95 int win = n_gui_add_window(gui, "W1", 10.0f, 20.0f, 400.0f, 300.0f);
96 sp_id = n_gui_add_splitpane(gui, win, 0, 0, 400, 300, N_GUI_SPLIT_VERTICAL, 0.5f, NULL, NULL);
97 dg_id = n_gui_add_datagrid(gui, win, 0, 0, 400, 200, NULL, NULL);
98 n_gui_datagrid_add_column(gui, dg_id, "A", 80.0f);
99 n_gui_datagrid_add_column(gui, dg_id, "B", 90.0f);
100 n_gui_datagrid_add_column(gui, dg_id, "C", 100.0f);
101 n_gui_widget_set_persist_key(gui, sp_id, "split.main");
102 n_gui_widget_set_persist_key(gui, dg_id, "grid.main");
103 expect_true("persist key round-trips", n_gui_widget_get_persist_key(gui, sp_id) && strcmp(n_gui_widget_get_persist_key(gui, sp_id), "split.main") == 0);
104
105 /* the user adjusts: drag the divider, resize + hide + reorder columns */
106 n_gui_splitpane_set_ratio(gui, sp_id, 0.3f);
107 n_gui_datagrid_set_column_width(gui, dg_id, 0, 123.0f);
108 n_gui_datagrid_set_column_visible(gui, dg_id, 1, 0); /* hide column B */
109 n_gui_datagrid_move_column(gui, dg_id, 2, 0); /* move C to the front */
110 expect_true("column C moved to display 0", n_gui_datagrid_display_to_physical(gui, dg_id, 0) == 2);
111
112 /* the user also reduces the window to its title bar (the minimise icon) */
114 expect_true("window not minimised initially", n_gui_window_is_minimised(gui, win) == 0);
116 expect_true("window minimised", n_gui_window_is_minimised(gui, win) == 1);
117
118 expect_true("save layout ok", n_gui_save_layout_json(gui, path) == 0);
120 }
121
122 /* ---- session 2: load into a FRESH ctx BEFORE the window exists (lazy path) ---- */
123 {
124 N_GUI_CTX* gui = n_gui_new_ctx(font);
125 expect_true("ctx 2 created", gui != NULL);
126 n_gui_set_display_size(gui, 1000.0f, 800.0f);
127
128 /* load first: the window/widgets do not exist yet, so state is stashed pending */
129 expect_true("load layout ok", n_gui_load_layout_json(gui, path) == 0);
130
131 int win = n_gui_add_window(gui, "W1", 0.0f, 0.0f, 400.0f, 300.0f);
132 int sp = n_gui_add_splitpane(gui, win, 0, 0, 400, 300, N_GUI_SPLIT_VERTICAL, 0.5f, NULL, NULL);
133 int dg = n_gui_add_datagrid(gui, win, 0, 0, 400, 200, NULL, NULL);
134 n_gui_datagrid_add_column(gui, dg, "A", 80.0f);
135 n_gui_datagrid_add_column(gui, dg, "B", 90.0f);
136 n_gui_datagrid_add_column(gui, dg, "C", 100.0f);
137
138 /* window geometry restored on create (pending_layout) */
140 expect_true("window x restored", w && feq(w->x, 10.0f));
141 expect_true("window y restored", w && feq(w->y, 20.0f));
142
143 /* before keying, defaults still hold */
144 expect_true("ratio default before key", feq(n_gui_splitpane_get_ratio(gui, sp), 0.5f));
145
146 /* keying applies the pending state */
147 n_gui_widget_set_persist_key(gui, sp, "split.main");
148 n_gui_widget_set_persist_key(gui, dg, "grid.main");
149
150 expect_true("splitpane ratio restored", feq(n_gui_splitpane_get_ratio(gui, sp), 0.3f));
151 expect_true("column A width restored", feq(n_gui_datagrid_get_column_width(gui, dg, 0), 123.0f));
152 expect_true("column B hidden restored", n_gui_datagrid_get_column_visible(gui, dg, 1) == 0);
153 expect_true("column order restored (C first)", n_gui_datagrid_display_to_physical(gui, dg, 0) == 2);
154
155 /* the minimised state survives the round-trip, and restore always clears it
156 (so an "open this dialog" action can show the content unconditionally) */
157 expect_true("minimised state restored", n_gui_window_is_minimised(gui, win) == 1);
159 expect_true("restore clears minimised", n_gui_window_is_minimised(gui, win) == 0);
160 n_gui_restore_window(gui, win); /* idempotent on an already-restored window */
161 expect_true("restore is idempotent", n_gui_window_is_minimised(gui, win) == 0);
162
163 /* a widget with a key that has no saved entry keeps its defaults (no crash) */
164 int sp2 = n_gui_add_splitpane(gui, win, 0, 0, 100, 100, N_GUI_SPLIT_HORIZONTAL, 0.42f, NULL, NULL);
165 n_gui_widget_set_persist_key(gui, sp2, "split.unsaved");
166 expect_true("unsaved key keeps default", feq(n_gui_splitpane_get_ratio(gui, sp2), 0.42f));
167
169 }
170
171 /* ---- guards: a non-persisted context saves/loads cleanly, keying an unknown id is safe ---- */
172 {
173 N_GUI_CTX* gui = n_gui_new_ctx(font);
174 n_gui_widget_set_persist_key(gui, 9999, "nope"); /* invalid id: no crash */
175 n_gui_restore_window(gui, 9999); /* invalid id: no crash */
176 expect_true("is_minimised on invalid id is 0", n_gui_window_is_minimised(gui, 9999) == 0);
177 expect_true("get_persist_key on invalid id is NULL", n_gui_widget_get_persist_key(gui, 9999) == NULL);
178 build_window(gui); /* exercises the build helper (no prior load: keys are no-ops) */
180 }
181
182 unlink(path);
183 al_destroy_font(font);
184
185 if (failures) {
186 printf("ex_gui_layout_persist: %d FAILURE(S)\n", failures);
187 n_log(LOG_ERR, "ex_gui_layout_persist: %d failure(s)", failures);
188 return 1;
189 }
190 printf("ex_gui_layout_persist: all checks passed\n");
191 return 0;
192}
static int failures
int main(void)
static void expect_true(const char *label, int cond)
static N_GUI_CTX * gui
static int feq(float a, float b)
static void build_window(N_GUI_CTX *gui)
float x
position x on screen
Definition n_gui.h:1005
float y
position y on screen
Definition n_gui.h:1007
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
Set the display (viewport) size for global scrollbar computation.
Definition n_gui.c:9034
float n_gui_splitpane_get_ratio(N_GUI_CTX *ctx, int widget_id)
get the current divider ratio of a split pane
Definition n_gui.c:3538
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
Definition n_gui.c:12712
void n_gui_minimize_window(N_GUI_CTX *ctx, int window_id)
Minimise a window (show title bar only)
Definition n_gui.c:1224
#define N_GUI_SPLIT_HORIZONTAL
split pane: horizontal divider, regions are top and bottom
Definition n_gui.h:130
int n_gui_add_splitpane(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, int orientation, float ratio, void(*on_change)(int, float, void *), void *user_data)
Add a split pane widget: a draggable divider between two regions.
Definition n_gui.c:2666
const char * n_gui_widget_get_persist_key(N_GUI_CTX *ctx, int widget_id)
Get a widget's persist key ("" when unset), or NULL when the widget is invalid.
Definition n_gui.c:2306
int n_gui_datagrid_add_column(N_GUI_CTX *ctx, int widget_id, const char *title, float width)
append a column with a header title and pixel width; returns the column index or -1
Definition n_gui.c:3805
void n_gui_widget_set_persist_key(N_GUI_CTX *ctx, int widget_id, const char *key)
Give a widget a stable key so its user-adjustable state (a splitpane's divider ratio,...
Definition n_gui.c:2293
void n_gui_datagrid_move_column(N_GUI_CTX *ctx, int widget_id, int from, int to)
move the column at display position from to display position to, shifting the columns in between (reo...
Definition n_gui.c:4156
N_GUI_WINDOW * n_gui_get_window(N_GUI_CTX *ctx, int window_id)
Get a window pointer by id.
Definition n_gui.c:1182
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:1063
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:903
int n_gui_datagrid_display_to_physical(N_GUI_CTX *ctx, int widget_id, int display_pos)
physical column shown at display position display_pos, or -1 if out of range
Definition n_gui.c:4150
float n_gui_datagrid_get_column_width(N_GUI_CTX *ctx, int widget_id, int col)
current pixel width of physical column col, or 0 if out of range
Definition n_gui.c:4144
#define N_GUI_SPLIT_VERTICAL
split pane: vertical divider, regions are left and right
Definition n_gui.h:128
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:995
int n_gui_window_is_minimised(N_GUI_CTX *ctx, int window_id)
Check if a window is currently minimised.
Definition n_gui.c:1255
void n_gui_restore_window(N_GUI_CTX *ctx, int window_id)
Restore a minimised window to its full size.
Definition n_gui.c:1242
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
Definition n_gui.c:1206
void n_gui_splitpane_set_ratio(N_GUI_CTX *ctx, int widget_id, float ratio)
set the divider ratio of a split pane (clamped to its min/max)
Definition n_gui.c:3550
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
Definition n_gui.c:12870
void n_gui_datagrid_set_column_width(N_GUI_CTX *ctx, int widget_id, int col, float width)
set the pixel width of physical column col (clamped to a small minimum)
Definition n_gui.c:4138
void n_gui_datagrid_set_column_visible(N_GUI_CTX *ctx, int widget_id, int col, int visible)
show (1) or hide (0) physical column col; hidden columns keep their cells but are skipped in the head...
Definition n_gui.c:4126
int n_gui_datagrid_get_column_visible(N_GUI_CTX *ctx, int widget_id, int col)
whether physical column col is visible (1) or hidden (0); 0 if out of range
Definition n_gui.c:4132
int n_gui_add_datagrid(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, void(*on_select)(int, int, void *), void *user_data)
Add a sortable data grid widget.
Definition n_gui.c:2787
The top-level GUI context that holds all windows.
Definition n_gui.h:1354
A pseudo window that contains widgets.
Definition n_gui.h:999
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:89
#define LOG_ERR
error conditions
Definition n_log.h:76
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:121
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.