Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_gui_colorwheel.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
27
28#ifdef HAVE_ALLEGRO
29
30#include <math.h>
31#include <stdlib.h>
32#include <string.h>
33
34#include <allegro5/allegro_color.h>
35#include <allegro5/allegro_primitives.h>
36
37#include "nilorea/n_common.h"
38#include "nilorea/n_log.h"
40
42#define N_GUI_COLORWHEEL_PI 3.14159265358979f
43
46#define N_GUI_COLORWHEEL_RINGS 8
48#define N_GUI_COLORWHEEL_SECTORS 48
49
73
74ALLEGRO_COLOR n_gui_colorwheel_hue(float u, float v, void* user_data) {
75 float s = sqrtf(u * u + v * v);
76 float h = atan2f(v, u) * 180.0f / N_GUI_COLORWHEEL_PI;
77 float r = 0.0f, g = 0.0f, b = 0.0f;
78
79 (void)user_data;
80 if (h < 0.0f) h += 360.0f;
81 if (s > 1.0f) s = 1.0f;
82 al_color_hsv_to_rgb(h, s, 1.0f, &r, &g, &b);
83 return al_map_rgb_f(r, g, b);
84}
85
87static void _wheel_clamp(float* u, float* v) {
88 float d = sqrtf(*u * *u + *v * *v);
89
90 if (d > 1.0f) {
91 *u /= d;
92 *v /= d;
93 }
94}
95
97static void _wheel_geometry(float x, float y, float w, float h, float* cx, float* cy, float* radius) {
98 *radius = (w < h ? w : h) * 0.5f - 2.0f;
99 if (*radius < 1.0f) *radius = 1.0f;
100 *cx = x + w * 0.5f;
101 *cy = y + h * 0.5f;
102}
103
105// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_DRAW
106// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
107static void _wheel_draw(N_GUI_WIDGET* widget, float x, float y, ALLEGRO_FONT* font, void* user_data) {
108 const _N_GUI_COLORWHEEL* cw = (const _N_GUI_COLORWHEEL*)user_data;
109 float cx = 0.0f, cy = 0.0f, radius = 0.0f;
110 int ring = 0, sector;
111
112 (void)font;
113 if (!widget || !cw) return;
114 _wheel_geometry(x, y, widget->w, widget->h, &cx, &cy, &radius);
115 /* The disc as quads, each coloured at its corners with the caller's
116 * colour there, so the shading follows whatever the point means. */
117 for (ring = 0; ring < N_GUI_COLORWHEEL_RINGS; ring++) {
118 float r0 = (float)ring / N_GUI_COLORWHEEL_RINGS, r1 = (float)(ring + 1) / N_GUI_COLORWHEEL_RINGS;
119
120 for (sector = 0; sector < N_GUI_COLORWHEEL_SECTORS; sector++) {
121 float a0 = 2.0f * N_GUI_COLORWHEEL_PI * (float)sector / N_GUI_COLORWHEEL_SECTORS;
122 float a1 = 2.0f * N_GUI_COLORWHEEL_PI * (float)(sector + 1) / N_GUI_COLORWHEEL_SECTORS;
123 float us[4] = {r0 * cosf(a0), r1 * cosf(a0), r1 * cosf(a1), r0 * cosf(a1)};
124 float vs[4] = {r0 * sinf(a0), r1 * sinf(a0), r1 * sinf(a1), r0 * sinf(a1)};
125 ALLEGRO_VERTEX vtx[6];
126 static const int order[6] = {0, 1, 2, 0, 2, 3};
127 int k = 0;
128
129 for (k = 0; k < 6; k++) {
130 int c = order[k];
131
132 memset(&vtx[k], 0, sizeof(vtx[k]));
133 vtx[k].x = cx + us[c] * radius;
134 vtx[k].y = cy - vs[c] * radius;
135 vtx[k].color = cw->colour(us[c], vs[c], cw->colour_user_data);
136 }
137 al_draw_prim(vtx, NULL, NULL, 0, 6, ALLEGRO_PRIM_TRIANGLE_LIST);
138 }
139 }
140 al_draw_circle(cx, cy, radius, widget->theme.border_normal, 1.0f);
141 al_draw_line(cx - radius, cy, cx + radius, cy, widget->theme.border_normal, 1.0f);
142 al_draw_line(cx, cy - radius, cx, cy + radius, widget->theme.border_normal, 1.0f);
143 {
144 float px = cx + cw->u * radius, py = cy - cw->v * radius;
145 float r = radius * 0.07f < 3.0f ? 3.0f : radius * 0.07f;
146
147 al_draw_filled_circle(px, py, r, widget->theme.text_normal);
148 al_draw_circle(px, py, r, widget->theme.bg_normal, 1.5f);
149 }
150}
151
153static _N_GUI_COLORWHEEL* _wheel_of(N_GUI_CTX* ctx, int widget_id) {
154 const N_GUI_WIDGET* wgt = ctx ? n_gui_get_widget(ctx, widget_id) : NULL;
155 const N_GUI_CUSTOM_DATA* cd = NULL;
156
157 if (!wgt || wgt->type != N_GUI_TYPE_CUSTOM || !wgt->data) return NULL;
158 cd = (const N_GUI_CUSTOM_DATA*)wgt->data;
159 /* the paint callback says what the widget is */
160 if (cd->draw != _wheel_draw) return NULL;
161 return (_N_GUI_COLORWHEEL*)cd->user_data;
162}
163
171
173// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_MOUSE
174// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
175static int _wheel_mouse(int widget_id, const N_GUI_MOUSE_EVENT* ev, void* user_data) {
176 const _N_GUI_COLORWHEEL_MOUSE* cm = (const _N_GUI_COLORWHEEL_MOUSE*)user_data;
177 _N_GUI_COLORWHEEL* cw = cm ? cm->cw : NULL;
178 const N_GUI_WIDGET* wgt = cm ? n_gui_get_widget(cm->ctx, widget_id) : NULL;
179 float cx = 0.0f, cy = 0.0f, radius = 0.0f;
180
181 if (!cw || !wgt || wgt->w <= 0.0f || wgt->h <= 0.0f) return 0;
182 _wheel_geometry(0.0f, 0.0f, wgt->w, wgt->h, &cx, &cy, &radius);
183 if (ev->type == N_GUI_MOUSE_DOWN && ev->button == 2) {
184 cw->u = 0.0f;
185 cw->v = 0.0f;
186 if (cw->on_change) cw->on_change(widget_id, 0, cw->user_data);
188 return 1;
189 }
190 if (ev->type == N_GUI_MOUSE_DOWN && ev->button == 1) {
191 /* A second press soon after and near the first is a double click:
192 * the point goes back to the centre, as a slider with a default goes
193 * back to it, and no drag starts, so the release does not move it
194 * away again. The context's own timing, the one n_gui_was_double_
195 * click() reports by. */
196 double now = al_get_time();
197 float dx = ev->x - cw->press_x, dy = ev->y - cw->press_y;
198 float near_px = (float)cm->ctx->double_click_radius;
199
200 if (cw->press_time > 0.0 && now - cw->press_time <= cm->ctx->double_click_time &&
201 dx * dx + dy * dy <= near_px * near_px) {
202 cw->press_time = 0.0;
203 cw->dragging = 0;
204 cw->u = 0.0f;
205 cw->v = 0.0f;
206 if (cw->on_change) cw->on_change(widget_id, 0, cw->user_data);
208 return 1;
209 }
210 cw->press_time = now;
211 cw->press_x = ev->x;
212 cw->press_y = ev->y;
213 }
214 if ((ev->type == N_GUI_MOUSE_DOWN && ev->button == 1) || (ev->type == N_GUI_MOUSE_DRAG && cw->dragging)) {
215 cw->u = (ev->x - cx) / radius;
216 cw->v = (cy - ev->y) / radius;
217 _wheel_clamp(&cw->u, &cw->v);
218 cw->dragging = 1;
219 if (cw->on_change) cw->on_change(widget_id, 1, cw->user_data);
221 return 1;
222 }
223 if (ev->type == N_GUI_MOUSE_UP && cw->dragging) {
224 cw->dragging = 0;
225 if (cw->on_change) cw->on_change(widget_id, 0, cw->user_data);
226 return 1;
227 }
228 return 0;
229}
230
239
241static void _wheel_block_destroy(void* user_data) {
242 free(user_data);
243}
244
245int n_gui_colorwheel_new(N_GUI_CTX* ctx, int window_id, float x, float y, float w, float h, N_GUI_COLORWHEEL_CHANGE on_change, void* user_data) {
246 _N_GUI_COLORWHEEL_BLOCK* block = NULL;
247 int id = -1;
248
249 __n_assert(ctx, return -1);
250 block = (_N_GUI_COLORWHEEL_BLOCK*)calloc(1, sizeof(*block));
251 if (!block) return -1;
252 block->cw.on_change = on_change;
253 block->cw.user_data = user_data;
255 block->mouse.ctx = ctx;
256 block->mouse.cw = &block->cw;
257 /* the block's first member is the state, so the draw callback's pointer
258 * and the block's are the same pointer */
259 id = n_gui_add_custom(ctx, window_id, x, y, w, h, _wheel_draw, &block->cw);
260 if (id < 0) {
261 free(block);
262 return -1;
263 }
265 n_gui_custom_set_mouse(ctx, id, _wheel_mouse, &block->mouse);
266 return id;
267}
268
269int n_gui_colorwheel_set(N_GUI_CTX* ctx, int widget_id, float u, float v) {
270 _N_GUI_COLORWHEEL* cw = _wheel_of(ctx, widget_id);
271
272 if (!cw || cw->dragging) return FALSE;
273 _wheel_clamp(&u, &v);
274 cw->u = u;
275 cw->v = v;
276 n_gui_mark_dirty(ctx);
277 return TRUE;
278}
279
280int n_gui_colorwheel_get(N_GUI_CTX* ctx, int widget_id, float* u, float* v) {
281 const _N_GUI_COLORWHEEL* cw = _wheel_of(ctx, widget_id);
282
283 if (!cw) return FALSE;
284 if (u) *u = cw->u;
285 if (v) *v = cw->v;
286 return TRUE;
287}
288
289void n_gui_colorwheel_set_colour(N_GUI_CTX* ctx, int widget_id, N_GUI_COLORWHEEL_COLOUR colour, void* user_data) {
290 _N_GUI_COLORWHEEL* cw = _wheel_of(ctx, widget_id);
291
292 if (!cw) return;
293 cw->colour = colour ? colour : n_gui_colorwheel_hue;
294 cw->colour_user_data = colour ? user_data : NULL;
295 n_gui_mark_dirty(ctx);
296}
297
298#endif /* HAVE_ALLEGRO */
static void on_change(const float rgba[4], void *user_data)
the picker's callback
FONT * font
the default 8x8 font, valid after allegro_init()
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:279
float h
height
Definition n_gui.h:1104
float y
pointer y from the widget's top edge; outside 0..h during a DRAG
Definition n_gui.h:1208
N_GUI_THEME theme
color theme for this widget
Definition n_gui.h:1118
int double_click_radius
how far the pointer may move between the two presses and still count, in pixels.
Definition n_gui.h:1725
float w
width
Definition n_gui.h:1102
ALLEGRO_COLOR text_normal
text normal
Definition n_gui.h:398
int type
widget type (N_GUI_TYPE_*)
Definition n_gui.h:1096
ALLEGRO_COLOR border_normal
border normal
Definition n_gui.h:392
void * user_data
opaque user pointer handed back to the callback (not owned by n_gui)
Definition n_gui.h:1233
int button
1 left, 2 right for DOWN; 1 for UP and DRAG; 0 otherwise
Definition n_gui.h:1204
N_GUI_CUSTOM_DRAW draw
paint callback invoked during n_gui_draw (NULL = draw nothing)
Definition n_gui.h:1231
void * data
widget-specific data (union via void pointer)
Definition n_gui.h:1122
int type
N_GUI_MOUSE_DOWN, _UP, _MOVE, _DRAG or _WHEEL.
Definition n_gui.h:1202
ALLEGRO_COLOR bg_normal
background normal
Definition n_gui.h:386
float x
pointer x from the widget's left edge; outside 0..w during a DRAG
Definition n_gui.h:1206
double double_click_time
how long after a press a second one still counts as a double click, in seconds.
Definition n_gui.h:1721
#define N_GUI_MOUSE_DOWN
custom widget mouse event: a press inside it, button 1 or 2
Definition n_gui.h:1187
#define N_GUI_MOUSE_DRAG
custom widget mouse event: the pointer moved with button 1 held after a press in it; reported outside...
Definition n_gui.h:1195
void n_gui_custom_set_mouse(N_GUI_CTX *ctx, int widget_id, N_GUI_CUSTOM_MOUSE on_mouse, void *user_data)
give a custom widget mouse input
Definition n_gui.c:5730
N_GUI_WIDGET * n_gui_get_widget(N_GUI_CTX *ctx, int widget_id)
Get a widget pointer by id.
Definition n_gui.c:3701
#define N_GUI_MOUSE_UP
custom widget mouse event: the release of a button 1 press that began in it, wherever the pointer is ...
Definition n_gui.h:1190
#define N_GUI_TYPE_CUSTOM
widget type: owner-draw custom widget (rendering delegated to a callback)
Definition n_gui.h:140
void n_gui_custom_set_destroy(N_GUI_CTX *ctx, int widget_id, void(*destroy)(void *user_data))
hand a custom widget's paint user pointer to the widget
Definition n_gui.c:5745
void n_gui_mark_dirty(N_GUI_CTX *ctx)
Mark the GUI as needing a redraw.
Definition n_gui.c:1188
int n_gui_add_custom(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, N_GUI_CUSTOM_DRAW draw, void *user_data)
add an owner-draw custom widget; returns the widget id or -1
Definition n_gui.c:5698
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
Owner-draw custom widget data: a paint callback, an optional mouse callback, and their user pointers.
Definition n_gui.h:1229
A mouse event handed to a custom widget, in the widget's own coordinates.
Definition n_gui.h:1200
A single GUI widget.
Definition n_gui.h:1092
ALLEGRO_COLOR(* N_GUI_COLORWHEEL_COLOUR)(float u, float v, void *user_data)
The colour the disc shows for a point.
ALLEGRO_COLOR n_gui_colorwheel_hue(float u, float v, void *user_data)
The default disc colour: HSV, hue by angle with red to the right and going counter clockwise,...
int n_gui_colorwheel_get(N_GUI_CTX *ctx, int widget_id, float *u, float *v)
Read the point.
int n_gui_colorwheel_new(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h, N_GUI_COLORWHEEL_CHANGE on_change, void *user_data)
Add a colour wheel, its point in the centre.
void(* N_GUI_COLORWHEEL_CHANGE)(int widget_id, int dragging, void *user_data)
Called when the point moves.
void n_gui_colorwheel_set_colour(N_GUI_CTX *ctx, int widget_id, N_GUI_COLORWHEEL_COLOUR colour, void *user_data)
Paint the disc with the caller's colours.
int n_gui_colorwheel_set(N_GUI_CTX *ctx, int widget_id, float u, float v)
Set the point.
Common headers and low-level functions & define.
int dragging
non-zero while the button is held
static void _wheel_block_destroy(void *user_data)
destroy callback for the block
float u
the point, across
float press_x
where it was, in the widget
float press_y
where it was, in the widget
void * user_data
handed back to on_change
#define N_GUI_COLORWHEEL_PI
pi, not taken from math.h's M_PI, which strict C does not have
static void _wheel_draw(N_GUI_WIDGET *widget, float x, float y, ALLEGRO_FONT *font, void *user_data)
custom draw callback
_N_GUI_COLORWHEEL * cw
wheel
static int _wheel_mouse(int widget_id, const N_GUI_MOUSE_EVENT *ev, void *user_data)
custom mouse callback
float v
the point, up
static _N_GUI_COLORWHEEL * _wheel_of(N_GUI_CTX *ctx, int widget_id)
helper: find the wheel behind a widget id, or NULL
double press_time
time of the last left press, al_get_time(), for the double click
_N_GUI_COLORWHEEL_MOUSE mouse
what the mouse callback sees
static void _wheel_clamp(float *u, float *v)
helper: the point kept inside the unit circle
N_GUI_COLORWHEEL_COLOUR colour
the disc's colour, the caller's or the default
static void _wheel_geometry(float x, float y, float w, float h, float *cx, float *cy, float *radius)
helper: the disc's centre and radius in a widget of w by h at x, y
#define N_GUI_COLORWHEEL_SECTORS
rings and sectors the disc is drawn with: enough to look smooth at the sizes a panel gives it
N_GUI_COLORWHEEL_CHANGE on_change
the caller's change callback
#define N_GUI_COLORWHEEL_RINGS
rings and sectors the disc is drawn with: enough to look smooth at the sizes a panel gives it
_N_GUI_COLORWHEEL cw
what the draw callback sees
void * colour_user_data
handed back to colour
a wheel's state, owned by its custom widget
a wheel's state and its mouse callback's context in one allocation, because a custom widget owns one ...
what a wheel's mouse callback needs: the context and the state
A colour wheel for N_GUI: one point in a disc, dragged.
Generic log system.