/* * Nuklear GLFW3 Demo Application - Modular UI Components * A simple desktop GUI application using GLFW3 and Nuklear with modular component architecture * * MODULAR COMPONENT STRUCTURE: * * This application demonstrates a clean, modular approach to UI component organization: * * 1. UI STATE MANAGEMENT: * - Centralized state in ui_state_t structure * - Clean separation between UI rendering and state management * - Easy state reset and manipulation functions * * 2. COMPONENT INTERFACE: * - Each UI component is a function with signature: void render_*_component(nk_context*, ui_state_t*) * - Components are registered in ui_components[] array * - Components can be enabled/disabled individually * - Easy to add new components by implementing the interface and adding to registry * * 3. CLEAN SEPARATION: * - GLFW window management is separate from UI rendering * - Nuklear integration is isolated in init/cleanup functions * - Application logic is separated from rendering logic * * 4. EXTENSIBILITY: * - Adding new components requires: implement render function + add to registry * - State management is centralized and easily extensible * - Component enable/disable functionality built-in * * HOW TO ADD NEW COMPONENTS: * 1. Add state variables to ui_state_t if needed * 2. Implement render_*_component(ctx, state) function * 3. Add component to ui_components[] array * 4. Optionally add state management functions */ #include #include #include #define GLFW_INCLUDE_NONE #include #include #define NK_INCLUDE_FIXED_TYPES #define NK_INCLUDE_STANDARD_IO #define NK_INCLUDE_STANDARD_VARARGS #define NK_INCLUDE_DEFAULT_ALLOCATOR #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT #define NK_INCLUDE_FONT_BAKING #define NK_INCLUDE_DEFAULT_FONT #define NK_IMPLEMENTATION #define NK_GLFW_GL3_IMPLEMENTATION #define NK_KEYSTATE_BASED_INPUT #include "nuklear.h" #include "nuklear_glfw_gl3.h" #include // Window dimensions static int window_width = 800; static int window_height = 600; static int min_window_width = 800; static int min_window_height = 600; static GLFWwindow* window = NULL; // Nuklear context static struct nk_context* ctx = NULL; static struct nk_glfw glfw = {0}; // Nuklear rendering constants #define MAX_VERTEX_BUFFER 512 * 1024 #define MAX_ELEMENT_BUFFER 128 * 1024 // ============================================================================= // UI COMPONENT STATE MANAGEMENT // ============================================================================= // UI Component State Structure typedef struct { // Text input component state char text_buffer[256]; // Slider component state float slider_value; // Checkbox component state int checkbox_value; // Radio button component state int option_selected; // Button component state int button_click_count; // Window information state char status_message[512]; } ui_state_t; // Global UI state instance static ui_state_t ui_state = { .text_buffer = "Hello World!", .slider_value = 0.5f, .checkbox_value = 1, .option_selected = 0, .button_click_count = 0, .status_message = "Application started" }; // ============================================================================= // UI COMPONENT INTERFACE DEFINITIONS // ============================================================================= // UI Component rendering function type typedef void (*ui_component_render_fn)(struct nk_context* ctx, ui_state_t* state); // UI Component structure typedef struct { const char* name; ui_component_render_fn render; int enabled; } ui_component_t; // ============================================================================= // FORWARD DECLARATIONS // ============================================================================= // Core application functions void init_glfw(void); void init_nuklear(void); void render_ui(struct nk_context* ctx); void cleanup(void); // Window management callbacks void window_size_callback(GLFWwindow* window, int width, int height); void window_iconify_callback(GLFWwindow* window, int iconified); void window_maximize_callback(GLFWwindow* window, int maximized); void error_callback(int error, const char* description); // UI utility functions float get_ui_scale_factor(void); struct nk_rect get_scaled_window_rect(void); // UI Component rendering functions void render_window_info_component(struct nk_context* ctx, ui_state_t* state); void render_button_component(struct nk_context* ctx, ui_state_t* state); void render_text_input_component(struct nk_context* ctx, ui_state_t* state); void render_slider_component(struct nk_context* ctx, ui_state_t* state); void render_checkbox_component(struct nk_context* ctx, ui_state_t* state); void render_radio_button_component(struct nk_context* ctx, ui_state_t* state); void render_status_component(struct nk_context* ctx, ui_state_t* state); // UI state management functions void ui_state_update_status(const char* message); void ui_state_reset(void); // UI component management functions void ui_component_set_enabled(const char* component_name, int enabled); int ui_component_is_enabled(const char* component_name); // Error callback for GLFW void error_callback(int error, const char* description) { fprintf(stderr, "GLFW Error %d: %s\n", error, description); } // Window resize callback void window_size_callback(GLFWwindow* window, int width, int height) { // Enforce minimum window size constraints if (width < min_window_width || height < min_window_height) { int new_width = width < min_window_width ? min_window_width : width; int new_height = height < min_window_height ? min_window_height : height; glfwSetWindowSize(window, new_width, new_height); return; } window_width = width; window_height = height; glViewport(0, 0, width, height); printf("Window resized to: %dx%d\n", width, height); } // Window iconify (minimize) callback void window_iconify_callback(GLFWwindow* window, int iconified) { if (iconified) { printf("Window minimized\n"); } else { printf("Window restored from minimized state\n"); } } // Window maximize callback void window_maximize_callback(GLFWwindow* window, int maximized) { if (maximized) { printf("Window maximized\n"); } else { printf("Window restored from maximized state\n"); } } // Calculate UI scale factor based on window size float get_ui_scale_factor(void) { // Base scale factor on window width, with minimum scale of 0.8 and maximum of 2.0 float base_width = 800.0f; float scale = (float)window_width / base_width; // Clamp scale factor to reasonable bounds if (scale < 0.8f) scale = 0.8f; if (scale > 2.0f) scale = 2.0f; return scale; } // Get scaled window rectangle for responsive UI layout struct nk_rect get_scaled_window_rect(void) { float scale = get_ui_scale_factor(); // Calculate responsive window position and size float base_x = 50.0f; float base_y = 50.0f; float base_width = 400.0f; float base_height = 500.0f; // Scale the window dimensions but keep it within screen bounds float scaled_width = base_width * scale; float scaled_height = base_height * scale; // Ensure the window fits within the screen if (scaled_width > window_width - 100) { scaled_width = window_width - 100; } if (scaled_height > window_height - 100) { scaled_height = window_height - 100; } // Center the window if it's getting too large float x = base_x; float y = base_y; if (scaled_width > base_width * 1.5f) { x = (window_width - scaled_width) / 2.0f; } if (scaled_height > base_height * 1.5f) { y = (window_height - scaled_height) / 2.0f; } return nk_rect(x, y, scaled_width, scaled_height); } // Initialize GLFW and create window void init_glfw(void) { // Set error callback glfwSetErrorCallback(error_callback); // Initialize GLFW if (!glfwInit()) { fprintf(stderr, "Failed to initialize GLFW\n"); exit(EXIT_FAILURE); } // Set OpenGL version to 3.3 core profile glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Create window window = glfwCreateWindow(window_width, window_height, "Nuklear GLFW3 Demo", NULL, NULL); if (!window) { fprintf(stderr, "Failed to create GLFW window\n"); glfwTerminate(); exit(EXIT_FAILURE); } //HWND hwnd = glfwGetWin32Window(window); // Make the window's context current glfwMakeContextCurrent(window); // Initialize OpenGL loader (gl3w) if (gl3wInit() != 0) { fprintf(stderr, "Failed to initialize OpenGL loader\n"); glfwDestroyWindow(window); glfwTerminate(); exit(EXIT_FAILURE); } // Set window size limits glfwSetWindowSizeLimits(window, min_window_width, min_window_height, GLFW_DONT_CARE, GLFW_DONT_CARE); // Set callbacks glfwSetWindowSizeCallback(window, window_size_callback); glfwSetWindowIconifyCallback(window, window_iconify_callback); glfwSetWindowMaximizeCallback(window, window_maximize_callback); // Enable vsync glfwSwapInterval(1); // Set initial viewport glViewport(0, 0, window_width, window_height); printf("GLFW initialized successfully\n"); printf("OpenGL Version: %s\n", glGetString(GL_VERSION)); printf("Window size limits set to: %dx%d minimum\n", min_window_width, min_window_height); printf("Initial window size: %dx%d\n", window_width, window_height); } // Initialize Nuklear with GLFW+OpenGL3 backend void init_nuklear(void) { // Initialize Nuklear context with GLFW+OpenGL3 backend ctx = nk_glfw3_init(&glfw, window, NK_GLFW3_INSTALL_CALLBACKS); if (!ctx) { fprintf(stderr, "Failed to initialize Nuklear context\n"); cleanup(); exit(EXIT_FAILURE); } // Load default font struct nk_font_atlas *atlas; nk_glfw3_font_stash_begin(&glfw, &atlas); nk_glfw3_font_stash_end(&glfw); printf("Nuklear initialized successfully\n"); // Override specific colors ctx->style.window.header.active.data.color = nk_rgb(28, 28, 28); ctx->style.window.background = nk_rgb(158, 204, 46); ctx->style.text.color = nk_rgb(158, 204, 46); // Override padding constraints //ctx->style.window.group_padding = nk_vec2(10, 10); //ctx->style.window.spacing = nk_vec2(0, 0); //ctx->style.window.padding = nk_vec2(0, 0); ctx->style.button.rounding = 10.f; // Test minimum window size constraint printf("Testing minimum window size constraint...\n"); glfwSetWindowSize(window, 800, 600); // Try to set below minimum glfwPollEvents(); // Process the resize event int test_width, test_height; glfwGetWindowSize(window, &test_width, &test_height); printf("Attempted to set window to 200x150, actual size: %dx%d\n", test_width, test_height); // Reset to normal size glfwSetWindowSize(window, window_width, window_height); } // ============================================================================= // UI COMPONENT REGISTRY // ============================================================================= // Define all available UI components static ui_component_t ui_components[] = { {"Window Info", render_window_info_component, 1}, {"Buttons", render_button_component, 1}, {"Text Input", render_text_input_component, 1}, {"Slider", render_slider_component, 1}, {"Checkbox", render_checkbox_component, 1}, {"Radio Buttons", render_radio_button_component, 1}, {"Status", render_status_component, 1} }; #define UI_COMPONENT_COUNT (sizeof(ui_components) / sizeof(ui_components[0])) // ============================================================================= // MAIN UI RENDERING FUNCTION // ============================================================================= // Render main UI using modular component system void render_ui(struct nk_context* ctx) { // Get responsive window rectangle struct nk_rect window_rect = nk_rect(0, 0, (float)window_width, (float)window_height);//get_scaled_window_rect(); // Main demo window with title bar showing application name and current window size char window_title[256]; snprintf(window_title, sizeof(window_title), "Nuklear GLFW3 Demo - Modular UI Components [%dx%d]", window_width, window_height); if (nk_begin(ctx, window_title, window_rect, NK_WINDOW_BORDER|NK_WINDOW_TITLE)) { //// Render all enabled UI components //for (int i = 0; i < UI_COMPONENT_COUNT; i++) { // if (ui_components[i].enabled && ui_components[i].render) { // ui_components[i].render(ctx, &ui_state); // // // Add separator between components (except for the last one) // if (i < UI_COMPONENT_COUNT - 1) { // nk_layout_row_dynamic(ctx, 10, 1); // nk_spacing(ctx, 1); // } // } //} nk_layout_row_dynamic(ctx, 300, 1); if (nk_group_begin(ctx, "Window Information:", NK_WINDOW_BORDER | NK_WINDOW_TITLE | NK_WINDOW_NO_SCROLLBAR)) { nk_layout_row_dynamic(ctx, 30, 1); for (int i = 0; i < UI_COMPONENT_COUNT; i++) { if (ui_components[i].enabled && ui_components[i].render) { ui_components[i].render(ctx, &ui_state); // Add separator between components (except for the last one) if (i < UI_COMPONENT_COUNT - 1) { nk_layout_row_dynamic(ctx, 10, 1); nk_spacing(ctx, 1); } } } nk_group_end(ctx); } } nk_end(ctx); } // ============================================================================= // UI COMPONENT IMPLEMENTATIONS // ============================================================================= // Window information component void render_window_info_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Window Information:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "Window Size: %dx%d", window_width, window_height); nk_labelf(ctx, NK_TEXT_LEFT, "UI Scale Factor: %.2f", scale); } // Button component with click tracking void render_button_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float button_height = 30.0f * scale; float label_height = 25.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Button Examples:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, button_height, 2); if (nk_button_label(ctx, "Click Me")) { state->button_click_count++; snprintf(state->status_message, sizeof(state->status_message), "Standard button clicked! Count: %d (Scale: %.2f)", state->button_click_count, scale); printf("%s\n", state->status_message); } if (nk_button_label(ctx, "Action Button")) { state->button_click_count++; snprintf(state->status_message, sizeof(state->status_message), "Action button pressed! Count: %d (Scale: %.2f)", state->button_click_count, scale); printf("%s\n", state->status_message); } nk_layout_row_dynamic(ctx, button_height, 1); if (nk_button_label(ctx, "Wide Button")) { state->button_click_count++; snprintf(state->status_message, sizeof(state->status_message), "Wide button activated! Count: %d (Scale: %.2f)", state->button_click_count, scale); printf("%s\n", state->status_message); } nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "Total button clicks: %d", state->button_click_count); } // Text input component void render_text_input_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; float input_height = 30.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Text Input Example:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Enter text below:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, input_height, 1); nk_edit_string_zero_terminated(ctx, NK_EDIT_FIELD, state->text_buffer, sizeof(state->text_buffer), nk_filter_default); nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "You typed: %s", state->text_buffer); } // Slider component void render_slider_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; float widget_height = 30.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Slider Control:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, widget_height, 1); float old_value = state->slider_value; state->slider_value = nk_slide_float(ctx, 0.0f, state->slider_value, 1.0f, 0.01f); // Update status when slider value changes if (old_value != state->slider_value) { snprintf(state->status_message, sizeof(state->status_message), "Slider value changed to: %.2f", state->slider_value); } nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "Slider value: %.2f", state->slider_value); } // Checkbox component void render_checkbox_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; float widget_height = 30.0f * scale; nk_layout_row_dynamic(ctx, widget_height, 1); int old_value = state->checkbox_value; state->checkbox_value = nk_check_label(ctx, "Enable feature", state->checkbox_value); // Update status when checkbox value changes if (old_value != state->checkbox_value) { snprintf(state->status_message, sizeof(state->status_message), "Feature %s", state->checkbox_value ? "enabled" : "disabled"); } nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "Checkbox is: %s", state->checkbox_value ? "Checked" : "Unchecked"); } // Radio button component void render_radio_button_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; float widget_height = 30.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Radio Button Options:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, widget_height, 1); if (nk_option_label(ctx, "Option A", state->option_selected == 0)) { if (state->option_selected != 0) { state->option_selected = 0; snprintf(state->status_message, sizeof(state->status_message), "Selected Option A (Scale: %.2f)", scale); printf("%s\n", state->status_message); } } nk_layout_row_dynamic(ctx, widget_height, 1); if (nk_option_label(ctx, "Option B", state->option_selected == 1)) { if (state->option_selected != 1) { state->option_selected = 1; snprintf(state->status_message, sizeof(state->status_message), "Selected Option B (Scale: %.2f)", scale); printf("%s\n", state->status_message); } } nk_layout_row_dynamic(ctx, widget_height, 1); if (nk_option_label(ctx, "Option C", state->option_selected == 2)) { if (state->option_selected != 2) { state->option_selected = 2; snprintf(state->status_message, sizeof(state->status_message), "Selected Option C (Scale: %.2f)", scale); printf("%s\n", state->status_message); } } nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "Selected: Option %c", 'A' + state->option_selected); } // Status component to show current application status void render_status_component(struct nk_context* ctx, ui_state_t* state) { float scale = get_ui_scale_factor(); float label_height = 25.0f * scale; float button_height = 30.0f * scale; nk_layout_row_dynamic(ctx, label_height, 1); nk_label(ctx, "Status & Controls:", NK_TEXT_LEFT); nk_layout_row_dynamic(ctx, label_height, 1); nk_labelf(ctx, NK_TEXT_LEFT, "%s", state->status_message); // Add a reset button to demonstrate easy component extension nk_layout_row_dynamic(ctx, button_height, 2); if (nk_button_label(ctx, "Reset UI State")) { ui_state_reset(); } // Add a component toggle demonstration static int show_demo_toggle = 0; if (nk_button_label(ctx, show_demo_toggle ? "Hide Demo" : "Show Demo")) { show_demo_toggle = !show_demo_toggle; ui_component_set_enabled("Window Info", show_demo_toggle); snprintf(state->status_message, sizeof(state->status_message), "Window Info component %s", show_demo_toggle ? "enabled" : "disabled"); } } // ============================================================================= // UI STATE MANAGEMENT FUNCTIONS // ============================================================================= // Update status message void ui_state_update_status(const char* message) { if (message) { snprintf(ui_state.status_message, sizeof(ui_state.status_message), "%s", message); } } // Reset UI state to defaults void ui_state_reset(void) { strcpy(ui_state.text_buffer, "Hello World!"); ui_state.slider_value = 0.5f; ui_state.checkbox_value = 1; ui_state.option_selected = 0; ui_state.button_click_count = 0; strcpy(ui_state.status_message, "UI state reset to defaults"); printf("UI state has been reset to defaults\n"); } // ============================================================================= // UTILITY FUNCTIONS FOR ADDING NEW COMPONENTS // ============================================================================= // Function to enable/disable specific UI components void ui_component_set_enabled(const char* component_name, int enabled) { for (int i = 0; i < UI_COMPONENT_COUNT; i++) { if (strcmp(ui_components[i].name, component_name) == 0) { ui_components[i].enabled = enabled; printf("Component '%s' %s\n", component_name, enabled ? "enabled" : "disabled"); return; } } printf("Component '%s' not found\n", component_name); } // Function to get component status int ui_component_is_enabled(const char* component_name) { for (int i = 0; i < UI_COMPONENT_COUNT; i++) { if (strcmp(ui_components[i].name, component_name) == 0) { return ui_components[i].enabled; } } return 0; // Component not found } // Cleanup resources void cleanup(void) { // Cleanup Nuklear resources if (ctx) { nk_glfw3_shutdown(&glfw); ctx = NULL; } // Cleanup GLFW resources if (window) { glfwDestroyWindow(window); } glfwTerminate(); } int main(int argc, char** argv) { printf("Nuklear GLFW3 Demo Application - Modular UI Components\n"); printf("Initializing modular UI component system...\n"); printf("Available components: %zu\n", UI_COMPONENT_COUNT); // List all available components for (int i = 0; i < UI_COMPONENT_COUNT; i++) { printf(" - %s: %s\n", ui_components[i].name, ui_components[i].enabled ? "enabled" : "disabled"); } // Initialize GLFW and create window init_glfw(); // Initialize Nuklear init_nuklear(); // Set initial status message ui_state_update_status("Modular UI system initialized successfully"); // Main render loop while (!glfwWindowShouldClose(window)) { // Poll events and handle input glfwPollEvents(); nk_glfw3_new_frame(&glfw); // Render UI using modular component system render_ui(ctx); // Clear the screen glClearColor(0.10f, 0.18f, 0.24f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); // Render Nuklear nk_glfw3_render(&glfw, NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER); // Swap front and back buffers glfwSwapBuffers(window); } // Cleanup cleanup(); printf("Modular UI system shut down successfully\n"); printf("Application closed successfully\n"); return 0; }