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

A slider's keyboard and wheel increment, apart from its snapping step (headless).

A slider's keyboard and wheel increment, apart from its snapping step (headless). n_gui_slider_set_step() sets one number that every input path reads: the drag, the arrow keys and the wheel. So a program that wants "one arrow press moves this by a third of a stop" also gets "a drag can only land on thirds of a stop", and a program that wants a smooth drag has to leave the step at zero and accept a twentieth of the range per key press.

Those are two questions and they now have two numbers. n_gui_slider_set_key_step() sets how far a key or a notch moves, and does not snap anything. It falls back to the snapping step, and then to a twentieth of the range, so a slider that sets neither behaves exactly as it did before this existed.

Author
Castagnier Mickael
Version
1.0
Date
20/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-9) {
n_log(LOG_ERR, "%s: FAILED, got %g wanted %g", label, got, want);
} else {
n_log(LOG_INFO, "%s: OK", label);
}
}
static ALLEGRO_EVENT key_event(int keycode) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_KEY_CHAR;
ev.keyboard.keycode = keycode;
return ev;
}
static ALLEGRO_EVENT mouse_event(int type, int x, int y, int dz) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = (unsigned int)type;
ev.mouse.x = x;
ev.mouse.y = y;
ev.mouse.dz = dz;
ev.mouse.button = 1;
return ev;
}
int main(void) {
ALLEGRO_FONT* font;
int win, plain, keyed, snapped;
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);
/* The range a photo editor's exposure control has: ten stops end to end.
A twentieth of that is half a stop, which is the behaviour this exists
to give a program a way out of. */
plain = n_gui_add_slider(gui, win, 0.0f, 0.0f, 200.0f, 20.0f, -5.0, 5.0, 0.0,
N_GUI_SLIDER_VALUE, NULL, NULL);
keyed = n_gui_add_slider(gui, win, 0.0f, 40.0f, 200.0f, 20.0f, -5.0, 5.0, 0.0,
N_GUI_SLIDER_VALUE, NULL, NULL);
snapped = n_gui_add_slider(gui, win, 0.0f, 80.0f, 200.0f, 20.0f, -5.0, 5.0, 0.0,
N_GUI_SLIDER_VALUE, NULL, NULL);
/* Nothing set: a twentieth of the range, exactly as before. */
expect_near("with neither set, a key moves a twentieth of the range",
/* The snapping step is still the fallback, so a caller that only ever set
that one sees no change either. */
n_gui_slider_set_step(gui, snapped, 0.25);
expect_near("the snapping step is the next fallback",
n_gui_slider_get_key_step(gui, snapped), 0.25);
/* And the new number wins over both. */
n_gui_slider_set_key_step(gui, keyed, 1.0 / 3.0);
expect_near("a key step of its own wins", n_gui_slider_get_key_step(gui, keyed),
1.0 / 3.0);
/* Setting it must not move the value, and must not snap it: a third of a
stop is not a divisor of anything here. */
n_gui_slider_set_key_step(gui, keyed, 1.0 / 3.0);
expect_near("setting it leaves the value alone", n_gui_slider_get_value(gui, keyed), 1.1);
/* One arrow press moves by the key step and not by a twentieth. */
n_gui_process_event(gui, key_event(ALLEGRO_KEY_RIGHT));
expect_near("one arrow press moves by the key step",
n_gui_slider_get_value(gui, keyed), 1.0 / 3.0);
n_gui_process_event(gui, key_event(ALLEGRO_KEY_LEFT));
expect_near("and back", n_gui_slider_get_value(gui, keyed), 0.0);
/* A wheel notch does the same. */
n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_AXES, 20, 50, 1));
expect_near("one wheel notch moves by the key step",
n_gui_slider_get_value(gui, keyed), 1.0 / 3.0);
/* The point of the whole thing: a drag lands where it was dragged, not on
a multiple of the key step. A slider with a snapping step still snaps,
which is what that one is for. */
n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_DOWN, 150, 50, 0));
n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_AXES, 150, 50, 0));
{
double dragged = n_gui_slider_get_value(gui, keyed);
double thirds = dragged / (1.0 / 3.0);
expect_true("a drag moved it", fabs(dragged) > 1e-9);
expect_true("and it did not land on a multiple of the key step",
fabs(thirds - floor(thirds + 0.5)) > 1e-6);
}
n_gui_process_event(gui, mouse_event(ALLEGRO_EVENT_MOUSE_BUTTON_UP, 150, 50, 0));
/* Zero puts it back to the fallback chain rather than meaning "no
movement": a slider that could not be moved by a key at all would be a
trap, and 0 is what a caller passes to undo the setting. */
expect_near("zero falls back rather than freezing the slider",
expect_near("and so does a negative", n_gui_slider_get_key_step(gui, keyed), 0.5);
/* A widget that is not a slider answers 0.0 rather than reading whatever
its own data happens to hold at that offset. */
{
int label = n_gui_add_label(gui, win, "x", 0.0f, 120.0f, 50.0f, 20.0f,
expect_near("a non-slider answers zero", n_gui_slider_get_key_step(gui, label), 0.0);
expect_near("and setting one on it does nothing",
}
expect_near("an unknown widget answers zero", n_gui_slider_get_key_step(gui, 9999), 0.0);
al_destroy_font(font);
if (failures) {
n_log(LOG_ERR, "ex_gui_slider_step: %d failure(s)", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_gui_slider_step: 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 ALLEGRO_EVENT key_event(int keycode, unsigned int modifiers)
Build a key event for keycode with modifiers.
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 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
#define N_GUI_SLIDER_VALUE
slider uses raw start/end values
Definition n_gui.h:178
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
void n_gui_set_focus(N_GUI_CTX *ctx, int widget_id)
Set keyboard focus to a specific widget.
Definition n_gui.c:3908
#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
void n_gui_slider_set_step(N_GUI_CTX *ctx, int widget_id, double step)
set slider step increment.
Definition n_gui.c:4014
double n_gui_slider_get_key_step(N_GUI_CTX *ctx, int widget_id)
get the arrow key and wheel increment a slider actually uses
Definition n_gui.c:4136
void n_gui_slider_set_key_step(N_GUI_CTX *ctx, int widget_id, double step)
set how far one arrow key press or wheel notch moves a slider
Definition n_gui.c:4061
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.