Skip to content

Commit db6f6ba

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 db6f6ba

5 files changed

Lines changed: 268 additions & 19 deletions

File tree

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: 95 additions & 7 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,8 +30618,38 @@ 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)
30646+
{
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)
3060830653
{
3060930654
int x,y,w,h;
3061030655
struct nk_window *win;
@@ -30625,14 +30670,34 @@ 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+
/* turn relative offset into absolute one, unless it's already absolute */
30676+
if ((tooltip & NK_TOOLTIP_ON_LEFT) && !(tooltip & NK_TOOLTIP_ABS_OFFSET))
30677+
offset.x *= -1;
30678+
if ((tooltip & NK_TOOLTIP_ABOVE) && !(tooltip & NK_TOOLTIP_ABS_OFFSET))
30679+
offset.y *= -1;
30680+
30681+
x = 0;
30682+
x -= !!(tooltip & NK_TOOLTIP_ON_LEFT );
30683+
x += !!(tooltip & NK_TOOLTIP_ON_RIGHT);
30684+
NK_ASSERT(x == -1 || x == 0 || x == 1);
30685+
x = -w/2 + (x * w/2);
30686+
x += nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x;
30687+
x += offset.x;
30688+
30689+
y = 0;
30690+
y -= !!(tooltip & NK_TOOLTIP_ABOVE);
30691+
y += !!(tooltip & NK_TOOLTIP_BELOW);
30692+
NK_ASSERT(y == -1 || y == 0 || y == 1);
30693+
y = -h/2 + (y * h/2);
30694+
y += nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y;
30695+
y += offset.y;
3063130696

3063230697
bounds.x = (float)x;
3063330698
bounds.y = (float)y;
3063430699
bounds.w = (float)w;
30635-
bounds.h = (float)h;
30700+
bounds.h = (float)nk_iceilf(nk_null_rect.h);
3063630701

3063730702
ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC,
3063830703
"__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds);
@@ -30641,7 +30706,6 @@ nk_tooltip_begin(struct nk_context *ctx, float width)
3064130706
ctx->current->layout->type = NK_PANEL_TOOLTIP;
3064230707
return ret;
3064330708
}
30644-
3064530709
NK_API void
3064630710
nk_tooltip_end(struct nk_context *ctx)
3064730711
{
@@ -30653,7 +30717,7 @@ nk_tooltip_end(struct nk_context *ctx)
3065330717
nk_popup_end(ctx);
3065430718
}
3065530719
NK_API void
30656-
nk_tooltip(struct nk_context *ctx, const char *text)
30720+
nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2 offset)
3065730721
{
3065830722
const struct nk_style *style;
3065930723
struct nk_vec2 padding;
@@ -30681,14 +30745,29 @@ nk_tooltip(struct nk_context *ctx, const char *text)
3068130745
text_height = (style->font->height + 2 * padding.y);
3068230746

3068330747
/* execute tooltip and fill with text */
30684-
if (nk_tooltip_begin(ctx, (float)text_width)) {
30748+
if (nk_tooltip_begin_offset(ctx, (float)text_width, tooltip, offset)) {
3068530749
nk_layout_row_dynamic(ctx, (float)text_height, 1);
3068630750
nk_text(ctx, text, text_len, NK_TEXT_LEFT);
3068730751
nk_tooltip_end(ctx);
3068830752
}
3068930753
}
30754+
NK_API void
30755+
nk_tooltip(struct nk_context *ctx, const char *text)
30756+
{
30757+
nk_tooltip_offset(ctx, text,
30758+
nk_tooltip_get_default_flags(ctx),
30759+
nk_tooltip_get_default_offset(ctx));
30760+
}
3069030761
#ifdef NK_INCLUDE_STANDARD_VARARGS
3069130762
NK_API void
30763+
nk_tooltipf_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, ...)
30764+
{
30765+
va_list args;
30766+
va_start(args, fmt);
30767+
nk_tooltipfv_offset(ctx, tooltip, offset, fmt, args);
30768+
va_end(args);
30769+
}
30770+
NK_API void
3069230771
nk_tooltipf(struct nk_context *ctx, const char *fmt, ...)
3069330772
{
3069430773
va_list args;
@@ -30697,6 +30776,13 @@ nk_tooltipf(struct nk_context *ctx, const char *fmt, ...)
3069730776
va_end(args);
3069830777
}
3069930778
NK_API void
30779+
nk_tooltipfv_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, va_list args)
30780+
{
30781+
char buf[256];
30782+
nk_strfmt(buf, NK_LEN(buf), fmt, args);
30783+
nk_tooltip_offset(ctx, buf, tooltip, offset);
30784+
}
30785+
NK_API void
3070030786
nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args)
3070130787
{
3070230788
char buf[256];
@@ -30762,6 +30848,8 @@ nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args)
3076230848
/// - [y]: Minor version with non-breaking API and library changes
3076330849
/// - [z]: Patch version with no direct changes to the API
3076430850
///
30851+
/// - 2026/02/17 (4.13.3) - Fixed default tooltip from being covered by the cursor,
30852+
/// added new tooltip variation that allows to set the offset manually
3076530853
/// - 2026/01/31 (4.13.2) - Fix: replace incorrect static asserts for size(nk_bool)
3076630854
/// - 2026/01/26 (4.13.1) - Fix: nk_do_property now uses NK_STRTOD via macro
3076730855
/// - 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)