Table of Contents
- src/n_gui_color.c
- Structures
- Functions
- Attributes
- Defines
- Detailed Description
- Functions Documentation
- function _n_gui_color_set_label
- function _n_gui_color_on_slider
- function _n_gui_color_on_close_window
- function _n_gui_color_on_close_button
- function _n_gui_color_draw_swatch
- function _n_gui_color_clamp
- Attributes Documentation
- Macros Documentation
- define N_GUI_COLOR_W
- define N_GUI_COLOR_SWATCH
- define N_GUI_COLOR_ROW
- define N_GUI_COLOR_PAD
- define N_GUI_COLOR_LABEL
- define N_GUI_COLOR_CHECK
- define N_GUI_COLOR_BTN_W
- Source code
src/n_gui_color.c
A colour picker dialog for N_GUI, and a swatch to show a colour. More...
Structures
| Name | |
|---|---|
| struct | N_GUI_COLOR_PICKER a colour picker: the ids of its widgets and the colour being edited |
Functions
| Name | |
|---|---|
| void | _n_gui_color_set_label(N_GUI_COLOR_PICKER * picker, int channel) helper: write one channel's label, its name and its value out of 255 |
| void | _n_gui_color_on_slider(int widget_id, double value, void * user_data) slider callback: one channel moved |
| void | _n_gui_color_on_close_window(int window_id, void * user_data) title bar close: the same as the Close button, so the callback is forgotten either way |
| void | _n_gui_color_on_close_button(int widget_id, void * user_data) button callback: Close |
| void | _n_gui_color_draw_swatch(N_GUI_WIDGET * widget, float x, float y, ALLEGRO_FONT * font, void * user_data) custom draw callback of the large swatch |
| float | _n_gui_color_clamp(float v) helper: clamp a channel to 0..1 |
Attributes
| Name | |
|---|---|
| const char *const[4] | _n_gui_color_channels the four channels, in the order of the sliders |
Defines
| Name | |
|---|---|
| N_GUI_COLOR_W window width at scale 1: wide enough that a slider moved by one step in 255 moves by at least a pixel |
|
| N_GUI_COLOR_SWATCH height of the large swatch at scale 1 |
|
| N_GUI_COLOR_ROW height of a slider or button row at scale 1 |
|
| N_GUI_COLOR_PAD padding at scale 1 |
|
| N_GUI_COLOR_LABEL height of a label at scale 1 |
|
| N_GUI_COLOR_CHECK side of one checkerboard square behind a swatch, in pixels. |
|
| N_GUI_COLOR_BTN_W width of the Close button at scale 1 |
Detailed Description
A colour picker dialog for N_GUI, and a swatch to show a colour.
Author: Castagnier Mickael
Version: 1.0
Date: 24/09/2026
Functions Documentation
function _n_gui_color_set_label
static void _n_gui_color_set_label(
N_GUI_COLOR_PICKER * picker,
int channel
)
helper: write one channel's label, its name and its value out of 255
function _n_gui_color_on_slider
static void _n_gui_color_on_slider(
int widget_id,
double value,
void * user_data
)
slider callback: one channel moved
function _n_gui_color_on_close_window
static void _n_gui_color_on_close_window(
int window_id,
void * user_data
)
title bar close: the same as the Close button, so the callback is forgotten either way
function _n_gui_color_on_close_button
static void _n_gui_color_on_close_button(
int widget_id,
void * user_data
)
button callback: Close
function _n_gui_color_draw_swatch
static void _n_gui_color_draw_swatch(
N_GUI_WIDGET * widget,
float x,
float y,
ALLEGRO_FONT * font,
void * user_data
)
custom draw callback of the large swatch
function _n_gui_color_clamp
static float _n_gui_color_clamp(
float v
)
helper: clamp a channel to 0..1
Attributes Documentation
variable _n_gui_color_channels
static const char *const[4] _n_gui_color_channels = {"Red", "Green", "Blue", "Alpha"};
the four channels, in the order of the sliders
Macros Documentation
define N_GUI_COLOR_W
#define N_GUI_COLOR_W 300.0f
window width at scale 1: wide enough that a slider moved by one step in 255 moves by at least a pixel
define N_GUI_COLOR_SWATCH
#define N_GUI_COLOR_SWATCH 48.0f
height of the large swatch at scale 1
define N_GUI_COLOR_ROW
#define N_GUI_COLOR_ROW 24.0f
height of a slider or button row at scale 1
define N_GUI_COLOR_PAD
#define N_GUI_COLOR_PAD 10.0f
padding at scale 1
define N_GUI_COLOR_LABEL
#define N_GUI_COLOR_LABEL 18.0f
height of a label at scale 1
define N_GUI_COLOR_CHECK
#define N_GUI_COLOR_CHECK 6.0f
side of one checkerboard square behind a swatch, in pixels.
Not scaled: it only has to show that something is see-through.
define N_GUI_COLOR_BTN_W
#define N_GUI_COLOR_BTN_W 90.0f
width of the Close button at scale 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
*/
#ifdef HAVE_ALLEGRO
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <allegro5/allegro_primitives.h>
#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_gui_color.h"
#define N_GUI_COLOR_W 300.0f
#define N_GUI_COLOR_ROW 24.0f
#define N_GUI_COLOR_LABEL 18.0f
#define N_GUI_COLOR_SWATCH 48.0f
#define N_GUI_COLOR_PAD 10.0f
#define N_GUI_COLOR_BTN_W 90.0f
#define N_GUI_COLOR_CHECK 6.0f
static const char* const _n_gui_color_channels[4] = {"Red", "Green", "Blue", "Alpha"};
struct N_GUI_COLOR_PICKER {
N_GUI_CTX* ctx;
int window;
int lbl_what;
int swatch;
int lbl[4];
int sl[4];
int btn_close;
float rgba[4];
int syncing;
N_GUI_COLOR_FN on_change;
void* user_data;
};
static float _n_gui_color_clamp(float v) {
return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v);
}
ALLEGRO_MOUSE_CURSOR* n_gui_eyedropper_cursor(float scale) {
ALLEGRO_MOUSE_CURSOR* cursor = NULL;
ALLEGRO_BITMAP* bitmap = NULL;
ALLEGRO_BITMAP* previous = al_get_target_bitmap();
ALLEGRO_STATE state;
int size = (int)(32.0f * scale + 0.5f);
float s = 0.0f, tip_x = 0.0f, tip_y = 0.0f, neck_x = 0.0f, neck_y = 0.0f, bulb_x = 0.0f, bulb_y = 0.0f;
if (size < 16) size = 16;
s = (float)size / 32.0f;
al_store_state(&state, ALLEGRO_STATE_NEW_BITMAP_PARAMETERS | ALLEGRO_STATE_BLENDER);
/* A memory bitmap: a cursor is copied out of it by the system, and a
video one would tie this to whichever display is current. */
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP);
bitmap = al_create_bitmap(size, size);
if (!bitmap) {
al_restore_state(&state);
n_log(LOG_ERR, "eyedropper cursor: could not create a %dx%d bitmap", size, size);
return NULL;
}
al_set_target_bitmap(bitmap);
al_clear_to_color(al_map_rgba(0, 0, 0, 0));
al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
/* Along the diagonal from the tip, bottom left, to the bulb, top right. */
tip_x = 1.5f * s;
tip_y = (float)size - 1.5f * s;
neck_x = 21.0f * s;
neck_y = 11.0f * s;
bulb_x = 25.5f * s;
bulb_y = 6.5f * s;
/* The tube: a black outline under a pale fill, the fill narrower, so a
black line shows around it on a light photograph and the pale one on
a dark photograph. */
al_draw_line(tip_x, tip_y, neck_x, neck_y, al_map_rgb(0, 0, 0), 6.0f * s);
al_draw_line(tip_x + 1.0f * s, tip_y - 1.0f * s, neck_x, neck_y, al_map_rgb(235, 240, 245), 3.0f * s);
/* The collar where the tube meets the bulb, across the diagonal. */
al_draw_line(neck_x - 3.5f * s, neck_y - 3.5f * s, neck_x + 3.5f * s, neck_y + 3.5f * s, al_map_rgb(0, 0, 0), 4.0f * s);
al_draw_line(neck_x - 2.5f * s, neck_y - 2.5f * s, neck_x + 2.5f * s, neck_y + 2.5f * s, al_map_rgb(235, 240, 245), 1.5f * s);
/* The bulb. */
al_draw_filled_circle(bulb_x, bulb_y, 6.0f * s, al_map_rgb(255, 255, 255));
al_draw_filled_circle(bulb_x, bulb_y, 5.0f * s, al_map_rgb(0, 0, 0));
al_draw_filled_circle(bulb_x, bulb_y, 3.5f * s, al_map_rgb(70, 70, 80));
/* The tip, one dark pixel where the colour is read. */
al_draw_filled_rectangle(0.0f, tip_y - 1.5f * s, 2.0f * s, (float)size, al_map_rgb(0, 0, 0));
al_set_target_bitmap(previous);
al_restore_state(&state);
cursor = al_create_mouse_cursor(bitmap, 0, size - 1);
al_destroy_bitmap(bitmap);
if (!cursor) n_log(LOG_ERR, "eyedropper cursor: Allegro could not make a cursor");
return cursor;
}
void n_gui_draw_color_swatch(float x, float y, float w, float h, const float rgba[4]) {
int rows = 0, cols = 0, row = 0;
if (!rgba || w <= 0.0f || h <= 0.0f) return;
/* The checkerboard first, so an alpha below one shows as squares coming
* through rather than as a darker colour. Whole squares, the last of each
* row and column cut at the swatch's edge. */
rows = (int)ceilf(h / N_GUI_COLOR_CHECK);
cols = (int)ceilf(w / N_GUI_COLOR_CHECK);
for (row = 0; row < rows; row++) {
float cy = y + (float)row * N_GUI_COLOR_CHECK;
float y2 = cy + N_GUI_COLOR_CHECK < y + h ? cy + N_GUI_COLOR_CHECK : y + h;
for (int col = 0; col < cols; col++) {
float cx = x + (float)col * N_GUI_COLOR_CHECK;
float x2 = cx + N_GUI_COLOR_CHECK < x + w ? cx + N_GUI_COLOR_CHECK : x + w;
float grey = ((row + col) & 1) ? 0.8f : 0.55f;
al_draw_filled_rectangle(cx, cy, x2, y2, al_map_rgb_f(grey, grey, grey));
}
}
/* Allegro's default blender takes premultiplied colour. */
al_draw_filled_rectangle(x, y, x + w, y + h, al_premul_rgba_f(_n_gui_color_clamp(rgba[0]), _n_gui_color_clamp(rgba[1]), _n_gui_color_clamp(rgba[2]), _n_gui_color_clamp(rgba[3])));
al_draw_rectangle(x + 0.5f, y + 0.5f, x + w - 0.5f, y + h - 0.5f, al_map_rgba(0, 0, 0, 160), 1.0f);
}
void n_gui_color_to_hex(const float rgba[4], char* out, size_t len) {
unsigned byte[4] = {0, 0, 0, 0};
int c = 0;
if (!out || len == 0) return;
if (!rgba) {
out[0] = '\0';
return;
}
for (c = 0; c < 4; c++) byte[c] = (unsigned)lroundf(_n_gui_color_clamp(rgba[c]) * 255.0f);
snprintf(out, len, "#%02X%02X%02X%02X", byte[0], byte[1], byte[2], byte[3]);
}
// cppcheck-suppress constParameterCallback ; callback signature must match N_GUI_CUSTOM_DRAW
// cppcheck-suppress constParameter ; the same, under the id cppcheck 2.10 uses
static void _n_gui_color_draw_swatch(N_GUI_WIDGET* widget, float x, float y, ALLEGRO_FONT* font, void* user_data) {
const N_GUI_COLOR_PICKER* picker = (const N_GUI_COLOR_PICKER*)user_data;
(void)font;
if (!widget || !picker) return;
n_gui_draw_color_swatch(x, y, widget->w, widget->h, picker->rgba);
}
static void _n_gui_color_set_label(N_GUI_COLOR_PICKER* picker, int channel) {
char text[32] = "";
snprintf(text, sizeof(text), "%s %ld", _n_gui_color_channels[channel], lroundf(picker->rgba[channel] * 255.0f));
n_gui_label_set_text(picker->ctx, picker->lbl[channel], text);
}
static void _n_gui_color_on_slider(int widget_id, double value, void* user_data) {
N_GUI_COLOR_PICKER* picker = (N_GUI_COLOR_PICKER*)user_data;
int c = 0;
if (!picker || picker->syncing) return;
for (c = 0; c < 4; c++) {
if (picker->sl[c] != widget_id) continue;
/* in steps of 1/255, so the number on the label is the one kept */
picker->rgba[c] = (float)lround(value) / 255.0f;
_n_gui_color_set_label(picker, c);
if (picker->on_change) picker->on_change(picker->rgba, picker->user_data);
n_gui_mark_dirty(picker->ctx);
return;
}
}
static void _n_gui_color_on_close_button(int widget_id, void* user_data) {
(void)widget_id;
n_gui_color_picker_close((N_GUI_COLOR_PICKER*)user_data);
}
static void _n_gui_color_on_close_window(int window_id, void* user_data) {
(void)window_id;
n_gui_color_picker_close((N_GUI_COLOR_PICKER*)user_data);
}
N_GUI_COLOR_PICKER* n_gui_color_picker_new(N_GUI_CTX* ctx, const char* title, float scale) {
N_GUI_COLOR_PICKER* picker = NULL;
const N_GUI_WINDOW* win = NULL;
float pad = 0.0f, row = 0.0f, label = 0.0f, swatch_h = 0.0f, client_w = 0.0f, client_h = 0.0f, inner = 0.0f, y = 0.0f;
int c = 0;
__n_assert(ctx, return NULL);
if (scale <= 0.0f) scale = 1.0f;
Malloc(picker, N_GUI_COLOR_PICKER, 1);
__n_assert(picker, return NULL);
picker->ctx = ctx;
pad = N_GUI_COLOR_PAD * scale;
row = N_GUI_COLOR_ROW * scale;
label = N_GUI_COLOR_LABEL * scale;
swatch_h = N_GUI_COLOR_SWATCH * scale;
client_w = N_GUI_COLOR_W * scale;
inner = client_w - 2.0f * pad;
/* what it is for, the swatch, four labelled sliders, Close */
client_h = pad + label + swatch_h + pad + 4.0f * (label + row) + pad + row + pad;
picker->window = n_gui_add_window(ctx, title ? title : "Colour", 0.0f, 0.0f, client_w, client_h);
if (picker->window < 0) {
Free(picker);
return NULL;
}
n_gui_window_set_flags(ctx, picker->window, N_GUI_WIN_BTN_CLOSE);
n_gui_window_set_zorder(ctx, picker->window, N_GUI_ZORDER_POPUP, 0);
n_gui_window_set_close_callback(ctx, picker->window, _n_gui_color_on_close_window, picker);
/* n_gui_add_window's height includes the title bar; make room for it */
win = n_gui_get_window(ctx, picker->window);
if (win) n_gui_window_set_rect(ctx, picker->window, 0.0f, 0.0f, client_w, client_h + win->titlebar_h);
y = pad;
picker->lbl_what = n_gui_add_label(ctx, picker->window, "", pad, y, inner, label, N_GUI_ALIGN_LEFT);
y += label;
picker->swatch = n_gui_add_custom(ctx, picker->window, pad, y, inner, swatch_h, _n_gui_color_draw_swatch, picker);
y += swatch_h + pad;
for (c = 0; c < 4; c++) {
picker->lbl[c] = n_gui_add_label(ctx, picker->window, _n_gui_color_channels[c], pad, y, inner, label, N_GUI_ALIGN_LEFT);
y += label;
picker->sl[c] = n_gui_add_slider(ctx, picker->window, pad, y, inner, row, 0.0, 255.0, 255.0, N_GUI_SLIDER_VALUE, _n_gui_color_on_slider, picker);
y += row;
if (picker->lbl[c] < 0 || picker->sl[c] < 0) break;
n_gui_slider_set_step(ctx, picker->sl[c], 1.0);
n_gui_slider_set_value_visible(ctx, picker->sl[c], 0);
n_gui_set_widget_tooltip(ctx, picker->sl[c], c < 3 ? "How much of this colour, 0 to 255" : "How solid, 0 see-through to 255 opaque");
}
y += pad;
picker->btn_close = n_gui_add_button(ctx, picker->window, "Close", client_w - pad - N_GUI_COLOR_BTN_W * scale, y, N_GUI_COLOR_BTN_W * scale, row, N_GUI_SHAPE_ROUNDED, _n_gui_color_on_close_button, picker);
n_gui_close_window(ctx, picker->window);
if (c < 4 || picker->lbl_what < 0 || picker->swatch < 0 || picker->btn_close < 0) {
n_log(LOG_ERR, "color picker: a widget could not be created");
/* the window stays in the context, closed; nothing in it can reach
* the freed handle while it is closed except its close callback */
n_gui_window_set_close_callback(ctx, picker->window, NULL, NULL);
Free(picker);
return NULL;
}
n_gui_set_widget_tooltip(ctx, picker->btn_close, "Every change is already applied; this only closes the window");
for (c = 0; c < 4; c++) picker->rgba[c] = 1.0f;
return picker;
}
void n_gui_color_picker_open(N_GUI_COLOR_PICKER* picker, const char* what, const float rgba[4], N_GUI_COLOR_FN on_change, void* user_data) {
const N_GUI_WINDOW* win = NULL;
float dw = 0.0f, dh = 0.0f;
int c = 0;
__n_assert(picker, return);
__n_assert(rgba, return);
for (c = 0; c < 4; c++) picker->rgba[c] = _n_gui_color_clamp(rgba[c]);
picker->on_change = on_change;
picker->user_data = user_data;
n_gui_label_set_text(picker->ctx, picker->lbl_what, what ? what : "");
/* set from code: the callbacks the sliders fire are not the user's */
picker->syncing = 1;
for (c = 0; c < 4; c++) {
n_gui_slider_set_value(picker->ctx, picker->sl[c], (double)lroundf(picker->rgba[c] * 255.0f));
_n_gui_color_set_label(picker, c);
}
picker->syncing = 0;
/* centred on the display: the context's size when it has one, else the
* current display's, else left where it is */
dw = picker->ctx->display_w;
dh = picker->ctx->display_h;
if ((dw <= 0.0f || dh <= 0.0f) && al_get_current_display()) {
dw = (float)al_get_display_width(al_get_current_display());
dh = (float)al_get_display_height(al_get_current_display());
}
win = n_gui_get_window(picker->ctx, picker->window);
if (win && dw > 0.0f && dh > 0.0f) {
float x = (dw - win->w) * 0.5f;
float y = (dh - win->h) * 0.5f;
n_gui_window_set_rect_axes(picker->ctx, picker->window, x < 0.0f ? 0.0f : x, y < 0.0f ? 0.0f : y, win->w, win->h, 0, 0);
}
n_gui_open_window(picker->ctx, picker->window);
n_gui_raise_window(picker->ctx, picker->window);
n_gui_mark_dirty(picker->ctx);
}
void n_gui_color_picker_close(N_GUI_COLOR_PICKER* picker) {
if (!picker) return;
n_gui_close_window(picker->ctx, picker->window);
/* nobody is told about a colour nobody is editing any more */
picker->on_change = NULL;
picker->user_data = NULL;
n_gui_mark_dirty(picker->ctx);
}
int n_gui_color_picker_is_open(const N_GUI_COLOR_PICKER* picker) {
if (!picker) return 0;
return n_gui_window_is_open(picker->ctx, picker->window) ? 1 : 0;
}
int n_gui_color_picker_get(const N_GUI_COLOR_PICKER* picker, float rgba[4]) {
int c = 0;
__n_assert(picker, return FALSE);
__n_assert(rgba, return FALSE);
for (c = 0; c < 4; c++) rgba[c] = picker->rgba[c];
return TRUE;
}
int n_gui_color_picker_window(const N_GUI_COLOR_PICKER* picker) {
return picker ? picker->window : -1;
}
int n_gui_color_picker_slider(const N_GUI_COLOR_PICKER* picker, int channel) {
if (!picker || channel < 0 || channel > 3) return -1;
return picker->sl[channel];
}
void n_gui_color_picker_free(N_GUI_COLOR_PICKER** picker) {
if (!picker || !*picker) return;
n_gui_color_picker_close(*picker);
/* the widgets outlive the handle; none may call back into it */
n_gui_window_set_close_callback((*picker)->ctx, (*picker)->window, NULL, NULL);
Free(*picker);
}
#endif /* HAVE_ALLEGRO */
Modules
- AABB3D
- ACCEPT_POOL
- ALLEGRO4
- ALLEGRO5
- ANIM_LIB
- ASTAR
- ATLAS_LIB
- AVRO
- CLOCK_SYNC
- COMMONS
- CONFIGFILE
- CYPHER_BASE64
- CYPHER_VIGENERE
- DEAD_RECKONING
- ENUMS
- EXCEPTIONS
- GAME_ENV
- GIT
- GUI
- HASH_TABLE
- ISOMETRIC_ENGINE
- LIST
- LOG
- LOGNODUP
- LZ4
- NETWORKING
- NETWORK_MSG
- N_COOKIES
- N_DIFF
- N_DIGEST
- N_DISK_CACHE
- N_DNS
- N_ECC
- N_ENTROPY
- N_FILES
- N_FLUIDS
- N_GUI_COLOR
- N_GUI_COLORWHEEL
- N_GUI_CURVE
- N_GUI_GRID
- N_GUI_POPUP
- N_HEX
- N_HTTP3
- N_HTTP_COOKIE
- N_HTTP_FORM
- N_HTTP_MIME
- N_HTTP_REQ
- N_HTTP_RESP
- N_HTTP_ROUTER
- N_HTTP_SERVER
- N_ICC
- N_JSON
- N_KAFKA
- N_LINALG
- N_MODULE
- N_OPENSSL_COMPAT
- N_PRETTY
- N_PTH
- N_QUERY
- N_RANDOM
- N_SHA256
- N_STR
- N_TIFF
- N_TIME
- N_USER
- N_X509
- N_ZIP
- PARTICLES
- PCRE
- PHYSICS
- SIGNALS
- STACK
- THREADS
- TRAJECTORY
- TREE
- ZLIB