From 749ecc5cd1c614fddcf039a6aa4ab1fe79bc9c57 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Fri, 27 Mar 2026 21:48:28 -0400 Subject: [PATCH 01/12] Use List View for File and Directory --- nuklear_console_file.h | 261 +++++++++++++++++++++++++++++++++++------ 1 file changed, 223 insertions(+), 38 deletions(-) diff --git a/nuklear_console_file.h b/nuklear_console_file.h index faa1a30..c41a756 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -9,6 +9,14 @@ #endif #endif // NK_CONSOLE_FILE_PATH_MAX +/** + * A single file or directory entry stored for the file widget's list view. + */ +typedef struct nk_console_file_entry { + char* label; /** The basename of the file or directory. */ + nk_bool is_directory; /** True if this entry is a directory. */ +} nk_console_file_entry; + /** * Custom data for the file widget. */ @@ -19,6 +27,9 @@ typedef struct nk_console_file_data { char directory[NK_CONSOLE_FILE_PATH_MAX]; /** When selecting a file, this is the current directory. */ void* file_user_data; /** Custom user data for the file system. */ nk_bool select_directory; /** Flag indicating if we are selecting a directory. */ + nk_console_file_entry* entries; /** Array of file/directory entries for the list view. */ + int entry_count; /** Number of valid entries. */ + int entry_capacity; /** Allocated capacity of the entries array. */ } nk_console_file_data; #if defined(__cplusplus) @@ -74,7 +85,7 @@ NK_API void nk_console_file_set_file_user_data(nk_console* file, void* user_data NK_API void* nk_console_file_get_file_user_data(nk_console* file); /** - * Add a individual file or directory to the given file widget as a child. + * Add an individual file or directory entry to the given file widget's list view. * * This should be called from the file system callbacks. See `nuklear_console_file_system.h` for examples. * @@ -82,7 +93,7 @@ NK_API void* nk_console_file_get_file_user_data(nk_console* file); * @param path The path to the file or directory. * @param is_directory True if the path is a directory. False otherwise. * - * @return The new button if it was successfully added, NULL otherwise. + * @return Non-NULL if the entry was successfully added, NULL otherwise. * * @see nk_console_file_destroy_tinydir() * @see nk_console_file_add_files_raylib() @@ -122,7 +133,6 @@ static const char* nk_console_file_basename(const char* path) { return NULL; } - // TODO: Ensure UTF-8 compatibility. int len = nk_strlen(path); for (int i = len - 1; i > 0; i--) { if (path[i] == '\\' || path[i] == '/') { @@ -196,18 +206,160 @@ static nk_console* nk_console_file_button_get_file_widget(nk_console* button) { } /** - * Free the individual file entry buttons. This clears the label. + * Free all file entries stored in the file data, resetting the count. */ -NK_API void nk_console_file_free_entry(nk_console* button, void* user_data) { +static void nk_console_file_entries_clear(nk_console_file_data* data) { + if (data == NULL || data->entries == NULL) { + return; + } + for (int i = 0; i < data->entry_count; i++) { + if (data->entries[i].label != NULL) { + nk_console_mfree(nk_handle_id(0), data->entries[i].label); + data->entries[i].label = NULL; + } + } + data->entry_count = 0; +} + +/** + * Destroy handler for the file widget — frees the entries array. + */ +static void nk_console_file_destroy(nk_console* file, void* user_data) { NK_UNUSED(user_data); - if (button == NULL) { + if (file == NULL || file->data == NULL) { + return; + } + nk_console_file_data* data = (nk_console_file_data*)file->data; + nk_console_file_entries_clear(data); + if (data->entries != NULL) { + nk_console_mfree(nk_handle_id(0), data->entries); + data->entries = NULL; + data->entry_capacity = 0; + } +} + +/** + * get_label callback for the file list view. Appends "/" to directory entries. + */ +NK_API const char* nk_console_file_list_view_get_label(struct nk_console* list_view, nk_uint index) { + static char dir_buf[NK_CONSOLE_FILE_PATH_MAX + 2]; + nk_console* file = nk_console_file_button_get_file_widget(list_view); + if (file == NULL || file->data == NULL) { + return NULL; + } + nk_console_file_data* data = (nk_console_file_data*)file->data; + if ((int)index >= data->entry_count) { + return NULL; + } + nk_console_file_entry* entry = &data->entries[index]; + if (entry->is_directory) { + nk_size len = (nk_size)nk_strlen(entry->label); + if (len + 2 > sizeof(dir_buf)) { + return entry->label; + } + NK_MEMCPY(dir_buf, entry->label, len); + dir_buf[len] = '/'; + dir_buf[len + 1] = '\0'; + return dir_buf; + } + return entry->label; +} + +/** + * Click handler for the file list view. Navigates into directories or selects files. + */ +NK_API void nk_console_file_list_view_onclick(nk_console* list_view, void* user_data) { + NK_UNUSED(user_data); + nk_console* file = nk_console_file_button_get_file_widget(list_view); + if (file == NULL || file->data == NULL) { + return; + } + nk_console_file_data* data = (nk_console_file_data*)file->data; + + nk_console_list_view_data* lv_data = (nk_console_list_view_data*)list_view->data; + nk_uint selected = lv_data->selected; + if ((int)selected >= data->entry_count) { + return; + } + + nk_console_file_entry* entry = &data->entries[selected]; + int len = nk_strlen(data->directory); + int entry_len = nk_strlen(entry->label); + + // Check that the resulting path fits in the directory buffer. + int dir_len_after_slash = (len == 1 && data->directory[0] == '.') ? 0 : (len > 0 ? len + 1 : 0); + if (dir_len_after_slash + entry_len + 1 > NK_CONSOLE_FILE_PATH_MAX) { + NK_ASSERT(0); // Path too long + nk_console_show_message(file, "Error: File path is too long."); + return; + } + + // Append a slash if the directory is not empty. + if (len == 1 && data->directory[0] == '.') { + len = 0; + data->directory[0] = '\0'; + } + else if (len > 0) { +// TODO: file: Make sure this is cross-platform. +#if defined(_WIN32) || defined(WIN32) + data->directory[len] = '\\'; +#else + data->directory[len] = '/'; +#endif + data->directory[len + 1] = '\0'; + len++; + } + + // Concatenate the entry label to the directory. + // TODO: file: Resolve the path properly, so the paths don't recurse. For example: folder/../folder + NK_MEMCPY(data->directory + len, entry->label, (nk_size)(entry_len + 1)); + + if (entry->is_directory) { + // Navigate into the directory. + nk_console_set_active_parent(file); + nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_refresh); + } + else { + // Copy the path to the file buffer. + int desired_length = nk_strlen(data->directory); + if (desired_length >= data->file_path_buffer_size) { + NK_ASSERT(0); // File path is too long + nk_console_show_message(file, "Error: File path is too long."); + } + else { + NK_MEMCPY(data->file_path_buffer, data->directory, (nk_size)desired_length); + data->file_path_buffer[desired_length] = '\0'; + nk_console_trigger_event(file, NK_CONSOLE_EVENT_CHANGED); + } + + // Exit the file browser. + nk_console_set_active_parent(file->parent); + } +} + +/** + * Click handler for the "select this directory" button in directory-selection mode. + */ +static void nk_console_file_select_dir_onclick(nk_console* button, void* user_data) { + NK_UNUSED(user_data); + nk_console* file = nk_console_file_button_get_file_widget(button); + if (file == NULL || file->data == NULL) { return; } + nk_console_file_data* data = (nk_console_file_data*)file->data; - if (button->label != NULL) { - nk_console_mfree(nk_handle_id(0), (void*)button->label); - button->label = NULL; + int desired_length = nk_strlen(data->directory); + if (desired_length >= data->file_path_buffer_size) { + NK_ASSERT(0); // Directory path is too long + nk_console_show_message(file, "Error: Directory path is too long."); + } + else { + NK_MEMCPY(data->file_path_buffer, data->directory, (nk_size)desired_length); + data->file_path_buffer[desired_length] = '\0'; + nk_console_trigger_event(file, NK_CONSOLE_EVENT_CHANGED); } + + nk_console_set_active_parent(file->parent); } NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { @@ -223,6 +375,15 @@ NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { nk_console_file_data* data = (nk_console_file_data*)file->data; int len = nk_strlen(data->directory); + int label_len = nk_strlen(button->label); + + // Check that the resulting path fits in the directory buffer. + int dir_len_after_slash = (len == 1 && data->directory[0] == '.') ? 0 : (len > 0 ? len + 1 : 0); + if (dir_len_after_slash + label_len + 1 > NK_CONSOLE_FILE_PATH_MAX) { + NK_ASSERT(0); // Path too long + nk_console_show_message(file, "Error: File path is too long."); + return; + } // Append a slash if the directory is not empty. if (len == 1 && data->directory[0] == '.') { @@ -242,8 +403,7 @@ NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { // Concatenate the button label to the directory. // TODO: file: Resolve the path properly, so the paths don't recurse. For example: folder/../folder - // TODO: file: Add UTF-8 support. - NK_MEMCPY(data->directory + len, (void*)button->label, (nk_size)(nk_strlen(button->label) + 1)); + NK_MEMCPY(data->directory + len, (void*)button->label, (nk_size)(label_len + 1)); enum nk_symbol_type symbol = nk_console_button_get_symbol(button); switch (symbol) { // Directory @@ -255,7 +415,6 @@ NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { default: // File { // Copy the string to the file buffer. - // TODO: Ensure UTF-8 compatibility. int desired_length = nk_strlen(data->directory); if (desired_length >= data->file_path_buffer_size) { NK_ASSERT(0); // File path is too long @@ -276,12 +435,18 @@ NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { } NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* path, nk_bool is_directory) { - if (parent == NULL || path == NULL || path[0] == '\0' || parent->data == NULL) { + if (parent == NULL || path == NULL || path[0] == '\0') { + return NULL; + } + + nk_console* file = nk_console_file_button_get_file_widget(parent); + if (file == NULL || file->data == NULL) { return NULL; } + nk_console_file_data* data = (nk_console_file_data*)file->data; + // Are we only selecting directories? - nk_console_file_data* data = (nk_console_file_data*)nk_console_file_button_get_file_widget(parent)->data; if (is_directory == nk_false && data->select_directory == nk_true) { return NULL; } @@ -297,30 +462,38 @@ NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* pat return NULL; } - // Add the button. - nk_console* button = nk_console_button(parent, NULL); - - // Copy the path for the label, and register an event to destroy it. - // TODO: file: Ensure UTF-8 compatibility. - button->label = (const char*)NK_CONSOLE_MALLOC(nk_handle_id(0), NULL, (nk_size)(sizeof(char)) * (nk_size)(len + 1)); - nk_console_add_event(button, NK_CONSOLE_EVENT_DESTROYED, &nk_console_file_free_entry); - - char* label = (char*)button->label; + // Grow the entries array if needed. + if (data->entry_count >= data->entry_capacity) { + int new_capacity = data->entry_capacity == 0 ? 16 : data->entry_capacity * 2; + nk_console_file_entry* new_entries = (nk_console_file_entry*)NK_CONSOLE_MALLOC( + nk_handle_id(0), NULL, (nk_size)(sizeof(nk_console_file_entry) * (nk_size)new_capacity)); + if (new_entries == NULL) { + return NULL; + } + if (data->entries != NULL) { + NK_MEMCPY(new_entries, data->entries, (nk_size)(sizeof(nk_console_file_entry) * (nk_size)data->entry_count)); + nk_console_mfree(nk_handle_id(0), data->entries); + } + data->entries = new_entries; + data->entry_capacity = new_capacity; + } - // Use the base name as the label. + // Copy the basename as the entry label. const char* basename = nk_console_file_basename(path); nk_size basename_len = (nk_size)nk_strlen(basename); + char* label = (char*)NK_CONSOLE_MALLOC(nk_handle_id(0), NULL, basename_len + 1); + if (label == NULL) { + return NULL; + } NK_MEMCPY(label, basename, basename_len); label[basename_len] = '\0'; - // Symbol - if (is_directory == nk_true) { - nk_console_button_set_symbol(button, NK_SYMBOL_TRIANGLE_RIGHT); - } + data->entries[data->entry_count].label = label; + data->entries[data->entry_count].is_directory = is_directory; + data->entry_count++; - // Event - nk_console_add_event(button, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_entry_onclick); - return button; + // Return the file widget as a non-NULL success indicator. + return file; } @@ -341,7 +514,7 @@ static int nk_console_file_get_directory_len(const char* file_path) { } /** - * Fills the files array with the files in the current directory. + * Fills the list view with the files in the current directory. */ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { NK_UNUSED(user_data); @@ -352,24 +525,28 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { nk_console_file_data* data = (nk_console_file_data*)widget->data; - // Clear out all the current entries. + // Clear existing entries before destroying children. + nk_console_file_entries_clear(data); + + // Clear out all the current children. nk_console_free_children(widget); // Add the back/cancel button nk_console* cancelButton = nk_console_button_onclick(widget, "Cancel", &nk_console_button_back); nk_console_button_set_symbol(cancelButton, NK_SYMBOL_X); - // Show the Active directory. + // Show the active directory. if (!data->select_directory) { // Active directory label nk_console* activeLabel = nk_console_label(widget, data->directory); activeLabel->alignment = NK_TEXT_CENTERED; } else { - // Add the select directory button. - nk_console* button = nk_console_file_add_entry(widget, data->directory, nk_true); + // Add a button to select the current directory. + nk_console* button = nk_console_button(widget, data->directory); nk_console_button_set_symbol(button, NK_SYMBOL_CIRCLE_SOLID); nk_console_set_tooltip(button, "Use this directory"); + nk_console_add_event(button, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_select_dir_onclick); } // Add the parent directory button @@ -379,8 +556,15 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { nk_console_set_active_widget(parent_directory_button); #ifdef NK_CONSOLE_FILE_ADD_FILES - // Iterate through the files in the directory, and add them as entries. - if (NK_CONSOLE_FILE_ADD_FILES(widget, data->directory) == nk_false) { + // Populate the entries array via the file system callback. + NK_CONSOLE_FILE_ADD_FILES(widget, data->directory); + + if (data->entry_count > 0) { + // Create a list view for the file entries. + nk_console* list_view = nk_console_list_view(widget, "file_entries", 10, (nk_uint)data->entry_count, &nk_console_file_list_view_get_label); + nk_console_add_event(list_view, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_list_view_onclick); + } + else { nk_console_label(widget, "No files found.")->alignment = NK_TEXT_CENTERED; } #else @@ -464,6 +648,7 @@ NK_API nk_console* nk_console_file(nk_console* parent, const char* label, char* widget->data = data; nk_console_add_event(widget, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_main_click); + nk_console_add_event(widget, NK_CONSOLE_EVENT_DESTROYED, &nk_console_file_destroy); return widget; } From a8fd2dd585abd9ab1276c9f4a5745272e39f4928 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Fri, 27 Mar 2026 22:08:42 -0400 Subject: [PATCH 02/12] Fixes --- nuklear_console.h | 1 + nuklear_console_progress.h | 2 +- nuklear_console_property.h | 14 ++++++++++++-- nuklear_console_textedit.h | 14 ++++++++------ nuklear_console_textedit_text.h | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/nuklear_console.h b/nuklear_console.h index ec727c7..02920fe 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -666,6 +666,7 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) // Back else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_B)) { if (nk_console_active_parent(top) == NULL) { + data->input_processed = nk_true; return; } diff --git a/nuklear_console_progress.h b/nuklear_console_progress.h index dbb708e..05f1f49 100644 --- a/nuklear_console_progress.h +++ b/nuklear_console_progress.h @@ -109,7 +109,7 @@ NK_API struct nk_rect nk_console_progress_render(nk_console* console) { console->ctx->style.progress.cursor_normal = cursor_active; } else { - console->ctx->style.progress.cursor_normal = cursor_active; + console->ctx->style.progress.cursor_normal = cursor_hover; } } diff --git a/nuklear_console_property.h b/nuklear_console_property.h index 7748ed1..64f75b7 100644 --- a/nuklear_console_property.h +++ b/nuklear_console_property.h @@ -171,16 +171,26 @@ NK_API struct nk_rect nk_console_property_render(nk_console* console) { switch (console->type) { case NK_CONSOLE_PROPERTY_INT: { char name[NK_MAX_NUMBER_BUFFER]; - NK_MEMCPY(name + 2, console->label, (nk_size)(nk_strlen(console->label) + 1)); + int label_len = nk_strlen(console->label); + if (label_len > NK_MAX_NUMBER_BUFFER - 3) { + label_len = NK_MAX_NUMBER_BUFFER - 3; + } name[0] = '#'; name[1] = '#'; + NK_MEMCPY(name + 2, console->label, (nk_size)label_len); + name[label_len + 2] = '\0'; nk_property_int(console->ctx, name, data->min_int, data->val_int, data->max_int, data->step_int, data->inc_per_pixel); } break; case NK_CONSOLE_PROPERTY_FLOAT: { char name[NK_MAX_NUMBER_BUFFER]; - NK_MEMCPY(name + 2, console->label, (nk_size)(nk_strlen(console->label) + 1)); + int label_len = nk_strlen(console->label); + if (label_len > NK_MAX_NUMBER_BUFFER - 3) { + label_len = NK_MAX_NUMBER_BUFFER - 3; + } name[0] = '#'; name[1] = '#'; + NK_MEMCPY(name + 2, console->label, (nk_size)label_len); + name[label_len + 2] = '\0'; nk_property_float(console->ctx, name, data->min_float, data->val_float, data->max_float, data->step_float, data->inc_per_pixel); } break; case NK_CONSOLE_SLIDER_INT: diff --git a/nuklear_console_textedit.h b/nuklear_console_textedit.h index 4f00a44..7254ad3 100644 --- a/nuklear_console_textedit.h +++ b/nuklear_console_textedit.h @@ -218,7 +218,9 @@ NK_API void nk_console_textedit_key_click(nk_console* key, void* user_data) { case NK_SYMBOL_TRIANGLE_LEFT: { int len = nk_strlen(data->buffer); if (len > 0) { - data->buffer[len - 1] = '\0'; + // Walk back past UTF-8 continuation bytes (0x80-0xBF) to erase the full codepoint. + do { len--; } while (len > 0 && ((unsigned char)data->buffer[len] & 0xC0) == 0x80); + data->buffer[len] = '\0'; nk_console_trigger_event(textedit, NK_CONSOLE_EVENT_CHANGED); } } break; @@ -235,11 +237,11 @@ NK_API void nk_console_textedit_key_click(nk_console* key, void* user_data) { // Any key character case NK_SYMBOL_NONE: { - // Add the character to the buffer. + // Append all bytes of the key label to support multi-byte UTF-8 glyphs. int len = nk_strlen(data->buffer); - if (len < data->buffer_size - 1) { - data->buffer[len] = key->label[0]; - data->buffer[len + 1] = '\0'; + int key_len = nk_strlen(key->label); + if (len + key_len < data->buffer_size) { + NK_MEMCPY(data->buffer + len, key->label, (nk_size)(key_len + 1)); nk_console_trigger_event(textedit, NK_CONSOLE_EVENT_CHANGED); } } break; @@ -270,7 +272,7 @@ NK_API void nk_console_textedit_button_main_click(nk_console* button, void* user // Create the textedit_text widget, which is the input box. nk_console_textedit_text(button); - // TODO: Add option for UTF-8 keys with nk_glyph. + // TODO: Add non-ASCII keyboard layouts (e.g. accented characters, CJK) using nk_glyph key labels. // First row: 1 - 0 nk_console* row = nk_console_row_begin(button); diff --git a/nuklear_console_textedit_text.h b/nuklear_console_textedit_text.h index 54a2a04..50d989e 100644 --- a/nuklear_console_textedit_text.h +++ b/nuklear_console_textedit_text.h @@ -85,7 +85,7 @@ NK_API struct nk_rect nk_console_textedit_text_render(nk_console* widget) { int mask_strlen = (int)nk_strlen(mask); if (mask_strlen < buffer_strlen) { // Characters deleted int real_len = (int)nk_strlen(data->buffer); - int new_real_len = real_len - buffer_strlen - mask_strlen; + int new_real_len = real_len - (buffer_strlen - mask_strlen); if (new_real_len < 0) { new_real_len = 0; } From 48af0e34b7e9c7f1fe2698cc1cee29b54983635e Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 02:02:37 -0400 Subject: [PATCH 03/12] Update --- nuklear_console.h | 6 + nuklear_console_file.h | 208 ++++++++++++++-------------------- nuklear_console_file_system.h | 20 ++-- nuklear_console_list_view.h | 11 +- 4 files changed, 111 insertions(+), 134 deletions(-) diff --git a/nuklear_console.h b/nuklear_console.h index 02920fe..4b65d11 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -1184,6 +1184,12 @@ NK_API void nk_console_navigate_back(nk_console* leaving_parent) { } nk_console* destination = (leaving_parent->parent != NULL) ? leaving_parent->parent : top; nk_console_set_active_parent(destination); + nk_console_set_active_widget(leaving_parent); + nk_console_top_data* data = (nk_console_top_data*)top->data; + if (data != NULL) { + data->scroll_requested = nk_true; + data->input_processed = nk_true; + } nk_console_trigger_event(leaving_parent, NK_CONSOLE_EVENT_BACK); } diff --git a/nuklear_console_file.h b/nuklear_console_file.h index c41a756..def36e4 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -27,6 +27,7 @@ typedef struct nk_console_file_data { char directory[NK_CONSOLE_FILE_PATH_MAX]; /** When selecting a file, this is the current directory. */ void* file_user_data; /** Custom user data for the file system. */ nk_bool select_directory; /** Flag indicating if we are selecting a directory. */ + char dir_label_buf[NK_CONSOLE_FILE_PATH_MAX + 2]; /** Scratch buffer for appending "/" to directory labels in the list view. */ nk_console_file_entry* entries; /** Array of file/directory entries for the list view. */ int entry_count; /** Number of valid entries. */ int entry_capacity; /** Allocated capacity of the entries array. */ @@ -52,8 +53,8 @@ NK_API nk_console* nk_console_file(nk_console* parent, const char* label, char* * Creates a directory widget that allows the user to select a directory. * * @param parent The parent widget. - * @param label The label for the file widget. For example: "Select a file". - * @param dir_buffer The buffer to store the file path. + * @param label The label for the file widget. For example: "Select a directory". + * @param dir_buffer The buffer to store the directory path. * @param dir_buffer_size The size of the buffer. * * @return The new file widget. @@ -95,7 +96,7 @@ NK_API void* nk_console_file_get_file_user_data(nk_console* file); * * @return Non-NULL if the entry was successfully added, NULL otherwise. * - * @see nk_console_file_destroy_tinydir() + * @see nk_console_file_add_files_tinydir() * @see nk_console_file_add_files_raylib() */ NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* path, nk_bool is_directory); @@ -241,60 +242,47 @@ static void nk_console_file_destroy(nk_console* file, void* user_data) { /** * get_label callback for the file list view. Appends "/" to directory entries. */ -NK_API const char* nk_console_file_list_view_get_label(struct nk_console* list_view, nk_uint index) { - static char dir_buf[NK_CONSOLE_FILE_PATH_MAX + 2]; +static const char* nk_console_file_list_view_get_label(struct nk_console* list_view, nk_uint index) { nk_console* file = nk_console_file_button_get_file_widget(list_view); if (file == NULL || file->data == NULL) { return NULL; } nk_console_file_data* data = (nk_console_file_data*)file->data; + if (data->entry_count == 0) { + return "(Empty directory)"; + } if ((int)index >= data->entry_count) { return NULL; } nk_console_file_entry* entry = &data->entries[index]; if (entry->is_directory) { nk_size len = (nk_size)nk_strlen(entry->label); - if (len + 2 > sizeof(dir_buf)) { + if (len + 2 > sizeof(data->dir_label_buf)) { return entry->label; } - NK_MEMCPY(dir_buf, entry->label, len); - dir_buf[len] = '/'; - dir_buf[len + 1] = '\0'; - return dir_buf; + NK_MEMCPY(data->dir_label_buf, entry->label, len); + data->dir_label_buf[len] = '/'; + data->dir_label_buf[len + 1] = '\0'; + return data->dir_label_buf; } return entry->label; } /** - * Click handler for the file list view. Navigates into directories or selects files. + * Appends a path component to data->directory, inserting the platform separator. + * Returns nk_false and shows an error if the result would overflow the buffer. */ -NK_API void nk_console_file_list_view_onclick(nk_console* list_view, void* user_data) { - NK_UNUSED(user_data); - nk_console* file = nk_console_file_button_get_file_widget(list_view); - if (file == NULL || file->data == NULL) { - return; - } - nk_console_file_data* data = (nk_console_file_data*)file->data; - - nk_console_list_view_data* lv_data = (nk_console_list_view_data*)list_view->data; - nk_uint selected = lv_data->selected; - if ((int)selected >= data->entry_count) { - return; - } - - nk_console_file_entry* entry = &data->entries[selected]; +static nk_bool nk_console_file_append_to_directory(nk_console_file_data* data, nk_console* file, const char* label) { int len = nk_strlen(data->directory); - int entry_len = nk_strlen(entry->label); + int label_len = nk_strlen(label); - // Check that the resulting path fits in the directory buffer. int dir_len_after_slash = (len == 1 && data->directory[0] == '.') ? 0 : (len > 0 ? len + 1 : 0); - if (dir_len_after_slash + entry_len + 1 > NK_CONSOLE_FILE_PATH_MAX) { + if (dir_len_after_slash + label_len + 1 > NK_CONSOLE_FILE_PATH_MAX) { NK_ASSERT(0); // Path too long nk_console_show_message(file, "Error: File path is too long."); - return; + return nk_false; } - // Append a slash if the directory is not empty. if (len == 1 && data->directory[0] == '.') { len = 0; data->directory[0] = '\0'; @@ -310,9 +298,32 @@ NK_API void nk_console_file_list_view_onclick(nk_console* list_view, void* user_ len++; } - // Concatenate the entry label to the directory. // TODO: file: Resolve the path properly, so the paths don't recurse. For example: folder/../folder - NK_MEMCPY(data->directory + len, entry->label, (nk_size)(entry_len + 1)); + NK_MEMCPY(data->directory + len, label, (nk_size)(label_len + 1)); + return nk_true; +} + +/** + * Click handler for the file list view. Navigates into directories or selects files. + */ +static void nk_console_file_list_view_onclick(nk_console* list_view, void* user_data) { + NK_UNUSED(user_data); + nk_console* file = nk_console_file_button_get_file_widget(list_view); + if (file == NULL || file->data == NULL) { + return; + } + nk_console_file_data* data = (nk_console_file_data*)file->data; + + nk_console_list_view_data* lv_data = (nk_console_list_view_data*)list_view->data; + nk_uint selected = lv_data->selected; + if ((int)selected >= data->entry_count) { + return; + } + + nk_console_file_entry* entry = &data->entries[selected]; + if (!nk_console_file_append_to_directory(data, file, entry->label)) { + return; + } if (entry->is_directory) { // Navigate into the directory. @@ -333,7 +344,7 @@ NK_API void nk_console_file_list_view_onclick(nk_console* list_view, void* user_ } // Exit the file browser. - nk_console_set_active_parent(file->parent); + nk_console_navigate_back(file); } } @@ -359,10 +370,11 @@ static void nk_console_file_select_dir_onclick(nk_console* button, void* user_da nk_console_trigger_event(file, NK_CONSOLE_EVENT_CHANGED); } - nk_console_set_active_parent(file->parent); + // Exit the file browser. + nk_console_navigate_back(file); } -NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { +static void nk_console_file_entry_onclick(nk_console* button, void* user_data) { NK_UNUSED(user_data); if (button == NULL || button->label == NULL) { return; @@ -374,64 +386,13 @@ NK_API void nk_console_file_entry_onclick(nk_console* button, void* user_data) { } nk_console_file_data* data = (nk_console_file_data*)file->data; - int len = nk_strlen(data->directory); - int label_len = nk_strlen(button->label); - - // Check that the resulting path fits in the directory buffer. - int dir_len_after_slash = (len == 1 && data->directory[0] == '.') ? 0 : (len > 0 ? len + 1 : 0); - if (dir_len_after_slash + label_len + 1 > NK_CONSOLE_FILE_PATH_MAX) { - NK_ASSERT(0); // Path too long - nk_console_show_message(file, "Error: File path is too long."); + if (!nk_console_file_append_to_directory(data, file, button->label)) { return; } - // Append a slash if the directory is not empty. - if (len == 1 && data->directory[0] == '.') { - len = 0; - data->directory[0] = '\0'; - } - else if (len > 0) { -// TODO: file: Make sure this is cross-platform. -#if defined(_WIN32) || defined(WIN32) - data->directory[len] = '\\'; -#else - data->directory[len] = '/'; -#endif - data->directory[len + 1] = '\0'; - len++; - } - - // Concatenate the button label to the directory. - // TODO: file: Resolve the path properly, so the paths don't recurse. For example: folder/../folder - NK_MEMCPY(data->directory + len, (void*)button->label, (nk_size)(label_len + 1)); - - enum nk_symbol_type symbol = nk_console_button_get_symbol(button); - switch (symbol) { // Directory - case NK_SYMBOL_TRIANGLE_LEFT: // Back - case NK_SYMBOL_TRIANGLE_RIGHT: // Folder - nk_console_set_active_parent(file); - nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_refresh); - break; - default: // File - { - // Copy the string to the file buffer. - int desired_length = nk_strlen(data->directory); - if (desired_length >= data->file_path_buffer_size) { - NK_ASSERT(0); // File path is too long - nk_console_show_message(file, "Error: File path is too long."); - } - else { - NK_MEMCPY(data->file_path_buffer, data->directory, (nk_size)desired_length); - data->file_path_buffer[desired_length] = '\0'; - - // Trigger the onchange event and exit. - nk_console_trigger_event(file, NK_CONSOLE_EVENT_CHANGED); - } - - // Now that we selected a file, we can exit. - nk_console_set_active_parent(file->parent); - } break; - } + // Navigate into the directory (or back to parent via ".."). + nk_console_set_active_parent(file); + nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_refresh); } NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* path, nk_bool is_directory) { @@ -525,47 +486,54 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { nk_console_file_data* data = (nk_console_file_data*)widget->data; - // Clear existing entries before destroying children. + // Clear existing entries. nk_console_file_entries_clear(data); - // Clear out all the current children. - nk_console_free_children(widget); - - // Add the back/cancel button - nk_console* cancelButton = nk_console_button_onclick(widget, "Cancel", &nk_console_button_back); - nk_console_button_set_symbol(cancelButton, NK_SYMBOL_X); + // Build the static children (cancel, directory label, parent dir button) on the first call only. + // children[1]'s label points directly to data->directory, so it auto-updates on subsequent calls. + if (widget->children == NULL || cvector_size(widget->children) == 0) { + // Add the back/cancel button + nk_console* cancelButton = nk_console_button_onclick(widget, "Cancel", &nk_console_button_back); + nk_console_button_set_symbol(cancelButton, NK_SYMBOL_X); + + // Show the active directory. + if (!data->select_directory) { + // Active directory label + nk_console* activeLabel = nk_console_label(widget, data->directory); + activeLabel->alignment = NK_TEXT_CENTERED; + } + else { + // Add a button to select the current directory. + nk_console* button = nk_console_button(widget, data->directory); + nk_console_button_set_symbol(button, NK_SYMBOL_CIRCLE_SOLID); + nk_console_set_tooltip(button, "Use this directory"); + nk_console_add_event(button, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_select_dir_onclick); + } - // Show the active directory. - if (!data->select_directory) { - // Active directory label - nk_console* activeLabel = nk_console_label(widget, data->directory); - activeLabel->alignment = NK_TEXT_CENTERED; - } - else { - // Add a button to select the current directory. - nk_console* button = nk_console_button(widget, data->directory); - nk_console_button_set_symbol(button, NK_SYMBOL_CIRCLE_SOLID); - nk_console_set_tooltip(button, "Use this directory"); - nk_console_add_event(button, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_select_dir_onclick); + // Add the parent directory button + nk_console* parent_directory_button = nk_console_button_onclick(widget, "..", &nk_console_file_entry_onclick); + nk_console_button_set_symbol(parent_directory_button, NK_SYMBOL_TRIANGLE_LEFT); + nk_console_set_tooltip(parent_directory_button, "Navigate to the parent directory"); } - // Add the parent directory button - nk_console* parent_directory_button = nk_console_button_onclick(widget, "..", &nk_console_file_entry_onclick); - nk_console_button_set_symbol(parent_directory_button, NK_SYMBOL_TRIANGLE_LEFT); - nk_console_set_tooltip(parent_directory_button, "Navigate to the parent directory"); - nk_console_set_active_widget(parent_directory_button); + // Focus the parent directory button (always children[2]). + NK_ASSERT(cvector_size(widget->children) > 2 && widget->children[2] != NULL); + nk_console_set_active_widget(widget->children[2]); #ifdef NK_CONSOLE_FILE_ADD_FILES // Populate the entries array via the file system callback. NK_CONSOLE_FILE_ADD_FILES(widget, data->directory); - if (data->entry_count > 0) { - // Create a list view for the file entries. - nk_console* list_view = nk_console_list_view(widget, "file_entries", 10, (nk_uint)data->entry_count, &nk_console_file_list_view_get_label); - nk_console_add_event(list_view, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_list_view_onclick); + // Show at least 1 item so the get_label callback can display "(Empty directory)". + nk_uint display_count = data->entry_count == 0 ? 1 : (nk_uint)data->entry_count; + if (cvector_size(widget->children) >= 4 && widget->children[3]->type == NK_CONSOLE_LIST_VIEW) { + // Update the existing list view in place. + nk_console_list_view_set_item_count(widget->children[3], display_count); } else { - nk_console_label(widget, "No files found.")->alignment = NK_TEXT_CENTERED; + // Create the list view for the first time. + nk_console* list_view = nk_console_list_view(widget, "file_entries", 10, display_count, &nk_console_file_list_view_get_label); + nk_console_add_event(list_view, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_list_view_onclick); } #else // NK_CONSOLE_FILE_ADD_FILES is undefined, so back out. diff --git a/nuklear_console_file_system.h b/nuklear_console_file_system.h index 55a3e86..aa02782 100644 --- a/nuklear_console_file_system.h +++ b/nuklear_console_file_system.h @@ -110,19 +110,15 @@ static nk_bool nk_console_file_add_files_raylib(nk_console* console, const char* nk_bool result = nk_false; - // Directories - for (int i = 0; i < filePathList.count; i++) { - if (DirectoryExists(filePathList.paths[i])) { - if (nk_console_file_add_entry(console, filePathList.paths[i], nk_true) != NULL) { - result = nk_true; + // Directories first, then files (two passes to group them). + for (int pass = 0; pass < 2; pass++) { + nk_bool want_dir = (pass == 0) ? nk_true : nk_false; + for (int i = 0; i < (int)filePathList.count; i++) { + nk_bool is_dir = DirectoryExists(filePathList.paths[i]) ? nk_true : nk_false; + if (is_dir != want_dir) { + continue; } - } - } - - // Files - for (int i = 0; i < filePathList.count; i++) { - if (FileExists(filePathList.paths[i]) && !DirectoryExists(filePathList.paths[i])) { - if (nk_console_file_add_entry(console, filePathList.paths[i], nk_false) != NULL) { + if (nk_console_file_add_entry(console, filePathList.paths[i], is_dir) != NULL) { result = nk_true; } } diff --git a/nuklear_console_list_view.h b/nuklear_console_list_view.h index 93b1c4f..fc8b452 100644 --- a/nuklear_console_list_view.h +++ b/nuklear_console_list_view.h @@ -79,6 +79,9 @@ NK_API nk_flags nk_console_list_view_flags(nk_console* list_view); */ NK_API void nk_console_list_view_set_flags(nk_console* list_view, nk_flags flags); +NK_API void nk_console_list_view_set_item_count(nk_console* list_view, nk_uint item_count); +NK_API nk_uint nk_console_list_view_item_count(nk_console* list_view); + #if defined(__cplusplus) } #endif @@ -89,8 +92,7 @@ NK_API void nk_console_list_view_set_flags(nk_console* list_view, nk_flags flags #ifndef NK_CONSOLE_LIST_VIEW_IMPLEMENTATION_ONCE #define NK_CONSOLE_LIST_VIEW_IMPLEMENTATION_ONCE - -NK_API nk_flags nk_console_list_view_item_count(nk_console* list_view) { +NK_API nk_uint nk_console_list_view_item_count(nk_console* list_view) { if (list_view == NULL || list_view->data == NULL || list_view->type != NK_CONSOLE_LIST_VIEW) { return 0; } @@ -104,6 +106,11 @@ NK_API void nk_console_list_view_set_item_count(nk_console* list_view, nk_uint i } nk_console_list_view_data* data = (nk_console_list_view_data*)list_view->data; data->row_count = item_count; + data->selected = 0; + data->_scroll_y = 0; + if (data->view.scroll_pointer) { + *data->view.scroll_pointer = 0; + } } NK_API nk_flags nk_console_list_view_flags(nk_console* list_view) { From 06b146982c08f5f7f2d04fd16ba8dfb9a84fdbfa Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 02:11:12 -0400 Subject: [PATCH 04/12] Update --- nuklear_console_file.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/nuklear_console_file.h b/nuklear_console_file.h index def36e4..ecffdb9 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -547,6 +547,25 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { #endif } +/** + * BACK event handler for the file widget — frees the children so they are rebuilt on next open. + */ +static void nk_console_file_back_free_children(nk_console* file, void* user_data) { + NK_UNUSED(user_data); + nk_console_free_children(file); + + if (file && file->data) { + nk_console* top = nk_console_get_top(file); + nk_console_top_data* data = (nk_console_top_data*)top->data; + data->scroll_requested = nk_true; + } +} + +static void nk_console_file_back(nk_console* file, void* user_data) { + NK_UNUSED(user_data); + nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_back_free_children); +} + /** * Button callback for the main file button. */ @@ -616,6 +635,7 @@ NK_API nk_console* nk_console_file(nk_console* parent, const char* label, char* widget->data = data; nk_console_add_event(widget, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_main_click); + nk_console_add_event(widget, NK_CONSOLE_EVENT_BACK, &nk_console_file_back); nk_console_add_event(widget, NK_CONSOLE_EVENT_DESTROYED, &nk_console_file_destroy); return widget; } From 248a0f86551cd06df7dcef6e3f546a69a71af1c7 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 13:01:56 -0400 Subject: [PATCH 05/12] Update --- nuklear_console_file.h | 51 +++++++++++++----------------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/nuklear_console_file.h b/nuklear_console_file.h index ecffdb9..e000c0c 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -28,9 +28,7 @@ typedef struct nk_console_file_data { void* file_user_data; /** Custom user data for the file system. */ nk_bool select_directory; /** Flag indicating if we are selecting a directory. */ char dir_label_buf[NK_CONSOLE_FILE_PATH_MAX + 2]; /** Scratch buffer for appending "/" to directory labels in the list view. */ - nk_console_file_entry* entries; /** Array of file/directory entries for the list view. */ - int entry_count; /** Number of valid entries. */ - int entry_capacity; /** Allocated capacity of the entries array. */ + nk_console_file_entry* entries; /** cvector of file/directory entries for the list view. */ } nk_console_file_data; #if defined(__cplusplus) @@ -207,19 +205,19 @@ static nk_console* nk_console_file_button_get_file_widget(nk_console* button) { } /** - * Free all file entries stored in the file data, resetting the count. + * Free all file entries stored in the file data. */ static void nk_console_file_entries_clear(nk_console_file_data* data) { - if (data == NULL || data->entries == NULL) { + if (data == NULL) { return; } - for (int i = 0; i < data->entry_count; i++) { + for (size_t i = 0; i < cvector_size(data->entries); i++) { if (data->entries[i].label != NULL) { nk_console_mfree(nk_handle_id(0), data->entries[i].label); data->entries[i].label = NULL; } } - data->entry_count = 0; + cvector_clear(data->entries); } /** @@ -232,11 +230,8 @@ static void nk_console_file_destroy(nk_console* file, void* user_data) { } nk_console_file_data* data = (nk_console_file_data*)file->data; nk_console_file_entries_clear(data); - if (data->entries != NULL) { - nk_console_mfree(nk_handle_id(0), data->entries); - data->entries = NULL; - data->entry_capacity = 0; - } + cvector_free(data->entries); + data->entries = NULL; } /** @@ -248,10 +243,10 @@ static const char* nk_console_file_list_view_get_label(struct nk_console* list_v return NULL; } nk_console_file_data* data = (nk_console_file_data*)file->data; - if (data->entry_count == 0) { + if (cvector_size(data->entries) == 0) { return "(Empty directory)"; } - if ((int)index >= data->entry_count) { + if (index >= cvector_size(data->entries)) { return NULL; } nk_console_file_entry* entry = &data->entries[index]; @@ -316,7 +311,7 @@ static void nk_console_file_list_view_onclick(nk_console* list_view, void* user_ nk_console_list_view_data* lv_data = (nk_console_list_view_data*)list_view->data; nk_uint selected = lv_data->selected; - if ((int)selected >= data->entry_count) { + if (selected >= cvector_size(data->entries)) { return; } @@ -423,22 +418,6 @@ NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* pat return NULL; } - // Grow the entries array if needed. - if (data->entry_count >= data->entry_capacity) { - int new_capacity = data->entry_capacity == 0 ? 16 : data->entry_capacity * 2; - nk_console_file_entry* new_entries = (nk_console_file_entry*)NK_CONSOLE_MALLOC( - nk_handle_id(0), NULL, (nk_size)(sizeof(nk_console_file_entry) * (nk_size)new_capacity)); - if (new_entries == NULL) { - return NULL; - } - if (data->entries != NULL) { - NK_MEMCPY(new_entries, data->entries, (nk_size)(sizeof(nk_console_file_entry) * (nk_size)data->entry_count)); - nk_console_mfree(nk_handle_id(0), data->entries); - } - data->entries = new_entries; - data->entry_capacity = new_capacity; - } - // Copy the basename as the entry label. const char* basename = nk_console_file_basename(path); nk_size basename_len = (nk_size)nk_strlen(basename); @@ -449,15 +428,15 @@ NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* pat NK_MEMCPY(label, basename, basename_len); label[basename_len] = '\0'; - data->entries[data->entry_count].label = label; - data->entries[data->entry_count].is_directory = is_directory; - data->entry_count++; + nk_console_file_entry entry; + entry.label = label; + entry.is_directory = is_directory; + cvector_push_back(data->entries, entry); // Return the file widget as a non-NULL success indicator. return file; } - /** * Gets the length of the directory string of the given file path. */ @@ -525,7 +504,7 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { NK_CONSOLE_FILE_ADD_FILES(widget, data->directory); // Show at least 1 item so the get_label callback can display "(Empty directory)". - nk_uint display_count = data->entry_count == 0 ? 1 : (nk_uint)data->entry_count; + nk_uint display_count = cvector_size(data->entries) == 0 ? 1 : (nk_uint)cvector_size(data->entries); if (cvector_size(widget->children) >= 4 && widget->children[3]->type == NK_CONSOLE_LIST_VIEW) { // Update the existing list view in place. nk_console_list_view_set_item_count(widget->children[3], display_count); From 770c32ba0c970686715a669f24e516923577935c Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 13:07:04 -0400 Subject: [PATCH 06/12] Clean up --- nuklear_console_file.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nuklear_console_file.h b/nuklear_console_file.h index e000c0c..346a344 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -234,6 +234,7 @@ static void nk_console_file_destroy(nk_console* file, void* user_data) { data->entries = NULL; } +#ifdef NK_CONSOLE_FILE_ADD_FILES /** * get_label callback for the file list view. Appends "/" to directory entries. */ @@ -262,6 +263,7 @@ static const char* nk_console_file_list_view_get_label(struct nk_console* list_v } return entry->label; } +#endif /** * Appends a path component to data->directory, inserting the platform separator. @@ -298,6 +300,7 @@ static nk_bool nk_console_file_append_to_directory(nk_console_file_data* data, n return nk_true; } +#ifdef NK_CONSOLE_FILE_ADD_FILES /** * Click handler for the file list view. Navigates into directories or selects files. */ @@ -342,6 +345,7 @@ static void nk_console_file_list_view_onclick(nk_console* list_view, void* user_ nk_console_navigate_back(file); } } +#endif /** * Click handler for the "select this directory" button in directory-selection mode. From 8c987ac5beefe4f5d2ea4a5b9b1947ab121caee6 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 13:38:24 -0400 Subject: [PATCH 07/12] Update scroll --- nuklear_console.h | 63 ++++++++++++++++++++----------------- nuklear_console_file.h | 6 +--- nuklear_console_list_view.h | 9 +++--- nuklear_console_tree.h | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/nuklear_console.h b/nuklear_console.h index 4b65d11..469fd7d 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -109,8 +109,8 @@ typedef struct nk_console { typedef struct nk_console_top_data { nk_console* active_parent; /** The parent that is currently being displayed. */ nk_bool input_processed; /** Whether or not user input has been processed. */ - nk_bool scroll_requested; /** True if we've switched active widget and need to check scrolling */ nk_bool scrollbar_required; /** True when the scrollbar is needed to be rendered. @see nk_console_render_window() */ + nk_console* scroll_to_widget; /** When set by nk_console_navigate_back, the render loop scrolls the window to this widget's bounds. */ /** * Message queue that is to be shown. @@ -565,22 +565,6 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) nk_console* top = nk_console_get_top(widget); nk_console_top_data* data = (nk_console_top_data*)top->data; - // Scroll to the active widget if needed. - if (data->scroll_requested) { - struct nk_rect content_region = nk_window_get_content_region(widget->ctx); - - nk_uint offsetx, offsety; - nk_window_get_scroll(widget->ctx, &offsetx, &offsety); - if (bounds.y + bounds.h > content_region.y + content_region.h + (float)offsety) { - nk_window_set_scroll(widget->ctx, offsetx, (nk_uint)(bounds.y + bounds.h - content_region.y - content_region.h)); - } - else if (bounds.y < content_region.y + (float)offsety) { - nk_window_set_scroll(widget->ctx, offsetx, (nk_uint)(bounds.y - content_region.y)); - } - - data->scroll_requested = nk_false; - } - // Handle hold-to-accelerate timers for up/down navigation. 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); @@ -606,33 +590,37 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) // Page Up if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { int widgetIndex = nk_console_get_widget_index(widget); + nk_console* target = NULL; int count = 0; while (--widgetIndex >= 0) { - nk_console* target = widget->parent->children[widgetIndex]; - if (target != NULL && nk_console_selectable(target)) { - nk_console_set_active_widget(target); - data->scroll_requested = nk_true; + nk_console* t = widget->parent->children[widgetIndex]; + if (t != NULL && nk_console_selectable(t)) { + nk_console_set_active_widget(t); + target = t; if (++count > 4) { break; } } } + if (target != NULL) data->scroll_to_widget = target; data->input_processed = nk_true; } // Page Down else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { int widgetIndex = nk_console_get_widget_index(widget); + nk_console* target = NULL; int count = 0; while (++widgetIndex < (int)cvector_size(widget->parent->children)) { - nk_console* target = widget->parent->children[widgetIndex]; - if (nk_console_selectable(target)) { - nk_console_set_active_widget(target); - data->scroll_requested = nk_true; + nk_console* t = widget->parent->children[widgetIndex]; + if (nk_console_selectable(t)) { + nk_console_set_active_widget(t); + target = t; if (++count > 4) { break; } } } + if (target != NULL) data->scroll_to_widget = target; data->input_processed = nk_true; } // Up @@ -643,7 +631,7 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) nk_console* target = widget->parent->children[widgetIndex]; if (nk_console_selectable(target)) { nk_console_set_active_widget(target); - data->scroll_requested = nk_true; + data->scroll_to_widget = target; break; } } @@ -657,7 +645,7 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) nk_console* target = widget->parent->children[widgetIndex]; if (nk_console_selectable(target)) { nk_console_set_active_widget(target); - data->scroll_requested = nk_true; + data->scroll_to_widget = target; break; } } @@ -672,7 +660,6 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) if (widget->parent != NULL) { nk_console_navigate_back(widget->parent); - data->scroll_requested = nk_true; } data->input_processed = nk_true; @@ -914,6 +901,24 @@ NK_API void nk_console_render(nk_console* console) { // Render the widget and get its bounds. struct nk_rect widget_bounds = console->render != NULL ? console->render(console) : nk_rect(0, 0, 0, 0); + // When nk_console_navigate_back targeted this widget, scroll the window to show it. + if (widget_bounds.w > 0 && widget_bounds.h > 0) { + nk_console* top = nk_console_get_top(console); + nk_console_top_data* top_data = (nk_console_top_data*)top->data; + if (top_data->scroll_to_widget == console) { + struct nk_rect content_region = nk_window_get_content_region(console->ctx); + nk_uint offsetx, offsety; + nk_window_get_scroll(console->ctx, &offsetx, &offsety); + if (widget_bounds.y + widget_bounds.h > content_region.y + content_region.h + (float)offsety) { + nk_window_set_scroll(console->ctx, offsetx, (nk_uint)(widget_bounds.y + widget_bounds.h - content_region.y - content_region.h)); + } + else if (widget_bounds.y < content_region.y + (float)offsety) { + nk_window_set_scroll(console->ctx, offsetx, (nk_uint)(widget_bounds.y - content_region.y)); + } + top_data->scroll_to_widget = NULL; + } + } + // Allow the mouse to switch focus to the widget. if (widget_bounds.w > 0 && widget_bounds.h > 0 && nk_input_is_mouse_moved(&console->ctx->input)) { // Make sure we consider the active scroll position of the window. @@ -1187,7 +1192,7 @@ NK_API void nk_console_navigate_back(nk_console* leaving_parent) { nk_console_set_active_widget(leaving_parent); nk_console_top_data* data = (nk_console_top_data*)top->data; if (data != NULL) { - data->scroll_requested = nk_true; + data->scroll_to_widget = leaving_parent; data->input_processed = nk_true; } nk_console_trigger_event(leaving_parent, NK_CONSOLE_EVENT_BACK); diff --git a/nuklear_console_file.h b/nuklear_console_file.h index 346a344..4986d19 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -537,11 +537,7 @@ static void nk_console_file_back_free_children(nk_console* file, void* user_data NK_UNUSED(user_data); nk_console_free_children(file); - if (file && file->data) { - nk_console* top = nk_console_get_top(file); - nk_console_top_data* data = (nk_console_top_data*)top->data; - data->scroll_requested = nk_true; - } + NK_UNUSED(file); } static void nk_console_file_back(nk_console* file, void* user_data) { diff --git a/nuklear_console_list_view.h b/nuklear_console_list_view.h index fc8b452..585712e 100644 --- a/nuklear_console_list_view.h +++ b/nuklear_console_list_view.h @@ -243,7 +243,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { nk_console* t = widget->parent->children[idx]; if (nk_console_selectable(t)) { nk_console_set_active_widget(t); - top_data->scroll_requested = nk_true; + top_data->scroll_to_widget = t; break; } } @@ -277,7 +277,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { nk_console* t = widget->parent->children[idx]; if (nk_console_selectable(t)) { nk_console_set_active_widget(t); - top_data->scroll_requested = nk_true; + top_data->scroll_to_widget = t; break; } } @@ -291,7 +291,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { nk_console* t = widget->parent->children[idx]; if (nk_console_selectable(t)) { nk_console_set_active_widget(t); - top_data->scroll_requested = nk_true; + top_data->scroll_to_widget = t; break; } } @@ -305,7 +305,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { nk_console* t = widget->parent->children[idx]; if (nk_console_selectable(t)) { nk_console_set_active_widget(t); - top_data->scroll_requested = nk_true; + top_data->scroll_to_widget = t; break; } } @@ -318,7 +318,6 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_B)) { if (widget->parent != NULL) { nk_console_navigate_back(widget->parent); - top_data->scroll_requested = nk_true; } top_data->input_processed = nk_true; } diff --git a/nuklear_console_tree.h b/nuklear_console_tree.h index 2eda1e9..c42bc4e 100644 --- a/nuklear_console_tree.h +++ b/nuklear_console_tree.h @@ -82,7 +82,7 @@ static void nk_console_tree_apply_expanded(nk_console* tree, nk_bool expanded) { nk_console* top = nk_console_get_top(tree); if (top != NULL && top->data != NULL) { nk_console_top_data* top_data = (nk_console_top_data*)top->data; - top_data->scroll_requested = nk_true; + top_data->scroll_to_widget = tree; top_data->scrollbar_required = nk_true; } } From 77beddefb04939ec68c3c9bba6f826145664faa1 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 13:43:35 -0400 Subject: [PATCH 08/12] celan up --- README.md | 2 +- nuklear_console.h | 4 ++-- nuklear_console_button.h | 2 +- nuklear_console_checkbox.h | 2 +- nuklear_console_image.h | 2 +- nuklear_console_label.h | 2 +- nuklear_console_progress.h | 2 +- nuklear_console_property.h | 2 +- nuklear_console_radio.h | 2 +- nuklear_console_row.h | 2 +- nuklear_console_textedit_text.h | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d1366ce..f1262b5 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ nk_console* nk_console_button_onclick(nk_console* parent, const char* text, void nk_console* nk_console_get_top(nk_console* widget); int nk_console_get_widget_index(nk_console* widget); void nk_console_check_tooltip(nk_console* console); -void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds); +void nk_console_check_up_down(nk_console* widget); nk_bool nk_console_is_active_widget(nk_console* widget); void nk_console_set_active_parent(nk_console* new_parent); void nk_console_set_active_widget(nk_console* widget); diff --git a/nuklear_console.h b/nuklear_console.h index 469fd7d..026a059 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -175,7 +175,7 @@ NK_API void nk_console_render_window(nk_console* console, const char* title, str NK_API nk_console* nk_console_get_top(nk_console* widget); NK_API int nk_console_get_widget_index(nk_console* widget); NK_API void nk_console_check_tooltip(nk_console* console); -NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds); +NK_API void nk_console_check_up_down(nk_console* widget); NK_API nk_bool nk_console_is_active_widget(nk_console* widget); NK_API nk_console* nk_console_active_parent(nk_console* console); NK_API void nk_console_set_active_parent(nk_console* new_parent); @@ -561,7 +561,7 @@ NK_API int nk_console_get_widget_index(nk_console* widget) { /** * Allow the user to move up and down between widgets. */ -NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) { +NK_API void nk_console_check_up_down(nk_console* widget) { nk_console* top = nk_console_get_top(widget); nk_console_top_data* data = (nk_console_top_data*)top->data; diff --git a/nuklear_console_button.h b/nuklear_console_button.h index 9d15dde..7f3f401 100644 --- a/nuklear_console_button.h +++ b/nuklear_console_button.h @@ -171,7 +171,7 @@ NK_API struct nk_rect nk_console_button_render(nk_console* console) { // Allow switching up/down in widgets if (nk_console_is_active_widget(console)) { - nk_console_check_up_down(console, widget_bounds); + nk_console_check_up_down(console); nk_console_check_tooltip(console); } diff --git a/nuklear_console_checkbox.h b/nuklear_console_checkbox.h index 850b7a4..77bff47 100644 --- a/nuklear_console_checkbox.h +++ b/nuklear_console_checkbox.h @@ -106,7 +106,7 @@ NK_API struct nk_rect nk_console_checkbox_render(nk_console* console) { // Allow switching up/down in widgets if (nk_console_is_active_widget(console)) { - nk_console_check_up_down(console, widget_bounds); + nk_console_check_up_down(console); nk_console_check_tooltip(console); } diff --git a/nuklear_console_image.h b/nuklear_console_image.h index 745b894..a070189 100644 --- a/nuklear_console_image.h +++ b/nuklear_console_image.h @@ -93,7 +93,7 @@ NK_API struct nk_rect nk_console_image_render(nk_console* widget) { } if (nk_console_is_active_widget(widget)) { - nk_console_check_up_down(widget, widget_bounds); + nk_console_check_up_down(widget); nk_console_check_tooltip(widget); } diff --git a/nuklear_console_label.h b/nuklear_console_label.h index ffce617..f8ca100 100644 --- a/nuklear_console_label.h +++ b/nuklear_console_label.h @@ -61,7 +61,7 @@ NK_API struct nk_rect nk_console_label_render(nk_console* widget) { nk_console_trigger_event(widget, NK_CONSOLE_EVENT_CLICKED); } else { - nk_console_check_up_down(widget, widget_bounds); + nk_console_check_up_down(widget); } nk_console_check_tooltip(widget); } diff --git a/nuklear_console_progress.h b/nuklear_console_progress.h index 05f1f49..1c4edfd 100644 --- a/nuklear_console_progress.h +++ b/nuklear_console_progress.h @@ -129,7 +129,7 @@ NK_API struct nk_rect nk_console_progress_render(nk_console* console) { // Allow switching up/down in widgets if (nk_console_is_active_widget(console)) { - nk_console_check_up_down(console, widget_bounds); + nk_console_check_up_down(console); nk_console_check_tooltip(console); } diff --git a/nuklear_console_property.h b/nuklear_console_property.h index 64f75b7..a346707 100644 --- a/nuklear_console_property.h +++ b/nuklear_console_property.h @@ -236,7 +236,7 @@ NK_API struct nk_rect nk_console_property_render(nk_console* console) { // Allow switching up/down in widgets if (nk_console_is_active_widget(console)) { - nk_console_check_up_down(console, widget_bounds); + nk_console_check_up_down(console); nk_console_check_tooltip(console); } diff --git a/nuklear_console_radio.h b/nuklear_console_radio.h index f6d07c6..52ebae6 100644 --- a/nuklear_console_radio.h +++ b/nuklear_console_radio.h @@ -212,7 +212,7 @@ NK_API struct nk_rect nk_console_radio_render(nk_console* widget) { // Allow switching up/down in widgets if (active) { - nk_console_check_up_down(widget, widget_bounds); + nk_console_check_up_down(widget); nk_console_check_tooltip(widget); } diff --git a/nuklear_console_row.h b/nuklear_console_row.h index b6fd225..3cbb6a4 100644 --- a/nuklear_console_row.h +++ b/nuklear_console_row.h @@ -224,7 +224,7 @@ NK_API struct nk_rect nk_console_row_render(nk_console* console) { // Consume directional input before children have a chance to. if (nk_console_is_active_widget(console)) { nk_console_row_check_left_right(console, top); - nk_console_check_up_down(console, widget_bounds); + nk_console_check_up_down(console); nk_console* active = nk_console_get_active_widget(console); // Attempt to accurately move vertically if the new widget is also a row. diff --git a/nuklear_console_textedit_text.h b/nuklear_console_textedit_text.h index 50d989e..4ca40a6 100644 --- a/nuklear_console_textedit_text.h +++ b/nuklear_console_textedit_text.h @@ -51,7 +51,7 @@ NK_API struct nk_rect nk_console_textedit_text_render(nk_console* widget) { } // Allow changing up/down only if they're not pressing backspace else if (!nk_input_is_key_pressed(&widget->ctx->input, NK_KEY_BACKSPACE)) { - nk_console_check_up_down(widget, widget_bounds); + nk_console_check_up_down(widget); } // Display the tooltip for the textedit. From bea3586cbfa43fe9b796c5a75bb63bfc6da6e68e Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 14:15:49 -0400 Subject: [PATCH 09/12] Fix --- demo/common/nuklear_console_demo.c | 3 ++- nuklear_console.h | 42 ++++++++++++++++++------------ nuklear_console_file.h | 6 ++--- nuklear_console_message.h | 2 +- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/demo/common/nuklear_console_demo.c b/demo/common/nuklear_console_demo.c index 8cbca06..30fa483 100644 --- a/demo/common/nuklear_console_demo.c +++ b/demo/common/nuklear_console_demo.c @@ -49,7 +49,8 @@ static int tree_option3 = 0; char list_view_labels[NK_CONSOLE_DEMO_LIST_VIEW_COUNT][32]; const char* list_view_event_get_label(struct nk_console* list_view, nk_uint index) { - if (index < 0 || index >= NK_CONSOLE_DEMO_LIST_VIEW_COUNT) { + NK_UNUSED(list_view); + if (index >= NK_CONSOLE_DEMO_LIST_VIEW_COUNT) { return NULL; } return list_view_labels[index]; diff --git a/nuklear_console.h b/nuklear_console.h index 026a059..982321e 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -595,14 +595,16 @@ NK_API void nk_console_check_up_down(nk_console* widget) { while (--widgetIndex >= 0) { nk_console* t = widget->parent->children[widgetIndex]; if (t != NULL && nk_console_selectable(t)) { - nk_console_set_active_widget(t); target = t; if (++count > 4) { break; } } } - if (target != NULL) data->scroll_to_widget = target; + if (target != NULL) { + nk_console_set_active_widget(target); + data->scroll_to_widget = target; + } data->input_processed = nk_true; } // Page Down @@ -610,17 +612,21 @@ NK_API void nk_console_check_up_down(nk_console* widget) { int widgetIndex = nk_console_get_widget_index(widget); nk_console* target = NULL; int count = 0; - while (++widgetIndex < (int)cvector_size(widget->parent->children)) { - nk_console* t = widget->parent->children[widgetIndex]; - if (nk_console_selectable(t)) { - nk_console_set_active_widget(t); - target = t; - if (++count > 4) { - break; + if (widget->parent != NULL) { + while (++widgetIndex < (int)cvector_size(widget->parent->children)) { + nk_console* t = widget->parent->children[widgetIndex]; + if (t != NULL && nk_console_selectable(t)) { + target = t; + if (++count > 4) { + break; + } } } } - if (target != NULL) data->scroll_to_widget = target; + if (target != NULL) { + nk_console_set_active_widget(target); + data->scroll_to_widget = target; + } data->input_processed = nk_true; } // Up @@ -629,7 +635,7 @@ NK_API void nk_console_check_up_down(nk_console* widget) { int widgetIndex = nk_console_get_widget_index(widget); while (--widgetIndex >= 0) { nk_console* target = widget->parent->children[widgetIndex]; - if (nk_console_selectable(target)) { + if (target != NULL && nk_console_selectable(target)) { nk_console_set_active_widget(target); data->scroll_to_widget = target; break; @@ -641,12 +647,14 @@ NK_API void nk_console_check_up_down(nk_console* widget) { else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_DOWN) || (down_held && up_down_repeat_fire)) { int widgetIndex = nk_console_get_widget_index(widget); - while (++widgetIndex < (int)cvector_size(widget->parent->children)) { - nk_console* target = widget->parent->children[widgetIndex]; - if (nk_console_selectable(target)) { - nk_console_set_active_widget(target); - data->scroll_to_widget = target; - break; + if (widget->parent != NULL) { + while (++widgetIndex < (int)cvector_size(widget->parent->children)) { + nk_console* target = widget->parent->children[widgetIndex]; + if (target != NULL && nk_console_selectable(target)) { + nk_console_set_active_widget(target); + data->scroll_to_widget = target; + break; + } } } data->input_processed = nk_true; diff --git a/nuklear_console_file.h b/nuklear_console_file.h index 4986d19..77d8dd7 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -244,7 +244,7 @@ static const char* nk_console_file_list_view_get_label(struct nk_console* list_v return NULL; } nk_console_file_data* data = (nk_console_file_data*)file->data; - if (cvector_size(data->entries) == 0) { + if (cvector_empty(data->entries)) { return "(Empty directory)"; } if (index >= cvector_size(data->entries)) { @@ -474,7 +474,7 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { // Build the static children (cancel, directory label, parent dir button) on the first call only. // children[1]'s label points directly to data->directory, so it auto-updates on subsequent calls. - if (widget->children == NULL || cvector_size(widget->children) == 0) { + if (widget->children == NULL || cvector_empty(widget->children)) { // Add the back/cancel button nk_console* cancelButton = nk_console_button_onclick(widget, "Cancel", &nk_console_button_back); nk_console_button_set_symbol(cancelButton, NK_SYMBOL_X); @@ -508,7 +508,7 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { NK_CONSOLE_FILE_ADD_FILES(widget, data->directory); // Show at least 1 item so the get_label callback can display "(Empty directory)". - nk_uint display_count = cvector_size(data->entries) == 0 ? 1 : (nk_uint)cvector_size(data->entries); + nk_uint display_count = cvector_empty(data->entries) ? 1 : (nk_uint)cvector_size(data->entries); if (cvector_size(widget->children) >= 4 && widget->children[3]->type == NK_CONSOLE_LIST_VIEW) { // Update the existing list view in place. nk_console_list_view_set_item_count(widget->children[3], display_count); diff --git a/nuklear_console_message.h b/nuklear_console_message.h index fc7cce7..2e47bed 100644 --- a/nuklear_console_message.h +++ b/nuklear_console_message.h @@ -121,7 +121,7 @@ NK_API void nk_console_message_render(nk_console* console, nk_console_message* m NK_API void nk_console_render_message(nk_console* console) { nk_console_top_data* data = (nk_console_top_data*)console->data; - if (data->messages == NULL || cvector_size(data->messages) == 0) { + if (data->messages == NULL || cvector_empty(data->messages)) { return; } From 88e3cc8935c44f383f9e641b35a9fdbdb7f99c06 Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sat, 28 Mar 2026 14:18:13 -0400 Subject: [PATCH 10/12] Apply suggestion from @RobLoach --- nuklear_console_file.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/nuklear_console_file.h b/nuklear_console_file.h index 77d8dd7..d846cd7 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -536,8 +536,6 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { static void nk_console_file_back_free_children(nk_console* file, void* user_data) { NK_UNUSED(user_data); nk_console_free_children(file); - - NK_UNUSED(file); } static void nk_console_file_back(nk_console* file, void* user_data) { From a77c244b162bc007186ec18c19704a937148b1af Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sun, 29 Mar 2026 21:38:29 -0400 Subject: [PATCH 11/12] Update codes --- demo/pntr/.cmake/Findpntr_app_starter.cmake | 4 ++ demo/raylib/CMakeLists.txt | 1 + demo/sdl_renderer/Makefile | 2 +- nuklear_console_file.h | 73 ++++++++++++++++----- nuklear_console_list_view.h | 4 +- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/demo/pntr/.cmake/Findpntr_app_starter.cmake b/demo/pntr/.cmake/Findpntr_app_starter.cmake index a72183a..29ac824 100644 --- a/demo/pntr/.cmake/Findpntr_app_starter.cmake +++ b/demo/pntr/.cmake/Findpntr_app_starter.cmake @@ -27,6 +27,10 @@ if (RAYLIB) ${SOURCES} ) + # Debug + target_compile_options(${project_name_raylib} PUBLIC -g) + set_target_properties(${project_name_raylib} PROPERTIES COMPILE_FLAGS "-g" LINK_FLAGS "-g") + target_link_libraries(${project_name_raylib} PUBLIC raylib_static ${LIBRARIES} diff --git a/demo/raylib/CMakeLists.txt b/demo/raylib/CMakeLists.txt index 6704104..b83c23a 100644 --- a/demo/raylib/CMakeLists.txt +++ b/demo/raylib/CMakeLists.txt @@ -70,6 +70,7 @@ endif() # Setup the example add_executable(${PROJECT_NAME} main.c) +target_compile_options(${PROJECT_NAME} PRIVATE -g) # Link dependencies target_link_libraries(${PROJECT_NAME} PUBLIC diff --git a/demo/sdl_renderer/Makefile b/demo/sdl_renderer/Makefile index a704f58..0db66df 100644 --- a/demo/sdl_renderer/Makefile +++ b/demo/sdl_renderer/Makefile @@ -4,7 +4,7 @@ BIN = nuklear_console_demo_sdl # Flags # Use C++ -CFLAGS += -std=c99 -pedantic -O0 +CFLAGS += -std=c99 -pedantic -O0 -g CXXFLAGS += -pedantic -O0 -fpermissive -std=c++17 CFLAGS += `sdl2-config --cflags` diff --git a/nuklear_console_file.h b/nuklear_console_file.h index d846cd7..29cc126 100644 --- a/nuklear_console_file.h +++ b/nuklear_console_file.h @@ -100,11 +100,9 @@ NK_API void* nk_console_file_get_file_user_data(nk_console* file); NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* path, nk_bool is_directory); /** - * Refreshes the file widget to display the contents of the current directory. + * Refreshes the file widget with the contents with its given directory. * - * @param widget The file widget to refresh. - * - * @see nk_console_file_data::directory + * @internal */ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data); @@ -126,6 +124,8 @@ extern "C" { /** * Gets the base name of a file path. + * + * @internal */ static const char* nk_console_file_basename(const char* path) { if (path == NULL) { @@ -186,6 +186,8 @@ NK_API struct nk_rect nk_console_file_render(nk_console* console) { /** * Gets the file widget from a child button. + * + * @internal */ static nk_console* nk_console_file_button_get_file_widget(nk_console* button) { if (button == NULL) { @@ -206,6 +208,8 @@ static nk_console* nk_console_file_button_get_file_widget(nk_console* button) { /** * Free all file entries stored in the file data. + * + * @internal */ static void nk_console_file_entries_clear(nk_console_file_data* data) { if (data == NULL) { @@ -221,14 +225,18 @@ static void nk_console_file_entries_clear(nk_console_file_data* data) { } /** - * Destroy handler for the file widget — frees the entries array. + * Event handler: Destroy the file widget. + * + * @internal */ -static void nk_console_file_destroy(nk_console* file, void* user_data) { +static void nk_console_file_event_destroy(nk_console* file, void* user_data) { NK_UNUSED(user_data); if (file == NULL || file->data == NULL) { return; } nk_console_file_data* data = (nk_console_file_data*)file->data; + + // Clear all the file entries. nk_console_file_entries_clear(data); cvector_free(data->entries); data->entries = NULL; @@ -237,6 +245,8 @@ static void nk_console_file_destroy(nk_console* file, void* user_data) { #ifdef NK_CONSOLE_FILE_ADD_FILES /** * get_label callback for the file list view. Appends "/" to directory entries. + * + * @internal */ static const char* nk_console_file_list_view_get_label(struct nk_console* list_view, nk_uint index) { nk_console* file = nk_console_file_button_get_file_widget(list_view); @@ -268,6 +278,8 @@ static const char* nk_console_file_list_view_get_label(struct nk_console* list_v /** * Appends a path component to data->directory, inserting the platform separator. * Returns nk_false and shows an error if the result would overflow the buffer. + * + * @internal */ static nk_bool nk_console_file_append_to_directory(nk_console_file_data* data, nk_console* file, const char* label) { int len = nk_strlen(data->directory); @@ -303,6 +315,8 @@ static nk_bool nk_console_file_append_to_directory(nk_console_file_data* data, n #ifdef NK_CONSOLE_FILE_ADD_FILES /** * Click handler for the file list view. Navigates into directories or selects files. + * + * @internal */ static void nk_console_file_list_view_onclick(nk_console* list_view, void* user_data) { NK_UNUSED(user_data); @@ -349,6 +363,8 @@ static void nk_console_file_list_view_onclick(nk_console* list_view, void* user_ /** * Click handler for the "select this directory" button in directory-selection mode. + * + * @internal */ static void nk_console_file_select_dir_onclick(nk_console* button, void* user_data) { NK_UNUSED(user_data); @@ -373,6 +389,11 @@ static void nk_console_file_select_dir_onclick(nk_console* button, void* user_da nk_console_navigate_back(file); } +/** + * Event hanlder for when the user clicks on an individual file entry. + * + * @internal + */ static void nk_console_file_entry_onclick(nk_console* button, void* user_data) { NK_UNUSED(user_data); if (button == NULL || button->label == NULL) { @@ -443,6 +464,8 @@ NK_API nk_console* nk_console_file_add_entry(nk_console* parent, const char* pat /** * Gets the length of the directory string of the given file path. + * + * @internal */ static int nk_console_file_get_directory_len(const char* file_path) { if (file_path == NULL) { @@ -514,7 +537,7 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { nk_console_list_view_set_item_count(widget->children[3], display_count); } else { - // Create the list view for the first time. + // Create the list view. nk_console* list_view = nk_console_list_view(widget, "file_entries", 10, display_count, &nk_console_file_list_view_get_label); nk_console_add_event(list_view, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_list_view_onclick); } @@ -531,22 +554,37 @@ NK_API void nk_console_file_refresh(nk_console* widget, void* user_data) { } /** - * BACK event handler for the file widget — frees the children so they are rebuilt on next open. + * Event handler to clear out unneeded data for the file widget. + * + * @see nk_console_file_event_back + * @internal */ -static void nk_console_file_back_free_children(nk_console* file, void* user_data) { - NK_UNUSED(user_data); +static void nk_console_file_event_back_post_render(nk_console* file, void* user_data) { + // Remove all the children, since we don't need them. nk_console_free_children(file); + + // Clear out all the file entries too. + nk_console_file_event_destroy(file, user_data); } -static void nk_console_file_back(nk_console* file, void* user_data) { +/** + * Event handler to clear out all unneeded data when not using the widget. + * @internal + */ +static void nk_console_file_event_back(nk_console* file, void* user_data) { NK_UNUSED(user_data); - nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_back_free_children); + // Clear it out at post-render to avoid segfaults. + nk_console_add_event(file, NK_CONSOLE_EVENT_POST_RENDER_ONCE, &nk_console_file_event_back_post_render); } /** - * Button callback for the main file button. + * Event handler: Called when the make file button is clicked. + * + * Will build out the sub-elements to select a file. + * + * @internal */ -static void nk_console_file_main_click(nk_console* button, void* user_data) { +static void nk_console_file_event_clicked(nk_console* button, void* user_data) { NK_UNUSED(user_data); if (button == NULL || button->data == NULL) { return; @@ -611,9 +649,10 @@ NK_API nk_console* nk_console_file(nk_console* parent, const char* label, char* widget->selectable = nk_true; widget->data = data; - nk_console_add_event(widget, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_main_click); - nk_console_add_event(widget, NK_CONSOLE_EVENT_BACK, &nk_console_file_back); - nk_console_add_event(widget, NK_CONSOLE_EVENT_DESTROYED, &nk_console_file_destroy); + nk_console_add_event(widget, NK_CONSOLE_EVENT_CLICKED, &nk_console_file_event_clicked); + nk_console_add_event(widget, NK_CONSOLE_EVENT_BACK, &nk_console_file_event_back); + nk_console_add_event(widget, NK_CONSOLE_EVENT_DESTROYED, &nk_console_file_event_destroy); + return widget; } diff --git a/nuklear_console_list_view.h b/nuklear_console_list_view.h index 585712e..962254a 100644 --- a/nuklear_console_list_view.h +++ b/nuklear_console_list_view.h @@ -196,7 +196,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { top_data->up_down_repeat_timer = 0; } - if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { + if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { // TODO: Add Page Up with NK_KEY_SCROLL_UP // Page up: jump selection up by rows_visible items. if (data->selected > 0) { data->selected = NK_MAX(0, data->selected - data->rows_visible); @@ -208,7 +208,7 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { } top_data->input_processed = nk_true; } - else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { + else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { // TODO: Add Page Down with NK_KEY_SCROLL_DOWN // Page down: jump selection down by rows_visible items. if (data->row_count > 0 && data->selected < data->row_count - 1) { data->selected = NK_MIN(data->row_count - 1, data->selected + data->rows_visible); From 4f7701aba5b3c6e76e92966c1783b7e7dcdaf37a Mon Sep 17 00:00:00 2001 From: Rob Loach Date: Sun, 29 Mar 2026 21:45:56 -0400 Subject: [PATCH 12/12] Fix scroll up/down --- nuklear_console_list_view.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nuklear_console_list_view.h b/nuklear_console_list_view.h index 962254a..dc6239b 100644 --- a/nuklear_console_list_view.h +++ b/nuklear_console_list_view.h @@ -196,19 +196,19 @@ NK_API struct nk_rect nk_console_list_view_render(nk_console* widget) { top_data->up_down_repeat_timer = 0; } - if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { // TODO: Add Page Up with NK_KEY_SCROLL_UP + if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB) || nk_input_is_key_pressed(&top->ctx->input, NK_KEY_SCROLL_UP)) { // Page up: jump selection up by rows_visible items. if (data->selected > 0) { - data->selected = NK_MAX(0, data->selected - data->rows_visible); + data->selected = (nk_uint)NK_MAX(0, (int)data->selected - (int)data->rows_visible); if (data->view.scroll_pointer) { - nk_uint new_scroll = (nk_uint)data->selected * (nk_uint)scroll_row_height; + nk_uint new_scroll = data->selected * (nk_uint)scroll_row_height; *data->view.scroll_pointer = new_scroll; data->_scroll_y = new_scroll; } } top_data->input_processed = nk_true; } - else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { // TODO: Add Page Down with NK_KEY_SCROLL_DOWN + else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB) || nk_input_is_key_pressed(&top->ctx->input, NK_KEY_SCROLL_DOWN)) { // Page down: jump selection down by rows_visible items. if (data->row_count > 0 && data->selected < data->row_count - 1) { data->selected = NK_MIN(data->row_count - 1, data->selected + data->rows_visible);