From f3bb199c966ef7108e6be4d76f0403f4df5a4de9 Mon Sep 17 00:00:00 2001 From: Fredrik Kallevik Date: Sat, 16 Nov 2024 16:56:34 +0100 Subject: [PATCH] Add support for multiple consoles/windows. Suggesting an initial solution for issue #5 --- demo/common/nuklear_console_demo.c | 230 ++++++++++++++++----------- demo/sdl_renderer/main.c | 44 +++++- nuklear_console.h | 242 ++++++++++++++++++++++++++++- 3 files changed, 414 insertions(+), 102 deletions(-) diff --git a/demo/common/nuklear_console_demo.c b/demo/common/nuklear_console_demo.c index 346398b..39fea57 100644 --- a/demo/common/nuklear_console_demo.c +++ b/demo/common/nuklear_console_demo.c @@ -10,56 +10,85 @@ #include "../../nuklear_console.h" // Demo -static struct nk_console* console = NULL; static struct nk_gamepads gamepads; static nk_bool shouldClose = nk_false; -// Theme -static int theme = THEME_DRACULA; - -// Progress -static nk_size progressValue = 50; - -// Combobox -static int weapon = 1; - -// Property -static int property_int_test = 20; -static float property_float_test = 0.4f; - -// Slider -static int slider_int_test = 20; -static float slider_float_test = 0.4f; - -// Radio -static int radio_option = 1; -static int radio_option2 = 0; - -// Checkbox -static nk_bool checkbox1 = nk_false; -static nk_bool checkbox2 = nk_false; -static nk_bool checkbox3 = nk_false; -static nk_bool checkbox4 = nk_false; -static nk_bool checkbox5 = nk_false; -static nk_bool checkbox6 = nk_true; - -// Messages -static int message_count = 0; - -// File -static char file_path_buffer[1024] = {0}; -static int file_path_buffer_size = 1024; - -// Textedit -static const int textedit_buffer_size = 256; -static char textedit_buffer[256] = "vurtun"; - -// Input -static int gamepad_number = 0; -static enum nk_gamepad_button gamepad_button = NK_GAMEPAD_BUTTON_A; - -// Color -static struct nk_colorf color = {0.31f, 1.0f, 0.48f, 1.0f}; +struct demo_console_state { + // Theme + int theme; + + // Progress + nk_size progressValue; + + // Combobox + int weapon; + + // Property + int property_int_test; + float property_float_test; + + // Slider + int slider_int_test; + float slider_float_test; + + // Radio + int radio_option; + int radio_option2; + + // Checkbox + nk_bool checkbox1; + nk_bool checkbox2; + nk_bool checkbox3; + nk_bool checkbox4; + nk_bool checkbox5; + nk_bool checkbox6; + + // Messages + int message_count; + + // File + char file_path_buffer[1024]; + int file_path_buffer_size; + + // Textedit + int textedit_buffer_size; + char textedit_buffer[256]; + + // Input + int gamepad_number; + enum nk_gamepad_button gamepad_button; + + // Color + struct nk_colorf color; +}; + +static struct demo_console_state demo_console_state_defaults(void) { + struct demo_console_state state = { + .theme = THEME_DRACULA, + .progressValue = 50, + .weapon = 1, + .property_int_test = 20, + .property_float_test = 0.4f, + .slider_int_test = 20, + .slider_float_test = 0.4f, + .radio_option = 1, + .radio_option2 = 0, + .checkbox1 = nk_false, + .checkbox2 = nk_false, + .checkbox3 = nk_false, + .checkbox4 = nk_false, + .checkbox5 = nk_false, + .checkbox6 = nk_true, + .message_count = 0, + .file_path_buffer_size = 1024, + .textedit_buffer_size = 256, + .color = {0.31f, 1.0f, 0.48f, 1.0f}, + .gamepad_number = 0, + .gamepad_button = NK_GAMEPAD_BUTTON_A, + }; + + return state; +} void button_clicked(struct nk_console* button, void* user_data) { NK_UNUSED(user_data); @@ -69,8 +98,8 @@ void button_clicked(struct nk_console* button, void* user_data) { } void theme_changed(struct nk_console* combobox, void* user_data) { - NK_UNUSED(user_data); - set_style(combobox->ctx, (enum theme)theme); + enum theme t = *(enum theme*)user_data; + set_style(combobox->ctx, t); } void exclude_other_checkbox(nk_console* unused, void* user_data) { @@ -86,9 +115,9 @@ void toggle_visibility(nk_console* unused, void* user_data) { } void nk_console_demo_show_message(struct nk_console* button, void* user_data) { - NK_UNUSED(user_data); + struct demo_console_state* state = (struct demo_console_state*)user_data; char message[128]; - snprintf(message, 128, "This is message #%d!", ++message_count); + snprintf(message, 128, "This is message #%d!", ++state->message_count); nk_console_show_message(button, message); } @@ -97,9 +126,7 @@ void nk_console_radio_changed(struct nk_console* radio, void* user_data) { nk_console_show_message(radio, radio->label); } -nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, struct nk_image image) { - console = nk_console_init(ctx); - +void nuklear_console_demo_init(struct nk_context* ctx, struct nk_console* console, struct demo_console_state* state, void* user_data, struct nk_image image) { nk_gamepad_init(&gamepads, ctx, user_data); nk_console_set_gamepads(console, &gamepads); @@ -138,22 +165,22 @@ nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, s nk_console* checkbox_button = nk_console_button(widgets, "Checkboxes"); { - nk_console_checkbox(checkbox_button, "Checkbox", &checkbox1) + nk_console_checkbox(checkbox_button, "Checkbox", &state->checkbox1) ->tooltip = "This is a checkbox!"; - nk_console_checkbox(checkbox_button, "Right aligned", &checkbox3) + nk_console_checkbox(checkbox_button, "Right aligned", &state->checkbox3) ->alignment = NK_TEXT_RIGHT; - nk_console_checkbox(checkbox_button, "Disabled Checkbox", &checkbox2) + nk_console_checkbox(checkbox_button, "Disabled Checkbox", &state->checkbox2) ->disabled = nk_true; // Onchange callbacks can be used to implement custom logic. // These two checkboxes disable each other when checked. - nk_console* exclude_a = nk_console_checkbox(checkbox_button, "Exclusive A (disables B)", &checkbox4); - nk_console* exclude_b = nk_console_checkbox(checkbox_button, "Exclusive B (disables A)", &checkbox5); + nk_console* exclude_a = nk_console_checkbox(checkbox_button, "Exclusive A (disables B)", &state->checkbox4); + nk_console* exclude_b = nk_console_checkbox(checkbox_button, "Exclusive B (disables A)", &state->checkbox5); nk_console_add_event_handler(exclude_a, NK_CONSOLE_EVENT_CHANGED, &exclude_other_checkbox, exclude_b, NULL); nk_console_add_event_handler(exclude_b, NK_CONSOLE_EVENT_CHANGED, &exclude_other_checkbox, exclude_a, NULL); // Checkbox that will show/hide the below label. - nk_console* checkbox_show_label = nk_console_checkbox(checkbox_button, "Show Label", &checkbox6); + nk_console* checkbox_show_label = nk_console_checkbox(checkbox_button, "Show Label", &state->checkbox6); nk_console* label_to_show = nk_console_label(checkbox_button, "This label is only shown when the checkbox is checked."); nk_console_add_event_handler(checkbox_show_label, NK_CONSOLE_EVENT_CHANGED, &toggle_visibility, label_to_show, NULL); @@ -179,20 +206,20 @@ nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, s nk_console* radios = nk_console_button(widgets, "Radios"); { nk_console_label(radios, "Option A:"); - nk_console_radio(radios, "Radio #1", &radio_option); - nk_console_radio(radios, "Radio #2", &radio_option); - nk_console_radio(radios, "Radio #3", &radio_option); - nk_console_radio(radios, "Radio #4", &radio_option); + nk_console_radio(radios, "Radio #1", &state->radio_option); + nk_console_radio(radios, "Radio #2", &state->radio_option); + nk_console_radio(radios, "Radio #3", &state->radio_option); + nk_console_radio(radios, "Radio #4", &state->radio_option); nk_console_label(radios, "Option B:"); - nk_console_radio(radios, "Left Aligned #1", &radio_option2)->alignment = NK_TEXT_LEFT; - nk_console_radio(radios, "Left Aligned #2", &radio_option2)->alignment = NK_TEXT_LEFT; - nk_console_radio(radios, "Center Aligned #1", &radio_option2)->alignment = NK_TEXT_CENTERED; - nk_console_radio(radios, "Center Aligned #2", &radio_option2)->alignment = NK_TEXT_CENTERED; - nk_console_radio(radios, "Right Aligned #1", &radio_option2)->alignment = NK_TEXT_RIGHT; - nk_console_radio(radios, "Right Aligned #2", &radio_option2)->alignment = NK_TEXT_RIGHT; - nk_console_radio(radios, "Disabled", &radio_option2)->disabled = nk_true; - nk_console_radio(radios, "Invisible", &radio_option2)->visible = nk_false; + nk_console_radio(radios, "Left Aligned #1", &state->radio_option2)->alignment = NK_TEXT_LEFT; + nk_console_radio(radios, "Left Aligned #2", &state->radio_option2)->alignment = NK_TEXT_LEFT; + nk_console_radio(radios, "Center Aligned #1", &state->radio_option2)->alignment = NK_TEXT_CENTERED; + nk_console_radio(radios, "Center Aligned #2", &state->radio_option2)->alignment = NK_TEXT_CENTERED; + nk_console_radio(radios, "Right Aligned #1", &state->radio_option2)->alignment = NK_TEXT_RIGHT; + nk_console_radio(radios, "Right Aligned #2", &state->radio_option2)->alignment = NK_TEXT_RIGHT; + nk_console_radio(radios, "Disabled", &state->radio_option2)->disabled = nk_true; + nk_console_radio(radios, "Invisible", &state->radio_option2)->visible = nk_false; nk_console_button_onclick(radios, "Back", &nk_console_button_back); } @@ -239,45 +266,45 @@ nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, s // Progress Bar nk_console* progressbar = nk_console_button(widgets, "Progress Bar"); { - nk_console_progress(progressbar, "Progress", &progressValue, 100); + nk_console_progress(progressbar, "Progress", &state->progressValue, 100); nk_console_button_onclick(progressbar, "Back", &nk_console_button_back); } // Input: From any gamepad (-1) - nk_console_input(widgets, "Input Button", -1, &gamepad_number, &gamepad_button); + nk_console_input(widgets, "Input Button", -1, &state->gamepad_number, &state->gamepad_button); // Combobox - nk_console_combobox(widgets, "ComboBox", "Fists;Chainsaw;Pistol;Shotgun;Chaingun", ';', &weapon) + nk_console_combobox(widgets, "ComboBox", "Fists;Chainsaw;Pistol;Shotgun;Chaingun", ';', &state->weapon) ->tooltip = "Choose a weapon! The chainsaw is the best!"; // Property nk_console* properties = nk_console_button(widgets, "Property"); { - nk_console_property_int(properties, "Property Int", 10, &property_int_test, 30, 1, 1); - nk_console_property_float(properties, "Property Float", 0.0f, &property_float_test, 2.0f, 0.1f, 1); + nk_console_property_int(properties, "Property Int", 10, &state->property_int_test, 30, 1, 1); + nk_console_property_float(properties, "Property Float", 0.0f, &state->property_float_test, 2.0f, 0.1f, 1); nk_console_button_onclick(properties, "Back", &nk_console_button_back); } // Sliders nk_console* sliders = nk_console_button(widgets, "Sliders"); { - nk_console_slider_float(sliders, "Slider Float", 0.0f, &slider_float_test, 2.0f, 0.1f)->tooltip = "Slider float is cool! It's what you want to use."; - nk_console_slider_int(sliders, "Slider Int", 0, &slider_int_test, 20, 1); + nk_console_slider_float(sliders, "Slider Float", 0.0f, &state->slider_float_test, 2.0f, 0.1f)->tooltip = "Slider float is cool! It's what you want to use."; + nk_console_slider_int(sliders, "Slider Int", 0, &state->slider_int_test, 20, 1); nk_console_button_onclick(sliders, "Back", &nk_console_button_back); } // Textedit - nk_console* textedit = nk_console_textedit(widgets, "Username", textedit_buffer, textedit_buffer_size); + nk_console* textedit = nk_console_textedit(widgets, "Username", state->textedit_buffer, state->textedit_buffer_size); nk_console_set_tooltip(textedit, "Enter your username!"); // Color - nk_console_color(widgets, "Select Color", &color, NK_RGBA); + nk_console_color(widgets, "Select Color", &state->color, NK_RGBA); // File - nk_console_file(widgets, "File", file_path_buffer, file_path_buffer_size); + nk_console_file(widgets, "File", state->file_path_buffer, state->file_path_buffer_size); // Messages - nk_console_button_onclick(widgets, "Show Message", &nk_console_demo_show_message); + nk_console_button_onclick_handler(widgets, "Show Message", &nk_console_demo_show_message, &state, NULL); // Back Button nk_console_button_set_symbol( @@ -285,10 +312,10 @@ nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, s NK_SYMBOL_TRIANGLE_LEFT); } - nk_console* theme_options = nk_console_combobox(console, "Theme", "Black;White;Red;Blue;Dark;Dracula;Default", ';', &theme); - nk_console_add_event(theme_options, NK_CONSOLE_EVENT_CHANGED, &theme_changed); + nk_console* theme_options = nk_console_combobox(console, "Theme", "Black;White;Red;Blue;Dark;Dracula;Default", ';', &state->theme); + nk_console_add_event_handler(theme_options, NK_CONSOLE_EVENT_CHANGED, &theme_changed, &state->theme, NULL); theme_options->tooltip = "Change the theme of the console!"; - set_style(ctx, (enum theme)theme); + set_style(ctx, (enum theme)state->theme); // Rows nk_console* calc = nk_console_button(console, "Calculator"); @@ -342,17 +369,38 @@ nk_console* nuklear_console_demo_init(struct nk_context* ctx, void* user_data, s nk_console_button(console, "Save Game")->disabled = nk_true; nk_console_button_onclick(console, "Quit Game", &button_clicked); - - return console; } -nk_bool nuklear_console_demo_render() { +nk_bool nuklear_console_demo_render(nk_console* console) { nk_console_render(console); - return shouldClose;; + return shouldClose; } -void nuklear_console_demo_free() { +nk_bool nuklear_console_demo_render_window(nk_console* console, int num, int width, int height, int flags) { + char title[18]; + snprintf(title, sizeof(title), "nuklear_console_%d", num); + + int margin = 10; + + nk_console_render_window( + console, + title, + nk_rect(margin + width * (num - 1), margin, width - margin, height - margin * 2), + flags); + + return shouldClose; +} + +#if CONSOLE_COUNT < 2 +void nuklear_console_demo_free(nk_console* console) { nk_gamepad_free(nk_console_get_gamepads(console)); nk_console_free(console); } +#else +void nuklear_console_demo_free(nk_consoles* consoles) { + nk_gamepad_free(&gamepads); + nk_consoles_free(consoles); +} +#endif + diff --git a/demo/sdl_renderer/main.c b/demo/sdl_renderer/main.c index 7053410..b22c275 100644 --- a/demo/sdl_renderer/main.c +++ b/demo/sdl_renderer/main.c @@ -23,8 +23,10 @@ #include "../../vendor/Nuklear/nuklear.h" #include "../../vendor/Nuklear/demo/sdl_renderer/nuklear_sdl_renderer.h" -#define WINDOW_WIDTH 800 -#define WINDOW_HEIGHT 600 +#define WINDOW_WIDTH 1280 +#define WINDOW_HEIGHT 720 +#define CONSOLE_COUNT 3 +NK_STATIC_ASSERT(CONSOLE_COUNT > 0); #include "../common/nuklear_console_demo.c" @@ -103,9 +105,26 @@ int main(int argc, char *argv[]) { SDL_FreeSurface(surface); } - nk_console* console = nuklear_console_demo_init(ctx, NULL, img); +#if CONSOLE_COUNT < 2 + nk_console* console = nk_console_init(ctx); + struct demo_console_state state = demo_console_state_defaults(); + nuklear_console_demo_init(ctx, console, &state, NULL, img); +#else + nk_consoles* consoles = nk_console_init_multiple(ctx, CONSOLE_COUNT, 0); + + struct demo_console_state states[CONSOLE_COUNT]; + + for (int i = 0; i < CONSOLE_COUNT; i++) { + states[i] = demo_console_state_defaults(); + nuklear_console_demo_init(ctx, consoles->consoles[i], &states[i], NULL, img); + } +#endif while (running) { +#if CONSOLE_COUNT > 1 + nk_console* console = nk_console_get_active_console(consoles); +#endif + /* Input */ SDL_Event evt; nk_input_begin(ctx); @@ -119,16 +138,25 @@ int main(int argc, char *argv[]) { } nk_input_end(ctx); + int flags = NK_WINDOW_SCROLL_AUTO_HIDE | NK_WINDOW_TITLE; /* GUI */ + /* Render it, and see if we're to stop running. */ +#if CONSOLE_COUNT < 2 if (nk_begin(ctx, "nuklear_console", nk_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT), flags)) { - /* Render it, and see if we're to stop running. */ - if (nuklear_console_demo_render()) { + if (nuklear_console_demo_render(console)) { running = 0; } } nk_end(ctx); +#else + for (int i = 0; i < CONSOLE_COUNT; i++) { + if (nuklear_console_demo_render_window(consoles->consoles[i], i + 1, WINDOW_WIDTH / CONSOLE_COUNT, WINDOW_HEIGHT, flags)) { + running = 0; + } + } +#endif SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); @@ -142,7 +170,11 @@ int main(int argc, char *argv[]) { if (texture != NULL) { SDL_DestroyTexture(texture); } - nuklear_console_demo_free(); +#if CONSOLE_COUNT < 2 + nuklear_console_demo_free(console); +#else + nuklear_console_demo_free(consoles); +#endif nk_sdl_shutdown(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(win); diff --git a/nuklear_console.h b/nuklear_console.h index 51c9b85..8b4597b 100644 --- a/nuklear_console.h +++ b/nuklear_console.h @@ -77,6 +77,14 @@ typedef struct nk_console_message { float duration; } nk_console_message; +typedef struct nk_consoles { + int active_console_index; /** The index of the active console. */ + struct nk_console** consoles; /** The consoles that are being managed. */ + struct nk_console* dispatcher; /** The console that is currently dispatching a console switch. */ + struct nk_console* target; /** The console that is the target of the console switch. */ + int count; /** The number of consoles that are being managed. */ +} nk_consoles; + typedef struct nk_console { nk_console_widget_type type; const char* label; @@ -123,6 +131,11 @@ typedef struct nk_console_top_data { */ struct nk_gamepads* gamepads; + /** + * The multiple consoles that are being managed, if any. + */ + struct nk_consoles* consoles; + /** * Custom user data. This is only applied to the top-level console. * @@ -138,7 +151,15 @@ NK_API void nk_console_free(nk_console* console); NK_API void nk_console_render(nk_console* console); NK_API void nk_console_render_window(nk_console* console, const char* title, struct nk_rect bounds, nk_uint flags); +// Multiple consoles in separate windows +NK_API struct nk_consoles* nk_console_init_multiple(struct nk_context* context, int count, int active_console_index); +NK_API void nk_consoles_free(nk_consoles* consoles); +NK_API void nk_console_set_active_console(nk_console* console); +NK_API nk_console* nk_console_get_active_console(nk_consoles* consoles); +NK_API void nk_console_handle_console_switch(nk_console* widget); + // Utilities +NK_API nk_bool nk_console_has_multiple_consoles(nk_console* widget); 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); @@ -408,7 +429,14 @@ NK_API nk_bool nk_console_is_active_widget(nk_console* widget) { return nk_false; } + nk_console* top = nk_console_get_top(widget); + + if (top->disabled) { + return nk_false; + } + nk_console* parent = widget->parent == NULL ? widget : widget->parent; + return parent->activeWidget == widget; } @@ -498,6 +526,11 @@ NK_API int nk_console_get_widget_index(nk_console* widget) { */ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) { nk_console* top = nk_console_get_top(widget); + + if (top->disabled) { + return; + } + nk_console_top_data* data = (nk_console_top_data*)top->data; // Scroll to the active widget if needed. @@ -514,7 +547,9 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) // Only process an active input once. if (data->input_processed == nk_false) { // Page Up - if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { + // SUGGESTION/TODO: Add NK_GAMEPAD_BUTTON_LT and NK_GAMEPAD_BUTTON_RT and bind them to Page Up and Page Down. + // Disabled in favor of console switching if there are multiple consoles. + if (!nk_console_has_multiple_consoles(top) && nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { int widgetIndex = nk_console_get_widget_index(widget); int count = 0; while (--widgetIndex >= 0) { @@ -529,7 +564,9 @@ NK_API void nk_console_check_up_down(nk_console* widget, struct nk_rect bounds) data->input_processed = nk_true; } // Page Down - else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { + // SUGGESTION/TODO: Add NK_GAMEPAD_BUTTON_LT and NK_GAMEPAD_BUTTON_RT and bind them to Page Up and Page Down. + // Disabled in favor of console switching if there are multiple consoles. + else if (!nk_console_has_multiple_consoles(top) && nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { int widgetIndex = nk_console_get_widget_index(widget); int count = 0; while (++widgetIndex < (int)cvector_size(widget->parent->children)) { @@ -688,6 +725,9 @@ NK_API void nk_console_render(nk_console* console) { // Reset the input state. data->input_processed = nk_false; + // Handle console switching. + nk_console_handle_console_switch(console); + // Render the active message. nk_console_render_message(console); @@ -757,8 +797,13 @@ NK_API void nk_console_render(nk_console* console) { nk_console* top = nk_console_get_top(console); nk_console_top_data* data = (nk_console_top_data*)top->data; if (data->input_processed == nk_false && nk_input_is_mouse_hovering_rect(&console->ctx->input, widget_bounds)) { - // Select the widget, if possible. if (nk_console_selectable(console)) { + // Ensure the console is active. + if (nk_console_has_multiple_consoles(top)) { + nk_console_set_active_console(top); + } + + // Select the widget. nk_console_set_active_widget(console); data->input_processed = nk_true; } @@ -799,6 +844,158 @@ NK_API nk_console* nk_console_init(struct nk_context* context) { return console; } +/** + * Initialize a new nk_consoles structure with the given number of consoles. + * + * @param context The associated Nuklear context. + * @param count The number of consoles to initialize. + * @param active_console_index The index of the console to make active. + * + * @return The initialized nk_consoles structure. + */ +NK_API nk_consoles* nk_console_init_multiple(struct nk_context* context, int count, int active_console_index) { + if (!context || count <= 0 || active_console_index < 0 || active_console_index >= count) { + fprintf(stderr, "Error: Invalid parameters passed to nk_console_init_multiple.\n"); + return NULL; + } + + nk_handle handle; + + // Allocate memory for the nk_consoles structure + nk_consoles* consoles = (nk_consoles*)nk_console_malloc(handle, NULL, sizeof(nk_consoles)); + if (!consoles) { + fprintf(stderr, "Error: Failed to allocate memory for nk_consoles.\n"); + return NULL; + } + nk_zero(consoles, sizeof(nk_consoles)); + + consoles->count = count; + consoles->active_console_index = active_console_index; + + // Allocate memory for the array of console pointers + consoles->consoles = (nk_console**)nk_console_malloc(handle, NULL, count * sizeof(nk_console*)); + if (!consoles->consoles) { + fprintf(stderr, "Error: Failed to allocate memory for console pointers.\n"); + nk_consoles_free(consoles); + return NULL; + } + + // Initialize each console + for (int i = 0; i < count; ++i) { + nk_console* console = nk_console_init(context); + if (!console) { + fprintf(stderr, "Error: Failed to initialize console at index %d.\n", i); + nk_consoles_free(consoles); + return NULL; + } + consoles->consoles[i] = console; + + // Link the consoles structure back to the console + nk_console_top_data* data = console->data; + if (data) data->consoles = consoles; + + // Disable all consoles except the active one + console->disabled = (i != active_console_index); + } + + return consoles; +} + +/** + * Check if the console is associated with multiple consoles. + * + * @param widget The widget to check. + * + * @return nk_true if the widget has multiple consoles, nk_false otherwise. + */ +NK_API nk_bool nk_console_has_multiple_consoles(nk_console* top) { + NK_ASSERT(top->parent == NULL); + nk_console_top_data* data = (nk_console_top_data*)top->data; + return data->consoles != NULL && data->consoles->count > 1; +} + +/** + * Set the given console as the active console. + * + * @param top The top-level console. + */ +NK_API void nk_console_set_active_console(nk_console* top) { + NK_ASSERT(top->parent == NULL); + + nk_console_top_data* data = (nk_console_top_data*)top->data; + + for (int i = 0; i < data->consoles->count; i++) { + if (data->consoles->consoles[i] == top) { + data->consoles->consoles[i]->disabled = nk_false; + data->consoles->active_console_index = i; + } + else { + data->consoles->consoles[i]->disabled = nk_true; + } + } +} + +/** + * Get the active console from the given consoles structure. + * + * @param consoles The consoles structure. + * + * @return The active console. + */ +NK_API nk_console* nk_console_get_active_console(nk_consoles* consoles) { + NK_ASSERT(consoles != NULL); + return consoles->consoles[consoles->active_console_index]; +} + +/** + * Handle console switching. + * + * A console switch is dispatched (queued) when the user presses the left or right bumper on the gamepad. + * The switch is then performed on the next frame. The occurence of the next frame is inferred + * from the dispatcher console's next call. + * + * @param top The top-level console. + */ +NK_API void nk_console_handle_console_switch(nk_console* top) { + NK_ASSERT(top->parent == NULL); + + nk_console_top_data* data = (nk_console_top_data*)top->data; + + // If there aren't multiple consoles, there's nothing to do. + if (!nk_console_has_multiple_consoles(top)) { + return; + } + + // Perform the queued console switch, if this console queued it. + if (data->consoles->dispatcher == top) { + // Switch to the next active console + nk_console_set_active_console(data->consoles->target); + + // Clear the queue after performing the switch + data->consoles->dispatcher = NULL; + data->consoles->target = NULL; + return; + } + + // A console switch is already underway, so no need to check for console switch input. + if (data->consoles->dispatcher != NULL) { + return; + } + + // Queue the next console to be activated + if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_RB)) { + int index = (data->consoles->active_console_index + 1) % data->consoles->count; + data->consoles->dispatcher = top; + data->consoles->target = data->consoles->consoles[index]; + } + // Queue the previous console to be activated + else if (nk_console_button_pushed(top, NK_GAMEPAD_BUTTON_LB)) { + int index = (data->consoles->active_console_index - 1 + data->consoles->count) % data->consoles->count; + data->consoles->dispatcher = top; + data->consoles->target = data->consoles->consoles[index]; + } +} + /** * Renders the given console to a new window with the given properties. * @@ -819,6 +1016,23 @@ NK_API void nk_console_render_window(nk_console* console, const char* title, str nk_end(console->ctx); } +/** + * Free all consoles associated with the given nk_consoles structure, + * as well as the structure itself. + */ +NK_API void nk_consoles_free(nk_consoles* consoles) { + if (consoles == NULL) { + return; + } + + for (int i = 0; i < consoles->count; i++) { + nk_console_free(consoles->consoles[i]); + } + + nk_console_mfree(nk_handle_id(0), consoles->consoles); + nk_console_mfree(nk_handle_id(0), consoles); +} + /** * Free the given nk_console's data. */ @@ -979,8 +1193,26 @@ NK_API nk_bool nk_console_button_pushed(nk_console* console, int button) { case NK_GAMEPAD_BUTTON_B: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_BACKSPACE) || (nk_input_is_mouse_pressed(&console->ctx->input, NK_BUTTON_RIGHT) && nk_window_is_hovered(console->ctx)); // case NK_GAMEPAD_BUTTON_X: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_A); // case NK_GAMEPAD_BUTTON_Y: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_S); - case NK_GAMEPAD_BUTTON_LB: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_DOWN) && nk_input_is_key_down(&console->ctx->input, NK_KEY_CTRL); - case NK_GAMEPAD_BUTTON_RB: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_UP) && nk_input_is_key_down(&console->ctx->input, NK_KEY_CTRL); + case NK_GAMEPAD_BUTTON_LB: { + // PageUp/PageDown is replaced with console switching if there are multiple consoles. + // Both could be supported when nuklear_gamepad supports NK_GAMEPAD_BUTTON_LT and NK_GAMEPAD_BUTTON_RT. + if (nk_console_has_multiple_consoles(console)) { + return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_TAB) && nk_input_is_key_down(&console->ctx->input, NK_KEY_SHIFT); + } + else { + return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_DOWN) && nk_input_is_key_down(&console->ctx->input, NK_KEY_CTRL); + }; + } + case NK_GAMEPAD_BUTTON_RB: { + // PageUp/PageDown is replaced with console switching if there are multiple consoles. + // Both could be supported when nuklear_gamepad supports NK_GAMEPAD_BUTTON_LT and NK_GAMEPAD_BUTTON_RT. + if (nk_console_has_multiple_consoles(console)) { + return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_TAB) && !nk_input_is_key_down(&console->ctx->input, NK_KEY_SHIFT); + } + else { + return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_UP) && nk_input_is_key_down(&console->ctx->input, NK_KEY_CTRL); + } + } case NK_GAMEPAD_BUTTON_BACK: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_SHIFT); // case NK_GAMEPAD_BUTTON_START: return nk_input_is_key_pressed(&console->ctx->input, NK_KEY_UP);