7 ex_gui_sectionlist_8c example
gullradriel edited this page 2026-10-12 07:59:13 +02:00

ex_gui_sectionlist.c

N_GUI_SECTIONLIST folding, offsets and hidden members (headless). The accordion stacks sections in one window: a header per section and the member widgets under it, with a fold that hides the members and pulls everything below up.

Two of the calls checked here are for a caller whose layout is not fixed at build time - one that recomputes every widget's rectangle when its window is resized, or whose sections hold a different number of controls from one moment to the next. n_gui_sectionlist_set_widget_offset() restates where a member sits inside its section, and n_gui_sectionlist_set_widget_hidden() takes one out of the stack without removing it. ```c

/*

  • 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 <unistd.h>

#include "nilorea/n_common.h" #include "nilorea/n_gui.h" #include "nilorea/n_log.h"

static int log_level = LOG_ERR; static int failures = 0;

static int process_args(int argc, char** argv) { int opt; while ((opt = getopt(argc, argv, "V:")) != -1) { switch (opt) { case 'V': if (!strcmp("LOG_NULL", optarg)) log_level = LOG_NULL; else if (!strcmp("LOG_NOTICE", optarg)) log_level = LOG_NOTICE; else if (!strcmp("LOG_INFO", optarg)) log_level = LOG_INFO; else if (!strcmp("LOG_ERR", optarg)) log_level = LOG_ERR; else if (!strcmp("LOG_DEBUG", optarg)) log_level = LOG_DEBUG; else return -1; break; default: return -1; } } return 0; }

static void expect_close(const char* label, float got, float want, float tol) { float d = got - want; if (d < 0) d = -d; if (d > tol) { n_log(LOG_ERR, "%s: got %.3f, expected %.3f", label, got, want); failures++; } }

static void expect_true(const char* label, int cond) { if (!cond) { n_log(LOG_ERR, "%s: condition false", label); failures++; } }

/* where a widget sits now / static float widget_y(N_GUI_CTX gui, int id) { const N_GUI_WIDGET* w = n_gui_get_widget(gui, id); return w ? w->y : -1.0f; }

static int widget_visible(N_GUI_CTX* gui, int id) { return n_gui_widget_is_visible(gui, id); }

int main(int argc, char** argv) { set_log_level(LOG_ERR); if (process_args(argc, argv) != 0) { fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]); return 2; } set_log_level(log_level);

if (!al_init() || !al_init_font_addon()) {
    n_log(LOG_ERR, "allegro init failed");
    return 1;
}
ALLEGRO_FONT* font = al_create_builtin_font();
if (!font) {
    n_log(LOG_ERR, "al_create_builtin_font failed");
    return 1;
}
N_GUI_CTX* gui = n_gui_new_ctx(font);
if (!gui) {
    n_log(LOG_ERR, "n_gui_new_ctx failed");
    al_destroy_font(font);
    return 1;
}
n_gui_set_display_size(gui, 400.0f, 800.0f);

int win = n_gui_add_window(gui, "Panel", 0.0f, 0.0f, 400.0f, 800.0f);
expect_true("window created", win >= 0);

/* Two sections of two members each. The header height is 20, the gap 4,
 * and the members are 30 tall, which makes every number below arithmetic
 * anybody can check by hand. */
N_GUI_SECTIONLIST* sl = n_gui_sectionlist_create(gui, win, 10.0f, 10.0f, 200.0f, 20.0f,
                                                 4.0f, NULL, NULL);
expect_true("section list created", sl != NULL);
if (!sl) {
    n_gui_destroy_ctx(&gui);
    al_destroy_font(font);
    return 1;
}

int first = n_gui_sectionlist_add_section(sl, "First", 10.0f, 0);
int a = n_gui_add_button(gui, win, "a", 10.0f, 34.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
                         NULL, NULL);
int b = n_gui_add_button(gui, win, "b", 10.0f, 64.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
                         NULL, NULL);
n_gui_sectionlist_add_widget(sl, first, a);
n_gui_sectionlist_add_widget(sl, first, b);

int second = n_gui_sectionlist_add_section(sl, "Second", 200.0f, 0);
int c = n_gui_add_button(gui, win, "c", 10.0f, 224.0f, 200.0f, 30.0f, N_GUI_SHAPE_ROUNDED,
                         NULL, NULL);
n_gui_sectionlist_add_widget(sl, second, c);
expect_true("two sections", n_gui_sectionlist_get_count(sl) == 2);

/* Stacked from y0: header at 10, its members at 34 and 64, the second
 * header after the first section's 60 of content plus a gap. */
n_gui_sectionlist_relayout(sl);
expect_close("first member", widget_y(gui, a), 34.0f, 0.01f);
expect_close("second member", widget_y(gui, b), 64.0f, 0.01f);
expect_close("second header's member", widget_y(gui, c), 122.0f, 0.01f);
/* 10 from the top, then per section a 20 header, a 4 gap, its content and
   another 4: 10 + 24 + 60 + 4 + 24 + 30 + 4. */
expect_close("content height", n_gui_sectionlist_content_height(sl), 156.0f, 0.01f);

/* Folding hides the members and pulls what is below them up. */
n_gui_sectionlist_set_folded(sl, first, 1);
expect_true("folded is folded", n_gui_sectionlist_is_folded(sl, first) == 1);
expect_true("a is hidden", !widget_visible(gui, a));
expect_true("b is hidden", !widget_visible(gui, b));
expect_close("the second section came up", widget_y(gui, c), 58.0f, 0.01f);

n_gui_sectionlist_set_folded(sl, first, 0);
expect_true("a is back", widget_visible(gui, a));
expect_close("and where it was", widget_y(gui, a), 34.0f, 0.01f);

/* A caller that lays its own members out says where they are. Moving b
 * down by ten moves the section's height and everything under it. */
n_gui_sectionlist_set_widget_offset(sl, first, b, 40.0f);
n_gui_sectionlist_relayout(sl);
expect_close("b at its new offset", widget_y(gui, b), 74.0f, 0.01f);
expect_close("the section grew", widget_y(gui, c), 132.0f, 0.01f);

/* A member the caller keeps hidden is not shown by an unfold and does not
 * count towards the height. With b out, the first section is a's thirty
 * rather than b's seventy, so what is under it comes up by forty. */
n_gui_sectionlist_set_widget_hidden(sl, first, b, 1);
n_gui_sectionlist_relayout(sl);
expect_true("hidden stays hidden", !widget_visible(gui, b));
expect_true("a is still shown", widget_visible(gui, a));
expect_close("the section shrank to what is left", widget_y(gui, c), 92.0f, 0.01f);

n_gui_sectionlist_set_widget_hidden(sl, first, b, 0);
n_gui_sectionlist_relayout(sl);
expect_true("unhidden comes back", widget_visible(gui, b));
expect_close("and the height with it", widget_y(gui, c), 132.0f, 0.01f);

/* A folded section hides a member the caller had hidden as well, and
 * unfolding does not bring it back. */
n_gui_sectionlist_set_widget_hidden(sl, first, b, 1);
n_gui_sectionlist_set_folded(sl, first, 1);
expect_true("both hidden while folded", !widget_visible(gui, a) && !widget_visible(gui, b));
n_gui_sectionlist_set_folded(sl, first, 0);
expect_true("only the caller's stays hidden",
            widget_visible(gui, a) && !widget_visible(gui, b));

/* And the whole list moved and widened, which is what a resized window
   does to it. Everything stacks from the new origin. */
n_gui_sectionlist_set_widget_hidden(sl, first, b, 0);
n_gui_sectionlist_set_geometry(sl, 20.0f, 50.0f, 300.0f, 20.0f, 4.0f);
n_gui_sectionlist_relayout(sl);
expect_close("moved to the new origin", widget_y(gui, a), 74.0f, 0.01f);
{
    N_GUI_WIDGET* hdr = n_gui_get_widget(gui, sl->sections[0].header_id);
    expect_close("and widened with it", hdr ? hdr->w : 0.0f, 300.0f, 0.01f);
    expect_close("from the new left", hdr ? hdr->x : 0.0f, 20.0f, 0.01f);
    /* The height too, and it is the one of the four worth checking: a
     * window that reflows its widgets when it is resized scales a header
     * like anything else, and a relayout that set only the other three
     * left that scaling in place. */
    if (hdr) hdr->h = 145.0f;
    n_gui_sectionlist_relayout(sl);
    expect_close("and the header keeps its own height", hdr ? hdr->h : 0.0f, 20.0f, 0.01f);
}

/* Another order: the second section on top. Its member then sits under
 * the first header position, and the first section follows it. From
 * y0 50: second header 50, c at 74, then 30 of c and a gap; first
 * header at 108, a at 132. */
{
    const int flipped[2] = {1, 0};
    const int messy[3] = {1, 1, 7};

    n_gui_sectionlist_set_order(sl, flipped, 2);
    expect_true("the order reads back", n_gui_sectionlist_get_order(sl, 0) == second &&
                                            n_gui_sectionlist_get_order(sl, 1) == first);
    expect_close("the second section's member on top", widget_y(gui, c), 74.0f, 0.01f);
    expect_close("the first section's below it", widget_y(gui, a), 132.0f, 0.01f);
    /* A section left out: gone with its header, the rest closes up. */
    n_gui_sectionlist_set_section_hidden(sl, second, 1);
    expect_true("a hidden section says so", n_gui_sectionlist_is_section_hidden(sl, second) == 1);
    expect_true("its member is hidden", !widget_visible(gui, c));
    expect_true("and its header",
                !widget_visible(gui, sl->sections[second].header_id));
    expect_close("the first comes up to the top", widget_y(gui, a), 74.0f, 0.01f);
    expect_close("and the height counts it alone", n_gui_sectionlist_content_height(sl), 148.0f, 0.01f);
    n_gui_sectionlist_set_section_hidden(sl, second, 0);
    expect_true("shown again", widget_visible(gui, c));
    /* An order with a repeat and an index that is not there still gives
     * every section once. */
    n_gui_sectionlist_set_order(sl, messy, 3);
    expect_true("a messy order is made whole", n_gui_sectionlist_get_order(sl, 0) == second &&
                                                   n_gui_sectionlist_get_order(sl, 1) == first);
    expect_true("past the end is -1", n_gui_sectionlist_get_order(sl, 2) == -1);
}

/* A full section refuses a member, says so, and returns -1; the one
 * that fits returns 0. Found in AfterAPro, whose plugin section
 * outgrew 48 members and lost four without a word. */
{
    int third = n_gui_sectionlist_add_section(sl, "Full", 600.0f, 0);
    int k = 0, refused = 0, added = 0;

    for (k = 0; k < N_GUI_SECTION_WIDGETS_MAX + 1; k++) {
        int w = n_gui_add_label(gui, win, "x", 10.0f, 630.0f, 50.0f, 10.0f, N_GUI_ALIGN_LEFT);
        if (n_gui_sectionlist_add_widget(sl, third, w) == 0)
            added++;
        else
            refused++;
    }
    expect_true("a section takes N_GUI_SECTION_WIDGETS_MAX members", added == N_GUI_SECTION_WIDGETS_MAX);
    expect_true("and refuses the next with -1", refused == 1);
}

n_gui_sectionlist_free(&sl);
expect_true("freed", sl == NULL);

n_gui_destroy_ctx(&gui);
al_destroy_font(font);

if (failures) {
    n_log(LOG_ERR, "ex_gui_sectionlist: %d failure(s)", failures);
    return 1;
}
n_log(LOG_NOTICE, "ex_gui_sectionlist: all checks passed");
return 0;

}


_Filename: ex_gui_sectionlist.c_