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);
}

7
src/pgpl.c Normal file
View file

@ -0,0 +1,7 @@
#include <pgpl.h>
PGPL_Mutex *render_lock;
void pgpl_init(void) { render_lock = pgpl_mutex_create(); }
void pgpl_deinit(void) { pgpl_mutex_destroy(render_lock); }

261
src/render/font.c Normal file
View file

@ -0,0 +1,261 @@
#include "internal.h"
#include <float.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#include <stb/stb_truetype.h>
#ifdef __WIN32__
#include <windows.h>
#endif
#define CHAR_AMOUNT 256
#define BANK_AMOUNT 256
typedef struct PGPL_FontBank {
stbtt_bakedchar baked[CHAR_AMOUNT];
uint8_t *bitmap;
PGPL_Texture *texture;
} PGPL_FontBank;
struct PGPL_Font {
PGPL_FontBank *banks[BANK_AMOUNT];
uint32_t glyph_size;
uint32_t chars_horiontal;
uint32_t chars_vertical;
uint8_t *data;
};
/* Converts a string to a wide string, don't forget to call free() on the
* returned pointer. */
static wchar_t *str_to_wstr(const char *str) {
size_t length = strlen(str);
wchar_t *wstr = malloc((length + 1) * sizeof(*wstr));
setlocale(LC_ALL, "en_US.UTF-8");
/* mbstowcs works just fine with UTF-8 strings on most platforms, except on
* Windows, where we have to use MultiByteToWideChar to do the exact same
* thing. */
#if defined(__WIN32__)
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, str, -1, wstr, length + 1);
#else
mbstowcs(wstr, str, length + 1);
#endif
return wstr;
}
PGPL_Font *pgpl_font_create_from_file(const char *filename,
uint32_t glyph_size) {
PGPL_Font *font;
FILE *font_file = fopen(filename, "r");
uint32_t size;
uint8_t *font_data;
if (font_file == NULL) {
perror("Failed to open font");
return NULL;
}
fseek(font_file, 0, SEEK_END);
size = ftell(font_file);
font_data = malloc(size);
rewind(font_file);
fread(font_data, size, 1, font_file);
font = pgpl_font_create(font_data, glyph_size);
fclose(font_file);
return font;
}
void pgpl_font_internal_bake(PGPL_Font *font, uint32_t index) {
font->banks[index] = malloc(sizeof(*font->banks[index]));
font->banks[index]->bitmap =
malloc(CHAR_AMOUNT * font->glyph_size * font->glyph_size);
stbtt_BakeFontBitmap(
font->data, 0, font->glyph_size, font->banks[index]->bitmap,
font->glyph_size * font->chars_horiontal,
font->glyph_size * font->chars_vertical, CHAR_AMOUNT * index, CHAR_AMOUNT,
font->banks[index]->baked);
}
PGPL_Font *pgpl_font_create(uint8_t *data, uint32_t glyph_size) {
PGPL_Font *font = malloc(sizeof(*font));
for (uint32_t i = 0; i < BANK_AMOUNT; i++) {
font->banks[i] = NULL;
}
font->glyph_size = glyph_size;
font->chars_vertical = 8;
font->chars_horiontal = CHAR_AMOUNT / font->chars_vertical;
font->data = data;
return font;
}
void pgpl_font_destroy(PGPL_Renderer *renderer, PGPL_Font *font) {
for (uint32_t i = 0; i < BANK_AMOUNT; i++) {
if (font->banks[i] != NULL) {
pgpl_render_destroy_texture(renderer, font->banks[i]->texture);
free(font->banks[i]);
}
}
free(font->data);
free(font);
}
void pgpl_font_render_glyph(PGPL_Renderer *renderer, PGPL_Font *font, wchar_t c,
PGPL_Color color, double x, double y, double size,
double *xoff, double *yoff) {
stbtt_aligned_quad q;
uint32_t bank_index = c / 256;
uint32_t bank_offset = c % 256;
double factor = size / font->glyph_size;
float xoff_stb = *xoff;
float yoff_stb = *yoff;
if (font->banks[bank_index] == NULL) {
pgpl_font_internal_bake(font, bank_index);
}
if (font->banks[bank_index]->bitmap != NULL) {
font->banks[bank_index]->texture = pgpl_render_create_texture(
renderer, font->banks[bank_index]->bitmap,
font->glyph_size * font->chars_horiontal,
font->glyph_size * font->chars_vertical, PGPL_TEXTURE_FILTER_LINEAR,
PGPL_TEXTURE_FORMAT_BW);
free(font->banks[bank_index]->bitmap);
font->banks[bank_index]->bitmap = NULL;
}
stbtt_GetBakedQuad(font->banks[bank_index]->baked,
font->glyph_size * font->chars_horiontal,
font->glyph_size * font->chars_vertical, bank_offset,
&xoff_stb, &yoff_stb, &q, 0);
pgpl_render_texture_extended(
renderer, font->banks[bank_index]->texture, (x + q.x0 * factor),
(y + q.y0 * factor + size / 1.75), (q.x1 - q.x0) * factor,
(q.y1 - q.y0) * factor, color, q.s0, q.t0, q.s1, q.t1);
*xoff = xoff_stb;
*yoff = yoff_stb;
}
/* This renders some text, also converts it to a wide string so that it
* supports Unicode characters when rendering.
*/
void pgpl_font_render_string(PGPL_Renderer *renderer, PGPL_Font *font,
const char *str, PGPL_Color color, double x,
double y, double size) {
wchar_t *wstr = str_to_wstr(str);
pgpl_font_render_wstring(renderer, font, wstr, color, x, y, size);
free(wstr);
}
void pgpl_font_render_wstring(PGPL_Renderer *renderer, PGPL_Font *font,
wchar_t *str, PGPL_Color color, double x,
double y, double size) {
double xoff = 0;
double yoff = 0;
for (uint32_t i = 0; str[i] != '\0'; i++) {
if (str[i] == '\n') {
xoff = 0;
y += size;
continue;
}
pgpl_font_render_glyph(renderer, font, str[i], color, x, y, size, &xoff,
&yoff);
}
}
/* Calculates the glyph coordinates, and returns them in the passed pointers. */
void pgpl_font_glyph_dimensions(PGPL_Font *font, wchar_t c, double size,
double *xoff, double *yoff,
PGPL_Rectangle *rectangle) {
stbtt_aligned_quad q;
uint32_t bank_index = c / 256;
uint32_t bank_offset = c % 256;
double factor = size / font->glyph_size;
float xoff_stb = *xoff;
float yoff_stb = *yoff;
if (font->banks[bank_index] == NULL) {
pgpl_font_internal_bake(font, bank_index);
}
stbtt_GetBakedQuad(font->banks[bank_index]->baked,
font->glyph_size * font->chars_horiontal,
font->glyph_size * font->chars_vertical, bank_offset,
&xoff_stb, &yoff_stb, &q, 0);
rectangle->top = q.y0 * factor + size / 1.5;
rectangle->left = q.x0 * factor;
rectangle->bottom = q.y1 * factor + size / 1.5;
rectangle->right = q.x1 * factor;
*xoff = xoff_stb;
*yoff = yoff_stb;
}
/* Calculates the coordinates of an entire string. Also returns them in the
* passed pointers.
*/
void pgpl_font_string_dimensions(PGPL_Font *font, const char *str, double size,
PGPL_Rectangle *rectangle) {
wchar_t *wstr = str_to_wstr(str);
pgpl_font_wstring_dimensions(font, wstr, size, rectangle);
free(wstr);
}
void pgpl_font_wstring_dimensions(PGPL_Font *font, wchar_t *str, double size,
PGPL_Rectangle *rectangle) {
double xoff = 0;
double yoff = 0;
rectangle->top = DBL_MAX;
rectangle->left = DBL_MAX;
rectangle->bottom = DBL_MIN;
rectangle->right = DBL_MIN;
for (uint32_t i = 0; str[i] != '\0'; i++) {
PGPL_Rectangle glyph_rectangle = {0, 0, 0, 0};
if (str[i] == '\n') {
xoff = 0;
yoff += size;
continue;
}
pgpl_font_glyph_dimensions(font, str[i], size, &xoff, &yoff, rectangle);
if (glyph_rectangle.top < rectangle->top) {
rectangle->top = glyph_rectangle.top;
}
if (glyph_rectangle.bottom > rectangle->bottom) {
rectangle->bottom = glyph_rectangle.bottom;
}
if (glyph_rectangle.left < rectangle->left) {
rectangle->left = glyph_rectangle.left;
}
if (glyph_rectangle.right > rectangle->right) {
rectangle->right = glyph_rectangle.right;
}
}
}

15
src/render/internal.h Normal file
View file

@ -0,0 +1,15 @@
#include <GL/gl.h>
#include <pgpl.h>
/* General Notes: All of the gl_* variables are translated from pixels to the
* range [-1.0, 1.0] so that they can be rendered with OpenGL.
*
* It's also important to note that this file should be platform independent,
* this code should be able to run on all OpenGL supporting devices.
*/
#define PI 3.1415926535897932384626433
struct PGPL_Texture {
GLuint id;
};

96
src/render/shapes.c Normal file
View file

@ -0,0 +1,96 @@
#include "internal.h"
#include <math.h>
void pgpl_render_rectangle(PGPL_Renderer *renderer, PGPL_Color color, double x,
double y, double width, double height) {
double gl_x = x / renderer->width;
double gl_y = y / renderer->height;
double gl_width = width / renderer->width;
double gl_height = height / renderer->height;
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_QUADS);
glVertex2d(gl_x, gl_y);
glVertex2d(gl_x + gl_width, gl_y);
glVertex2d(gl_x + gl_width, gl_y + gl_height);
glVertex2d(gl_x, gl_y + gl_height);
glEnd();
}
void pgpl_render_line(PGPL_Renderer *renderer, PGPL_Color color, double x0,
double y0, double x1, double y1) {
double gl_x0 = x0 / renderer->width;
double gl_y0 = y0 / renderer->height;
double gl_x1 = x1 / renderer->width;
double gl_y1 = y1 / renderer->height;
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_LINES);
glVertex2d(gl_x0, gl_y0);
glVertex2d(gl_x1, gl_y1);
glEnd();
}
void pgpl_render_circle(PGPL_Renderer *renderer, PGPL_Color color, double x,
double y, double radius, double triangle_amount) {
GLfloat two_pi = 2 * PI;
double gl_x, gl_y;
x += radius;
y += radius;
gl_x = x / renderer->width;
gl_y = y / renderer->height;
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_TRIANGLE_FAN);
glVertex2d(gl_x, gl_y);
for (uint32_t i = 0; i <= triangle_amount; i++) {
gl_x = (x + (radius * cos(i * two_pi / triangle_amount))) / renderer->width;
gl_y =
(y + (radius * sin(i * two_pi / triangle_amount))) / renderer->height;
glVertex2d(gl_x, gl_y);
}
glEnd();
}
void pgpl_render_point(PGPL_Renderer *renderer, PGPL_Color color, double x,
double y) {
double gl_x = x / renderer->width;
double gl_y = y / renderer->height;
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_POINTS);
glVertex2d(gl_x, gl_y);
glEnd();
}
void pgpl_render_triangle(PGPL_Renderer *renderer, PGPL_Color color, double ax,
double ay, double bx, double by, double cx,
double cy) {
double gl_ax = ax / renderer->width;
double gl_ay = ay / renderer->height;
double gl_bx = bx / renderer->width;
double gl_by = by / renderer->height;
double gl_cx = cx / renderer->width;
double gl_cy = cy / renderer->height;
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_TRIANGLES);
glVertex2d(gl_ax, gl_ay);
glVertex2d(gl_bx, gl_by);
glVertex2d(gl_cx, gl_cy);
glEnd();
}

143
src/render/texture.c Normal file
View file

@ -0,0 +1,143 @@
#include "internal.h"
#include <stdio.h>
#include <stdlib.h>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#include <stb/stb_image.h>
PGPL_Texture *
pgpl_render_create_texture_file_memory(PGPL_Renderer *renderer,
const uint8_t *file_data, uint32_t len,
PGPL_TextureFilter filter) {
int32_t x, y, comp;
stbi_uc *data =
stbi_load_from_memory(file_data, len, &x, &y, &comp, STBI_rgb_alpha);
PGPL_Texture *texture;
if (data != NULL) {
texture = pgpl_render_create_texture(renderer, data, x, y, filter,
PGPL_TEXTURE_FORMAT_RGBA);
} else {
fprintf(stderr, "Failed to open image: %s\n", stbi_failure_reason());
return NULL;
}
stbi_image_free(data);
return texture;
}
PGPL_Texture *pgpl_render_create_texture_file(PGPL_Renderer *renderer,
const char *filename,
PGPL_TextureFilter filter) {
int32_t x, y, comp;
stbi_uc *data = stbi_load(filename, &x, &y, &comp, STBI_rgb_alpha);
PGPL_Texture *texture;
if (data != NULL) {
texture = pgpl_render_create_texture(renderer, data, x, y, filter,
PGPL_TEXTURE_FORMAT_RGBA);
} else {
fprintf(stderr, "Failed to open image: %s\n", stbi_failure_reason());
return NULL;
}
stbi_image_free(data);
return texture;
}
/* Creates an OpenGL Texture. The filterers and format are specified with a
* switch case. */
PGPL_Texture *pgpl_render_create_texture(PGPL_Renderer *renderer,
const uint8_t *data, uint32_t width,
uint32_t height,
PGPL_TextureFilter filter,
PGPL_TextureFormat format) {
PGPL_Texture *texture = malloc(sizeof(*texture));
int32_t gl_filter = 0;
int32_t gl_format = 0;
(void)renderer;
switch (filter) {
case PGPL_TEXTURE_FILTER_LINEAR:
gl_filter = GL_LINEAR;
break;
case PGPL_TEXTURE_FILTER_NEAREST:
gl_filter = GL_NEAREST;
break;
}
switch (format) {
case PGPL_TEXTURE_FORMAT_RGBA:
gl_format = GL_RGBA;
break;
case PGPL_TEXTURE_FORMAT_RGB:
gl_format = GL_RGB;
break;
case PGPL_TEXTURE_FORMAT_BW:
gl_format = GL_ALPHA;
break;
}
glGenTextures(1, &texture->id);
glBindTexture(GL_TEXTURE_2D, texture->id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter);
glTexImage2D(GL_TEXTURE_2D, 0, gl_format, width, height, 0, gl_format,
GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
/* Null check so that it can be freed in the case of it being impossible to get
* another renderer
*/
void pgpl_render_destroy_texture(PGPL_Renderer *renderer,
PGPL_Texture *texture) {
if (renderer != NULL) {
glDeleteTextures(1, &texture->id);
}
free(texture);
}
void pgpl_render_texture(PGPL_Renderer *renderer, PGPL_Texture *texture,
double x, double y, double width, double height) {
pgpl_render_texture_extended(renderer, texture, x, y, width, height, 0xFFFFFF,
0.0f, 0.0f, 1.0f, 1.0f);
}
void pgpl_render_texture_extended(PGPL_Renderer *renderer,
PGPL_Texture *texture, double x, double y,
double width, double height, PGPL_Color color,
double s0, double t0, double s1, double t1) {
double gl_x = x / renderer->width;
double gl_y = y / renderer->height;
double gl_width = width / renderer->width;
double gl_height = height / renderer->height;
glBindTexture(GL_TEXTURE_2D, texture->id);
glColor4ub(pgpl_color_get_red(color), pgpl_color_get_green(color),
pgpl_color_get_blue(color), pgpl_color_get_alpha(color));
glBegin(GL_QUADS);
glTexCoord2d(s0, t0);
glVertex2d(gl_x, gl_y);
glTexCoord2d(s1, t0);
glVertex2d(gl_x + gl_width, gl_y);
glTexCoord2d(s1, t1);
glVertex2d(gl_x + gl_width, gl_y + gl_height);
glTexCoord2d(s0, t1);
glVertex2d(gl_x, gl_y + gl_height);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
}

44
src/thread.c Normal file
View file

@ -0,0 +1,44 @@
#include <pgpl.h>
#include <pthread.h>
#include <stdlib.h>
struct PGPL_Thread {
pthread_t thread;
};
struct PGPL_Mutex {
pthread_mutex_t mutex;
};
PGPL_Thread *pgpl_thread_create(void *(*function)(void *arg), void *arg) {
PGPL_Thread *thread = malloc(sizeof(*thread));
pthread_create(&thread->thread, NULL, function, arg);
return thread;
}
void pgpl_thread_destroy(PGPL_Thread *thread) { free(thread); }
void pgpl_thread_join(PGPL_Thread *thread) {
pthread_join(thread->thread, NULL);
}
PGPL_Mutex *pgpl_mutex_create(void) {
PGPL_Mutex *mutex = malloc(sizeof(*mutex));
pthread_mutex_init(&mutex->mutex, NULL);
return mutex;
}
void pgpl_mutex_destroy(PGPL_Mutex *mutex) {
pthread_mutex_destroy(&mutex->mutex);
free(mutex);
}
void pgpl_mutex_lock(PGPL_Mutex *mutex) { pthread_mutex_lock(&mutex->mutex); }
bool pgpl_mutex_try_lock(PGPL_Mutex *mutex) {
return pthread_mutex_trylock(&mutex->mutex) == 0;
}
void pgpl_mutex_unlock(PGPL_Mutex *mutex) {
pthread_mutex_unlock(&mutex->mutex);
}

48
src/timer.c Normal file
View file

@ -0,0 +1,48 @@
/* _DEFAULT_SOURCE must be defined for usleep to be declared on C99 (glibc). */
#define _DEFAULT_SOURCE
/* _XPG4_2 must be defined on OpenIndiana for usleep to be defined. */
#define _XPG4_2
#include <pgpl.h>
#include <stdlib.h>
#include <time.h>
#ifdef __WIN32__
#include <windows.h>
#else
#include <unistd.h>
#endif
struct PGPL_Timer {
clock_t time;
};
PGPL_Timer *pgpl_timer_create(void) {
PGPL_Timer *timer = malloc(sizeof(PGPL_Timer));
timer->time = clock() / (CLOCKS_PER_SEC / 1000.0);
return timer;
}
void pgpl_timer_destroy(PGPL_Timer *timer) { free(timer); }
uint32_t pgpl_timer_get_delta(PGPL_Timer *timer) {
clock_t new_time = clock() / (CLOCKS_PER_SEC / 1000.0);
clock_t delta;
if (timer->time != 0) {
delta = new_time / timer->time;
} else {
delta = 0;
}
timer->time = new_time;
return delta;
}
void pgpl_timer_sleep(uint32_t milliseconds) {
if (milliseconds > 0) {
#ifdef __WIN32__
Sleep(milliseconds);
#else
usleep(1000 * milliseconds);
#endif
}
}

105
src/vector.c Normal file
View file

@ -0,0 +1,105 @@
#include <pgpl.h>
#include <stdlib.h>
#include <string.h>
/* TODO: Further test this file. */
struct PGPL_Vector {
void *data;
uint32_t data_size;
uint32_t length;
uint32_t allocated;
void *last_delete;
};
#define pgpl_vector_internal_conditional_resize(vector) \
if (vector->length > vector->allocated) { \
vector->allocated *= 2; \
vector->data = \
realloc(vector->data, vector->allocated * vector->data_size); \
}
#define pgpl_vector_internal_memcpy_offset(vector, i) \
(char *)vector->data + (vector->data_size * (i))
PGPL_Vector *pgpl_vector_create(uint32_t data_size) {
PGPL_Vector *vector = malloc(sizeof(*vector));
vector->data_size = data_size;
vector->allocated = 1;
vector->length = 0;
vector->last_delete = malloc(vector->data_size);
vector->data = malloc(vector->allocated * vector->data_size);
return vector;
}
void pgpl_vector_destroy(PGPL_Vector *vector) {
free(vector->data);
free(vector->last_delete);
free(vector);
}
void pgpl_vector_push_back(PGPL_Vector *vector, void *data) {
vector->length++;
pgpl_vector_internal_conditional_resize(vector);
memcpy(pgpl_vector_internal_memcpy_offset(vector, vector->length - 1), data,
vector->data_size);
}
void pgpl_vector_push_front(PGPL_Vector *vector, void *data) {
vector->length++;
pgpl_vector_internal_conditional_resize(vector);
memmove(pgpl_vector_internal_memcpy_offset(vector, 1), vector->data,
vector->data_size * vector->length);
memcpy(vector->data, data, vector->data_size);
}
void *pgpl_vector_pop_back(PGPL_Vector *vector) {
memcpy(vector->last_delete,
pgpl_vector_internal_memcpy_offset(vector, vector->length - 1),
vector->data_size);
vector->length--;
return vector->last_delete;
}
void *pgpl_vector_pop_front(PGPL_Vector *vector) {
memcpy(vector->last_delete, vector->data, vector->data_size);
vector->length--;
memmove(vector->data, pgpl_vector_internal_memcpy_offset(vector, 1),
vector->data_size * vector->length);
return vector->last_delete;
}
void *pgpl_vector_peek_back(PGPL_Vector *vector) {
return pgpl_vector_internal_memcpy_offset(vector, vector->length - 1);
}
void *pgpl_vector_peek_front(PGPL_Vector *vector) { return vector->data; }
void *pgpl_vector_delete_index(PGPL_Vector *vector, uint32_t index) {
memcpy(vector->last_delete, pgpl_vector_internal_memcpy_offset(vector, index),
vector->data_size);
memmove(pgpl_vector_internal_memcpy_offset(vector, index),
pgpl_vector_internal_memcpy_offset(vector, index + 1),
vector->data_size * (vector->length - index - 1));
vector->length--;
return vector->last_delete;
}
void pgpl_vector_insert_after_index(PGPL_Vector *vector, uint32_t index,
void *data) {
vector->length++;
pgpl_vector_internal_conditional_resize(vector);
memmove(pgpl_vector_internal_memcpy_offset(vector, index + 2),
pgpl_vector_internal_memcpy_offset(vector, index + 1),
vector->data_size * (vector->length - index - 2));
memcpy(pgpl_vector_internal_memcpy_offset(vector, index + 1), data,
vector->data_size);
}
void *pgpl_vector_get_index(PGPL_Vector *vector, uint32_t index) {
return pgpl_vector_internal_memcpy_offset(vector, index);
}
uint32_t pgpl_vector_get_length(PGPL_Vector *vector) { return vector->length; }

24
src/window/internal.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef PGPL_WINDOW_INTERNAL_H
#define PGPL_WINDOW_INTERNAL_H
#include <pgpl.h>
/* This is needed if there are multiple windows open. Created by pgpl_init,
* destroyed by pgpl_deinit. */
extern PGPL_Mutex *render_lock;
typedef struct PGPL_WindowShared {
double scale;
PGPL_Vector *event_types;
PGPL_Vector *event_data;
} PGPL_WindowShared;
PGPL_WindowEventType
pgpl_window_internal_fetch_window_event(PGPL_Window *window,
PGPL_WindowEventData *event_data);
void pgpl_window_internal_start_render(PGPL_Window *window);
void pgpl_window_internal_finish_render(PGPL_Window *window);
#endif

363
src/window/thread.c Normal file
View file

@ -0,0 +1,363 @@
#include "internal.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef enum PGPL_WindowThreadAction {
PGPL_WINDOW_THREAD_NONE,
PGPL_WINDOW_THREAD_DESTROY,
PGPL_WINDOW_THREAD_SHOW,
PGPL_WINDOW_THREAD_HIDE,
PGPL_WINDOW_THREAD_SET_TITLE,
PGPL_WINDOW_THREAD_GET_TITLE,
PGPL_WINDOW_THREAD_SET_SIZE,
PGPL_WINDOW_THREAD_GET_SIZE,
PGPL_WINDOW_THREAD_SET_POSITION,
PGPL_WINDOW_THREAD_GET_POSITION,
PGPL_WINDOW_THREAD_SET_SCALE,
PGPL_WINDOW_THREAD_GET_SCALE,
PGPL_WINDOW_THREAD_SET_TRANSIENT,
PGPL_WINDOW_THREAD_REPARENT,
PGPL_WINDOW_THREAD_GET_RAW_WINDOW,
PGPL_WINDOW_THREAD_CREATE_EVENT
} PGPL_WindowThreadAction;
typedef struct PGPL_WindowThreadCreateParameters {
PGPL_WindowThread *window_thread;
const char *title;
uint32_t width, height;
int32_t x, y;
} PGPL_WindowThreadCreateParameters;
typedef union PGPL_WindowThreadActionData {
struct {
uint32_t width;
uint32_t height;
} size_data;
struct {
int32_t x;
int32_t y;
} position_data;
const char *title;
double scale;
void *raw_window;
struct {
PGPL_WindowEventType type;
PGPL_WindowEventData *data;
} event;
} PGPL_WindowThreadActionData;
struct PGPL_WindowThread {
PGPL_Window *window;
PGPL_Thread *thread;
PGPL_Mutex *action_lock;
PGPL_Mutex *process_ready_lock;
PGPL_Mutex *result_ready_lock;
PGPL_WindowThreadAction action;
PGPL_WindowThreadActionData data;
bool (*event_loop)(PGPL_Window *window, PGPL_WindowEventType event,
PGPL_WindowEventData *event_data, void *user_data);
void *user_data;
};
/* This is the main event_loop for the threads. It fetches the Window events and
* calls the event loop callback. It also receives other events from the
* functions later in this file.
*
* TODO: Allow choosing the framerate.
*/
static void *pgpl_window_event_loop(void *arg) {
PGPL_WindowThreadCreateParameters *args = arg;
PGPL_WindowThread *window_thread = args->window_thread;
PGPL_Timer *timer;
bool running = true;
window_thread->window = pgpl_window_create(args->title, args->width,
args->height, args->x, args->y);
free(arg);
pgpl_mutex_unlock(window_thread->action_lock);
timer = pgpl_timer_create();
while (running) {
PGPL_WindowEventData event_data;
while (running) {
PGPL_WindowEventType event_type =
pgpl_window_fetch_event(window_thread->window, &event_data);
bool lock_status = pgpl_mutex_try_lock(window_thread->process_ready_lock);
if (lock_status != true && event_type == PGPL_WINDOW_EVENT_NONE) {
break;
}
if (event_type != PGPL_WINDOW_EVENT_NONE) {
if (!window_thread->event_loop(window_thread->window, event_type,
&event_data, window_thread->user_data)) {
running = false;
}
}
if (lock_status == true) {
switch (window_thread->action) {
case PGPL_WINDOW_THREAD_NONE:
break;
case PGPL_WINDOW_THREAD_DESTROY:
window_thread->event_loop(window_thread->window,
PGPL_WINDOW_EVENT_CLOSE, &event_data,
window_thread->user_data);
running = 0;
break;
case PGPL_WINDOW_THREAD_SHOW:
pgpl_window_show(window_thread->window);
break;
case PGPL_WINDOW_THREAD_HIDE:
pgpl_window_hide(window_thread->window);
break;
case PGPL_WINDOW_THREAD_SET_TITLE:
pgpl_window_set_title(window_thread->window,
window_thread->data.title);
break;
case PGPL_WINDOW_THREAD_GET_TITLE:
window_thread->data.title =
pgpl_window_get_title(window_thread->window);
break;
case PGPL_WINDOW_THREAD_SET_SIZE:
pgpl_window_set_size(window_thread->window,
window_thread->data.size_data.width,
window_thread->data.size_data.height);
break;
case PGPL_WINDOW_THREAD_GET_SIZE:
pgpl_window_get_size(window_thread->window,
&window_thread->data.size_data.width,
&window_thread->data.size_data.height);
break;
case PGPL_WINDOW_THREAD_SET_POSITION:
pgpl_window_set_position(window_thread->window,
window_thread->data.position_data.x,
window_thread->data.position_data.y);
break;
case PGPL_WINDOW_THREAD_GET_POSITION:
pgpl_window_get_position(window_thread->window,
&window_thread->data.position_data.x,
&window_thread->data.position_data.y);
break;
case PGPL_WINDOW_THREAD_SET_SCALE:
pgpl_window_set_scale(window_thread->window,
window_thread->data.scale);
break;
case PGPL_WINDOW_THREAD_GET_SCALE:
window_thread->data.scale =
pgpl_window_get_scale(window_thread->window);
break;
case PGPL_WINDOW_THREAD_SET_TRANSIENT:
pgpl_window_set_transient(window_thread->window,
window_thread->data.raw_window);
break;
case PGPL_WINDOW_THREAD_REPARENT:
pgpl_window_reparent_to(window_thread->window,
window_thread->data.raw_window);
break;
case PGPL_WINDOW_THREAD_GET_RAW_WINDOW:
window_thread->data.raw_window =
pgpl_window_get_raw_window(window_thread->window);
break;
case PGPL_WINDOW_THREAD_CREATE_EVENT:
pgpl_window_create_event(window_thread->window,
window_thread->data.event.type,
window_thread->data.event.data);
break;
}
pgpl_mutex_unlock(window_thread->result_ready_lock);
}
}
pgpl_window_create_event(window_thread->window,
PGPL_WINDOW_EVENT_RENDER_REQUEST, NULL);
pgpl_timer_sleep(20 - pgpl_timer_get_delta(timer));
}
pgpl_timer_destroy(timer);
pgpl_window_destroy(window_thread->window);
pgpl_mutex_destroy(window_thread->action_lock);
pgpl_mutex_destroy(window_thread->process_ready_lock);
pgpl_mutex_destroy(window_thread->result_ready_lock);
pgpl_thread_destroy(window_thread->thread);
free(window_thread);
return NULL;
}
/* Creates all needed mutexes. */
PGPL_WindowThread *pgpl_window_thread_create(
const char *title, uint32_t width, uint32_t height, int32_t x, int32_t y,
bool (*event_loop)(PGPL_Window *window, PGPL_WindowEventType event,
PGPL_WindowEventData *event_data, void *user_data),
void *user_data) {
PGPL_WindowThreadCreateParameters *args = malloc(sizeof(*args));
args->window_thread = malloc(sizeof(*args->window_thread));
args->window_thread->user_data = user_data;
args->window_thread->event_loop = event_loop;
args->window_thread->action = PGPL_WINDOW_THREAD_NONE;
args->title = title;
args->width = width;
args->height = height;
args->x = x;
args->y = y;
args->window_thread->action_lock = pgpl_mutex_create();
args->window_thread->process_ready_lock = pgpl_mutex_create();
args->window_thread->result_ready_lock = pgpl_mutex_create();
pgpl_mutex_lock(args->window_thread->action_lock);
pgpl_mutex_lock(args->window_thread->process_ready_lock);
pgpl_mutex_lock(args->window_thread->result_ready_lock);
args->window_thread->thread =
pgpl_thread_create(pgpl_window_event_loop, args);
return args->window_thread;
}
PGPL_WindowThreadActionData *
pgpl_window_thread_internal_send_message(PGPL_WindowThread *window_thread,
PGPL_WindowThreadAction action,
PGPL_WindowThreadActionData *data) {
pgpl_mutex_lock(window_thread->action_lock);
window_thread->action = action;
if (data != NULL) {
window_thread->data = *data;
}
pgpl_mutex_unlock(window_thread->process_ready_lock);
pgpl_mutex_lock(window_thread->result_ready_lock);
if (data != NULL) {
*data = window_thread->data;
}
pgpl_mutex_unlock(window_thread->action_lock);
return data;
}
void pgpl_window_thread_destroy(PGPL_WindowThread *window_thread) {
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_DESTROY, NULL);
}
void pgpl_window_thread_show(PGPL_WindowThread *window_thread) {
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_SHOW, NULL);
}
void pgpl_window_thread_hide(PGPL_WindowThread *window_thread) {
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_HIDE, NULL);
}
void pgpl_window_thread_set_title(PGPL_WindowThread *window_thread,
const char *title) {
PGPL_WindowThreadActionData data;
data.title = title;
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_SET_TITLE, &data);
}
const char *pgpl_window_thread_get_title(PGPL_WindowThread *window_thread) {
PGPL_WindowThreadActionData data;
return pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_GET_TITLE, &data)
->title;
}
void pgpl_window_thread_get_size(PGPL_WindowThread *window_thread,
uint32_t *width, uint32_t *height) {
PGPL_WindowThreadActionData data;
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_GET_SIZE, &data);
*width = data.size_data.width;
*height = data.size_data.height;
}
void pgpl_window_thread_set_size(PGPL_WindowThread *window_thread,
uint32_t width, uint32_t height) {
PGPL_WindowThreadActionData data;
data.size_data.width = width;
data.size_data.height = height;
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_SET_SIZE, &data);
}
void pgpl_window_thread_get_position(PGPL_WindowThread *window_thread,
int32_t *x, int32_t *y) {
PGPL_WindowThreadActionData data;
pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_GET_POSITION, &data);
*x = data.position_data.x;
*y = data.position_data.y;
}
void pgpl_window_thread_set_position(PGPL_WindowThread *window_thread,
int32_t x, int32_t y) {
PGPL_WindowThreadActionData data;
data.position_data.x = x;
data.position_data.y = y;
pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_SET_POSITION, &data);
}
double pgpl_window_thread_get_scale(PGPL_WindowThread *window_thread) {
PGPL_WindowThreadActionData data;
return pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_GET_SCALE, &data)
->scale;
}
void pgpl_window_thread_set_scale(PGPL_WindowThread *window_thread,
double scale) {
PGPL_WindowThreadActionData data;
data.scale = scale;
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_SET_SCALE, &data);
}
void pgpl_window_thread_set_transient(PGPL_WindowThread *window_thread,
void *parent) {
PGPL_WindowThreadActionData data;
data.raw_window = parent;
pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_SET_TRANSIENT, &data);
}
void pgpl_window_thread_reparent_to(PGPL_WindowThread *window_thread,
void *parent) {
PGPL_WindowThreadActionData data;
data.raw_window = parent;
pgpl_window_thread_internal_send_message(window_thread,
PGPL_WINDOW_THREAD_REPARENT, &data);
}
void *pgpl_window_thread_get_raw_window(PGPL_WindowThread *window_thread) {
PGPL_WindowThreadActionData data;
return pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_GET_RAW_WINDOW, &data)
->raw_window;
}
void pgpl_window_thread_create_event(PGPL_WindowThread *window_thread,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data) {
PGPL_WindowThreadActionData data;
data.event.type = event_type;
data.event.data = event_data;
pgpl_window_thread_internal_send_message(
window_thread, PGPL_WINDOW_THREAD_CREATE_EVENT, &data);
}
void pgpl_window_thread_await_destruction(PGPL_WindowThread *window_thread) {
pgpl_thread_join(window_thread->thread);
}

331
src/window/window-win32.c Normal file
View file

@ -0,0 +1,331 @@
#ifdef __WIN32__
#include "internal.h"
#include <GL/gl.h>
#include <string.h>
#include <windows.h>
struct PGPL_Window {
PGPL_WindowShared shared;
HWND hwnd;
WNDCLASS wc;
HDC hdc;
HGLRC wgl_context;
PGPL_Mutex *window_proc_vector_lock;
PGPL_Vector *window_proc_event_type;
PGPL_Vector *window_proc_event_data;
int32_t last_mouse_x, last_mouse_y;
char title[128];
};
/* Our WindowProc function, this needs to be overhauled a bit, and certain
* things in here could be solved with more macros, to clean up and shorten the
* code a bit.
*/
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
LPARAM lParam) {
PGPL_Window *window = (PGPL_Window *)GetWindowLongPtr(hwnd, 0);
PGPL_WindowEventType event_type;
PGPL_WindowEventData event_data;
if (window == NULL) {
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
pgpl_mutex_lock(window->window_proc_vector_lock);
switch (uMsg) {
case WM_SETCURSOR:
if (LOWORD(lParam) == HTCLIENT) {
SetCursor(LoadCursor(NULL, IDC_ARROW));
pgpl_mutex_unlock(window->window_proc_vector_lock);
return TRUE;
} else {
pgpl_mutex_unlock(window->window_proc_vector_lock);
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
break;
case WM_KEYDOWN:
event_type = PGPL_WINDOW_EVENT_KEY_PRESS;
event_data.input_event.key = wParam;
event_data.input_event.x = window->last_mouse_x;
event_data.input_event.y = window->last_mouse_y;
break;
case WM_KEYUP:
event_type = PGPL_WINDOW_EVENT_KEY_RELEASE;
event_data.input_event.key = wParam;
event_data.input_event.x = window->last_mouse_x;
event_data.input_event.y = window->last_mouse_y;
break;
case WM_LBUTTONDOWN: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_PRESS;
event_data.input_event.key = 1;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_LBUTTONUP: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_RELEASE;
event_data.input_event.key = 1;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_MBUTTONDOWN: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_PRESS;
event_data.input_event.key = 2;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_MBUTTONUP: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_RELEASE;
event_data.input_event.key = 2;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_RBUTTONDOWN: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_PRESS;
event_data.input_event.key = 3;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_RBUTTONUP: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_RELEASE;
event_data.input_event.key = 3;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_MOUSEWHEEL: {
/* TODO: This code probably doesn't work that well. Unlike X11, this only
* returns a MOUSE_PRESS event, and if the scrolls are more granular, this
* will scroll really fast.
*/
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_PRESS;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
event_data.input_event.key = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? 4 : 5;
break;
}
case WM_MOUSEMOVE: {
POINTS points = MAKEPOINTS(lParam);
event_type = PGPL_WINDOW_EVENT_MOUSE_MOVE;
window->last_mouse_x = points.x;
window->last_mouse_y = points.y;
event_data.input_event.key = -1;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;
}
case WM_CLOSE:
event_type = PGPL_WINDOW_EVENT_CLOSE;
break;
default:
event_type = PGPL_WINDOW_EVENT_NONE;
pgpl_mutex_unlock(window->window_proc_vector_lock);
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
pgpl_vector_push_back(window->window_proc_event_type, &event_type);
pgpl_vector_push_back(window->window_proc_event_data, &event_data);
pgpl_mutex_unlock(window->window_proc_vector_lock);
return 0;
}
PGPL_Window *pgpl_window_create(const char *title, uint32_t width,
uint32_t height, int32_t x, int32_t y) {
PGPL_Window *window = malloc(sizeof(*window));
int32_t pixel_format;
PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
32,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
24,
8,
0,
PFD_MAIN_PLANE,
0,
0,
0,
0};
/* TODO: Move into shared function? */
window->shared.scale = 1.0;
window->shared.event_data = pgpl_vector_create(sizeof(PGPL_WindowEventData));
window->shared.event_types = pgpl_vector_create(sizeof(PGPL_WindowEventType));
window->window_proc_event_data =
pgpl_vector_create(sizeof(PGPL_WindowEventData));
window->window_proc_event_type =
pgpl_vector_create(sizeof(PGPL_WindowEventType));
memset(&window->wc, 0, sizeof(window->wc));
window->window_proc_vector_lock = pgpl_mutex_create();
window->wc.lpfnWndProc = WindowProc;
window->wc.cbWndExtra = sizeof(PGPL_Window *);
window->wc.lpszClassName = "PGPL_Window Window";
window->wc.style = CS_OWNDC;
RegisterClass(&window->wc);
window->hwnd =
CreateWindowEx(0, window->wc.lpszClassName, title, WS_OVERLAPPEDWINDOW, x,
y, width, height, NULL, NULL, window->wc.hInstance, NULL);
if (window->hwnd == NULL) {
return NULL;
}
SetWindowLongPtr(window->hwnd, 0, (LONG_PTR)window);
SetWindowPos(window->hwnd, NULL, x, y, width, height, 0);
window->hdc = GetDC(window->hwnd);
pixel_format = ChoosePixelFormat(window->hdc, &pfd);
SetPixelFormat(window->hdc, pixel_format, &pfd);
window->wgl_context = wglCreateContext(window->hdc);
return window;
}
void pgpl_window_destroy(PGPL_Window *window) {
/* TODO: Also turn this into a generic function perhaps. */
pgpl_vector_destroy(window->shared.event_data);
pgpl_vector_destroy(window->shared.event_types);
pgpl_mutex_destroy(window->window_proc_vector_lock);
wglMakeCurrent(NULL, NULL);
wglDeleteContext(window->wgl_context);
ReleaseDC(window->hwnd, window->hdc);
UnregisterClass(window->wc.lpszClassName, window->wc.hInstance);
DestroyWindow(window->hwnd);
}
void pgpl_window_show(PGPL_Window *window) {
ShowWindow(window->hwnd, SW_SHOW);
}
void pgpl_window_hide(PGPL_Window *window) {
ShowWindow(window->hwnd, SW_HIDE);
}
void pgpl_window_set_title(PGPL_Window *window, const char *title) {
SetWindowText(window->hwnd, title);
}
/* 128 is allowed as the max count, because it includes the null terminator */
const char *pgpl_window_get_title(PGPL_Window *window) {
GetWindowText(window->hwnd, window->title, 128);
return window->title;
}
void pgpl_window_get_size(PGPL_Window *window, uint32_t *width,
uint32_t *height) {
RECT rect;
GetClientRect(window->hwnd, &rect);
*width = rect.right - rect.left;
*height = rect.bottom - rect.top;
}
void pgpl_window_set_size(PGPL_Window *window, uint32_t width,
uint32_t height) {
SetWindowPos(window->hwnd, NULL, 0, 0, width, height, SWP_NOMOVE);
}
void pgpl_window_get_position(PGPL_Window *window, int32_t *x, int32_t *y) {
WINDOWPLACEMENT placement;
GetWindowPlacement(window->hwnd, &placement);
*x = placement.ptMinPosition.x;
*y = placement.ptMinPosition.y;
}
void pgpl_window_set_position(PGPL_Window *window, int32_t x, int32_t y) {
SetWindowPos(window->hwnd, NULL, x, y, 0, 0, SWP_NOSIZE);
}
/* Fetches an event, the mutex might be useless entirely. This basically calls
* the WindowProc, and takes it's data back out to be returned. */
PGPL_WindowEventType
pgpl_window_internal_fetch_window_event(PGPL_Window *window,
PGPL_WindowEventData *event_data) {
MSG msg;
PGPL_WindowEventType event_type = PGPL_WINDOW_EVENT_NONE;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
pgpl_mutex_lock(window->window_proc_vector_lock);
if (pgpl_vector_get_length(window->window_proc_event_type) > 0) {
event_type = *(PGPL_WindowEventType *)pgpl_vector_pop_front(
window->window_proc_event_type);
*event_data = *(PGPL_WindowEventData *)pgpl_vector_pop_front(
window->window_proc_event_data);
}
pgpl_mutex_unlock(window->window_proc_vector_lock);
return event_type;
}
/* Here we reparent the window, this is because under Windows, this creates a
* subwindow of sorts on the other window, so this behaves "similarily" to the
* X11 function. */
void pgpl_window_set_transient(PGPL_Window *window, void *parent) {
SetParent(window->hwnd, parent);
}
/* These style changes are done to remove the borders around the window, which
* are usually undesireable in reparenting contexts, especially when working
* with audio plugins. */
void pgpl_window_reparent_to(PGPL_Window *window, void *parent) {
LONG style = GetWindowLongPtr(window->hwnd, GWL_STYLE);
LONG ex_style = GetWindowLongPtr(window->hwnd, GWL_EXSTYLE);
style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
WS_SYSMENU);
ex_style &= ~(WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE);
SetWindowLongPtr(window->hwnd, GWL_STYLE, style);
SetWindowLongPtr(window->hwnd, GWL_EXSTYLE, ex_style);
pgpl_window_set_transient(window, parent);
}
void *pgpl_window_get_raw_window(PGPL_Window *window) { return window->hwnd; }
void pgpl_window_internal_start_render(PGPL_Window *window) {
pgpl_mutex_lock(render_lock);
wglMakeCurrent(window->hdc, window->wgl_context);
}
void pgpl_window_internal_finish_render(PGPL_Window *window) {
wglSwapLayerBuffers(window->hdc, WGL_SWAP_MAIN_PLANE);
pgpl_mutex_unlock(render_lock);
}
#endif

232
src/window/window-x11.c Normal file
View file

@ -0,0 +1,232 @@
#ifdef __unix__
#include "internal.h"
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <stdlib.h>
struct PGPL_Window {
PGPL_WindowShared shared;
Display *display;
Window window;
Atom wm_delete;
GLXContext glx_context;
char *title;
bool reparented;
};
/* Creates a basic X11 window with GLX. We select all the events that we handle
* in here, and create the most basic GLX context that we need. */
PGPL_Window *pgpl_window_create(const char *title, uint32_t width,
uint32_t height, int32_t x, int32_t y) {
PGPL_Window *window = malloc(sizeof(*window));
int32_t screen;
Window root;
XSetWindowAttributes window_attributes;
GLint attributes[5] = {GLX_RGBA, GLX_DOUBLEBUFFER, None};
XVisualInfo *visual;
window->title = NULL;
/* TODO: Move into shared function? */
window->shared.scale = 1.0;
window->shared.event_data = pgpl_vector_create(sizeof(PGPL_WindowEventData));
window->shared.event_types = pgpl_vector_create(sizeof(PGPL_WindowEventType));
window->reparented = 0;
window->display = XOpenDisplay(NULL);
screen = DefaultScreen(window->display);
root = DefaultRootWindow(window->display);
visual = glXChooseVisual(window->display, screen, attributes);
window_attributes.colormap =
XCreateColormap(window->display, root, visual->visual, AllocNone);
window_attributes.event_mask = KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
PointerMotionMask;
window->window = XCreateWindow(window->display, root, x, y, width, height, 0,
visual->depth, InputOutput, visual->visual,
CWColormap | CWEventMask, &window_attributes);
XStoreName(window->display, window->window, title);
/* If the window gets closed by the WM, this let's us catch that and
* gracefully exit. */
window->wm_delete = XInternAtom(window->display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(window->display, window->window, &window->wm_delete, 1);
window->glx_context =
glXCreateContext(window->display, visual, NULL, GL_TRUE);
XFlush(window->display);
free(visual);
return window;
}
void pgpl_window_destroy(PGPL_Window *window) {
/* TODO: Also turn this into a generic function perhaps. */
pgpl_vector_destroy(window->shared.event_data);
pgpl_vector_destroy(window->shared.event_types);
if (window->title != NULL) {
XFree(window->title);
}
glXDestroyContext(window->display, window->glx_context);
/* Can cause problems when reparented, so we just don't destroy the window if
* we have been reparented. */
if (!window->reparented) {
XDestroyWindow(window->display, window->window);
}
XCloseDisplay(window->display);
free(window);
}
void pgpl_window_show(PGPL_Window *window) {
XMapWindow(window->display, window->window);
XFlush(window->display);
}
void pgpl_window_hide(PGPL_Window *window) {
XUnmapWindow(window->display, window->window);
XFlush(window->display);
}
void pgpl_window_set_title(PGPL_Window *window, const char *title) {
XStoreName(window->display, window->window, title);
XFlush(window->display);
}
const char *pgpl_window_get_title(PGPL_Window *window) {
if (window->title != NULL) {
XFree(window->title);
}
XFetchName(window->display, window->window, &window->title);
return window->title;
}
void pgpl_window_get_size(PGPL_Window *window, uint32_t *width,
uint32_t *height) {
XWindowAttributes attributes;
XGetWindowAttributes(window->display, window->window, &attributes);
if (width != NULL) {
*width = attributes.width;
}
if (height != NULL) {
*height = attributes.height;
}
}
void pgpl_window_set_size(PGPL_Window *window, uint32_t width,
uint32_t height) {
XResizeWindow(window->display, window->window, width, height);
XFlush(window->display);
}
void pgpl_window_get_position(PGPL_Window *window, int32_t *x, int32_t *y) {
XWindowAttributes attributes;
XGetWindowAttributes(window->display, window->window, &attributes);
if (x != NULL) {
*x = attributes.x;
}
if (y != NULL) {
*y = attributes.y;
}
}
void pgpl_window_set_position(PGPL_Window *window, int32_t x, int32_t y) {
XMoveWindow(window->display, window->window, x, y);
XFlush(window->display);
}
/* This fetches the event from X11 and translates it to our own format, returns
* an Error if the Event was unknown.
*
* TODO: Translate the keymaps */
PGPL_WindowEventType
pgpl_window_internal_fetch_window_event(PGPL_Window *window,
PGPL_WindowEventData *event_data) {
if (XPending(window->display)) {
XEvent event;
XNextEvent(window->display, &event);
switch (event.type) {
case KeyPress:
if (event_data != NULL) {
event_data->input_event.x = event.xkey.x;
event_data->input_event.y = event.xkey.y;
event_data->input_event.key = event.xkey.keycode;
}
return PGPL_WINDOW_EVENT_KEY_PRESS;
break;
case KeyRelease:
if (event_data != NULL) {
event_data->input_event.x = event.xkey.x;
event_data->input_event.y = event.xkey.y;
event_data->input_event.key = event.xkey.keycode;
}
return PGPL_WINDOW_EVENT_KEY_RELEASE;
break;
case ButtonPress:
if (event_data != NULL) {
event_data->input_event.x = event.xbutton.x;
event_data->input_event.y = event.xbutton.y;
event_data->input_event.key = event.xbutton.button;
}
return PGPL_WINDOW_EVENT_MOUSE_PRESS;
break;
case ButtonRelease:
if (event_data != NULL) {
event_data->input_event.x = event.xbutton.x;
event_data->input_event.y = event.xbutton.y;
event_data->input_event.key = event.xbutton.button;
}
return PGPL_WINDOW_EVENT_MOUSE_RELEASE;
break;
case MotionNotify:
if (event_data != NULL) {
event_data->input_event.x = event.xbutton.x;
event_data->input_event.y = event.xbutton.y;
event_data->input_event.key = 0;
}
return PGPL_WINDOW_EVENT_MOUSE_MOVE;
break;
case ClientMessage:
if (event.xclient.data.l[0] == (long)window->wm_delete)
return PGPL_WINDOW_EVENT_CLOSE;
return PGPL_WINDOW_EVENT_ERROR;
break;
default:
return PGPL_WINDOW_EVENT_ERROR;
}
}
return PGPL_WINDOW_EVENT_NONE;
}
void pgpl_window_set_transient(PGPL_Window *window, void *parent) {
XSetTransientForHint(window->display, window->window, *(Window *)parent);
}
void pgpl_window_reparent_to(PGPL_Window *window, void *parent) {
XReparentWindow(window->display, window->window, *(Window *)parent, 0, 0);
window->reparented = true;
}
void *pgpl_window_get_raw_window(PGPL_Window *window) {
return &window->window;
}
void pgpl_window_internal_start_render(PGPL_Window *window) {
pgpl_mutex_lock(render_lock);
glXMakeCurrent(window->display, window->window, window->glx_context);
}
void pgpl_window_internal_finish_render(PGPL_Window *window) {
glXSwapBuffers(window->display, window->window);
pgpl_mutex_unlock(render_lock);
}
#endif

93
src/window/window.c Normal file
View file

@ -0,0 +1,93 @@
#include "internal.h"
#include <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
/* Platform agnostic functions for our PGPL_Window "class" */
void pgpl_window_set_scale(PGPL_Window *window, double scale) {
PGPL_WindowShared *window_shared = (PGPL_WindowShared *)window;
window_shared->scale = scale;
}
double pgpl_window_get_scale(PGPL_Window *window) {
PGPL_WindowShared *window_shared = (PGPL_WindowShared *)window;
return window_shared->scale;
}
PGPL_WindowEventType pgpl_window_fetch_event(PGPL_Window *window,
PGPL_WindowEventData *event_data) {
PGPL_WindowShared *window_shared = (PGPL_WindowShared *)window;
PGPL_WindowEventType event_type;
while ((event_type = pgpl_window_internal_fetch_window_event(
window, event_data)) != PGPL_WINDOW_EVENT_NONE) {
if (event_type == PGPL_WINDOW_EVENT_MOUSE_PRESS ||
event_type == PGPL_WINDOW_EVENT_MOUSE_RELEASE ||
event_type == PGPL_WINDOW_EVENT_KEY_PRESS ||
event_type == PGPL_WINDOW_EVENT_KEY_RELEASE ||
event_type == PGPL_WINDOW_EVENT_MOUSE_MOVE) {
event_data->input_event.x /= window_shared->scale;
event_data->input_event.y /= window_shared->scale;
}
pgpl_vector_push_back(window_shared->event_types, &event_type);
pgpl_vector_push_back(window_shared->event_data, event_data);
}
if (pgpl_vector_get_length(window_shared->event_types) > 0) {
*event_data = *(PGPL_WindowEventData *)pgpl_vector_pop_front(
window_shared->event_data);
event_type = *(PGPL_WindowEventType *)pgpl_vector_pop_front(
window_shared->event_types);
return event_type;
} else {
return PGPL_WINDOW_EVENT_NONE;
}
}
void pgpl_window_create_event(PGPL_Window *window,
PGPL_WindowEventType event_type,
PGPL_WindowEventData *event_data) {
PGPL_WindowShared *window_shared = (PGPL_WindowShared *)window;
pgpl_vector_push_back(window_shared->event_types, &event_type);
if (event_data == NULL) {
PGPL_WindowEventData data;
pgpl_vector_push_back(window_shared->event_data, &data);
} else {
pgpl_vector_push_back(window_shared->event_data, &event_data);
}
}
/* Enables important OpenGL settings, like transparency support, and sets up our
* projection matrix to be easier to work with. */
PGPL_Renderer *pgpl_window_start_render(PGPL_Window *window, PGPL_Color color) {
PGPL_WindowShared *window_shared = (PGPL_WindowShared *)window;
PGPL_Renderer *renderer = malloc(sizeof(*renderer));
pgpl_window_internal_start_render(window);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
pgpl_window_get_size(window, &renderer->width, &renderer->height);
glViewport(0, 0, renderer->width, renderer->height);
glPushMatrix();
glOrtho(0.0, 1.0, 1.0, 0.0, 0.0, 1.0);
glScalef(window_shared->scale, window_shared->scale, window_shared->scale);
glClearColor(
pgpl_color_get_red(color) / 255.0, pgpl_color_get_green(color) / 255.0,
pgpl_color_get_blue(color) / 255.0, pgpl_color_get_alpha(color) / 255.0);
glClear(GL_COLOR_BUFFER_BIT);
return renderer;
}
void pgpl_window_finish_render(PGPL_Window *window, PGPL_Renderer *renderer) {
glPopMatrix();
pgpl_window_internal_finish_render(window);
free(renderer);
}