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

examples/ex_gui_double_click.c

Functions

Name
ALLEGRO_EVENT mouse_event(int type, int x, int y)
Build a left-button mouse event at (x, y).
int main(void )
void expect_true(const char * label, int cond)
Assert a condition, logging which check failed.
void click_at(N_GUI_CTX * gui, int x, int y)
Press and release the primary button at (x, y).
int click_and_report(N_GUI_CTX * gui, int x, int y)
Press the primary button at (x, y) and report the double click.

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 left-button 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 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 click_at

static void click_at(
    N_GUI_CTX * gui,
    int x,
    int y
)

Press and release the primary button at (x, y).

Parameters:

  • gui GUI context.
  • x Cursor x.
  • y Cursor y.

function click_and_report

static int click_and_report(
    N_GUI_CTX * gui,
    int x,
    int y
)

Press the primary button at (x, y) and report the double click.

Parameters:

  • gui GUI context.
  • x Cursor x.
  • y Cursor y.

Return: The widget the press double clicked, or -1.

The release is sent afterwards, so the answer is read at the moment the press was processed, which is when a host would read it.

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 <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 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 click_at(N_GUI_CTX* gui, int x, int y) {
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_AXES, x, y));
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, x, y));
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, x, y));
}

static int click_and_report(N_GUI_CTX* gui, int x, int y) {
    int got;

    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_AXES, x, y));
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, x, y));
    got = n_gui_double_clicked_widget(gui);
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, x, y));
    return got;
}

int main(void) {
    ALLEGRO_FONT* font;
    N_GUI_CTX* gui;
    int win, a, b;

    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);
    a = n_gui_add_button(gui, win, "A", 0.0f, 0.0f, 100.0f, 40.0f, N_GUI_SHAPE_RECT, NULL, NULL);
    b = n_gui_add_button(gui, win, "B", 0.0f, 60.0f, 100.0f, 40.0f, N_GUI_SHAPE_RECT, NULL, NULL);
    n_gui_open_window(gui, win);

    /* Nothing has happened yet. */
    expect_true("nothing is a double click to start with",
                n_gui_double_clicked_widget(gui) == -1);

    /* One press is not a double click. */
    expect_true("one press is not a double click", click_and_report(gui, 20, 20) == -1);

    /* Two on the same widget, in the same place, straight away. */
    expect_true("two presses on the same widget are", click_and_report(gui, 20, 20) == a);

    /* The pair is spent: a third press starts again rather than reporting a
       second double click, so a triple click fires the gesture once. */
    expect_true("a third press is not another double click",
                click_and_report(gui, 20, 20) == -1);
    expect_true("but the fourth is", click_and_report(gui, 20, 20) == a);

    /* Two presses on different widgets are two single clicks. */
    click_at(gui, 20, 20);
    expect_true("a press on another widget is not a double click",
                click_and_report(gui, 20, 80) == -1);

    /* The radius. This is the test that matters and the one a host rolling its
       own leaves out: two quick presses far apart are a drag, not a gesture. */
    n_gui_set_double_click_radius(gui, 4);
    click_at(gui, 20, 20);
    expect_true("two presses too far apart are not a double click",
                click_and_report(gui, 60, 20) == -1);
    /* Just inside it, on the same widget, still counts. */
    click_at(gui, 20, 20);
    expect_true("but a couple of pixels of hand movement still counts",
                click_and_report(gui, 22, 21) == a);

    /* A radius of zero demands the same pixel, which is a legal thing to ask
       for and must not be read as "no radius test at all". */
    n_gui_set_double_click_radius(gui, 0);
    click_at(gui, 20, 20);
    expect_true("a radius of zero refuses one pixel of movement",
                click_and_report(gui, 21, 20) == -1);
    click_at(gui, 20, 20);
    expect_true("and accepts the same pixel", click_and_report(gui, 20, 20) == a);
    n_gui_set_double_click_radius(gui, 4);

    /* An interval of zero switches the reporting off rather than making every
       press a double click. */
    n_gui_set_double_click_time(gui, 0.0);
    click_at(gui, 20, 20);
    expect_true("an interval of zero switches it off", click_and_report(gui, 20, 20) == -1);
    n_gui_set_double_click_time(gui, 0.4);

    /* The per-widget question. A press on the other button first, to clear
       whatever pair the checks above left half open: without it the click
       below is the second of that pair, is spent by it, and the press after
       it starts a fresh one. */
    click_at(gui, 20, 80);
    click_at(gui, 20, 20);
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_AXES, 20, 20));
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 20, 20));
    expect_true("the widget it happened on says yes", n_gui_was_double_click(gui, a));
    expect_true("another widget says no", !n_gui_was_double_click(gui, b));
    /* -1 means "no double click", so asking about -1 must not answer yes. */
    expect_true("and -1 says no", !n_gui_was_double_click(gui, -1));
    n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, 20, 20));

    /* It describes the press just processed and nothing else: the release that
       follows must clear it, or a host reading it a frame later acts twice. */
    expect_true("the following event clears it", n_gui_double_clicked_widget(gui) == -1);

    /* Two presses on empty space are not a double click on anything. */
    click_at(gui, 380, 280);
    expect_true("empty space double clicks nothing", click_and_report(gui, 380, 280) == -1);

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