Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_sectionlist.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
37
38#define ALLEGRO_UNSTABLE 1
39
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44
45#include "nilorea/n_common.h"
46#include "nilorea/n_gui.h"
47#include "nilorea/n_log.h"
48
49static int log_level = LOG_ERR;
50static int failures = 0;
51
52static int process_args(int argc, char** argv) {
53 int opt;
54 while ((opt = getopt(argc, argv, "V:")) != -1) {
55 switch (opt) {
56 case 'V':
57 if (!strcmp("LOG_NULL", optarg))
59 else if (!strcmp("LOG_NOTICE", optarg))
61 else if (!strcmp("LOG_INFO", optarg))
63 else if (!strcmp("LOG_ERR", optarg))
65 else if (!strcmp("LOG_DEBUG", optarg))
67 else
68 return -1;
69 break;
70 default:
71 return -1;
72 }
73 }
74 return 0;
75}
76
77static void expect_close(const char* label, float got, float want, float tol) {
78 float d = got - want;
79 if (d < 0) d = -d;
80 if (d > tol) {
81 n_log(LOG_ERR, "%s: got %.3f, expected %.3f", label, got, want);
82 failures++;
83 }
84}
85
86static void expect_true(const char* label, int cond) {
87 if (!cond) {
88 n_log(LOG_ERR, "%s: condition false", label);
89 failures++;
90 }
91}
92
93/* where a widget sits now */
94static float widget_y(N_GUI_CTX* gui, int id) {
95 const N_GUI_WIDGET* w = n_gui_get_widget(gui, id);
96 return w ? w->y : -1.0f;
97}
98
99static int widget_visible(N_GUI_CTX* gui, int id) {
100 return n_gui_widget_is_visible(gui, id);
101}
102
103int main(int argc, char** argv) {
105 if (process_args(argc, argv) != 0) {
106 fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
107 return 2;
108 }
110
111 if (!al_init() || !al_init_font_addon()) {
112 n_log(LOG_ERR, "allegro init failed");
113 return 1;
114 }
115 ALLEGRO_FONT* font = al_create_builtin_font();
116 if (!font) {
117 n_log(LOG_ERR, "al_create_builtin_font failed");
118 return 1;
119 }
121 if (!gui) {
122 n_log(LOG_ERR, "n_gui_new_ctx failed");
123 al_destroy_font(font);
124 return 1;
125 }
126 n_gui_set_display_size(gui, 400.0f, 800.0f);
127
128 int win = n_gui_add_window(gui, "Panel", 0.0f, 0.0f, 400.0f, 800.0f);
129 expect_true("window created", win >= 0);
130
131 /* Two sections of two members each. The header height is 20, the gap 4,
132 * and the members are 30 tall, which makes every number below arithmetic
133 * anybody can check by hand. */
134 N_GUI_SECTIONLIST* sl = n_gui_sectionlist_create(gui, win, 10.0f, 10.0f, 200.0f, 20.0f,
135 4.0f, NULL, NULL);
136 expect_true("section list created", sl != NULL);
137 if (!sl) {
139 al_destroy_font(font);
140 return 1;
141 }
142
143 int first = n_gui_sectionlist_add_section(sl, "First", 10.0f, 0);
144 int a = n_gui_add_button(gui, win, "a", 10.0f, 34.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
145 NULL, NULL);
146 int b = n_gui_add_button(gui, win, "b", 10.0f, 64.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
147 NULL, NULL);
148 n_gui_sectionlist_add_widget(sl, first, a);
149 n_gui_sectionlist_add_widget(sl, first, b);
150
151 int second = n_gui_sectionlist_add_section(sl, "Second", 200.0f, 0);
152 int c = n_gui_add_button(gui, win, "c", 10.0f, 224.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
153 NULL, NULL);
154 n_gui_sectionlist_add_widget(sl, second, c);
155 expect_true("two sections", n_gui_sectionlist_get_count(sl) == 2);
156
157 /* Stacked from y0: header at 10, its members at 34 and 64, the second
158 * header after the first section's 60 of content plus a gap. */
160 expect_close("first member", widget_y(gui, a), 34.0f, 0.01f);
161 expect_close("second member", widget_y(gui, b), 64.0f, 0.01f);
162 expect_close("second header's member", widget_y(gui, c), 122.0f, 0.01f);
163 /* 10 from the top, then per section a 20 header, a 4 gap, its content and
164 another 4: 10 + 24 + 60 + 4 + 24 + 30 + 4. */
165 expect_close("content height", n_gui_sectionlist_content_height(sl), 156.0f, 0.01f);
166
167 /* Folding hides the members and pulls what is below them up. */
168 n_gui_sectionlist_set_folded(sl, first, 1);
169 expect_true("folded is folded", n_gui_sectionlist_is_folded(sl, first) == 1);
170 expect_true("a is hidden", !widget_visible(gui, a));
171 expect_true("b is hidden", !widget_visible(gui, b));
172 expect_close("the second section came up", widget_y(gui, c), 58.0f, 0.01f);
173
174 n_gui_sectionlist_set_folded(sl, first, 0);
175 expect_true("a is back", widget_visible(gui, a));
176 expect_close("and where it was", widget_y(gui, a), 34.0f, 0.01f);
177
178 /* A caller that lays its own members out says where they are. Moving b
179 * down by ten moves the section's height and everything under it. */
180 n_gui_sectionlist_set_widget_offset(sl, first, b, 40.0f);
182 expect_close("b at its new offset", widget_y(gui, b), 74.0f, 0.01f);
183 expect_close("the section grew", widget_y(gui, c), 132.0f, 0.01f);
184
185 /* A member the caller keeps hidden is not shown by an unfold and does not
186 * count towards the height. With b out, the first section is a's thirty
187 * rather than b's seventy, so what is under it comes up by forty. */
190 expect_true("hidden stays hidden", !widget_visible(gui, b));
191 expect_true("a is still shown", widget_visible(gui, a));
192 expect_close("the section shrank to what is left", widget_y(gui, c), 92.0f, 0.01f);
193
196 expect_true("unhidden comes back", widget_visible(gui, b));
197 expect_close("and the height with it", widget_y(gui, c), 132.0f, 0.01f);
198
199 /* A folded section hides a member the caller had hidden as well, and
200 * unfolding does not bring it back. */
202 n_gui_sectionlist_set_folded(sl, first, 1);
203 expect_true("both hidden while folded", !widget_visible(gui, a) && !widget_visible(gui, b));
204 n_gui_sectionlist_set_folded(sl, first, 0);
205 expect_true("only the caller's stays hidden",
207
208 /* And the whole list moved and widened, which is what a resized window
209 does to it. Everything stacks from the new origin. */
211 n_gui_sectionlist_set_geometry(sl, 20.0f, 50.0f, 300.0f, 20.0f, 4.0f);
213 expect_close("moved to the new origin", widget_y(gui, a), 74.0f, 0.01f);
214 {
216 expect_close("and widened with it", hdr ? hdr->w : 0.0f, 300.0f, 0.01f);
217 expect_close("from the new left", hdr ? hdr->x : 0.0f, 20.0f, 0.01f);
218 /* The height too, and it is the one of the four worth checking: a
219 * window that reflows its widgets when it is resized scales a header
220 * like anything else, and a relayout that set only the other three
221 * left that scaling in place. */
222 if (hdr) hdr->h = 145.0f;
224 expect_close("and the header keeps its own height", hdr ? hdr->h : 0.0f, 20.0f, 0.01f);
225 }
226
227 /* Another order: the second section on top. Its member then sits under
228 * the first header position, and the first section follows it. From
229 * y0 50: second header 50, c at 74, then 30 of c and a gap; first
230 * header at 108, a at 132. */
231 {
232 const int flipped[2] = {1, 0};
233 const int messy[3] = {1, 1, 7};
234
235 n_gui_sectionlist_set_order(sl, flipped, 2);
236 expect_true("the order reads back", n_gui_sectionlist_get_order(sl, 0) == second &&
237 n_gui_sectionlist_get_order(sl, 1) == first);
238 expect_close("the second section's member on top", widget_y(gui, c), 74.0f, 0.01f);
239 expect_close("the first section's below it", widget_y(gui, a), 132.0f, 0.01f);
240 /* A section left out: gone with its header, the rest closes up. */
242 expect_true("a hidden section says so", n_gui_sectionlist_is_section_hidden(sl, second) == 1);
243 expect_true("its member is hidden", !widget_visible(gui, c));
244 expect_true("and its header",
245 !widget_visible(gui, sl->sections[second].header_id));
246 expect_close("the first comes up to the top", widget_y(gui, a), 74.0f, 0.01f);
247 expect_close("and the height counts it alone", n_gui_sectionlist_content_height(sl), 148.0f, 0.01f);
249 expect_true("shown again", widget_visible(gui, c));
250 /* An order with a repeat and an index that is not there still gives
251 * every section once. */
252 n_gui_sectionlist_set_order(sl, messy, 3);
253 expect_true("a messy order is made whole", n_gui_sectionlist_get_order(sl, 0) == second &&
254 n_gui_sectionlist_get_order(sl, 1) == first);
255 expect_true("past the end is -1", n_gui_sectionlist_get_order(sl, 2) == -1);
256 }
257
258 /* A full section refuses a member, says so, and returns -1; the one
259 * that fits returns 0. Found in AfterAPro, whose plugin section
260 * outgrew 48 members and lost four without a word. */
261 {
262 int third = n_gui_sectionlist_add_section(sl, "Full", 600.0f, 0);
263 int k = 0, refused = 0, added = 0;
264
265 for (k = 0; k < N_GUI_SECTION_WIDGETS_MAX + 1; k++) {
266 int w = n_gui_add_label(gui, win, "x", 10.0f, 630.0f, 50.0f, 10.0f, N_GUI_ALIGN_LEFT);
267 if (n_gui_sectionlist_add_widget(sl, third, w) == 0)
268 added++;
269 else
270 refused++;
271 }
272 expect_true("a section takes N_GUI_SECTION_WIDGETS_MAX members", added == N_GUI_SECTION_WIDGETS_MAX);
273 expect_true("and refuses the next with -1", refused == 1);
274 }
275
277 expect_true("freed", sl == NULL);
278
280 al_destroy_font(font);
281
282 if (failures) {
283 n_log(LOG_ERR, "ex_gui_sectionlist: %d failure(s)", failures);
284 return 1;
285 }
286 n_log(LOG_NOTICE, "ex_gui_sectionlist: all checks passed");
287 return 0;
288}
void process_args(int argc, char **argv)
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 int log_level
static N_GUI_CTX * gui
static void expect_close(const char *label, float got, float want, float tol)
static int widget_visible(N_GUI_CTX *gui, int id)
static float widget_y(N_GUI_CTX *gui, int id)
FONT * font
the default 8x8 font, valid after allegro_init()
float h
height
Definition n_gui.h:1104
float w
width
Definition n_gui.h:1102
float x
position x relative to parent window
Definition n_gui.h:1098
int header_id
clickable header button widget id
Definition n_gui.h:3366
N_GUI_SECTION sections[16]
section storage
Definition n_gui.h:3386
float y
position y relative to parent window
Definition n_gui.h:1100
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
void n_gui_sectionlist_set_folded(N_GUI_SECTIONLIST *sl, int section_index, int folded)
set a section's folded state and relayout; does NOT fire on_toggle (for programmatic restore)
Definition n_gui.c:19261
void n_gui_sectionlist_set_widget_offset(N_GUI_SECTIONLIST *sl, int section_index, int widget_id, float dy)
state a member's y offset from the section's content top, for a caller that computes its own layout a...
Definition n_gui.c:19137
#define N_GUI_ALIGN_LEFT
left aligned text
Definition n_gui.h:212
void n_gui_sectionlist_set_widget_hidden(N_GUI_SECTIONLIST *sl, int section_index, int widget_id, int hidden)
keep a member hidden whatever the fold state, and leave it out of the section's height.
Definition n_gui.c:19143
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
float n_gui_sectionlist_content_height(const N_GUI_SECTIONLIST *sl)
total content height in pixels under the current fold state, for sizing/scrolling the host window
Definition n_gui.c:19272
#define N_GUI_SECTION_WIDGETS_MAX
maximum number of member widgets per section.
Definition n_gui.h:3361
N_GUI_SECTIONLIST * n_gui_sectionlist_create(N_GUI_CTX *ctx, int window_id, float x, float y0, float width, float header_h, float gap, void(*on_toggle)(int, int, void *), void *user_data)
create an empty section list anchored at (x,y0) in window_id; sections stack downward from y0
Definition n_gui.c:19064
void n_gui_sectionlist_set_order(N_GUI_SECTIONLIST *sl, const int *order, int count)
Stack the sections in another order.
Definition n_gui.c:19226
N_GUI_WIDGET * n_gui_get_widget(N_GUI_CTX *ctx, int widget_id)
Get a widget pointer by id.
Definition n_gui.c:3701
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_sectionlist_set_geometry(N_GUI_SECTIONLIST *sl, float x, float y0, float width, float header_h, float gap)
restate where the list stacks from, how wide its headers are and the two heights it stacks with,...
Definition n_gui.c:19150
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
int n_gui_sectionlist_add_widget(N_GUI_SECTIONLIST *sl, int section_index, int widget_id)
register an existing widget as a member of a section (captures its current y as the fold offset).
Definition n_gui.c:19103
int n_gui_sectionlist_get_count(const N_GUI_SECTIONLIST *sl)
number of sections in the list
Definition n_gui.c:19283
int n_gui_sectionlist_add_section(N_GUI_SECTIONLIST *sl, const char *title, float header_y, int folded)
add a section with a header button at header_y (members added next are captured relative to it); retu...
Definition n_gui.c:19083
int n_gui_sectionlist_is_section_hidden(const N_GUI_SECTIONLIST *sl, int section_index)
Is a section left out of the stack.
Definition n_gui.c:19256
int n_gui_sectionlist_get_order(const N_GUI_SECTIONLIST *sl, int position)
The section stacked at a position.
Definition n_gui.c:19245
#define N_GUI_SHAPE_ROUNDED
rounded rectangle shape
Definition n_gui.h:172
int n_gui_sectionlist_is_folded(const N_GUI_SECTIONLIST *sl, int section_index)
query a section's folded state (1 = folded, 0 = expanded)
Definition n_gui.c:19267
void n_gui_sectionlist_set_section_hidden(N_GUI_SECTIONLIST *sl, int section_index, int hidden)
Leave a whole section out of the stack, header and members, or put it back.
Definition n_gui.c:19250
void n_gui_sectionlist_free(N_GUI_SECTIONLIST **sl)
free the section list (does not destroy the N_GUI widgets or window)
Definition n_gui.c:19287
void n_gui_sectionlist_relayout(N_GUI_SECTIONLIST *sl)
re-stack all sections from y0 by current fold state: hide folded members, show and reposition expande...
Definition n_gui.c:19164
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.
Definition n_gui.c:2720
int n_gui_widget_is_visible(N_GUI_CTX *ctx, int widget_id)
Is a widget currently shown.
Definition n_gui.c:2316
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
a vertical accordion of foldable sections inside one window; folding hides a section's widgets and re...
Definition n_gui.h:3378
A single GUI widget.
Definition n_gui.h:1092
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#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_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.