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

examples/ex_gui_sectionlist.c

Functions

Name
float widget_y(N_GUI_CTX * gui, int id)
int widget_visible(N_GUI_CTX * gui, int id)
int process_args(int argc, char ** argv)
int main(int argc, char ** argv)
void expect_true(const char * label, int cond)
void expect_close(const char * label, float got, float want, float tol)

Attributes

Name
int log_level
int failures

Defines

Name
ALLEGRO_UNSTABLE

Functions Documentation

function widget_y

static float widget_y(
    N_GUI_CTX * gui,
    int id
)

function widget_visible

static int widget_visible(
    N_GUI_CTX * gui,
    int id
)

function process_args

static int process_args(
    int argc,
    char ** argv
)

function main

int main(
    int argc,
    char ** argv
)

function expect_true

static void expect_true(
    const char * label,
    int cond
)

function expect_close

static void expect_close(
    const char * label,
    float got,
    float want,
    float tol
)

Attributes Documentation

variable log_level

static int log_level = 3;

variable failures

static int failures = 0;

Macros Documentation

define ALLEGRO_UNSTABLE

#define ALLEGRO_UNSTABLE 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
 */


#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;
}