slider widget, tons of fixes and improvements for win32, reworked font loading.

This commit is contained in:
Patrick 2025-10-07 21:45:48 +02:00
commit 69f8a273f3
18 changed files with 452 additions and 90 deletions

View file

@ -34,6 +34,71 @@ void pgpl_gui_theme_configure(PGPL_GuiTheme *theme, PGPL_Color base_color,
theme->font = font;
}
void pgpl_gui_widget_adjust_event_params(PGPL_GuiWidget *widget,
PGPL_GuiTheme *theme, double *x,
double *y, double *max_width,
double *max_height) {
double width, height;
pgpl_gui_widget_max_content_size(widget, theme, &width, &height, *max_width,
*max_height);
widget->get_content_size(widget, theme, &width, &height, width, 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->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->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;
}
void pgpl_gui_widget_max_content_size(PGPL_GuiWidget *widget,
PGPL_GuiTheme *theme, double *width,
double *height, double max_width,
@ -67,11 +132,13 @@ void pgpl_gui_widget_render_full(PGPL_GuiWidget *widget,
theme = &widget->theme_override;
}
x += +widget->offset_x;
y += +widget->offset_y;
x += widget->offset_x;
y += widget->offset_y;
widget->get_content_size(widget, theme, &width, &height, max_width,
max_height);
pgpl_gui_widget_max_content_size(widget, theme, &width, &height, max_width,
max_height);
widget->get_content_size(widget, theme, &width, &height, width, height);
if (!widget->expand_x) {
max_width = width;
@ -143,6 +210,13 @@ void pgpl_gui_widget_render_full(PGPL_GuiWidget *widget,
widget->render_content(widget, renderer, theme, x, y, max_width, max_height);
}
bool pgpl_gui_widget_within_area(double x, double y, double area_x,
double area_y, double area_width,
double area_height) {
return (x >= area_x && x <= (area_x + area_width)) &&
(y >= area_y && y <= (area_y + area_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,
@ -155,12 +229,8 @@ bool pgpl_gui_widget_within_bounds(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
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;
return pgpl_gui_widget_within_area(x, y, widget_x, widget_y, max_width,
max_height);
}
void pgpl_gui_widget_configure(PGPL_GuiWidget *widget, double offset_x,

View file

@ -5,8 +5,10 @@ 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);
(void)theme;
*width = max_width;
*height = max_height;
}
static void render_content(PGPL_GuiWidget *widget, PGPL_Renderer *renderer,
@ -79,12 +81,15 @@ static void event(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme, PGPL_Gui *gui,
for (uint32_t i = 0; i < length; i++) {
PGPL_GuiWidget *child_widget =
*(PGPL_GuiWidget **)pgpl_vector_get_index(container_layout->widgets, i);
PGPL_GuiTheme *child_theme;
if (child_widget->use_theme_override) {
theme = &child_widget->theme_override;
child_theme = &child_widget->theme_override;
} else {
child_theme = theme;
}
child_widget->event(child_widget, theme, gui, event_type, event_data,
child_widget->event(child_widget, child_theme, gui, event_type, event_data,
x + child_widget->offset_x, y + child_widget->offset_y,
max_width, max_height);

201
src/gui/widgets/slider.c Normal file
View file

@ -0,0 +1,201 @@
#include <pgpl.h>
#include <stdlib.h>
static void get_slider_size(PGPL_GuiSliderWidget *slider_widget,
double *slider_x, double *slider_y, double x,
double y, double max_width, double max_height) {
double offset;
if (slider_widget->inversed) {
offset = 1.0 - slider_widget->current_value;
} else {
offset = slider_widget->current_value;
}
if (slider_widget->vertical) {
*slider_x = x - slider_widget->bar_height / 2.0;
*slider_y = y + offset * (max_height - slider_widget->bar_height) -
slider_widget->bar_height / 2.0;
} else {
*slider_x = x + offset * (max_width - slider_widget->bar_height) -
slider_widget->bar_height / 2.0;
*slider_y = y - slider_widget->bar_height / 2.0;
}
}
static void get_content_size(PGPL_GuiWidget *widget, PGPL_GuiTheme *theme,
double *width, double *height, double max_width,
double max_height) {
PGPL_GuiSliderWidget *slider_widget = (PGPL_GuiSliderWidget *)widget;
(void)theme;
if (slider_widget->vertical) {
*width = slider_widget->bar_height;
*height = max_height;
} else {
*width = max_width;
*height = slider_widget->bar_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_GuiSliderWidget *slider_widget = (PGPL_GuiSliderWidget *)widget;
double slider_x;
double slider_y;
double completion_x;
double completion_y;
double completion_width;
double completion_height;
get_slider_size(slider_widget, &slider_x, &slider_y, x, y, max_width,
max_height);
if (slider_widget->vertical) {
max_width = slider_widget->bar_height;
} else {
max_height = slider_widget->bar_height;
}
pgpl_render_rectangle(renderer, theme->text_color[widget->gui_color], x, y,
max_width, max_height);
pgpl_render_rectangle(renderer, theme->background_color,
x + slider_widget->bar_height / 4.0,
y + slider_widget->bar_height / 4.0,
max_width - slider_widget->bar_height / 2.0,
max_height - slider_widget->bar_height / 2.0);
max_width -= slider_widget->bar_height / 2.0;
max_height -= slider_widget->bar_height / 2.0;
if (slider_widget->inversed) {
if (slider_widget->vertical) {
completion_x = x + slider_widget->bar_height / 4.0;
completion_y = y + slider_widget->bar_height / 4.0 +
max_height * (1.0 - slider_widget->current_value);
completion_width = max_width;
completion_height = max_height * slider_widget->current_value;
} else {
completion_x = x + slider_widget->bar_height / 4.0 +
max_width * (1.0 - slider_widget->current_value);
completion_y = y + slider_widget->bar_height / 4.0;
completion_width = max_width * slider_widget->current_value;
completion_height = max_height;
}
} else {
completion_x = x + slider_widget->bar_height / 4.0;
completion_y = y + slider_widget->bar_height / 4.0;
if (slider_widget->vertical) {
completion_width = max_width;
completion_height = max_height * slider_widget->current_value;
} else {
completion_width = max_width * slider_widget->current_value;
completion_height = max_height;
}
}
pgpl_render_rectangle(
renderer, theme->widget_background_color[widget->gui_color], completion_x,
completion_y, completion_width, completion_height);
pgpl_render_circle(renderer, theme->text_color[widget->gui_color], slider_x,
slider_y, slider_widget->bar_height, 32);
}
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) {
if (event_type == PGPL_WINDOW_EVENT_MOUSE_PRESS ||
event_type == PGPL_WINDOW_EVENT_MOUSE_RELEASE ||
event_type == PGPL_WINDOW_EVENT_MOUSE_MOVE) {
PGPL_GuiSliderWidget *slider_widget = (PGPL_GuiSliderWidget *)widget;
double slider_x;
double slider_y;
pgpl_gui_widget_adjust_event_params(widget, theme, &x, &y, &max_width,
&max_height);
get_slider_size(slider_widget, &slider_x, &slider_y, x, y, max_width,
max_height);
if (event_type == PGPL_WINDOW_EVENT_MOUSE_PRESS) {
if (pgpl_gui_widget_within_area(event_data->input_event.x,
event_data->input_event.y, slider_x,
slider_y, slider_widget->bar_height * 2,
slider_widget->bar_height * 2)) {
slider_widget->sliding = true;
if (slider_widget->vertical) {
slider_widget->last_pos = event_data->input_event.y;
} else {
slider_widget->last_pos = event_data->input_event.x;
}
}
} else if (event_type == PGPL_WINDOW_EVENT_MOUSE_RELEASE) {
slider_widget->sliding = false;
} else if (slider_widget->sliding) {
double new_pos;
double axis_length;
if (slider_widget->vertical) {
new_pos = event_data->input_event.y;
axis_length = max_height;
} else {
new_pos = event_data->input_event.x;
axis_length = max_width;
}
axis_length -= slider_widget->bar_height;
if (slider_widget->inversed) {
slider_widget->current_value -=
(new_pos - slider_widget->last_pos) / axis_length;
} else {
slider_widget->current_value +=
(new_pos - slider_widget->last_pos) / axis_length;
}
if (slider_widget->current_value > 1.0) {
slider_widget->current_value = 1.0;
} else if (slider_widget->current_value < 0.0) {
slider_widget->current_value = 0.0;
}
slider_widget->slide_callback(gui->app_data,
slider_widget->current_value);
slider_widget->last_pos = new_pos;
}
}
}
static void destroy(PGPL_GuiWidget *widget) {
pgpl_gui_slider_widget_destroy((PGPL_GuiSliderWidget *)widget);
}
PGPL_GuiSliderWidget *pgpl_gui_slider_widget_create(
uint32_t bar_height, bool inversed, bool vertical, double value,
void (*slide_callback)(void *app_data, double value)) {
PGPL_GuiSliderWidget *slider_widget = calloc(1, sizeof(*slider_widget));
slider_widget->parent.id = "PGPL_GuiSliderWidget";
slider_widget->parent.get_content_size = get_content_size;
slider_widget->parent.render_content = render_content;
slider_widget->parent.event = event;
slider_widget->parent.destroy = destroy;
slider_widget->bar_height = bar_height;
slider_widget->inversed = inversed;
slider_widget->vertical = vertical;
slider_widget->current_value = value;
slider_widget->slide_callback = slide_callback;
return slider_widget;
}
void pgpl_gui_slider_widget_destroy(PGPL_GuiSliderWidget *slider_widget) {
free(slider_widget);
}

View file

@ -1,3 +1,4 @@
#include <locale.h>
#include <pgpl.h>
PGPL_Mutex *pgpl_internal_render_lock;
@ -5,6 +6,7 @@ PGPL_Mutex *pgpl_internal_log_lock;
FILE *pgpl_internal_log_file;
void pgpl_init(void) {
setlocale(LC_ALL, "C.UTF-8");
pgpl_internal_render_lock = pgpl_mutex_create();
pgpl_internal_log_lock = pgpl_mutex_create();
pgpl_internal_log_file = stderr;

View file

@ -9,8 +9,8 @@
#define STBTT_STATIC
#include <stb/stb_truetype.h>
#define CHAR_AMOUNT 256
#define BANK_AMOUNT 256
#define CHAR_AMOUNT 8
#define BANK_AMOUNT (65536 / CHAR_AMOUNT)
typedef struct PGPL_FontBank {
stbtt_bakedchar baked[CHAR_AMOUNT];
@ -93,8 +93,8 @@ 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;
uint32_t bank_index = c / CHAR_AMOUNT;
uint32_t bank_offset = c % CHAR_AMOUNT;
double factor = size / font->glyph_size;
float xoff_stb = *xoff;
float yoff_stb = *yoff;
@ -121,7 +121,7 @@ void pgpl_font_render_glyph(PGPL_Renderer *renderer, PGPL_Font *font, wchar_t c,
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,
(y + q.y0 * factor + size / 1.6), (q.x1 - q.x0) * factor,
(q.y1 - q.y0) * factor, color, q.s0, q.t0, q.s1, q.t1);
*xoff = xoff_stb;
@ -164,8 +164,8 @@ 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;
uint32_t bank_index = c / CHAR_AMOUNT;
uint32_t bank_offset = c % CHAR_AMOUNT;
double factor = size / font->glyph_size;
float xoff_stb = *xoff;
float yoff_stb = *yoff;
@ -179,9 +179,9 @@ void pgpl_font_glyph_dimensions(PGPL_Font *font, wchar_t c, double size,
font->glyph_size * font->chars_vertical, bank_offset,
&xoff_stb, &yoff_stb, &q, 0);
rectangle->top = q.y0 * factor + size / 1.5;
rectangle->top = q.y0 * factor + size / 1.6;
rectangle->left = q.x0 * factor;
rectangle->bottom = q.y1 * factor + size / 1.5;
rectangle->bottom = q.y1 * factor + size / 1.6;
rectangle->right = q.x1 * factor;
*xoff = xoff_stb;

View file

@ -13,3 +13,5 @@
struct PGPL_Texture {
GLuint id;
};
void pgpl_render_internal_handle_opengl_error(int line, const char *file);

42
src/render/render.c Normal file
View file

@ -0,0 +1,42 @@
#include "internal.h"
void pgpl_render_internal_handle_opengl_error(int line, const char *file) {
GLenum error = glGetError();
if (error != GL_NO_ERROR) {
switch (error) {
case GL_INVALID_ENUM:
pgpl_log_message(PGPL_LOG_LEVEL_WARN,
"OpenGL invalid enum at line %d in file %s", line, file);
break;
case GL_INVALID_VALUE:
pgpl_log_message(PGPL_LOG_LEVEL_WARN,
"OpenGL invalid value at line %d in file %s", line,
file);
break;
case GL_INVALID_OPERATION:
pgpl_log_message(PGPL_LOG_LEVEL_WARN,
"OpenGL invalid operation at line %d", line);
break;
case GL_STACK_OVERFLOW:
pgpl_log_message(PGPL_LOG_LEVEL_WARN,
"OpenGL stack overflow at line %d in file %s", line,
file);
break;
case GL_STACK_UNDERFLOW:
pgpl_log_message(PGPL_LOG_LEVEL_WARN,
"OpenGL stack underflow at line %d in file %s", line,
file);
break;
case GL_OUT_OF_MEMORY:
pgpl_log_message(PGPL_LOG_LEVEL_ERROR,
"OpenGL out of memory at line %d in file %s", line,
file);
break;
default:
pgpl_log_message(PGPL_LOG_LEVEL_ERROR,
"Unknown OpenGL error at line %d in file %s", line,
file);
}
}
}

View file

@ -1,6 +1,4 @@
#include "internal.h"
#include <stdio.h>
#include <stdlib.h>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
@ -94,6 +92,8 @@ PGPL_Texture *pgpl_render_create_texture(PGPL_Renderer *renderer,
glTexImage2D(GL_TEXTURE_2D, 0, gl_format, width, height, 0, gl_format,
GL_UNSIGNED_BYTE, data);
pgpl_render_internal_handle_opengl_error(__LINE__, __FILE__);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;

View file

@ -1,4 +1,3 @@
#include <locale.h>
#include <pgpl.h>
#include <stdlib.h>
#include <string.h>
@ -11,13 +10,11 @@ wchar_t *pgpl_string_to_wide_string(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);
MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, length + 1);
#else
mbstowcs(wstr, str, length + 1);
#endif

View file

@ -14,6 +14,7 @@ struct PGPL_Window {
PGPL_Vector *window_proc_event_type;
PGPL_Vector *window_proc_event_data;
int32_t last_mouse_x, last_mouse_y;
int32_t scroll_accumulator;
char title[128];
};
@ -44,18 +45,30 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
break;
case WM_KEYDOWN:
case WM_KEYDOWN: {
wchar_t buffer[16];
BYTE keyboard_state[256];
GetKeyboardState(keyboard_state);
buffer[ToUnicode(wParam, (lParam >> 16) & 0x00ff, keyboard_state, buffer,
15, 0)] = 0;
event_type = PGPL_WINDOW_EVENT_KEY_PRESS;
event_data.input_event.key = wParam;
event_data.input_event.key = buffer[0];
event_data.input_event.x = window->last_mouse_x;
event_data.input_event.y = window->last_mouse_y;
break;
case WM_KEYUP:
}
case WM_KEYUP: {
wchar_t buffer[16];
BYTE keyboard_state[256];
GetKeyboardState(keyboard_state);
buffer[ToUnicode(wParam, (lParam >> 16) & 0x00ff, keyboard_state, buffer,
15, 0)] = 0;
event_type = PGPL_WINDOW_EVENT_KEY_RELEASE;
event_data.input_event.key = wParam;
event_data.input_event.key = buffer[0];
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;
@ -105,15 +118,18 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
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);
window->scroll_accumulator += GET_WHEEL_DELTA_WPARAM(wParam);
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;
if (window->scroll_accumulator >= 120) {
event_data.input_event.key = 4;
window->scroll_accumulator -= 120;
} else if (window->scroll_accumulator <= -120) {
event_data.input_event.key = 5;
window->scroll_accumulator += 120;
}
break;
}
case WM_MOUSEMOVE: {
@ -121,7 +137,7 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
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.key = 0;
event_data.input_event.x = points.x;
event_data.input_event.y = points.y;
break;

View file

@ -1,6 +1,5 @@
#include "internal.h"
#include <GL/gl.h>
#include <stdio.h>
#include <stdlib.h>
/* Platform agnostic functions for our PGPL_Window "class" */