Initial commit
This commit is contained in:
commit
56f7cd1875
105 changed files with 22109 additions and 0 deletions
125
src/gui/gui.c
Normal file
125
src/gui/gui.c
Normal 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
187
src/gui/helpers.c
Normal 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
88
src/gui/widgets/button.c
Normal 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
169
src/gui/widgets/container.c
Normal 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
59
src/gui/widgets/empty.c
Normal 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
65
src/gui/widgets/text.c
Normal 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
7
src/pgpl.c
Normal 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
261
src/render/font.c
Normal 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
15
src/render/internal.h
Normal 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
96
src/render/shapes.c
Normal 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
143
src/render/texture.c
Normal 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
44
src/thread.c
Normal 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
48
src/timer.c
Normal 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
105
src/vector.c
Normal 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
24
src/window/internal.h
Normal 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
363
src/window/thread.c
Normal 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
331
src/window/window-win32.c
Normal 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
232
src/window/window-x11.c
Normal 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
93
src/window/window.c
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue