diff --git a/demo/common/overview.c b/demo/common/overview.c index 87d5f686f..3dae90b14 100644 --- a/demo/common/overview.c +++ b/demo/common/overview.c @@ -839,12 +839,85 @@ overview(struct nk_context *ctx) } else popup_active = nk_false; } - /* tooltip */ - nk_layout_row_static(ctx, 30, 150, 1); + /* tooltips */ + nk_layout_row_static(ctx, 30, 400, 1); bounds = nk_widget_bounds(ctx); - nk_label(ctx, "Hover me for tooltip", NK_TEXT_LEFT); - if (nk_input_is_mouse_hovering_rect(in, bounds)) - nk_tooltip(ctx, "This is a tooltip"); + nk_label(ctx, "Hover for default tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_rect(in, bounds)) { + nk_tooltip(ctx, "This is very boring default tooltip..."); + } + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover for Gnome-like tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_rect(in, bounds)) { + struct nk_vec2 offset = { 0, 15 }; + nk_tooltip_offset(ctx, "Gnome centers above cursor with greater y offset", NK_TOOLTIP_ABOVE, offset); + } + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover for above-on-left tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_rect(in, bounds)) { + struct nk_vec2 offset = { 0, 0 }; + nk_tooltip_offset(ctx, "above-on-left from cursor with 0:0 offset", NK_TOOLTIP_ABOVE|NK_TOOLTIP_ON_LEFT, offset); + } + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover for MAGIC!", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_rect(in, bounds)) { + static double accum_time_seconds = 0.0; + const double speed = 3.0, radius = 50.0; + struct nk_vec2 offset; + offset.x = radius * cos(accum_time_seconds * speed); + offset.y = radius * sin(accum_time_seconds * speed); + nk_tooltip_offset(ctx, "WOW!", NK_TOOLTIP_ABS_OFFSET, offset); + accum_time_seconds += (double)(ctx->delta_time_seconds); + } + + /* editor for custom tooltip */ + { + static char text_buf[64] = {0}; + static int text_len = 0; + static int text_initialized = 0; + static nk_flags tooltip = 0; + static struct nk_vec2 offset = {0}; + if (!text_initialized) { + const char text_default[] = "you can customize this!"; + NK_ASSERT(sizeof(text_default) < sizeof(text_buf)); + memcpy(text_buf, text_default, sizeof(text_default)); + text_len = sizeof(text_default) - 1; + text_initialized = 1; + } + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover for custom tooltip (you can customize it below)", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_rect(in, bounds)) { + nk_tooltip_offset(ctx, text_buf, tooltip, offset); + } + nk_layout_row_dynamic(ctx, 1, 1); + nk_rule_horizontal(ctx, nk_white, nk_true); + nk_layout_row_dynamic(ctx, 30, 2); + nk_label(ctx, "custom tooltip text:", NK_TEXT_LEFT); + nk_edit_string(ctx, NK_EDIT_FIELD, text_buf, &text_len, sizeof(text_buf), nk_filter_default); + NK_ASSERT(text_len < (int)sizeof(text_buf)); + text_buf[text_len] = '\0'; /* TODO: why nk_edit_string is NOT setting this on its own? */ + nk_layout_row_dynamic(ctx, 30, 1); + #define TOOLTIP_FOREACH_FLAG(BODY) \ + BODY(ABOVE) \ + BODY(BELOW) \ + BODY(ON_LEFT) \ + BODY(ON_RIGHT) \ + BODY(ABS_OFFSET) + #define TOOLTIP_CHECKBOX_FLAG(FLAG) \ + { \ + nk_bool checked = !!(tooltip & NK_TOOLTIP_##FLAG); \ + nk_checkbox_label_align(ctx, "custom tooltip flag: " #FLAG, &checked, NK_WIDGET_RIGHT, NK_TEXT_LEFT);\ + tooltip = checked ? (tooltip | NK_TOOLTIP_##FLAG) : (tooltip & ~NK_TOOLTIP_##FLAG); \ + } + TOOLTIP_FOREACH_FLAG(TOOLTIP_CHECKBOX_FLAG) + #undef TOOLTIP_FOREACH_FLAG + #undef TOOLTIP_CHECKBOX_FLAG + nk_layout_row_dynamic(ctx, 30, 2); + nk_label(ctx, "custom tooltip offset", NK_TEXT_LEFT); + nk_property_float(ctx, "x", -100.0f, &offset.x, 100.0f, 5.0f, 0.5f); + nk_label(ctx, "custom tooltip offset", NK_TEXT_LEFT); + nk_property_float(ctx, "y", -100.0f, &offset.y, 100.0f, 5.0f, 0.5f); + } nk_tree_pop(ctx); } diff --git a/nuklear.h b/nuklear.h index ed61bbd85..1ac0d86d2 100644 --- a/nuklear.h +++ b/nuklear.h @@ -3826,12 +3826,27 @@ NK_API void nk_contextual_end(struct nk_context*); * TOOLTIP * * ============================================================================= */ +enum nk_tooltip_flags { + /**!< tells where tooltip should appear relatively to the cursor, can be combined (e.g. BELOW|ON_RIGHT) */ + NK_TOOLTIP_ABOVE = NK_FLAG(0), + NK_TOOLTIP_BELOW = NK_FLAG(1), + NK_TOOLTIP_ON_LEFT = NK_FLAG(2), + NK_TOOLTIP_ON_RIGHT = NK_FLAG(3), + /**!< if set, the offset will be absolute, instead of relative */ + NK_TOOLTIP_ABS_OFFSET = NK_FLAG(4) + /* FIXME: https://github.com/Immediate-Mode-UI/Nuklear/issues/899 */ + /*NK_TOOLTIP_CLAMP_IN_SCREEN = NK_FLAG(5),*/ +}; NK_API void nk_tooltip(struct nk_context*, const char*); +NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2); #ifdef NK_INCLUDE_STANDARD_VARARGS NK_API void nk_tooltipf(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(2); NK_API void nk_tooltipfv(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2); +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); +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); #endif NK_API nk_bool nk_tooltip_begin(struct nk_context*, float width); +NK_API nk_bool nk_tooltip_begin_offset(struct nk_context*, float width, nk_flags tooltip, struct nk_vec2); NK_API void nk_tooltip_end(struct nk_context*); /* ============================================================================= * @@ -30603,10 +30618,40 @@ nk_combobox_callback(struct nk_context *ctx, * TOOLTIP * * ===============================================================*/ +NK_LIB struct nk_vec2 +nk_tooltip_get_default_offset(const struct nk_context *ctx) +{ + struct nk_vec2 offset = {0}; + NK_ASSERT(ctx); + if (!ctx) return offset; + if (ctx->style.cursor_active) { + /* nuklear is drawing its own cursor so we can reuse its size (best case!) */ + offset.x = ctx->style.window.padding.x + ctx->style.cursor_active->size.x; + offset.x = ctx->style.window.padding.y + ctx->style.cursor_active->size.y; + } else if (ctx->style.font) { + /* assume that cursor size is similar to font height (flawed but reasonable)*/ + offset.y = ctx->style.window.padding.x + ctx->style.font->height; + offset.x = ctx->style.window.padding.y + ctx->style.font->height; + } + return offset; +} +NK_LIB nk_flags +nk_tooltip_get_default_flags(const struct nk_context *ctx) +{ + NK_UNUSED(ctx); + return NK_TOOLTIP_BELOW|NK_TOOLTIP_ON_RIGHT; +} NK_API nk_bool nk_tooltip_begin(struct nk_context *ctx, float width) { - int x,y,w,h; + return nk_tooltip_begin_offset(ctx, width, + nk_tooltip_get_default_flags(ctx), + nk_tooltip_get_default_offset(ctx)); +} +NK_API nk_bool +nk_tooltip_begin_offset(struct nk_context *ctx, float width, nk_flags tooltip, struct nk_vec2 offset) +{ + int x,y,w,h,mul_x,mul_y; struct nk_window *win; const struct nk_input *in; struct nk_rect bounds; @@ -30625,14 +30670,38 @@ nk_tooltip_begin(struct nk_context *ctx, float width) return 0; w = nk_iceilf(width); - h = nk_iceilf(nk_null_rect.h); - x = nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x; - y = nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y; + h = ctx->current->layout->row.min_height; + + /* find axis multipliers based on bitmask state */ + mul_x = 0; + mul_x -= !!(tooltip & NK_TOOLTIP_ON_LEFT ); + mul_x += !!(tooltip & NK_TOOLTIP_ON_RIGHT); + NK_ASSERT(mul_x == -1 || mul_x == 0 || mul_x == 1); + mul_y = 0; + mul_y -= !!(tooltip & NK_TOOLTIP_ABOVE); + mul_y += !!(tooltip & NK_TOOLTIP_BELOW); + NK_ASSERT(mul_y == -1 || mul_y == 0 || mul_y == 1); + + /* turn relative offset into absolute, unless it's already absolute + * notice that offset axis is ignored in cases where mul==0 + * (if you don't like this behavior, make sure to use ABS_OFFSET flag)*/ + if (!(tooltip & NK_TOOLTIP_ABS_OFFSET)) { + offset.x *= mul_x; + offset.y *= mul_y; + } + + /* find origin */ + x = -w/2 + (mul_x * w/2); + x += nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x; + x += offset.x; + y = -h/2 + (mul_y * h/2); + y += nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y; + y += offset.y; bounds.x = (float)x; bounds.y = (float)y; bounds.w = (float)w; - bounds.h = (float)h; + bounds.h = (float)nk_iceilf(nk_null_rect.h); ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC, "__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds); @@ -30641,7 +30710,6 @@ nk_tooltip_begin(struct nk_context *ctx, float width) ctx->current->layout->type = NK_PANEL_TOOLTIP; return ret; } - NK_API void nk_tooltip_end(struct nk_context *ctx) { @@ -30653,7 +30721,7 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } NK_API void -nk_tooltip(struct nk_context *ctx, const char *text) +nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2 offset) { const struct nk_style *style; struct nk_vec2 padding; @@ -30681,14 +30749,29 @@ nk_tooltip(struct nk_context *ctx, const char *text) text_height = (style->font->height + 2 * padding.y); /* execute tooltip and fill with text */ - if (nk_tooltip_begin(ctx, (float)text_width)) { + if (nk_tooltip_begin_offset(ctx, (float)text_width, tooltip, offset)) { nk_layout_row_dynamic(ctx, (float)text_height, 1); nk_text(ctx, text, text_len, NK_TEXT_LEFT); nk_tooltip_end(ctx); } } +NK_API void +nk_tooltip(struct nk_context *ctx, const char *text) +{ + nk_tooltip_offset(ctx, text, + nk_tooltip_get_default_flags(ctx), + nk_tooltip_get_default_offset(ctx)); +} #ifdef NK_INCLUDE_STANDARD_VARARGS NK_API void +nk_tooltipf_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_tooltipfv_offset(ctx, tooltip, offset, fmt, args); + va_end(args); +} +NK_API void nk_tooltipf(struct nk_context *ctx, const char *fmt, ...) { va_list args; @@ -30697,6 +30780,13 @@ nk_tooltipf(struct nk_context *ctx, const char *fmt, ...) va_end(args); } NK_API void +nk_tooltipfv_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_tooltip_offset(ctx, buf, tooltip, offset); +} +NK_API void nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args) { char buf[256]; @@ -30762,6 +30852,8 @@ nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args) /// - [y]: Minor version with non-breaking API and library changes /// - [z]: Patch version with no direct changes to the API /// +/// - 2026/02/17 (4.13.3) - Fixed default tooltip from being covered by the cursor, +/// added new tooltip variation that allows to set the offset manually /// - 2026/01/31 (4.13.2) - Fix: replace incorrect static asserts for size(nk_bool) /// - 2026/01/26 (4.13.1) - Fix: nk_do_property now uses NK_STRTOD via macro /// - Fix: failure to build from source, due to diff --git a/src/CHANGELOG b/src/CHANGELOG index ec11e0d7a..716e3a5fa 100644 --- a/src/CHANGELOG +++ b/src/CHANGELOG @@ -7,6 +7,8 @@ /// - [y]: Minor version with non-breaking API and library changes /// - [z]: Patch version with no direct changes to the API /// +/// - 2026/02/17 (4.13.3) - Fixed default tooltip from being covered by the cursor, +/// added new tooltip variation that allows to set the offset manually /// - 2026/01/31 (4.13.2) - Fix: replace incorrect static asserts for size(nk_bool) /// - 2026/01/26 (4.13.1) - Fix: nk_do_property now uses NK_STRTOD via macro /// - Fix: failure to build from source, due to diff --git a/src/nuklear.h b/src/nuklear.h index d61f8bca2..f16c679f5 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -3603,12 +3603,27 @@ NK_API void nk_contextual_end(struct nk_context*); * TOOLTIP * * ============================================================================= */ +enum nk_tooltip_flags { + /**!< tells where tooltip should appear relatively to the cursor, can be combined (e.g. BELOW|ON_RIGHT) */ + NK_TOOLTIP_ABOVE = NK_FLAG(0), + NK_TOOLTIP_BELOW = NK_FLAG(1), + NK_TOOLTIP_ON_LEFT = NK_FLAG(2), + NK_TOOLTIP_ON_RIGHT = NK_FLAG(3), + /**!< if set, the offset will be absolute, instead of relative */ + NK_TOOLTIP_ABS_OFFSET = NK_FLAG(4) + /* FIXME: https://github.com/Immediate-Mode-UI/Nuklear/issues/899 */ + /*NK_TOOLTIP_CLAMP_IN_SCREEN = NK_FLAG(5),*/ +}; NK_API void nk_tooltip(struct nk_context*, const char*); +NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2); #ifdef NK_INCLUDE_STANDARD_VARARGS NK_API void nk_tooltipf(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, ...) NK_PRINTF_VARARG_FUNC(2); NK_API void nk_tooltipfv(struct nk_context*, NK_PRINTF_FORMAT_STRING const char*, va_list) NK_PRINTF_VALIST_FUNC(2); +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); +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); #endif NK_API nk_bool nk_tooltip_begin(struct nk_context*, float width); +NK_API nk_bool nk_tooltip_begin_offset(struct nk_context*, float width, nk_flags tooltip, struct nk_vec2); NK_API void nk_tooltip_end(struct nk_context*); /* ============================================================================= * diff --git a/src/nuklear_tooltip.c b/src/nuklear_tooltip.c index f4d78c692..4aab883ae 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -6,10 +6,40 @@ * TOOLTIP * * ===============================================================*/ +NK_LIB struct nk_vec2 +nk_tooltip_get_default_offset(const struct nk_context *ctx) +{ + struct nk_vec2 offset = {0}; + NK_ASSERT(ctx); + if (!ctx) return offset; + if (ctx->style.cursor_active) { + /* nuklear is drawing its own cursor so we can reuse its size (best case!) */ + offset.x = ctx->style.window.padding.x + ctx->style.cursor_active->size.x; + offset.x = ctx->style.window.padding.y + ctx->style.cursor_active->size.y; + } else if (ctx->style.font) { + /* assume that cursor size is similar to font height (flawed but reasonable)*/ + offset.y = ctx->style.window.padding.x + ctx->style.font->height; + offset.x = ctx->style.window.padding.y + ctx->style.font->height; + } + return offset; +} +NK_LIB nk_flags +nk_tooltip_get_default_flags(const struct nk_context *ctx) +{ + NK_UNUSED(ctx); + return NK_TOOLTIP_BELOW|NK_TOOLTIP_ON_RIGHT; +} NK_API nk_bool nk_tooltip_begin(struct nk_context *ctx, float width) { - int x,y,w,h; + return nk_tooltip_begin_offset(ctx, width, + nk_tooltip_get_default_flags(ctx), + nk_tooltip_get_default_offset(ctx)); +} +NK_API nk_bool +nk_tooltip_begin_offset(struct nk_context *ctx, float width, nk_flags tooltip, struct nk_vec2 offset) +{ + int x,y,w,h,mul_x,mul_y; struct nk_window *win; const struct nk_input *in; struct nk_rect bounds; @@ -28,14 +58,38 @@ nk_tooltip_begin(struct nk_context *ctx, float width) return 0; w = nk_iceilf(width); - h = nk_iceilf(nk_null_rect.h); - x = nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x; - y = nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y; + h = ctx->current->layout->row.min_height; + + /* find axis multipliers based on bitmask state */ + mul_x = 0; + mul_x -= !!(tooltip & NK_TOOLTIP_ON_LEFT ); + mul_x += !!(tooltip & NK_TOOLTIP_ON_RIGHT); + NK_ASSERT(mul_x == -1 || mul_x == 0 || mul_x == 1); + mul_y = 0; + mul_y -= !!(tooltip & NK_TOOLTIP_ABOVE); + mul_y += !!(tooltip & NK_TOOLTIP_BELOW); + NK_ASSERT(mul_y == -1 || mul_y == 0 || mul_y == 1); + + /* turn relative offset into absolute, unless it's already absolute + * notice that offset axis is ignored in cases where mul==0 + * (if you don't like this behavior, make sure to use ABS_OFFSET flag)*/ + if (!(tooltip & NK_TOOLTIP_ABS_OFFSET)) { + offset.x *= mul_x; + offset.y *= mul_y; + } + + /* find origin */ + x = -w/2 + (mul_x * w/2); + x += nk_ifloorf(in->mouse.pos.x + 1) - (int)win->layout->clip.x; + x += offset.x; + y = -h/2 + (mul_y * h/2); + y += nk_ifloorf(in->mouse.pos.y + 1) - (int)win->layout->clip.y; + y += offset.y; bounds.x = (float)x; bounds.y = (float)y; bounds.w = (float)w; - bounds.h = (float)h; + bounds.h = (float)nk_iceilf(nk_null_rect.h); ret = nk_popup_begin(ctx, NK_POPUP_DYNAMIC, "__##Tooltip##__", NK_WINDOW_NO_SCROLLBAR|NK_WINDOW_BORDER, bounds); @@ -44,7 +98,6 @@ nk_tooltip_begin(struct nk_context *ctx, float width) ctx->current->layout->type = NK_PANEL_TOOLTIP; return ret; } - NK_API void nk_tooltip_end(struct nk_context *ctx) { @@ -56,7 +109,7 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } NK_API void -nk_tooltip(struct nk_context *ctx, const char *text) +nk_tooltip_offset(struct nk_context *ctx, const char *text, nk_flags tooltip, struct nk_vec2 offset) { const struct nk_style *style; struct nk_vec2 padding; @@ -84,14 +137,29 @@ nk_tooltip(struct nk_context *ctx, const char *text) text_height = (style->font->height + 2 * padding.y); /* execute tooltip and fill with text */ - if (nk_tooltip_begin(ctx, (float)text_width)) { + if (nk_tooltip_begin_offset(ctx, (float)text_width, tooltip, offset)) { nk_layout_row_dynamic(ctx, (float)text_height, 1); nk_text(ctx, text, text_len, NK_TEXT_LEFT); nk_tooltip_end(ctx); } } +NK_API void +nk_tooltip(struct nk_context *ctx, const char *text) +{ + nk_tooltip_offset(ctx, text, + nk_tooltip_get_default_flags(ctx), + nk_tooltip_get_default_offset(ctx)); +} #ifdef NK_INCLUDE_STANDARD_VARARGS NK_API void +nk_tooltipf_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + nk_tooltipfv_offset(ctx, tooltip, offset, fmt, args); + va_end(args); +} +NK_API void nk_tooltipf(struct nk_context *ctx, const char *fmt, ...) { va_list args; @@ -100,6 +168,13 @@ nk_tooltipf(struct nk_context *ctx, const char *fmt, ...) va_end(args); } NK_API void +nk_tooltipfv_offset(struct nk_context *ctx, nk_flags tooltip, struct nk_vec2 offset, const char *fmt, va_list args) +{ + char buf[256]; + nk_strfmt(buf, NK_LEN(buf), fmt, args); + nk_tooltip_offset(ctx, buf, tooltip, offset); +} +NK_API void nk_tooltipfv(struct nk_context *ctx, const char *fmt, va_list args) { char buf[256];