diff --git a/demo/common/overview.c b/demo/common/overview.c index e5f7774cf..57bd29b28 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 nk_bool clicked = nk_false; + /* menu contextual */ nk_layout_row_static(ctx, 30, 160, 1); bounds = nk_widget_bounds(ctx); @@ -846,6 +850,24 @@ 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 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 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"); + } + + 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 c2b48941c..bc4cc40fb 100644 --- a/nuklear.h +++ b/nuklear.h @@ -3836,6 +3836,9 @@ 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*); +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); @@ -4961,6 +4964,10 @@ 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_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); @@ -5596,6 +5603,7 @@ struct nk_style_window { enum nk_tooltip_pos tooltip_origin; struct nk_vec2 tooltip_offset; + float tooltip_delay; }; struct nk_style { @@ -18451,6 +18459,139 @@ 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) +{ + 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_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) +{ + 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)) { + *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; + } + +} + +/** + * # 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) +{ + 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_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) +{ + 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) { @@ -19247,13 +19388,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) @@ -30766,6 +30908,45 @@ 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) +{ + 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); + } +} + +/** + * 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) +{ + NK_ASSERT(ctx); + 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); + } +} + +/** + * 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) +{ + 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 2498c20ad..f9488d04a 100644 --- a/src/nuklear.h +++ b/src/nuklear.h @@ -3620,6 +3620,9 @@ 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*); +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); @@ -4745,6 +4748,10 @@ 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_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); @@ -5380,6 +5387,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 9480a522e..f1fb3ae63 100644 --- a/src/nuklear_input.c +++ b/src/nuklear_input.c @@ -217,6 +217,139 @@ 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) +{ + 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_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) +{ + 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)) { + *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; + } + +} + +/** + * # 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) +{ + 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_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) +{ + 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) { 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..08a00a440 100644 --- a/src/nuklear_tooltip.c +++ b/src/nuklear_tooltip.c @@ -104,6 +104,45 @@ 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) +{ + 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); + } +} + +/** + * 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) +{ + NK_ASSERT(ctx); + 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); + } +} + +/** + * 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) +{ + 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) {