Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_slider_handle.c

A slider's handle stays inside the widget at every value (headless).

A slider's handle stays inside the widget at every value (headless). The handle is a circle centred on the value's position. Centring it on the widget's own left edge at the minimum puts half the circle outside the widget, where the clipping rectangle cuts it off, so a slider sitting at its minimum drew a half moon rather than a handle; the maximum had the same fault on the other side.

The centre travels along a strip inset by the handle's radius at each end instead. The same strip is what a press or a drag is measured against, so the handle still lands under the pointer; measuring the drag against the whole widget while drawing the handle on the inset strip would put the two a radius apart at the ends, which is worse than the fault it fixed.

Author
Castagnier Mickael
Version
1.0
Date
21/09/2026
/*
* 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_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);
} 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);
} 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);
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;
int win, wide, narrow, tall, tiny;
float r = 0.0f, pos = 0.0f;
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;
}
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);
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);
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_handle_pos(gui, wide, &pos, &r);
expect_near("at the minimum the handle sits one radius in", (double)pos, (double)r);
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_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");
}
/* 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_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,
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));
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;
}
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static ALLEGRO_EVENT mouse_event(int type, int x, int y, int dz)
Build a left-button mouse event at (x, y).
static N_GUI_CTX * gui
static void expect_near(const char *label, double got, double want)
Assert two values are equal to within a tolerance.
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.
static void type(N_GUI_CTX *gui, const char *s)
Type a whole string into whatever has focus.
FONT * font
the default 8x8 font, valid after allegro_init()
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
set the display (viewport) size so global scrollbars can be computed.
Definition n_gui.c:12348
#define N_GUI_ALIGN_LEFT
left aligned text
Definition n_gui.h:212
int n_gui_add_slider(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, double min_val, double max_val, double initial, int mode, void(*on_change)(int, double, void *), void *user_data)
Add a slider widget.
Definition n_gui.c:3106
int n_gui_add_vslider(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, double min_val, double max_val, double initial, int mode, void(*on_change)(int, double, void *), void *user_data)
Add a vertical slider widget.
Definition n_gui.c:3148
#define N_GUI_SLIDER_VALUE
slider uses raw start/end values
Definition n_gui.h:178
int n_gui_slider_handle_pos(N_GUI_CTX *ctx, int widget_id, float *pos, float *radius)
Where a slider's handle is drawn and how big it is.
Definition n_gui.c:4108
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
Definition n_gui.c:13611
int n_gui_add_label(N_GUI_CTX *ctx, int window_id, const char *text, float x, float y, float w, float h, int align)
Add a static text label.
Definition n_gui.c:3639
double n_gui_slider_get_value(N_GUI_CTX *ctx, int widget_id)
get the current value of a slider widget
Definition n_gui.c:3962
#define N_GUI_WIN_FRAMELESS
frameless window: no title bar drawn, drag via window body unless N_GUI_WIN_FIXED_POSITION is also se...
Definition n_gui.h:276
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
Definition n_gui.c:1277
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:1103
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
void n_gui_open_window(N_GUI_CTX *ctx, int window_id)
Open (show) a window.
Definition n_gui.c:1436
void n_gui_slider_set_value(N_GUI_CTX *ctx, int widget_id, double value)
set the value of a slider widget
Definition n_gui.c:3976
void n_gui_window_set_flags(N_GUI_CTX *ctx, int window_id, int flags)
Set feature flags on a window.
Definition n_gui.c:2515
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_INFO
informational
Definition n_log.h:106
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.