Initial commit

This commit is contained in:
Patrick 2025-10-03 20:16:11 +02:00
commit 56f7cd1875
105 changed files with 22109 additions and 0 deletions

125
src/gui/gui.c Normal file
View file

@ -0,0 +1,125 @@
#include <pgpl.h>
#include <stdio.h>
#include <stdlib.h>
static bool pgpl_gui_internal_event_loop(PGPL_Window *window,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data,
void *user_data) {
PGPL_Gui *gui = user_data;
pgpl_mutex_lock(gui->mutex);
if (event_type == PGPL_WINDOW_EVENT_NONE) {
} else if (event_type == PGPL_WINDOW_EVENT_RENDER_REQUEST) {
PGPL_Renderer *renderer =
pgpl_window_start_render(window, gui->theme.background_color);
uint32_t width, height;
uint32_t length = pgpl_vector_get_length(gui->widgets);
pgpl_window_get_size(window, &width, &height);
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(gui->widgets, i);
PGPL_GuiTheme *theme = &gui->theme;
if (widget->use_theme_override) {
theme = &widget->theme_override;
}
pgpl_gui_widget_render_full(widget, renderer, theme, 0, 0, width, height);
}
pgpl_window_finish_render(window, renderer);
} else if (event_type == PGPL_WINDOW_EVENT_CLOSE) {
pgpl_gui_destroy(gui, true);
return false;
} else if (event_type == PGPL_WINDOW_EVENT_ERROR) {
printf("PGPL_Window has experienced an unexpected error!\nExiting...");
} else {
uint32_t width, height;
uint32_t length = pgpl_vector_get_length(gui->widgets);
pgpl_window_get_size(window, &width, &height);
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(gui->widgets, i);
widget->event(widget, &gui->theme, gui, event_type, event_data,
widget->offset_x, widget->offset_y, width, height);
}
}
pgpl_mutex_unlock(gui->mutex);
return true;
}
PGPL_Gui *pgpl_gui_create(const char *title, uint32_t width, uint32_t height,
int32_t x, int32_t y, PGPL_GuiTheme *theme,
void *app_data) {
PGPL_Gui *gui = malloc(sizeof(*gui));
gui->app_data = app_data;
gui->widgets = pgpl_vector_create(sizeof(PGPL_GuiWidget *));
gui->theme = *theme;
gui->window_thread = pgpl_window_thread_create(
title, width, height, x, y, pgpl_gui_internal_event_loop, gui);
gui->mutex = pgpl_mutex_create();
return gui;
}
void pgpl_gui_destroy(PGPL_Gui *gui, bool internal) {
uint32_t length = pgpl_vector_get_length(gui->widgets);
if (!internal) {
pgpl_mutex_lock(gui->mutex);
}
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(gui->widgets, i);
widget->destroy(widget);
}
pgpl_mutex_destroy(gui->mutex);
if (!internal) {
pgpl_window_thread_destroy(gui->window_thread);
}
pgpl_vector_destroy(gui->widgets);
free(gui);
}
void pgpl_gui_run_and_show(PGPL_Gui *gui, bool wait) {
pgpl_window_thread_show(gui->window_thread);
if (wait) {
pgpl_window_thread_await_destruction(gui->window_thread);
}
}
void pgpl_gui_widget_add(PGPL_Gui *gui, PGPL_GuiWidget *widget) {
pgpl_mutex_lock(gui->mutex);
pgpl_vector_push_back(gui->widgets, &widget);
pgpl_mutex_unlock(gui->mutex);
}
uint32_t pgpl_gui_widget_amount(PGPL_Gui *gui) {
return pgpl_vector_get_length(gui->widgets);
}
void pgpl_gui_widget_remove(PGPL_Gui *gui, uint32_t index) {
pgpl_mutex_lock(gui->mutex);
PGPL_GuiWidget *widget =
*(PGPL_GuiWidget **)pgpl_vector_delete_index(gui->widgets, index);
widget->destroy(widget);
pgpl_mutex_unlock(gui->mutex);
}
PGPL_GuiWidget *pgpl_gui_widget_get(PGPL_Gui *gui, uint32_t index) {
pgpl_mutex_lock(gui->mutex);
return *(PGPL_GuiWidget **)pgpl_vector_get_index(gui->widgets, index);
pgpl_mutex_unlock(gui->mutex);
}

187
src/gui/helpers.c Normal file
View file

@ -0,0 +1,187 @@
#include <pgpl.h>
void pgpl_gui_theme_configure(PGPL_GuiTheme *theme, PGPL_Color base_color,
double content_font_size, PGPL_Font *font) {
theme->background_color = pgpl_color_divide(base_color, 8);
theme->widget_background_color[0] = pgpl_color_divide(base_color, 5);
theme->widget_background_color[1] = pgpl_color_divide(base_color, 4);
theme->widget_background_color[2] = pgpl_color_divide(base_color, 3);
theme->text_color[0] = pgpl_color_divide(base_color, 1.5);
theme->text_color[1] = pgpl_color_divide(base_color, 1.25);
theme->text_color[2] = base_color;
theme->font_size[0] = content_font_size * 3;
theme->font_size[1] = content_font_size * 2;
theme->font_size[2] = content_font_size;
theme->margin.top = content_font_size / 6;
theme->margin.left = theme->margin.top;
theme->margin.bottom = theme->margin.top;
theme->margin.right = theme->margin.top;
theme->border.top = content_font_size / 12;
theme->border.left = theme->border.top;
theme->border.bottom = theme->border.top;
theme->border.right = theme->border.top;
theme->padding.top = content_font_size / 6;
theme->padding.left = theme->padding.top;
theme->padding.bottom = theme->padding.top;
theme->padding.right = theme->padding.top;
theme->font = font;
}
void pgpl_gui_widget_max_content_size(PGPL_GuiWidget *widget,
PGPL_GuiTheme *theme, double *width,
double *height, double max_width,
double max_height) {
*width = max_width;
*height = max_height;
if (!widget->ignore_margin) {
*width -= theme->margin.left + theme->margin.right;
*height -= theme->margin.top + theme->margin.bottom;
}
if (!widget->ignore_border) {
*width -= theme->border.left + theme->border.right;
*height -= theme->border.top + theme->border.bottom;
}
if (!widget->ignore_padding) {
*width -= theme->padding.left + theme->padding.right;
*height -= theme->padding.top + theme->padding.bottom;
}
}
void pgpl_gui_widget_render_full(PGPL_GuiWidget *widget,
PGPL_Renderer *renderer, PGPL_GuiTheme *theme,
double x, double y, double max_width,
double max_height) {
double width, height;
if (widget->use_theme_override) {
theme = &widget->theme_override;
}
x += +widget->offset_x;
y += +widget->offset_y;
widget->get_content_size(widget, theme, &width, &height, max_width,
max_height);
if (!widget->expand_x) {
max_width = width;
if (!widget->ignore_margin) {
max_width += theme->margin.left + theme->margin.right;
}
if (!widget->ignore_border) {
max_width += theme->border.left + theme->border.right;
}
if (!widget->ignore_padding) {
max_width += theme->padding.left + theme->padding.right;
}
}
if (!widget->expand_y) {
max_height = height;
if (!widget->ignore_margin) {
max_height += theme->margin.top + theme->margin.bottom;
}
if (!widget->ignore_border) {
max_height += theme->border.top + theme->border.bottom;
}
if (!widget->ignore_padding) {
max_height += theme->padding.top + theme->padding.bottom;
}
}
if (!widget->ignore_margin) {
x += theme->margin.left;
y += theme->margin.top;
max_width -= theme->margin.left + theme->margin.right;
max_height -= theme->margin.top + theme->margin.bottom;
}
if (widget->border) {
pgpl_render_rectangle(renderer, theme->text_color[widget->gui_color], x, y,
max_width, max_height);
}
if (!widget->ignore_border) {
x += theme->border.left;
y += theme->border.top;
max_width -= theme->border.left + theme->border.right;
max_height -= theme->border.top + theme->border.bottom;
}
if (widget->background) {
pgpl_render_rectangle(renderer,
theme->widget_background_color[widget->gui_color], x,
y, max_width, max_height);
} else {
pgpl_render_rectangle(renderer, theme->background_color, x, y, max_width,
max_height);
}
if (!widget->ignore_padding) {
x += theme->padding.left;
y += theme->padding.top;
max_width -= theme->padding.left + theme->padding.right;
max_height -= theme->padding.top + theme->padding.bottom;
}
x += (max_width - width) / 2;
y += (max_height - height) / 2;
widget->render_content(widget, renderer, theme, x, y, max_width, max_height);
}
bool pgpl_gui_widget_within_bounds(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double widget_x, double widget_y,
double max_width, double max_height,
double x, double y) {
(void)widget;
widget_x += theme->margin.left;
widget_y += theme->margin.top;
max_width -= theme->margin.left + theme->margin.right;
max_height -= theme->margin.top + theme->margin.bottom;
if ((x >= widget_x && x <= (widget_x + max_width)) &&
(y >= widget_y && y <= (widget_y + max_height))) {
return true;
}
return false;
}
void pgpl_gui_widget_configure(PGPL_GuiWidget *widget, double offset_x,
double offset_y, bool expand_x, bool expand_y,
bool border, bool background,
PGPL_GuiFontSize font_size,
PGPL_GuiTheme *theme_override,
bool use_theme_override, bool ignore_margin,
bool ignore_border, bool ignore_padding) {
widget->offset_x = offset_x;
widget->offset_y = offset_y;
widget->expand_x = expand_x;
widget->expand_y = expand_y;
widget->border = border;
widget->background = background;
widget->font_size = font_size;
if (theme_override != NULL) {
widget->theme_override = *theme_override;
}
widget->use_theme_override = use_theme_override;
widget->ignore_margin = ignore_margin;
widget->ignore_border = ignore_border;
widget->ignore_padding = ignore_padding;
}

88
src/gui/widgets/button.c Normal file
View file

@ -0,0 +1,88 @@
#include <pgpl.h>
#include <stdlib.h>
static void get_content_size(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double *width, double *height, double max_width,
double max_height) {
PGPL_GuiButtonWidget *button_widget = (PGPL_GuiButtonWidget *)widget;
PGPL_Rectangle rect;
(void)max_width;
(void)max_height;
pgpl_font_string_dimensions(theme->font, button_widget->text,
theme->font_size[widget->font_size], &rect);
*width = rect.right - rect.left;
*height = rect.bottom - rect.top;
}
static void render_content(PGPL_GuiWidget *widget, PGPL_Renderer *renderer,
PGPL_GuiTheme *theme, double x, double y,
double max_width, double max_height) {
PGPL_GuiButtonWidget *button_widget = (PGPL_GuiButtonWidget *)widget;
(void)max_width;
(void)max_height;
pgpl_font_render_string(renderer, theme->font, button_widget->text,
theme->text_color[widget->gui_color], x, y,
theme->font_size[widget->font_size]);
}
static void event(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme, PGPL_Gui *gui,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data, double x, double y,
double max_width, double max_height) {
PGPL_GuiButtonWidget *button_widget = (PGPL_GuiButtonWidget *)widget;
if (event_type == PGPL_WINDOW_EVENT_MOUSE_PRESS ||
event_type == PGPL_WINDOW_EVENT_MOUSE_RELEASE ||
event_type == PGPL_WINDOW_EVENT_MOUSE_MOVE) {
if (pgpl_gui_widget_within_bounds(widget, theme, x, y, max_width,
max_height, event_data->input_event.x,
event_data->input_event.y)) {
if (event_type == PGPL_WINDOW_EVENT_MOUSE_PRESS &&
event_data->input_event.key == PGPL_WINDOW_MOUSE_BUTTON_LEFT) {
widget->gui_color = PGPL_GUI_STATUS_COLOR_ACTIVE;
return;
} else if (event_type == PGPL_WINDOW_EVENT_MOUSE_RELEASE &&
event_data->input_event.key == PGPL_WINDOW_MOUSE_BUTTON_LEFT) {
if (widget->gui_color == PGPL_GUI_STATUS_COLOR_ACTIVE) {
button_widget->click_callback(gui->app_data);
}
widget->gui_color = PGPL_GUI_STATUS_COLOR_HOVER;
return;
} else {
if (widget->gui_color != PGPL_GUI_STATUS_COLOR_ACTIVE) {
widget->gui_color = PGPL_GUI_STATUS_COLOR_HOVER;
}
return;
}
}
}
widget->gui_color = PGPL_GUI_STATUS_COLOR_NORMAL;
}
static void destroy(PGPL_GuiWidget *widget) {
pgpl_gui_button_widget_destroy((PGPL_GuiButtonWidget *)widget);
}
PGPL_GuiButtonWidget *
pgpl_gui_button_widget_create(const char *text,
void (*click_callback)(void *app_data)) {
PGPL_GuiButtonWidget *button_widget = calloc(1, sizeof(*button_widget));
button_widget->parent.id = "PGPL_GuiButtonWidget";
button_widget->parent.get_content_size = get_content_size;
button_widget->parent.render_content = render_content;
button_widget->parent.event = event;
button_widget->parent.destroy = destroy;
button_widget->text = text;
button_widget->click_callback = click_callback;
return button_widget;
}
void pgpl_gui_button_widget_destroy(PGPL_GuiButtonWidget *button_widget) {
free(button_widget);
}

169
src/gui/widgets/container.c Normal file
View file

@ -0,0 +1,169 @@
#include <pgpl.h>
#include <stdlib.h>
static void get_content_size(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double *width, double *height, double max_width,
double max_height) {
(void)widget;
pgpl_gui_widget_max_content_size(widget, theme, width, height, max_width,
max_height);
}
static void render_content(PGPL_GuiWidget *widget, PGPL_Renderer *renderer,
PGPL_GuiTheme *theme, double x, double y,
double max_width, double max_height) {
PGPL_GuiContainerLayout *container_layout = (PGPL_GuiContainerLayout *)widget;
uint32_t length = pgpl_vector_get_length(container_layout->widgets);
switch (container_layout->direction) {
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_VERTICAL:
max_height /= length;
break;
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_HORIZONTAL:
max_width /= length;
break;
}
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *child_widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(container_layout->widgets, i);
pgpl_gui_widget_render_full(child_widget, renderer, theme, x, y, max_width,
max_height);
switch (container_layout->direction) {
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_VERTICAL:
y += max_height;
break;
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_HORIZONTAL:
x += max_width;
break;
}
}
}
static void event(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme, PGPL_Gui *gui,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data, double x, double y,
double max_width, double max_height) {
PGPL_GuiContainerLayout *container_layout = (PGPL_GuiContainerLayout *)widget;
uint32_t length = pgpl_vector_get_length(container_layout->widgets);
pgpl_gui_widget_max_content_size(widget, theme, &max_width, &max_height,
max_width, max_height);
switch (container_layout->direction) {
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_VERTICAL:
max_height /= length;
break;
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_HORIZONTAL:
max_width /= length;
break;
}
if (!widget->ignore_margin) {
x += theme->margin.left;
y += theme->margin.top;
}
if (!widget->ignore_border) {
x += theme->border.left;
y += theme->border.top;
}
if (!widget->ignore_padding) {
x += theme->padding.left;
y += theme->padding.top;
}
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *child_widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(container_layout->widgets, i);
if (child_widget->use_theme_override) {
theme = &child_widget->theme_override;
}
child_widget->event(child_widget, theme, gui, event_type, event_data,
x + child_widget->offset_x, y + child_widget->offset_y,
max_width, max_height);
switch (container_layout->direction) {
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_VERTICAL:
y += max_height;
break;
case PGPL_GUI_CONTAINER_LAYOUT_DIRECTION_HORIZONTAL:
x += max_width;
break;
}
}
}
static void destroy(PGPL_GuiWidget *widget) {
pgpl_gui_container_layout_destroy((PGPL_GuiContainerLayout *)widget);
}
PGPL_GuiContainerLayout *
pgpl_gui_container_layout_create(PGPL_GuiContainerLayoutDirection direction) {
PGPL_GuiContainerLayout *container_layout =
calloc(1, sizeof(*container_layout));
container_layout->parent.id = "PGPL_GuiContainerLayout";
container_layout->parent.get_content_size = get_content_size;
container_layout->parent.render_content = render_content;
container_layout->parent.event = event;
container_layout->parent.destroy = destroy;
container_layout->widgets = pgpl_vector_create(sizeof(PGPL_GuiWidget *));
container_layout->direction = direction;
container_layout->mutex = pgpl_mutex_create();
return container_layout;
}
void pgpl_gui_container_layout_widget_add(
PGPL_GuiContainerLayout *container_layout, PGPL_GuiWidget *widget) {
pgpl_mutex_lock(container_layout->mutex);
pgpl_vector_push_back(container_layout->widgets, &widget);
pgpl_mutex_unlock(container_layout->mutex);
}
PGPL_GuiWidget *
pgpl_gui_container_layout_widget_get(PGPL_GuiContainerLayout *container_layout,
uint32_t index) {
PGPL_GuiWidget *widget;
pgpl_mutex_lock(container_layout->mutex);
widget = *(PGPL_GuiWidget **)pgpl_vector_delete_index(
container_layout->widgets, index);
pgpl_mutex_unlock(container_layout->mutex);
return widget;
}
void pgpl_gui_container_layout_widget_delete(
PGPL_GuiContainerLayout *container_layout, uint32_t index) {
PGPL_GuiWidget *widget;
pgpl_mutex_lock(container_layout->mutex);
widget = *(PGPL_GuiWidget **)pgpl_vector_delete_index(
container_layout->widgets, index);
widget->destroy(widget);
pgpl_mutex_unlock(container_layout->mutex);
}
uint32_t pgpl_gui_container_layout_widget_amount(
PGPL_GuiContainerLayout *container_layout) {
return pgpl_vector_get_length(container_layout->widgets);
}
void pgpl_gui_container_layout_destroy(
PGPL_GuiContainerLayout *container_layout) {
uint32_t length = pgpl_vector_get_length(container_layout->widgets);
pgpl_mutex_lock(container_layout->mutex);
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(container_layout->widgets, i);
widget->destroy(widget);
}
pgpl_mutex_destroy(container_layout->mutex);
pgpl_vector_destroy(container_layout->widgets);
free(container_layout);
}

59
src/gui/widgets/empty.c Normal file
View file

@ -0,0 +1,59 @@
#include <pgpl.h>
#include <stdlib.h>
static void get_content_size(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double *width, double *height, double max_width,
double max_height) {
(void)widget;
(void)theme;
(void)max_width;
(void)max_height;
*width = 0;
*height = 0;
}
static void render_content(PGPL_GuiWidget *widget, PGPL_Renderer *renderer,
PGPL_GuiTheme *theme, double x, double y,
double max_width, double max_height) {
(void)widget;
(void)theme;
(void)renderer;
(void)x;
(void)y;
(void)max_width;
(void)max_height;
}
static void event(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme, PGPL_Gui *gui,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data, double x, double y,
double max_width, double max_height) {
(void)widget;
(void)theme;
(void)gui;
(void)event_type;
(void)event_data;
(void)x;
(void)y;
(void)max_width;
(void)max_height;
}
static void destroy(PGPL_GuiWidget *widget) {
pgpl_gui_empty_widget_destroy(widget);
}
PGPL_GuiWidget *pgpl_gui_empty_widget_create(void) {
PGPL_GuiWidget *empty_widget = calloc(1, sizeof(*empty_widget));
empty_widget->id = "PGPL_GuiWidget";
empty_widget->get_content_size = get_content_size;
empty_widget->render_content = render_content;
empty_widget->event = event;
empty_widget->destroy = destroy;
return empty_widget;
}
void pgpl_gui_empty_widget_destroy(PGPL_GuiWidget *empty_widget) {
free(empty_widget);
}

65
src/gui/widgets/text.c Normal file
View file

@ -0,0 +1,65 @@
#include <pgpl.h>
#include <stdlib.h>
static void get_content_size(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double *width, double *height, double max_width,
double max_height) {
PGPL_GuiTextWidget *text_widget = (PGPL_GuiTextWidget *)widget;
PGPL_Rectangle rect;
(void)max_width;
(void)max_height;
pgpl_font_string_dimensions(theme->font, text_widget->text,
theme->font_size[widget->font_size], &rect);
*width = rect.right - rect.left;
*height = rect.bottom - rect.top;
}
static void render_content(PGPL_GuiWidget *widget, PGPL_Renderer *renderer,
PGPL_GuiTheme *theme, double x, double y,
double max_width, double max_height) {
PGPL_GuiTextWidget *text_widget = (PGPL_GuiTextWidget *)widget;
(void)max_width;
(void)max_height;
pgpl_font_render_string(renderer, theme->font, text_widget->text,
theme->text_color[widget->gui_color], x, y,
theme->font_size[widget->font_size]);
}
static void event(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme, PGPL_Gui *gui,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data, double x, double y,
double max_width, double max_height) {
(void)widget;
(void)theme;
(void)gui;
(void)event_type;
(void)event_data;
(void)x;
(void)y;
(void)max_width;
(void)max_height;
}
static void destroy(PGPL_GuiWidget *widget) {
pgpl_gui_text_widget_destroy((PGPL_GuiTextWidget *)widget);
}
PGPL_GuiTextWidget *pgpl_gui_text_widget_create(const char *text) {
PGPL_GuiTextWidget *text_widget = calloc(1, sizeof(*text_widget));
text_widget->parent.id = "PGPL_GuiTextWidget";
text_widget->parent.get_content_size = get_content_size;
text_widget->parent.render_content = render_content;
text_widget->parent.event = event;
text_widget->parent.destroy = destroy;
text_widget->text = text;
return text_widget;
}
void pgpl_gui_text_widget_destroy(PGPL_GuiTextWidget *text_widget) {
free(text_widget);
}