Files
Nuklear-GLFW3-Demo/main.c
2025-07-31 22:29:51 -04:00

685 lines
24 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <GL/gl3w.h>
#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"
// Window dimensions
static int window_width = 800;
static int window_height = 600;
static int min_window_width = 400;
static int min_window_height = 300;
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);
}
// 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");
// Test minimum window size constraint
printf("Testing minimum window size constraint...\n");
glfwSetWindowSize(window, 200, 150); // 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 = 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_MOVABLE|NK_WINDOW_SCALABLE|
NK_WINDOW_MINIMIZABLE|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_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;
}