From f70c3ccfdb51cecacdaa236fbb957ca053b9734b Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Mon, 20 Apr 2026 12:46:25 -0700 Subject: [PATCH 1/5] Add nk_input_is_mouse_hovering_delay_rect() to implement delayed popups --- demo/common/overview.c | 11 +++++++++++ nuklear.h | 14 ++++++++++++++ src/nuklear.h | 1 + src/nuklear_input.c | 13 +++++++++++++ 4 files changed, 39 insertions(+) diff --git a/demo/common/overview.c b/demo/common/overview.c index e5f7774cf..6ee9f3d23 100644 --- a/demo/common/overview.c +++ b/demo/common/overview.c @@ -770,6 +770,10 @@ overview(struct nk_context *ctx) const struct nk_input *in = &ctx->input; struct nk_rect bounds; + /* seconds */ + static float delay_timer = 0.0; + static float delay = 0.75; + /* menu contextual */ nk_layout_row_static(ctx, 30, 160, 1); bounds = nk_widget_bounds(ctx); @@ -846,6 +850,13 @@ overview(struct nk_context *ctx) if (nk_input_is_mouse_hovering_rect(in, bounds)) { nk_tooltip(ctx, "This is a default tooltip"); } + + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover for delayed tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, &delay_timer, delay)) { + nk_tooltip(ctx, "This is a delayed 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)) { diff --git a/nuklear.h b/nuklear.h index c2b48941c..af9232d73 100644 --- a/nuklear.h +++ b/nuklear.h @@ -4961,6 +4961,7 @@ NK_API nk_bool nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, en NK_API nk_bool nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); @@ -18452,6 +18453,19 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } NK_API nk_bool +nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) +{ + NK_ASSERT(ctx); + if (!ctx) return nk_false; + if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + *timer += ctx->delta_time_seconds; + } else { + *timer = 0; + } + return *timer >= delay; + +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; diff --git a/src/nuklear.h b/src/nuklear.h index 2498c20ad..2943c3740 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -4745,6 +4745,7 @@ NK_API nk_bool nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, en NK_API nk_bool nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); diff --git a/src/nuklear_input.c b/src/nuklear_input.c index 9480a522e..e08de3e05 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -218,6 +218,19 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } NK_API nk_bool +nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) +{ + NK_ASSERT(ctx); + if (!ctx) return nk_false; + if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + *timer += ctx->delta_time_seconds; + } else { + *timer = 0; + } + return *timer >= delay; + +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; From 99dfa97bbd9248e497d3b4ca803de18ab6cf00ff Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Mon, 20 Apr 2026 16:02:36 -0700 Subject: [PATCH 2/5] Add tooltip convenience functions, and a default delay used only with appropriate convenience function --- demo/common/overview.c | 11 +++++++---- nuklear.h | 29 ++++++++++++++++++++++++++--- src/nuklear.h | 3 +++ src/nuklear_input.c | 5 +++-- src/nuklear_style.c | 3 ++- src/nuklear_tooltip.c | 18 ++++++++++++++++++ 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/demo/common/overview.c b/demo/common/overview.c index 6ee9f3d23..84399d027 100644 --- a/demo/common/overview.c +++ b/demo/common/overview.c @@ -772,7 +772,6 @@ overview(struct nk_context *ctx) /* seconds */ static float delay_timer = 0.0; - static float delay = 0.75; /* menu contextual */ nk_layout_row_static(ctx, 30, 160, 1); @@ -852,9 +851,13 @@ overview(struct nk_context *ctx) } bounds = nk_widget_bounds(ctx); - nk_label(ctx, "Hover for delayed tooltip", NK_TEXT_LEFT); - if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, &delay_timer, delay)) { - nk_tooltip(ctx, "This is a delayed tooltip"); + nk_label(ctx, "Hover for default delayed tooltip", NK_TEXT_LEFT); + nk_do_tooltip_delay(ctx, "This is a delayed tooltip", bounds, &delay_timer); + + bounds = nk_widget_bounds(ctx); + nk_label(ctx, "Hover longer a custom delayed tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, &delay_timer, 1.5)) { + nk_tooltip(ctx, "This is a custom delayed tooltip"); } bounds = nk_widget_bounds(ctx); diff --git a/nuklear.h b/nuklear.h index af9232d73..c6bdabddd 100644 --- a/nuklear.h +++ b/nuklear.h @@ -3836,6 +3836,8 @@ NK_API void nk_contextual_end(struct nk_context*); * ============================================================================= */ NK_API void nk_tooltip(struct nk_context*, const char*); NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset); +NK_API void nk_do_tooltip(struct nk_context*, const char*, struct nk_rect); +NK_API void nk_do_tooltip_delay(struct nk_context*, const char*, struct nk_rect, float*); #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); @@ -5597,6 +5599,7 @@ struct nk_style_window { enum nk_tooltip_pos tooltip_origin; struct nk_vec2 tooltip_offset; + float tooltip_delay; }; struct nk_style { @@ -18459,10 +18462,11 @@ nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_re if (!ctx) return nk_false; if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { *timer += ctx->delta_time_seconds; - } else { + return *timer >= delay; + } else if (NK_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { *timer = 0; } - return *timer >= delay; + return nk_false; } NK_API nk_bool @@ -19261,13 +19265,14 @@ nk_style_from_table(struct nk_context *ctx, const struct nk_color *table) win->tooltip_padding = nk_vec2(4,4); /* default tooltip just down and to the right of the cursor - * so it doesn't cover the text + * so it doesn't cover the text and a default delay of 0.5 seconds * * TODO might be worth consolidating tooltip styling * into its own style structure, though it is a * type of window...*/ win->tooltip_origin = NK_TOP_LEFT; win->tooltip_offset = nk_vec2(12, 12); + win->tooltip_delay = 0.5f; } NK_API void nk_style_set_font(struct nk_context *ctx, const struct nk_user_font *font) @@ -30780,6 +30785,24 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } +NK_API void +nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_rect(&ctx->input, bounds)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + +NK_API void +nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset) { diff --git a/src/nuklear.h b/src/nuklear.h index 2943c3740..686aa70b3 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -3620,6 +3620,8 @@ NK_API void nk_contextual_end(struct nk_context*); * ============================================================================= */ NK_API void nk_tooltip(struct nk_context*, const char*); NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset); +NK_API void nk_do_tooltip(struct nk_context*, const char*, struct nk_rect); +NK_API void nk_do_tooltip_delay(struct nk_context*, const char*, struct nk_rect, float*); #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); @@ -5381,6 +5383,7 @@ struct nk_style_window { enum nk_tooltip_pos tooltip_origin; struct nk_vec2 tooltip_offset; + float tooltip_delay; }; struct nk_style { diff --git a/src/nuklear_input.c b/src/nuklear_input.c index e08de3e05..322fb5b6a 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -224,10 +224,11 @@ nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_re if (!ctx) return nk_false; if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { *timer += ctx->delta_time_seconds; - } else { + return *timer >= delay; + } else if (NK_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { *timer = 0; } - return *timer >= delay; + return nk_false; } NK_API nk_bool diff --git a/src/nuklear_style.c b/src/nuklear_style.c index aff1a827b..b7adcd489 100644 --- a/src/nuklear_style.c +++ b/src/nuklear_style.c @@ -722,13 +722,14 @@ nk_style_from_table(struct nk_context *ctx, const struct nk_color *table) win->tooltip_padding = nk_vec2(4,4); /* default tooltip just down and to the right of the cursor - * so it doesn't cover the text + * so it doesn't cover the text and a default delay of 0.5 seconds * * TODO might be worth consolidating tooltip styling * into its own style structure, though it is a * type of window...*/ win->tooltip_origin = NK_TOP_LEFT; win->tooltip_offset = nk_vec2(12, 12); + win->tooltip_delay = 0.5f; } NK_API void nk_style_set_font(struct nk_context *ctx, const struct nk_user_font *font) diff --git a/src/nuklear_tooltip.c b/src/nuklear_tooltip.c index 1fee2e377..a67c6516b 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -104,6 +104,24 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } +NK_API void +nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_rect(&ctx->input, bounds)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + +NK_API void +nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset) { From e2023777db55dd9f5568a242e63057b3d74831be Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Mon, 20 Apr 2026 16:42:36 -0700 Subject: [PATCH 3/5] Add 2 input hovering motionless input functions, use delayed version for delayed tooltips --- demo/common/overview.c | 6 ++--- nuklear.h | 53 +++++++++++++++++++++++++++++++++++------- src/nuklear.h | 2 ++ src/nuklear_input.c | 49 ++++++++++++++++++++++++++++++++------ src/nuklear_tooltip.c | 2 +- 5 files changed, 93 insertions(+), 19 deletions(-) diff --git a/demo/common/overview.c b/demo/common/overview.c index 84399d027..1698f5eb4 100644 --- a/demo/common/overview.c +++ b/demo/common/overview.c @@ -851,12 +851,12 @@ overview(struct nk_context *ctx) } bounds = nk_widget_bounds(ctx); - nk_label(ctx, "Hover for default delayed tooltip", NK_TEXT_LEFT); + nk_label(ctx, "Hover motionless for a default delayed tooltip", NK_TEXT_LEFT); nk_do_tooltip_delay(ctx, "This is a delayed tooltip", bounds, &delay_timer); bounds = nk_widget_bounds(ctx); - nk_label(ctx, "Hover longer a custom delayed tooltip", NK_TEXT_LEFT); - if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, &delay_timer, 1.5)) { + nk_label(ctx, "Hover motionless longer a custom delayed tooltip", NK_TEXT_LEFT); + if (nk_input_is_mouse_hovering_still_delay_rect(ctx, bounds, &delay_timer, 1.5)) { nk_tooltip(ctx, "This is a custom delayed tooltip"); } diff --git a/nuklear.h b/nuklear.h index c6bdabddd..f377a1516 100644 --- a/nuklear.h +++ b/nuklear.h @@ -4963,7 +4963,9 @@ NK_API nk_bool nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, en NK_API nk_bool nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); +NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); @@ -18456,20 +18458,55 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } NK_API nk_bool +nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect rect) +{ + if (!i) return nk_false; + return (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h) && + !nk_input_is_mouse_moved(i)); +} +NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { NK_ASSERT(ctx); - if (!ctx) return nk_false; - if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { - *timer += ctx->delta_time_seconds; - return *timer >= delay; - } else if (NK_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { - *timer = 0; + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; } - return nk_false; } NK_API nk_bool +nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) +{ + NK_ASSERT(ctx); + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + /* once it triggers, moving within the bounds should not make it disappear */ + if (*timer >= delay) { + return nk_true; + } + if (!nk_input_is_mouse_moved(i)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } + *timer = 0; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; + } +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; @@ -30798,7 +30835,7 @@ NK_API void nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) { NK_ASSERT(ctx); - if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { + if (nk_input_is_mouse_hovering_still_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); } } diff --git a/src/nuklear.h b/src/nuklear.h index 686aa70b3..45d0f516b 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -4747,7 +4747,9 @@ NK_API nk_bool nk_input_is_mouse_click_down_in_rect(const struct nk_input *i, en NK_API nk_bool nk_input_any_mouse_click_in_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk_rect); +NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); +NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); diff --git a/src/nuklear_input.c b/src/nuklear_input.c index 322fb5b6a..10778f1ef 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -218,20 +218,55 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } NK_API nk_bool +nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect rect) +{ + if (!i) return nk_false; + return (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h) && + !nk_input_is_mouse_moved(i)); +} +NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { NK_ASSERT(ctx); - if (!ctx) return nk_false; - if (NK_INBOX(ctx->input.mouse.pos.x, ctx->input.mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { - *timer += ctx->delta_time_seconds; - return *timer >= delay; - } else if (NK_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { - *timer = 0; + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; } - return nk_false; } NK_API nk_bool +nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) +{ + NK_ASSERT(ctx); + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + /* once it triggers, moving within the bounds should not make it disappear */ + if (*timer >= delay) { + return nk_true; + } + if (!nk_input_is_mouse_moved(i)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } + *timer = 0; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; + } +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; diff --git a/src/nuklear_tooltip.c b/src/nuklear_tooltip.c index a67c6516b..ae3342551 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -117,7 +117,7 @@ NK_API void nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) { NK_ASSERT(ctx); - if (nk_input_is_mouse_hovering_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { + if (nk_input_is_mouse_hovering_still_delay_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay)) { nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); } } From 46baf55bb50c8c38ae8bcdc949d9092d6f273d02 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 5 May 2026 16:02:42 -0700 Subject: [PATCH 4/5] Add tooltip disappears when item is clicked functionality --- demo/common/overview.c | 8 ++++++++ nuklear.h | 43 ++++++++++++++++++++++++++++++++++++++++++ src/nuklear.h | 2 ++ src/nuklear_input.c | 32 +++++++++++++++++++++++++++++++ src/nuklear_tooltip.c | 9 +++++++++ 5 files changed, 94 insertions(+) diff --git a/demo/common/overview.c b/demo/common/overview.c index 1698f5eb4..57bd29b28 100644 --- a/demo/common/overview.c +++ b/demo/common/overview.c @@ -772,6 +772,7 @@ overview(struct nk_context *ctx) /* seconds */ static float delay_timer = 0.0; + static nk_bool clicked = nk_false; /* menu contextual */ nk_layout_row_static(ctx, 30, 160, 1); @@ -860,6 +861,13 @@ overview(struct nk_context *ctx) nk_tooltip(ctx, "This is a custom delayed tooltip"); } + bounds = nk_widget_bounds(ctx); + if (nk_button_label(ctx, "Delayed tooltip with click sensitivity")) { + clicked = nk_true; + } + nk_do_tooltip_delay_clicked(ctx, "disappears when clicked, timer starts when you move again", bounds, &delay_timer, &clicked); + + 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)) { diff --git a/nuklear.h b/nuklear.h index f377a1516..babc20ae4 100644 --- a/nuklear.h +++ b/nuklear.h @@ -3838,6 +3838,7 @@ NK_API void nk_tooltip(struct nk_context*, const char*); NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset); NK_API void nk_do_tooltip(struct nk_context*, const char*, struct nk_rect); NK_API void nk_do_tooltip_delay(struct nk_context*, const char*, struct nk_rect, float*); +NK_API void nk_do_tooltip_delay_clicked(struct nk_context*, const char*, struct nk_rect, float* timer, nk_bool*); #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); @@ -4966,6 +4967,7 @@ NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context*, struct nk_rect, float*, float); +NK_API nk_bool nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context*, struct nk_rect, float*, float, nk_bool*); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); @@ -18507,6 +18509,38 @@ nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct } } NK_API nk_bool +nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay, nk_bool* clicked) +{ + NK_ASSERT(ctx); + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (*clicked) { + /* could also be based on maintaining hover rather than motionless once clicked */ + if (!nk_input_is_mouse_moved(i)) { + return nk_false; + } + *clicked = nk_false; + *timer = 0; + } + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + /* once it triggers, moving within the bounds should not make it disappear */ + if (*timer >= delay) { + return nk_true; + } + if (!nk_input_is_mouse_moved(i)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } + *timer = 0; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; + } +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; @@ -30840,6 +30874,15 @@ nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bou } } +NK_API void +nk_do_tooltip_delay_clicked(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer, nk_bool* clicked) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_still_delay_clicked_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay, clicked)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset) { diff --git a/src/nuklear.h b/src/nuklear.h index 45d0f516b..f9488d04a 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -3622,6 +3622,7 @@ NK_API void nk_tooltip(struct nk_context*, const char*); NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset); NK_API void nk_do_tooltip(struct nk_context*, const char*, struct nk_rect); NK_API void nk_do_tooltip_delay(struct nk_context*, const char*, struct nk_rect, float*); +NK_API void nk_do_tooltip_delay_clicked(struct nk_context*, const char*, struct nk_rect, float* timer, nk_bool*); #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); @@ -4750,6 +4751,7 @@ NK_API nk_bool nk_input_is_mouse_hovering_rect(const struct nk_input*, struct nk NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input*, struct nk_rect); NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context*, struct nk_rect, float*, float); NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context*, struct nk_rect, float*, float); +NK_API nk_bool nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context*, struct nk_rect, float*, float, nk_bool*); NK_API nk_bool nk_input_is_mouse_moved(const struct nk_input*); NK_API nk_bool nk_input_mouse_clicked(const struct nk_input*, enum nk_buttons, struct nk_rect); NK_API nk_bool nk_input_is_mouse_down(const struct nk_input*, enum nk_buttons); diff --git a/src/nuklear_input.c b/src/nuklear_input.c index 10778f1ef..2db82c0b1 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -267,6 +267,38 @@ nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct } } NK_API nk_bool +nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay, nk_bool* clicked) +{ + NK_ASSERT(ctx); + if (!ctx) { + return nk_false; + } else { + const struct nk_input* i = &ctx->input; + if (*clicked) { + /* could also be based on maintaining hover rather than motionless once clicked */ + if (!nk_input_is_mouse_moved(i)) { + return nk_false; + } + *clicked = nk_false; + *timer = 0; + } + if (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h)) { + /* once it triggers, moving within the bounds should not make it disappear */ + if (*timer >= delay) { + return nk_true; + } + if (!nk_input_is_mouse_moved(i)) { + *timer += ctx->delta_time_seconds; + return *timer >= delay; + } + *timer = 0; + } else if (NK_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h)) { + *timer = 0; + } + return nk_false; + } +} +NK_API nk_bool nk_input_is_mouse_prev_hovering_rect(const struct nk_input *i, struct nk_rect rect) { if (!i) return nk_false; diff --git a/src/nuklear_tooltip.c b/src/nuklear_tooltip.c index ae3342551..938955982 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -122,6 +122,15 @@ nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bou } } +NK_API void +nk_do_tooltip_delay_clicked(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer, nk_bool* clicked) +{ + NK_ASSERT(ctx); + if (nk_input_is_mouse_hovering_still_delay_clicked_rect(ctx, bounds, timer, ctx->style.window.tooltip_delay, clicked)) { + nk_tooltip_offset(ctx, text, ctx->style.window.tooltip_origin, ctx->style.window.tooltip_offset); + } +} + NK_API void nk_tooltip_offset(struct nk_context *ctx, const char *text, enum nk_tooltip_pos position, struct nk_vec2 offset) { From 631f233790496162bbd5fb1f600d72ae55940510 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 12 May 2026 19:05:32 -0700 Subject: [PATCH 5/5] Add Doxygen comments above definitions --- nuklear.h | 64 +++++++++++++++++++++++++++++++++++++++++++ src/nuklear_input.c | 52 +++++++++++++++++++++++++++++++++++ src/nuklear_tooltip.c | 12 ++++++++ 3 files changed, 128 insertions(+) diff --git a/nuklear.h b/nuklear.h index babc20ae4..bc4cc40fb 100644 --- a/nuklear.h +++ b/nuklear.h @@ -18459,6 +18459,12 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) if (!i) return nk_false; return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } + +/** + * # nk_input_is_mouse_hovering_still_rect + * Returns true if the mouse is hovering over rect and hasn't moved since the last + * frame, false otherwise. + */ NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect rect) { @@ -18466,6 +18472,20 @@ nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect r return (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h) && !nk_input_is_mouse_moved(i)); } + +/** + * # nk_input_is_mouse_hovering_delay_rect + * Returns true if the mouse has been hovering over rect for `delay` seconds or more. + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * + * \returns `true` if the the mouse has hovered long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { @@ -18484,6 +18504,21 @@ nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_re } } + +/** + * # nk_input_is_mouse_hovering_still_delay_rect + * Returns true if the mouse has been hovering motionless over rect for `delay` seconds or more. The timer + * does not reset once the delay is reached as long as you are still hovering over the rect. + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * + * \returns `true` if the the mouse has hovered without moving long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { @@ -18508,6 +18543,23 @@ nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct return nk_false; } } + +/** + * # nk_input_is_mouse_hovering_still_delay_clicked_rect + * Works the same as `nk_input_is_mouse_hovering_still_delay_rect` unless clicked is set. If clicked is true, + * then it returns false regardless of the timer value as long as the mouse is motionless. It goes back to working + * as normal once the mouse has moved (clicked is set to false, timer to 0). + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * \param[in|out] clicked | Must point to an nk_bool used to indicate whether the item in question has been clicked (reset to false internally on mouse motion) + * + * \returns `true` if the the mouse has hovered without moving long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay, nk_bool* clicked) { @@ -30856,6 +30908,9 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } +/** + * Display a default tooltip if the mouse is hovering over the rect `bounds` + */ NK_API void nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) { @@ -30865,6 +30920,10 @@ nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) } } +/** + * Display a default tooltip if the mouse hovers motionless for the default delay (ctx->style.window.tooltip_delay) + * `timer` is used to track the time across frames. + */ NK_API void nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) { @@ -30874,6 +30933,11 @@ nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bou } } +/** + * Display a default tooltip if the mouse hovers motionless for the default delay (ctx->style.window.tooltip_delay) unless + * `clicked` is true. `clicked` will be reset to false (and `timer` to 0) when the mouse moves. + * `timer` is used to track the time across frames. + */ NK_API void nk_do_tooltip_delay_clicked(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer, nk_bool* clicked) { diff --git a/src/nuklear_input.c b/src/nuklear_input.c index 2db82c0b1..f1fb3ae63 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -217,6 +217,12 @@ nk_input_is_mouse_hovering_rect(const struct nk_input *i, struct nk_rect rect) if (!i) return nk_false; return NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); } + +/** + * # nk_input_is_mouse_hovering_still_rect + * Returns true if the mouse is hovering over rect and hasn't moved since the last + * frame, false otherwise. + */ NK_API nk_bool nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect rect) { @@ -224,6 +230,20 @@ nk_input_is_mouse_hovering_still_rect(const struct nk_input *i, struct nk_rect r return (NK_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h) && !nk_input_is_mouse_moved(i)); } + +/** + * # nk_input_is_mouse_hovering_delay_rect + * Returns true if the mouse has been hovering over rect for `delay` seconds or more. + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * + * \returns `true` if the the mouse has hovered long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { @@ -242,6 +262,21 @@ nk_input_is_mouse_hovering_delay_rect(const struct nk_context *ctx, struct nk_re } } + +/** + * # nk_input_is_mouse_hovering_still_delay_rect + * Returns true if the mouse has been hovering motionless over rect for `delay` seconds or more. The timer + * does not reset once the delay is reached as long as you are still hovering over the rect. + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * + * \returns `true` if the the mouse has hovered without moving long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay) { @@ -266,6 +301,23 @@ nk_input_is_mouse_hovering_still_delay_rect(const struct nk_context *ctx, struct return nk_false; } } + +/** + * # nk_input_is_mouse_hovering_still_delay_clicked_rect + * Works the same as `nk_input_is_mouse_hovering_still_delay_rect` unless clicked is set. If clicked is true, + * then it returns false regardless of the timer value as long as the mouse is motionless. It goes back to working + * as normal once the mouse has moved (clicked is set to false, timer to 0). + * + * Parameter | Description + * ------------|--------------------------------------------------------------- + * \param[in] ctx | Must point to an either stack or heap allocated `nk_context` struct + * \param[in] rect | The rect area you're checking against + * \param[in|out] timer | Must point to a float used to track the total seconds hovered across frames + * \param[in] delay | The wait time in seconds + * \param[in|out] clicked | Must point to an nk_bool used to indicate whether the item in question has been clicked (reset to false internally on mouse motion) + * + * \returns `true` if the the mouse has hovered without moving long enough, `false otherwise` + */ NK_API nk_bool nk_input_is_mouse_hovering_still_delay_clicked_rect(const struct nk_context *ctx, struct nk_rect rect, float* timer, float delay, nk_bool* clicked) { diff --git a/src/nuklear_tooltip.c b/src/nuklear_tooltip.c index 938955982..08a00a440 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -104,6 +104,9 @@ nk_tooltip_end(struct nk_context *ctx) nk_popup_end(ctx); } +/** + * Display a default tooltip if the mouse is hovering over the rect `bounds` + */ NK_API void nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) { @@ -113,6 +116,10 @@ nk_do_tooltip(struct nk_context* ctx, const char* text, struct nk_rect bounds) } } +/** + * Display a default tooltip if the mouse hovers motionless for the default delay (ctx->style.window.tooltip_delay) + * `timer` is used to track the time across frames. + */ NK_API void nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer) { @@ -122,6 +129,11 @@ nk_do_tooltip_delay(struct nk_context* ctx, const char* text, struct nk_rect bou } } +/** + * Display a default tooltip if the mouse hovers motionless for the default delay (ctx->style.window.tooltip_delay) unless + * `clicked` is true. `clicked` will be reset to false (and `timer` to 0) when the mouse moves. + * `timer` is used to track the time across frames. + */ NK_API void nk_do_tooltip_delay_clicked(struct nk_context* ctx, const char* text, struct nk_rect bounds, float* timer, nk_bool* clicked) {