6 ex__gui__slider__handle_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_gui_slider_handle.c

Functions

Name
ALLEGRO_EVENT mouse_event(int type, int x, int y)
Build a mouse event at (x, y).
int main(void )
void handle_stays_inside(N_GUI_CTX * gui, int id, float along, const char * what)
Check that a slider's handle is drawn whole wherever its value is.
void expect_true(const char * label, int cond)
Assert a condition, logging which check failed.
void expect_near(const char * label, double got, double want)
Assert two values are equal to within a tolerance.

Attributes

Name
int failures
Count of failed assertions.

Defines

Name
ALLEGRO_UNSTABLE

Functions Documentation

function mouse_event

static ALLEGRO_EVENT mouse_event(
    int type,
    int x,
    int y
)

Build a mouse event at (x, y).

Parameters:

  • type The ALLEGRO_EVENT_MOUSE_* type.
  • x Cursor x.
  • y Cursor y.

Return: The event.

function main

int main(
    void 
)

function handle_stays_inside

static void handle_stays_inside(
    N_GUI_CTX * gui,
    int id,
    float along,
    const char * what
)

Check that a slider's handle is drawn whole wherever its value is.

Parameters:

  • gui The context.
  • id The slider.
  • along The widget's length along its own axis.
  • what A name for the log lines.

function expect_true

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

Assert a condition, logging which check failed.

Parameters:

  • label What is being checked.
  • cond Non-zero to pass.

function expect_near

static void expect_near(
    const char * label,
    double got,
    double want
)

Assert two values are equal to within a tolerance.

Parameters:

  • label What is being checked.
  • got The value under test.
  • want The expected value.

Attributes Documentation

variable failures

static int failures = 0;

Count of failed assertions.

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 <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

static int failures = 0;

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

static void expect_near(const char* label, double got, double want) {
    if (fabs(got - want) > 1e-6) {
        n_log(LOG_ERR, "%s: FAILED, got %g wanted %g", label, got, want);
        failures++;
    } else {
        n_log(LOG_INFO, "%s: OK", label);
    }
}

static ALLEGRO_EVENT mouse_event(int type, int x, int y) {
    ALLEGRO_EVENT ev;
    memset(&ev, 0, sizeof(ev));
    ev.type = (unsigned int)type;
    ev.mouse.x = x;
    ev.mouse.y = y;
    ev.mouse.button = 1;
    return ev;
}

static void handle_stays_inside(N_GUI_CTX* gui, int id, float along, const char* what) {
    const double values[5] = {0.0, 0.25, 0.5, 0.75, 1.0};
    char label[128];
    int i = 0;

    for (i = 0; i < 5; i++) {
        float pos = 0.0f, r = 0.0f;

        n_gui_slider_set_value(gui, id, values[i]);
        if (!n_gui_slider_handle_pos(gui, id, &pos, &r)) {
            n_log(LOG_ERR, "%s: handle position refused", what);
            failures++;
            return;
        }
        /* Half a pixel of slack: the two sides are the same arithmetic done
         * in a different order, and this is about the rule, not about the
         * last bit of a float. */
        snprintf(label, sizeof(label), "%s at %.2f keeps its left edge inside", what,
                 values[i]);
        expect_true(label, pos - r >= -0.5f);
        snprintf(label, sizeof(label), "%s at %.2f keeps its right edge inside", what,
                 values[i]);
        expect_true(label, pos + r <= along + 0.5f);
    }
}

int main(void) {
    ALLEGRO_FONT* font;
    N_GUI_CTX* gui;
    int win, wide, narrow, tall, tiny;
    float r = 0.0f, pos = 0.0f;

    set_log_level(LOG_ERR);
    if (!al_init() || !al_init_font_addon()) {
        n_log(LOG_ERR, "allegro init failed");
        return 1;
    }
    font = al_create_builtin_font(); /* no display needed */
    if (!font) {
        n_log(LOG_ERR, "al_create_builtin_font failed");
        return 1;
    }
    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, 800.0f, 600.0f);
    win = n_gui_add_window(gui, "W", 0.0f, 0.0f, 400.0f, 300.0f);
    n_gui_window_set_flags(gui, win, N_GUI_WIN_FRAMELESS);

    wide = n_gui_add_slider(gui, win, 0.0f, 0.0f, 200.0f, 24.0f, 0.0, 1.0, 0.0,
                            N_GUI_SLIDER_VALUE, NULL, NULL);
    narrow = n_gui_add_slider(gui, win, 0.0f, 40.0f, 60.0f, 12.0f, 0.0, 1.0, 0.0,
                              N_GUI_SLIDER_VALUE, NULL, NULL);
    tall = n_gui_add_vslider(gui, win, 240.0f, 0.0f, 24.0f, 200.0f, 0.0, 1.0, 0.0,
                             N_GUI_SLIDER_VALUE, NULL, NULL);
    /* Shorter than two radii, which has nowhere for a handle to travel. It
       must answer rather than produce a negative strip. */
    tiny = n_gui_add_slider(gui, win, 0.0f, 80.0f, 6.0f, 24.0f, 0.0, 1.0, 0.0,
                            N_GUI_SLIDER_VALUE, NULL, NULL);
    n_gui_open_window(gui, win);

    handle_stays_inside(gui, wide, 200.0f, "a wide slider");
    handle_stays_inside(gui, narrow, 60.0f, "a narrow slider");
    handle_stays_inside(gui, tall, 200.0f, "a vertical slider");

    /* The minimum puts the handle's left edge on the widget's, not half a
       handle outside it. That is the whole report, stated as a number. */
    n_gui_slider_set_value(gui, wide, 0.0);
    n_gui_slider_handle_pos(gui, wide, &pos, &r);
    expect_near("at the minimum the handle sits one radius in", (double)pos, (double)r);
    n_gui_slider_set_value(gui, wide, 1.0);
    n_gui_slider_handle_pos(gui, wide, &pos, &r);
    expect_near("and at the maximum one radius short of the far end",
                (double)pos, 200.0 - (double)r);

    /* A vertical one runs the other way, so its minimum is at the bottom. */
    n_gui_slider_set_value(gui, tall, 0.0);
    n_gui_slider_handle_pos(gui, tall, &pos, &r);
    expect_near("a vertical slider's minimum is at the bottom", (double)pos,
                200.0 - (double)r);

    /* A slider with no room to travel answers, and puts the handle in the
       middle rather than somewhere outside itself. */
    if (n_gui_slider_handle_pos(gui, tiny, &pos, &r)) {
        expect_true("a slider too small to travel still answers", pos >= 0.0f);
        expect_true("and puts its handle inside itself", pos <= 6.0f);
    } else {
        n_log(LOG_ERR, "a slider too small to travel refused: FAILED");
        failures++;
    }

    /* A press is measured against the same strip, so the handle lands under
       the pointer rather than a radius away from it. One press, at one radius
       in, which is the position the minimum is drawn at: measuring against
       the whole widget instead would read that as a twentieth of the range.

       One press and not a drag, because this program synthesises its events
       rather than receiving them, and the state a real pointer carries
       between a press and the move that follows it is not reproduced here. */
    n_gui_slider_set_value(gui, wide, 0.5);
    n_gui_slider_handle_pos(gui, wide, &pos, &r);
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, (int)r, 12));
    expect_near("a press one radius in is the minimum", n_gui_slider_get_value(gui, wide),
                0.0);
    n_gui_slider_handle_pos(gui, wide, &pos, &r);
    expect_near("and the handle lands under it", (double)pos, (double)r);
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, (int)r, 12));

    /* A widget that is not a slider says so rather than reading whatever its
       own data holds at that offset. */
    {
        int lbl = n_gui_add_label(gui, win, "x", 0.0f, 120.0f, 50.0f, 20.0f,
                                  N_GUI_ALIGN_LEFT);

        expect_true("a non-slider is refused", !n_gui_slider_handle_pos(gui, lbl, &pos, &r));
    }
    expect_true("an unknown widget is refused",
                !n_gui_slider_handle_pos(gui, 9999, &pos, &r));

    n_gui_destroy_ctx(&gui);
    al_destroy_font(font);
    if (failures) {
        n_log(LOG_ERR, "ex_gui_slider_handle: %d failure(s)", failures);
        return 1;
    }
    n_log(LOG_NOTICE, "ex_gui_slider_handle: all checks passed");
    return 0;
}