Skip to content

Commit 0d62612

Browse files
feat(tooltip): add new variation of tooltip with offset
Adds new variation of nk_tooltip_* that let's you specify the relative position to the cursor and the offset. This was done in order to prevent cursor from covering the content of tooltip. The default tooltip should now provide more sane offset (still not perfect!) Also, added a tooltip editor to demo overview in order to test it. Parts of this code were originally written by Robert Winkler (thanks!) Origin: #881 Co-authored-by: Robert Winkler <rob121618@gmail.com>
1 parent 3f71ae1 commit 0d62612

File tree

5 files changed

+278
-21
lines changed

5 files changed

+278
-21
lines changed

demo/common/overview.c

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -839,12 +839,85 @@ overview(struct nk_context *ctx)
839839
} else popup_active = nk_false;
840840
}
841841

842-
/* tooltip */
843-
nk_layout_row_static(ctx, 30, 150, 1);
842+
/* tooltips */
843+
nk_layout_row_static(ctx, 30, 400, 1);
844844
bounds = nk_widget_bounds(ctx);
845-
nk_label(ctx, "Hover me for tooltip", NK_TEXT_LEFT);
846-
if (nk_input_is_mouse_hovering_rect(in, bounds))
847-
nk_tooltip(ctx, "This is a tooltip");
845+
nk_label(ctx, "Hover for default tooltip", NK_TEXT_LEFT);
846+
if (nk_input_is_mouse_hovering_rect(in, bounds)) {
847+
nk_tooltip(ctx, "This is very boring default tooltip...");
848+
}
849+
bounds = nk_widget_bounds(ctx);
850+
nk_label(ctx, "Hover for Gnome-like tooltip", NK_TEXT_LEFT);
851+
if (nk_input_is_mouse_hovering_rect(in, bounds)) {
852+
struct nk_vec2 offset = { 0, 15 };
853+
nk_tooltip_offset(ctx, "Gnome centers above cursor with greater y offset", NK_TOOLTIP_ABOVE, offset);
854+
}
855+
bounds = nk_widget_bounds(ctx);
856+
nk_label(ctx, "Hover for above-on-left tooltip", NK_TEXT_LEFT);
857+
if (nk_input_is_mouse_hovering_rect(in, bounds)) {
858+
struct nk_vec2 offset = { 0, 0 };
859+
nk_tooltip_offset(ctx, "above-on-left from cursor with 0:0 offset", NK_TOOLTIP_ABOVE|NK_TOOLTIP_ON_LEFT, offset);
860+
}
861+
bounds = nk_widget_bounds(ctx);
862+
nk_label(ctx, "Hover for MAGIC!", NK_TEXT_LEFT);
863+
if (nk_input_is_mouse_hovering_rect(in, bounds)) {
864+
static double accum_time_seconds = 0.0;
865+
const double speed = 3.0, radius = 50.0;
866+
struct nk_vec2 offset;
867+
offset.x = radius * cos(accum_time_seconds * speed);
868+
offset.y = radius * sin(accum_time_seconds * speed);
869+
nk_tooltip_offset(ctx, "WOW!", NK_TOOLTIP_ABS_OFFSET, offset);
870+
accum_time_seconds += (double)(ctx->delta_time_seconds);
871+
}
872+
873+
/* editor for custom tooltip */
874+
{
875+
static char text_buf[64] = {0};
876+
static int text_len = 0;
877+
static int text_initialized = 0;
878+
static nk_flags tooltip = 0;
879+
static struct nk_vec2 offset = {0};
880+
if (!text_initialized) {
881+
const char text_default[] = "you can customize this!";
882+
NK_ASSERT(sizeof(text_default) < sizeof(text_buf));
883+
memcpy(text_buf, text_default, sizeof(text_default));
884+
text_len = sizeof(text_default) - 1;
885+
text_initialized = 1;
886+
}
887+
bounds = nk_widget_bounds(ctx);
888+
nk_label(ctx, "Hover for custom tooltip (you can customize it below)", NK_TEXT_LEFT);
889+
if (nk_input_is_mouse_hovering_rect(in, bounds)) {
890+
nk_tooltip_offset(ctx, text_buf, tooltip, offset);
891+
}
892+
nk_layout_row_dynamic(ctx, 1, 1);
893+
nk_rule_horizontal(ctx, nk_white, nk_true);
894+
nk_layout_row_dynamic(ctx, 30, 2);
895+
nk_label(ctx, "custom tooltip text:", NK_TEXT_LEFT);
896+
nk_edit_string(ctx, NK_EDIT_FIELD, text_buf, &text_len, sizeof(text_buf), nk_filter_default);
897+
NK_ASSERT(text_len < (int)sizeof(text_buf));
898+
text_buf[text_len] = '\0'; /* TODO: why nk_edit_string is NOT setting this on its own? */
899+
nk_layout_row_dynamic(ctx, 30, 1);
900+
#define TOOLTIP_FOREACH_FLAG(BODY) \
901+
BODY(ABOVE) \
902+
BODY(BELOW) \
903+
BODY(ON_LEFT) \
904+
BODY(ON_RIGHT) \
905+
BODY(ABS_OFFSET)
906+
#define TOOLTIP_CHECKBOX_FLAG(FLAG) \
907+
{ \
908+
nk_bool checked = !!(tooltip & NK_TOOLTIP_##FLAG); \
909+
nk_checkbox_label_align(ctx, "custom tooltip flag: " #FLAG, &checked, NK_WIDGET_RIGHT, NK_TEXT_LEFT);\
910+
tooltip = checked ? (tooltip | NK_TOOLTIP_##FLAG) : (tooltip & ~NK_TOOLTIP_##FLAG); \
911+
}
912+
TOOLTIP_FOREACH_FLAG(TOOLTIP_CHECKBOX_FLAG)
913+
#undef TOOLTIP_FOREACH_FLAG
914+
#undef TOOLTIP_CHECKBOX_FLAG
915+
nk_layout_row_dynamic(ctx, 30, 2);
916+
nk_label(ctx, "custom tooltip offset", NK_TEXT_LEFT);
917+
nk_property_float(ctx, "x", -100.0f, &offset.x, 100.0f, 5.0f, 0.5f);
918+
nk_label(ctx, "custom tooltip offset", NK_TEXT_LEFT);
919+
nk_property_float(ctx, "y", -100.0f, &offset.y, 100.0f, 5.0f, 0.5f);
920+
}
848921

849922
nk_tree_pop(ctx);
850923
}

nuklear.h

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3826,12 +3826,27 @@ NK_API void nk_contextual_end(struct nk_context*);
38263826
* TOOLTIP
38273827
*
38283828
* ============================================================================= */
3829+
enum nk_tooltip_flags {
3830+
/**!< tells where tooltip should appear relatively to the cursor, can be combined (e.g. BELOW|ON_RIGHT) */
3831+
NK_TOOLTIP_ABOVE = NK_FLAG(0),
3832+
NK_TOOLTIP_BELOW = NK_FLAG(1),
3833+
NK_TOOLTIP_ON_LEFT = NK_FLAG(2),
3834+
NK_TOOLTIP_ON_RIGHT = NK_FLAG(3),
3835+
/**!< if set, the offset will be absolute, instead of relative */
3836+
NK_TOOLTIP_ABS_OFFSET = NK_FLAG(4)
3837+
/* FIXME: https://github.com/Immediate-Mode-UI/Nuklear/issues/899 */
3838+
/*NK_TOOLTIP_CLAMP_IN_SCREEN = NK_FLAG(5),*/
3839+
};
38293840
NK_API void nk_tooltip(struct nk_context*, const char*);
3841+
NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2);
38303842
#ifdef NK_INCLUDE_STANDARD_VARARGS
38313843
NK_API void nk_tooltipf(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(2);
38323844
NK_API void nk_tooltipfv(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2);
3845+
NK_API void nk_tooltipf_offset(struct nk_context*, nk_flags tooltip, struct nk_vec2, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(4);
3846+
NK_API void nk_tooltipfv_offset(struct nk_context*, nk_flags tooltip, struct nk_vec2, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(4);
38333847
#endif
38343848
NK_API nk_bool nk_tooltip_begin(struct nk_context*, float width);
3849+
NK_API nk_bool nk_tooltip_begin_offset(struct nk_context*, float width, nk_flags tooltip, struct nk_vec2);
38353850
NK_API void nk_tooltip_end(struct nk_context*);
38363851
/* =============================================================================
38373852
*
@@ -30603,10 +30618,40 @@ nk_combobox_callback(struct nk_context *ctx,
3060330618
* TOOLTIP
3060430619
*
3060530620
* ===============================================================*/
30621+
NK_LIB struct nk_vec2
30622+
nk_tooltip_get_default_offset(const struct nk_context *ctx)
30623+
{
30624+
struct nk_vec2 offset = {0};
30625+
NK_ASSERT(ctx);
30626+
if (!ctx) return offset;
30627+
if (ctx->style.cursor_active) {
30628+
/* nuklear is drawing its own cursor so we can reuse its size (best case!) */
30629+
offset.x = ctx->style.window.padding.x + ctx->style.cursor_active->size.x;
30630+
offset.x = ctx->style.window.padding.y + ctx->style.cursor_active->size.y;
30631+
} else if (ctx->style.font) {
30632+
/* assume that cursor size is similar to font height (flawed but reasonable)*/
30633+
offset.y = ctx->style.window.padding.x + ctx->style.font->height;
30634+
offset.x = ctx->style.window.padding.y + ctx->style.font->height;
30635+
}
30636+
return offset;
30637+
}
30638+
NK_LIB nk_flags
30639+
nk_tooltip_get_default_flags(const struct nk_context *ctx)
30640+
{
30641+
NK_UNUSED(ctx);
30642+
return NK_TOOLTIP_BELOW|NK_TOOLTIP_ON_RIGHT;
30643+
}
3060630644
NK_API nk_bool
3060730645
nk_tooltip_begin(struct nk_context *ctx, float width)
3060830646
{
30609-
int x,y,w,h;
30647+
return nk_tooltip_begin_offset(ctx, width,
30648+
nk_tooltip_get_default_flags(ctx),
30649+
nk_tooltip_get_default_offset(ctx));
30650+
}
30651+
NK_API nk_bool
30652+
nk_tooltip_begin_offset(struct nk_context *ctx, float width, nk_flags tooltip, struct nk_vec2 offset)
30653+
{
30654+
int x,y,w,h,mul_x,mul_y;
3061030655
struct nk_window *win;
3061130656
const struct nk_input *in;
3061230657
struct nk_rect bounds;
@@ -30625,14 +30670,38 @@ nk_tooltip_begin(struct nk_context *ctx, float width)
3062530670
return 0;
3062630671

3062730672
w = nk_iceilf(width);
30628-
h = nk_iceilf(nk_null_rect.h);
30629-
x = nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x;
30630-
y = nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y;
30673+
h = ctx->current->layout->row.min_height;
30674+
30675+
/* find axis multipliers based on bitmask state */
30676+
mul_x = 0;
30677+
mul_x -= !!(tooltip & NK_TOOLTIP_ON_LEFT );
30678+
mul_x += !!(tooltip & NK_TOOLTIP_ON_RIGHT);
30679+
NK_ASSERT(mul_x == -1 || mul_x == 0 || mul_x == 1);
30680+
mul_y = 0;
30681+
mul_y -= !!(tooltip & NK_TOOLTIP_ABOVE);
30682+
mul_y += !!(tooltip & NK_TOOLTIP_BELOW);
30683+
NK_ASSERT(mul_y == -1 || mul_y == 0 || mul_y == 1);
30684+
30685+
/* turn relative offset into absolute, unless it's already absolute
30686+
* notice that offset axis is ignored in cases where mul==0
30687+
* (if you don't like this behavior, make sure to use ABS_OFFSET flag)*/
30688+
if (!(tooltip & NK_TOOLTIP_ABS_OFFSET)) {
30689+
offset.x *= mul_x;
30690+
offset.y *= mul_y;
30691+
}
30692+
30693+
/* find origin */
30694+
x = -w/2 + (mul_x * w/2);
30695+
x += nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x;
30696+
x += offset.x;
30697+
y = -h/2 + (mul_y * h/2);
30698+
y += nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y;
30699+
y += offset.y;
3063130700

3063230701
bounds.x = (float)x;
3063330702
bounds.y = (float)y;
3063430703
bounds.w = (float)w;
30635-
bounds.h = (float)h;
30704+
bounds.h = (float)nk_iceilf(nk_null_rect.h);
3063630705

3063730706
ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC,
3063830707
"__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds);
@@ -30641,7 +30710,6 @@ nk_tooltip_begin(struct nk_context *ctx, float width)
3064130710
ctx->current->layout->type = NK_PANEL_TOOLTIP;
3064230711
return ret;
3064330712
}
30644-
3064530713
NK_API void
3064630714
nk_tooltip_end(struct nk_context *ctx)
3064730715
{
@@ -30653,7 +30721,7 @@ nk_tooltip_end(struct nk_context *ctx)
3065330721
nk_popup_end(ctx);
3065430722
}
3065530723
NK_API void
30656-
nk_tooltip(struct nk_context *ctx, const char *text)
30724+
nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2 offset)
3065730725
{
3065830726
const struct nk_style *style;
3065930727
struct nk_vec2 padding;
@@ -30681,14 +30749,29 @@ nk_tooltip(struct nk_context *ctx, const char *text)
3068130749
text_height = (style->font->height + 2 * padding.y);
3068230750

3068330751
/* execute tooltip and fill with text */
30684-
if (nk_tooltip_begin(ctx, (float)text_width)) {
30752+
if (nk_tooltip_begin_offset(ctx, (float)text_width, tooltip, offset)) {
3068530753
nk_layout_row_dynamic(ctx, (float)text_height, 1);
3068630754
nk_text(ctx, text, text_len, NK_TEXT_LEFT);
3068730755
nk_tooltip_end(ctx);
3068830756
}
3068930757
}
30758+
NK_API void
30759+
nk_tooltip(struct nk_context *ctx, const char *text)
30760+
{
30761+
nk_tooltip_offset(ctx, text,
30762+
nk_tooltip_get_default_flags(ctx),
30763+
nk_tooltip_get_default_offset(ctx));
30764+
}
3069030765
#ifdef NK_INCLUDE_STANDARD_VARARGS
3069130766
NK_API void
30767+
nk_tooltipf_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, ...)
30768+
{
30769+
va_list args;
30770+
va_start(args, fmt);
30771+
nk_tooltipfv_offset(ctx, tooltip, offset, fmt, args);
30772+
va_end(args);
30773+
}
30774+
NK_API void
3069230775
nk_tooltipf(struct nk_context *ctx, const char *fmt, ...)
3069330776
{
3069430777
va_list args;
@@ -30697,6 +30780,13 @@ nk_tooltipf(struct nk_context *ctx, const char *fmt, ...)
3069730780
va_end(args);
3069830781
}
3069930782
NK_API void
30783+
nk_tooltipfv_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, va_list args)
30784+
{
30785+
char buf[256];
30786+
nk_strfmt(buf, NK_LEN(buf), fmt, args);
30787+
nk_tooltip_offset(ctx, buf, tooltip, offset);
30788+
}
30789+
NK_API void
3070030790
nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args)
3070130791
{
3070230792
char buf[256];
@@ -30762,6 +30852,8 @@ nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args)
3076230852
/// - [y]: Minor version with non-breaking API and library changes
3076330853
/// - [z]: Patch version with no direct changes to the API
3076430854
///
30855+
/// - 2026/02/17 (4.13.3) - Fixed default tooltip from being covered by the cursor,
30856+
/// added new tooltip variation that allows to set the offset manually
3076530857
/// - 2026/01/31 (4.13.2) - Fix: replace incorrect static asserts for size(nk_bool)
3076630858
/// - 2026/01/26 (4.13.1) - Fix: nk_do_property now uses NK_STRTOD via macro
3076730859
/// - Fix: failure to build from source, due to

src/CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
/// - [y]: Minor version with non-breaking API and library changes
88
/// - [z]: Patch version with no direct changes to the API
99
///
10+
/// - 2026/02/17 (4.13.3) - Fixed default tooltip from being covered by the cursor,
11+
/// added new tooltip variation that allows to set the offset manually
1012
/// - 2026/01/31 (4.13.2) - Fix: replace incorrect static asserts for size(nk_bool)
1113
/// - 2026/01/26 (4.13.1) - Fix: nk_do_property now uses NK_STRTOD via macro
1214
/// - Fix: failure to build from source, due to

src/nuklear.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3603,12 +3603,27 @@ NK_API void nk_contextual_end(struct nk_context*);
36033603
* TOOLTIP
36043604
*
36053605
* ============================================================================= */
3606+
enum nk_tooltip_flags {
3607+
/**!< tells where tooltip should appear relatively to the cursor, can be combined (e.g. BELOW|ON_RIGHT) */
3608+
NK_TOOLTIP_ABOVE = NK_FLAG(0),
3609+
NK_TOOLTIP_BELOW = NK_FLAG(1),
3610+
NK_TOOLTIP_ON_LEFT = NK_FLAG(2),
3611+
NK_TOOLTIP_ON_RIGHT = NK_FLAG(3),
3612+
/**!< if set, the offset will be absolute, instead of relative */
3613+
NK_TOOLTIP_ABS_OFFSET = NK_FLAG(4)
3614+
/* FIXME: https://github.com/Immediate-Mode-UI/Nuklear/issues/899 */
3615+
/*NK_TOOLTIP_CLAMP_IN_SCREEN = NK_FLAG(5),*/
3616+
};
36063617
NK_API void nk_tooltip(struct nk_context*, const char*);
3618+
NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2);
36073619
#ifdef NK_INCLUDE_STANDARD_VARARGS
36083620
NK_API void nk_tooltipf(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(2);
36093621
NK_API void nk_tooltipfv(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2);
3622+
NK_API void nk_tooltipf_offset(struct nk_context*, nk_flags tooltip, struct nk_vec2, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(4);
3623+
NK_API void nk_tooltipfv_offset(struct nk_context*, nk_flags tooltip, struct nk_vec2, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(4);
36103624
#endif
36113625
NK_API nk_bool nk_tooltip_begin(struct nk_context*, float width);
3626+
NK_API nk_bool nk_tooltip_begin_offset(struct nk_context*, float width, nk_flags tooltip, struct nk_vec2);
36123627
NK_API void nk_tooltip_end(struct nk_context*);
36133628
/* =============================================================================
36143629
*

0 commit comments

Comments
 (0)