diff --git a/demo/common/nuklear_console_demo.c b/demo/common/nuklear_console_demo.c index b8bf972..477d3c0 100644 --- a/demo/common/nuklear_console_demo.c +++ b/demo/common/nuklear_console_demo.c @@ -128,6 +128,11 @@ void nk_console_demo_show_message(struct nk_console* button, void* user_data) { nk_console_show_message(button, message); } +void nk_console_demo_show_marquee_message(struct nk_console* button, void* user_data) { + NK_UNUSED(user_data); + nk_console_show_message(button, "This is a very long marquee message that scrolls across the screen because it is too wide to fit!"); +} + void nk_console_quit_button_focused(struct nk_console* widget, void* user_data) { NK_UNUSED(user_data); nk_console_show_message(widget, "Are you sure you want to quit?"); @@ -428,6 +433,11 @@ struct nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_ // Messages nk_console_button_onclick(widgets, "Show Message", &nk_console_demo_show_message); + nk_console_button_onclick(widgets, "Show Marquee Message", &nk_console_demo_show_marquee_message); + + // Long tooltip + nk_console_button_onclick(widgets, "Long Tooltip Button", NULL) + ->tooltip = "This is a very long tooltip that will marquee scroll across the bottom of the screen because it is too wide to fit!"; // Back Button nk_console_button_set_symbol( diff --git a/demo/raylib/.cmake/Findnuklear_gamepad.cmake b/demo/raylib/.cmake/Findnuklear_gamepad.cmake new file mode 100644 index 0000000..080910a --- /dev/null +++ b/demo/raylib/.cmake/Findnuklear_gamepad.cmake @@ -0,0 +1,17 @@ +if (NOT NUKLEAR_GAMEPAD_VERSION) + set(NUKLEAR_GAMEPAD_VERSION 1.1.0) +endif() + +include(FetchContent) +FetchContent_Declare( + nuklear_gamepad + DOWNLOAD_EXTRACT_TIMESTAMP OFF + URL https://github.com/RobLoach/nuklear_gamepad/archive/refs/tags/v${NUKLEAR_GAMEPAD_VERSION}.tar.gz +) +FetchContent_GetProperties(nuklear_gamepad) + +if (NOT nuklear_gamepad_POPULATED) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(nuklear_gamepad) + add_subdirectory(${nuklear_gamepad_SOURCE_DIR} ${nuklear_gamepad_BINARY_DIR}) +endif() diff --git a/demo/raylib/.cmake/Findraylib.cmake b/demo/raylib/.cmake/Findraylib.cmake new file mode 100644 index 0000000..1f31c77 --- /dev/null +++ b/demo/raylib/.cmake/Findraylib.cmake @@ -0,0 +1,20 @@ +# RAYLIB_VERSION +if (NOT RAYLIB_VERSION) + set(RAYLIB_VERSION 6.0) +endif() + +include(FetchContent) +FetchContent_Declare( + raylib + DOWNLOAD_EXTRACT_TIMESTAMP OFF + URL https://github.com/raysan5/raylib/archive/refs/tags/${RAYLIB_VERSION}.tar.gz +) +FetchContent_GetProperties(raylib) + +if (NOT raylib_POPULATED) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(raylib) + set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(BUILD_GAMES OFF CACHE BOOL "" FORCE) + add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) +endif() diff --git a/demo/raylib/.cmake/Findraylib_nuklear.cmake b/demo/raylib/.cmake/Findraylib_nuklear.cmake new file mode 100644 index 0000000..7b19920 --- /dev/null +++ b/demo/raylib/.cmake/Findraylib_nuklear.cmake @@ -0,0 +1,17 @@ +if (NOT RAYLIB_NUKLEAR_VERSION) + set(RAYLIB_NUKLEAR_VERSION 6.0.1) +endif() + +include(FetchContent) +FetchContent_Declare( + raylib_nuklear + DOWNLOAD_EXTRACT_TIMESTAMP OFF + URL https://github.com/RobLoach/raylib-nuklear/archive/refs/tags/v${RAYLIB_NUKLEAR_VERSION}.tar.gz +) +FetchContent_GetProperties(raylib_nuklear) + +if (NOT raylib_nuklear_POPULATED) + set(FETCHCONTENT_QUIET NO) + FetchContent_Populate(raylib_nuklear) + add_subdirectory(${raylib_nuklear_SOURCE_DIR} ${raylib_nuklear_BINARY_DIR}) +endif() diff --git a/demo/raylib/CMakeLists.txt b/demo/raylib/CMakeLists.txt index e1e41e9..0120c2c 100644 --- a/demo/raylib/CMakeLists.txt +++ b/demo/raylib/CMakeLists.txt @@ -5,57 +5,17 @@ project(nuklear_console_demo_raylib) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) +# Register the cmake folder for find_package() +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/.cmake") + # raylib -find_package(raylib QUIET) -if (NOT raylib_FOUND) - include(FetchContent) - FetchContent_Declare( - raylib - GIT_REPOSITORY https://github.com/raysan5/raylib.git - GIT_TAG 6.0 - ) - FetchContent_GetProperties(raylib) - if (NOT raylib_POPULATED) # Have we downloaded raylib yet? - set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib) - set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) - endif() -endif() +find_package(raylib REQUIRED) # raylib_nuklear -find_package(raylib_nuklear QUIET) -if (NOT raylib_nuklear_FOUND) - include(FetchContent) - FetchContent_Declare( - raylib_nuklear - GIT_REPOSITORY https://github.com/RobLoach/raylib-nuklear.git - GIT_TAG master - ) - FetchContent_GetProperties(raylib_nuklear) - if (NOT raylib_nuklear_POPULATED) # Have we downloaded raylib yet? - set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib_nuklear) - add_subdirectory(${raylib_nuklear_SOURCE_DIR} ${raylib_nuklear_BINARY_DIR}) - endif() -endif() +find_package(raylib_nuklear REQUIRED) # nuklear_gamepad -find_package(nuklear_gamepad QUIET) -if (NOT nuklear_gamepad_FOUND) - include(FetchContent) - FetchContent_Declare( - nuklear_gamepad - GIT_REPOSITORY https://github.com/RobLoach/nuklear_gamepad.git - GIT_TAG master - ) - FetchContent_GetProperties(nuklear_gamepad) - if (NOT nuklear_gamepad_POPULATED) # Have we downloaded raylib yet? - set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(nuklear_gamepad) - add_subdirectory(${nuklear_gamepad_SOURCE_DIR} ${nuklear_gamepad_BINARY_DIR}) - endif() -endif() +find_package(nuklear_gamepad REQUIRED) # Resources if (EXISTS ${PROJECT_SOURCE_DIR}/resources) diff --git a/nuklear_console.h b/nuklear_console.h index f91db25..4c7cc9e 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -82,6 +82,7 @@ typedef enum { typedef struct nk_console_message { char text[256]; float duration; + float scroll_x; } nk_console_message; typedef struct nk_console { @@ -169,6 +170,9 @@ typedef struct nk_console_top_data { nk_uint drag_scroll_start_y; /** Window scroll Y at drag start. */ nk_uint drag_scroll_max_x; /** Maximum scroll X, updated each frame after render. */ nk_uint drag_scroll_max_y; /** Maximum scroll Y, updated each frame after render. */ + + float tooltip_scroll_x; /** Horizontal marquee offset for the active tooltip. */ + const char* tooltip_last; /** Pointer to the last tooltip shown; used to detect tooltip changes and reset scroll. */ } nk_console_top_data; #if defined(__cplusplus) @@ -401,6 +405,78 @@ NK_API nk_bool nk_console_navigate_to_path(nk_console* console, const char* path #endif #include CVECTOR_H +#ifndef NK_CONSOLE_MARQUEE_SCROLL_SPEED +#define NK_CONSOLE_MARQUEE_SCROLL_SPEED 60.0f +#endif +#ifndef NK_CONSOLE_MARQUEE_SCROLL_PAUSE +#define NK_CONSOLE_MARQUEE_SCROLL_PAUSE 1.5f +#endif + +/** + * Advance a marquee scroll offset and return a pointer to the visible slice of text. + * Returns `text` unchanged when the text fits or delta time is unavailable. + * The caller owns `buf` (minimum `buf_size` bytes). + */ +static const char* nk_console_marquee_slice( + struct nk_context* ctx, + const char* text, int text_len, + float full_text_width, float avail_width, + float speed, float pause, + float* scroll_x, + char* buf, int buf_size) +{ + if (full_text_width <= avail_width || ctx->delta_time_seconds <= 0) { + return text; + } + float pause_pixels = pause * speed; + float total_cycle = full_text_width + pause_pixels; + *scroll_x += ctx->delta_time_seconds * speed; + if (*scroll_x > total_cycle) { + *scroll_x -= total_cycle; + } + float offset = *scroll_x - pause_pixels; + if (offset <= 0.0f) { + return text; + } + int start = 0; + for (int i = 1; i <= text_len; i++) { + float w = ctx->style.font->width(ctx->style.font->userdata, ctx->style.font->height, text, i); + if (w >= offset) { start = i - 1; break; } + if (i == text_len) { start = text_len; } + } + int copy_len = text_len - start; + if (copy_len >= buf_size) { + copy_len = buf_size - 1; + } + NK_MEMCPY(buf, text + start, (nk_size)copy_len); + buf[copy_len] = '\0'; + return buf; +} + +/** + * Render a single-line marquee tooltip of `tooltip_width` at the current mock mouse position. + */ +static void nk_console_tooltip_render_marquee( + struct nk_context* ctx, + const char* text, int text_len, + float full_text_width, + float tooltip_width, float text_height, + float speed, float pause, + float* scroll_x) +{ + float avail_width = tooltip_width - ctx->style.window.padding.x * 2.0f; + char display_buf[256]; + const char* display_text = nk_console_marquee_slice(ctx, text, text_len, + full_text_width, avail_width, speed, pause, scroll_x, display_buf, (int)sizeof(display_buf)); + struct nk_vec2 zero; + nk_zero_struct(zero); + if (nk_tooltip_begin_offset(ctx, tooltip_width, NK_TOP_LEFT, zero)) { + nk_layout_row_dynamic(ctx, text_height, 1); + nk_label(ctx, display_text, NK_TEXT_LEFT); + nk_tooltip_end(ctx); + } +} + #ifdef __cplusplus extern "C" { #endif @@ -780,37 +856,45 @@ NK_API nk_bool nk_console_selectable(nk_console* widget) { return widget->selectable && widget->visible && !widget->disabled; } +#ifndef NK_CONSOLE_TOOLTIP_SCROLL_SPEED +#define NK_CONSOLE_TOOLTIP_SCROLL_SPEED NK_CONSOLE_MARQUEE_SCROLL_SPEED +#endif +#ifndef NK_CONSOLE_TOOLTIP_SCROLL_PAUSE +#define NK_CONSOLE_TOOLTIP_SCROLL_PAUSE NK_CONSOLE_MARQUEE_SCROLL_PAUSE +#endif + /** * Display a tooltip with the given text. * * @see nk_tooltip() - * @todo Support multiline tooltips with nk_text_calculate_text_bounds() */ -static void nk_console_tooltip_display(struct nk_context* ctx, const char* text) { - const struct nk_style* style; - struct nk_vec2 padding; - struct nk_vec2 zero; - nk_zero_struct(zero); - - style = &ctx->style; - padding = style->window.padding; - - float text_height = (style->font->height + padding.y); +static void nk_console_tooltip_display(nk_console* console, const char* text) { + struct nk_context* ctx = console->ctx; + const struct nk_style* style = &ctx->style; + float text_height = style->font->height + style->window.padding.y; float x = ctx->input.mouse.pos.x; float y = ctx->input.mouse.pos.y; - // Display the tooltip at the bottom of the window, manipulating the mouse position struct nk_rect windowbounds = nk_window_get_bounds(ctx); ctx->input.mouse.pos.x = windowbounds.x; - ctx->input.mouse.pos.y = windowbounds.y + windowbounds.h - text_height - padding.y * 2.0f - ctx->style.window.border; + ctx->input.mouse.pos.y = windowbounds.y + windowbounds.h - text_height - style->window.padding.y * 2.0f - style->window.border; - if (nk_tooltip_begin_offset(ctx, windowbounds.w - ctx->style.window.border, NK_TOP_LEFT, zero)) { - nk_layout_row_dynamic(ctx, text_height, 1); - nk_text_wrap(ctx, text, nk_strlen(text)); - nk_tooltip_end(ctx); + nk_console_top_data* data = (nk_console_top_data*)nk_console_get_top(console)->data; + + // Reset scroll on tooltip change. Relies on pointer identity — works for string + // literals and persistent pointers; resets every frame for stack-allocated strings. + if (data->tooltip_last != text) { + data->tooltip_last = text; + data->tooltip_scroll_x = 0.0f; } - // Restore the mouse x/y positions. + int text_len = nk_strlen(text); + float full_text_width = style->font->width(style->font->userdata, style->font->height, text, text_len); + nk_console_tooltip_render_marquee(ctx, text, text_len, full_text_width, + windowbounds.w - style->window.border, text_height, + NK_CONSOLE_TOOLTIP_SCROLL_SPEED, NK_CONSOLE_TOOLTIP_SCROLL_PAUSE, + &data->tooltip_scroll_x); + ctx->input.mouse.pos.x = x; ctx->input.mouse.pos.y = y; } @@ -824,7 +908,7 @@ NK_API void nk_console_check_tooltip(nk_console* console) { } if (console->tooltip != NULL) { - nk_console_tooltip_display(console->ctx, console->tooltip); + nk_console_tooltip_display(console, console->tooltip); } } diff --git a/nuklear_console_file.h b/nuklear_console_file.h index 411c752..0b6082b 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -249,7 +249,7 @@ NK_API struct nk_rect nk_console_file_render(nk_console* console) { nk_console_layout_widget(console); - // Display the label (skipped in file_action mode — it becomes the button text instead). + // Display the label (skipped in file_action mode - it becomes the button text instead). if (!data->file_action && console->label != NULL && console->label[0] != '\0') { if (!nk_console_is_active_widget(console)) { nk_widget_disable_begin(console->ctx); diff --git a/nuklear_console_key.h b/nuklear_console_key.h index e44ead5..0ea105f 100644 --- a/nuklear_console_key.h +++ b/nuklear_console_key.h @@ -127,7 +127,7 @@ NK_API const char* nk_console_key_name(nk_rune key) { } } - /* Unicode character — encode into a static buffer and return it. */ + /* Unicode character - encode into a static buffer and return it. */ if (key == 32) return "Space"; static char nk_console_key_char_buf[NK_UTF_SIZE + 1]; int len = nk_utf_encode(key, nk_console_key_char_buf, NK_UTF_SIZE); @@ -241,7 +241,7 @@ static struct nk_rect nk_console_key_active_render(nk_console* console) { nk_console_top_data* top_data = (nk_console_top_data*)top->data; if (top_data->input_processed == nk_false) { /* Typed character input takes priority over special keys. Control - * characters (< 32) are excluded — they overlap with nk_keys values + * characters (< 32) are excluded - they overlap with nk_keys values * and are handled by the special-key loop below. */ if (console->ctx->input.keyboard.text_len > 0) { nk_rune ch = 0; diff --git a/nuklear_console_list_view.h b/nuklear_console_list_view.h index 6b35130..13e9f99 100644 --- a/nuklear_console_list_view.h +++ b/nuklear_console_list_view.h @@ -176,7 +176,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { // Handle keyboard/gamepad navigation. if (is_active && !top_data->input_processed) { - // Hold-to-accelerate timers — mirrors nk_console_check_up_down. + // Hold-to-accelerate timers - mirrors nk_console_check_up_down. nk_bool up_held = nk_console_button_down(top, NK_GAMEPAD_BUTTON_UP); nk_bool down_held = nk_console_button_down(top, NK_GAMEPAD_BUTTON_DOWN); nk_bool repeat_fire = nk_false; diff --git a/nuklear_console_message.h b/nuklear_console_message.h index 2e47bed..fdabadb 100644 --- a/nuklear_console_message.h +++ b/nuklear_console_message.h @@ -75,6 +75,13 @@ NK_API void nk_console_show_message(nk_console* console, const char* text) { cvector_push_back(data->messages, message); } +#ifndef NK_CONSOLE_MESSAGE_SCROLL_SPEED +#define NK_CONSOLE_MESSAGE_SCROLL_SPEED NK_CONSOLE_MARQUEE_SCROLL_SPEED +#endif +#ifndef NK_CONSOLE_MESSAGE_SCROLL_PAUSE +#define NK_CONSOLE_MESSAGE_SCROLL_PAUSE NK_CONSOLE_MARQUEE_SCROLL_PAUSE +#endif + NK_API void nk_console_message_render(nk_console* console, nk_console_message* message) { if (message == NULL || console->data == NULL) { return; @@ -107,13 +114,13 @@ NK_API void nk_console_message_render(nk_console* console, nk_console_message* m } } - // Display the tooltip where the mocked mouse is. - struct nk_vec2 zero = {0, 0}; - if (nk_tooltip_begin_offset(ctx, bounds.w - ctx->style.window.border, NK_TOP_LEFT, zero)) { - nk_layout_row_dynamic(ctx, text_height, 1); - nk_label(ctx, message->text, NK_TEXT_LEFT); - nk_tooltip_end(ctx); - } + float tooltip_width = bounds.w - ctx->style.window.border; + int text_len = nk_strlen(message->text); + float full_text_width = ctx->style.font->width(ctx->style.font->userdata, ctx->style.font->height, message->text, text_len); + nk_console_tooltip_render_marquee(ctx, message->text, text_len, full_text_width, + tooltip_width, text_height, + NK_CONSOLE_MESSAGE_SCROLL_SPEED, NK_CONSOLE_MESSAGE_SCROLL_PAUSE, + &message->scroll_x); // Restore the mouse x/y positions. ctx->input.mouse.pos = mouse_pos;