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

examples/ex_gui_curve.c

Functions

Name
void right(N_GUI_CTX * gui, float cx, float cy)
right press
int process_args(int argc, char ** argv)
read -V LOGLEVEL
void on_change(int widget_id, int dragging, void * user_data)
change callback
int near(float a, float b)
is a near b
int main(int argc, char ** argv)
float flat(const float * xs, const float * ys, int count, float x, void * user_data)
a caller's own curve: flat
void feed(N_GUI_CTX * gui, int type, int x, int y, int button)
feed one mouse event at display coordinates
void expect_true(const char * what, int ok)
record one check
void drag(N_GUI_CTX * gui, float fx, float fy, float tx, float ty)
press, drag to, release
void background(float x, float y, float w, float h, void * user_data)
a background

Attributes

Name
int log_level
int failures
int drag_changes
int changes
changes seen: all of them, and those that were drag steps
int background_drawn
did the background draw

Defines

Name
AT_Y(cy)
as AT_X, for y
AT_X(cx)
the editor sits at 50,50 and is 200 by 100: display position of a curve coordinate
ALLEGRO_UNSTABLE

Functions Documentation

function right

static void right(
    N_GUI_CTX * gui,
    float cx,
    float cy
)

right press

function process_args

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

read -V LOGLEVEL

function on_change

static void on_change(
    int widget_id,
    int dragging,
    void * user_data
)

change callback

function near

static int near(
    float a,
    float b
)

is a near b

function main

int main(
    int argc,
    char ** argv
)

function flat

static float flat(
    const float * xs,
    const float * ys,
    int count,
    float x,
    void * user_data
)

a caller's own curve: flat

function feed

static void feed(
    N_GUI_CTX * gui,
    int type,
    int x,
    int y,
    int button
)

feed one mouse event at display coordinates

function expect_true

static void expect_true(
    const char * what,
    int ok
)

record one check

function drag

static void drag(
    N_GUI_CTX * gui,
    float fx,
    float fy,
    float tx,
    float ty
)

press, drag to, release

function background

static void background(
    float x,
    float y,
    float w,
    float h,
    void * user_data
)

a background

Attributes Documentation

variable log_level

static int log_level = 3;

variable failures

static int failures = 0;

variable drag_changes

static int drag_changes = 0;

variable changes

static int changes = 0;

changes seen: all of them, and those that were drag steps

variable background_drawn

static int background_drawn = 0;

did the background draw

Macros Documentation

define AT_Y

#define AT_Y(
    cy
)
((int)(50.0f + (1.0f - (cy)) * 100.0f + 0.5f))

as AT_X, for y

define AT_X

#define AT_X(
    cx
)
((int)(50.0f + (cx) * 200.0f + 0.5f))

the editor sits at 50,50 and is 200 by 100: display position of a curve coordinate

define ALLEGRO_UNSTABLE

#define ALLEGRO_UNSTABLE 1

Source code



#define ALLEGRO_UNSTABLE 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <allegro5/allegro_primitives.h>

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

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

static int changes = 0, drag_changes = 0;
static int background_drawn = 0;

static void expect_true(const char* what, int ok) {
    if (!ok) {
        n_log(LOG_ERR, "FAIL: %s", what);
        failures++;
    } else {
        n_log(LOG_INFO, "ok: %s", what);
    }
}

static int process_args(int argc, char** argv) {
    int opt = 0;

    while ((opt = getopt(argc, argv, "V:")) != -1) {
        if (opt == 'V') {
            log_level = atoi(optarg);
        } else {
            return -1;
        }
    }
    return 0;
}

static void on_change(int widget_id, int dragging, void* user_data) {
    (void)widget_id;
    (void)user_data;
    changes++;
    if (dragging) drag_changes++;
}

static float flat(const float* xs, const float* ys, int count, float x, void* user_data) {
    (void)xs;
    (void)ys;
    (void)count;
    (void)x;
    (void)user_data;
    return 0.5f;
}

static void background(float x, float y, float w, float h, void* user_data) {
    (void)user_data;
    background_drawn = 1;
    al_draw_filled_rectangle(x, y, x + w, y + h, al_map_rgba(40, 40, 40, 128));
}

static void feed(N_GUI_CTX* gui, int type, int x, int y, int button) {
    ALLEGRO_EVENT ev;

    memset(&ev, 0, sizeof(ev));
    ev.type = (ALLEGRO_EVENT_TYPE)type;
    ev.mouse.x = x;
    ev.mouse.y = y;
    ev.mouse.dx = type == ALLEGRO_EVENT_MOUSE_AXES ? 1 : 0;
    ev.mouse.button = (unsigned int)button;
    n_gui_process_event(gui, ev);
}

#define AT_X(cx) ((int)(50.0f + (cx) * 200.0f + 0.5f))
#define AT_Y(cy) ((int)(50.0f + (1.0f - (cy)) * 100.0f + 0.5f))

static void drag(N_GUI_CTX* gui, float fx, float fy, float tx, float ty) {
    feed(gui, ALLEGRO_EVENT_MOUSE_AXES, AT_X(fx), AT_Y(fy), 0);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, AT_X(fx), AT_Y(fy), 1);
    feed(gui, ALLEGRO_EVENT_MOUSE_AXES, AT_X(tx), AT_Y(ty), 0);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, AT_X(tx), AT_Y(ty), 1);
}

static void right(N_GUI_CTX* gui, float cx, float cy) {
    feed(gui, ALLEGRO_EVENT_MOUSE_AXES, AT_X(cx), AT_Y(cy), 0);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, AT_X(cx), AT_Y(cy), 2);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, AT_X(cx), AT_Y(cy), 2);
}

static int near(float a, float b) {
    return a - b < 0.011f && b - a < 0.011f;
}

int main(int argc, char** argv) {
    float xs[8], ys[8];
    int n = 0, i = 0, monotone = 1, bounded = 1;

    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() || !al_init_primitives_addon()) {
        n_log(LOG_ERR, "allegro init failed");
        return 1;
    }
    ALLEGRO_FONT* font = al_create_builtin_font();
    if (!font) return 1;
    N_GUI_CTX* gui = n_gui_new_ctx(font);
    if (!gui) {
        al_destroy_font(font);
        return 1;
    }
    n_gui_set_display_size(gui, 400.0f, 300.0f);
    int 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);
    n_gui_open_window(gui, win);

    int ed = n_gui_curve_new(gui, win, 50.0f, 50.0f, 200.0f, 100.0f, 4, on_change, NULL);
    expect_true("an editor is made", ed >= 0);
    n = n_gui_curve_get_points(gui, ed, xs, ys, 8);
    expect_true("it starts as the identity", n == 2 && xs[0] == 0.0f && ys[0] == 0.0f && xs[1] == 1.0f && ys[1] == 1.0f);
    expect_true("which evaluates as a straight line", near(n_gui_curve_eval(gui, ed, 0.3f), 0.3f));

    /* add, then drag */
    drag(gui, 0.5f, 0.75f, 0.3f, 0.9f);
    n = n_gui_curve_get_points(gui, ed, xs, ys, 8);
    expect_true("a press off the points adds one", n == 3);
    expect_true("the add is one change, then drag steps", changes == 2 && drag_changes == 1);
    expect_true("the drag moved it", near(xs[1], 0.3f) && near(ys[1], 0.9f));

    /* past its neighbour */
    drag(gui, 0.3f, 0.9f, 1.0f, 0.5f);
    n_gui_curve_get_points(gui, ed, xs, ys, 8);
    expect_true("an inner point stops short of its neighbour", xs[1] < xs[2] && xs[1] > 0.99f);

    /* an end keeps its x */
    drag(gui, 0.0f, 0.0f, 0.4f, 0.5f);
    n_gui_curve_get_points(gui, ed, xs, ys, 8);
    expect_true("an end moves up and down only", xs[0] == 0.0f && near(ys[0], 0.5f));

    /* removal */
    right(gui, 0.0f, 0.5f);
    expect_true("an end cannot be removed", n_gui_curve_get_points(gui, ed, NULL, NULL, 0) == 3);
    n_gui_curve_get_points(gui, ed, xs, ys, 8);
    right(gui, xs[1], ys[1]);
    expect_true("an inner point can", n_gui_curve_get_points(gui, ed, NULL, NULL, 0) == 2);

    /* set from code */
    {
        const float sx[4] = {1.0f, 0.25f, 0.0f, 0.75f};
        const float sy[4] = {1.0f, 0.1f, 0.0f, 0.9f};

        expect_true("points set from code", n_gui_curve_set_points(gui, ed, sx, sy, 4));
        n = n_gui_curve_get_points(gui, ed, xs, ys, 8);
        expect_true("come back sorted", n == 4 && xs[0] == 0.0f && xs[1] == 0.25f && ys[1] == 0.1f && xs[3] == 1.0f);
        expect_true("more than the maximum is refused", n_gui_curve_set_points(gui, ed, sx, sy, 5) == FALSE);
    }
    /* the maximum, by the mouse */
    feed(gui, ALLEGRO_EVENT_MOUSE_AXES, AT_X(0.5f), AT_Y(0.2f), 0);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, AT_X(0.5f), AT_Y(0.2f), 1);
    expect_true("a press adds nothing past the maximum", n_gui_curve_get_points(gui, ed, NULL, NULL, 0) == 4);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, AT_X(0.5f), AT_Y(0.2f), 1);

    /* no set while dragging */
    feed(gui, ALLEGRO_EVENT_MOUSE_AXES, AT_X(0.25f), AT_Y(0.1f), 0);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, AT_X(0.25f), AT_Y(0.1f), 1);
    expect_true("set is refused during a drag", n_gui_curve_set_points(gui, ed, xs, ys, 2) == FALSE);
    feed(gui, ALLEGRO_EVENT_MOUSE_BUTTON_UP, AT_X(0.25f), AT_Y(0.1f), 1);
    expect_true("and accepted after it", n_gui_curve_set_points(gui, ed, xs, ys, 4) == TRUE);

    /* the caller's curve */
    n_gui_curve_set_eval(gui, ed, flat, NULL);
    expect_true("the caller's curve is the editor's", n_gui_curve_eval(gui, ed, 0.9f) == 0.5f);
    n_gui_curve_set_eval(gui, ed, NULL, NULL);

    /* the built-in spline */
    {
        const float px[3] = {0.0f, 0.5f, 1.0f};
        const float py[3] = {0.0f, 1.0f, 1.0f};
        float prev = 0.0f;

        for (i = 0; i <= 200; i++) {
            float v = n_gui_curve_eval_points(px, py, 3, (float)i / 200.0f);

            if (v < prev - 1e-6f) monotone = 0;
            if (v > 1.0f || v < 0.0f) bounded = 0;
            prev = v;
        }
        expect_true("the spline through rising points never falls", monotone);
        expect_true("and never overshoots", bounded);
        expect_true("it holds its end value past the last point", n_gui_curve_eval_points(px, py, 2, 0.9f) == 1.0f);
    }
    {
        int label = n_gui_add_label(gui, win, "not an editor", 10.0f, 200.0f, 100.0f, 20.0f, N_GUI_ALIGN_LEFT);

        expect_true("a widget that is not an editor is refused", n_gui_curve_get_points(gui, label, NULL, NULL, 0) == -1);
    }

    /* drawn */
    {
        ALLEGRO_BITMAP* target = NULL;
        int old = al_get_new_bitmap_flags();

        n_gui_curve_set_background(gui, ed, background, NULL);
        n_gui_curve_set_point_radius(gui, ed, 5.0f);
        al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
        target = al_create_bitmap(400, 300);
        al_set_new_bitmap_flags(old);
        al_set_target_bitmap(target);
        al_clear_to_color(al_map_rgb(0, 0, 0));
        n_gui_draw(gui);
        al_set_target_bitmap(NULL);
        al_destroy_bitmap(target);
        expect_true("the background is drawn under it", background_drawn);
    }

    n_gui_destroy_ctx(&gui);
    al_destroy_font(font);
    if (failures) {
        fprintf(stderr, "ex_gui_curve: %d failure(s)\n", failures);
        return 1;
    }
    printf("ex_gui_curve: ok\n");
    return 0;
}