Compare commits

...

13 Commits

Author SHA1 Message Date
Mike Gorchak
543d81a7e4
Merge 098a8c1697 into 0d2d85d19c 2025-08-18 12:02:19 +00:00
Doug Binks
0d2d85d19c Revert "Wayland: Keyboard leave event handler now processes key repeats" 2025-08-15 11:27:59 +02:00
Jan Hendrik Farr
768e81a0eb Wayland: Fix key repeat halting
Key repeat shoud only be halted when the repeating key
is released, not when another key is released.
2025-08-14 15:35:04 +02:00
Camilla Löwy
161fb1b6f6 Wayland: Fix fallback decoration scroll events
The fallback decorations would emit scroll events as if scrolling had
occurred over the content area of the window.
2025-08-12 17:11:27 +02:00
Camilla Löwy
645a35a38e Wayland: Cleanup 2025-08-12 17:11:27 +02:00
Camilla Löwy
7523b0e6bd Wayland: Move fallback decoration pointer logic
Decluttered the wl_pointer handlers by moving the bulk of fallback
decoration related logic to separate functions.
2025-08-12 17:11:26 +02:00
Camilla Löwy
5190a30d8a Wayland: Move fallback decoration struct member
The cursorPreviousName member was only used for the fallback decorations
but was not grouped with other related members.
2025-08-12 17:11:26 +02:00
Camilla Löwy
ddbb8e0f2c Wayland: Fix fallback decoration cursor position
If fallback decorations were in use, pointer motion over a decoration
surface would cause glfwGetCursorPos to provide incorrect cursor
positions.

The cursor position is now only updated when the pointer is over the
content area of the window, similar to libdecor and XDG decorations.
2025-08-12 17:11:24 +02:00
Camilla Löwy
5245180c56 Formatting 2025-08-12 17:10:43 +02:00
Doug Binks
7b51a8eb31 Wayland: Keyboard leave event handler now processes key repeats
- Fixes #2736
2025-08-10 18:27:44 +02:00
Camilla Löwy
ac10768495 Wayland: Fix memory leaks in data offer reading
The buffer storing the contents of the data offer being read could leak
if buffer reallocation or reading from the pipe failed.
2025-07-18 12:31:07 +02:00
Doug Binks
feb2a6b728 Wayland: Reset key repeat timer on window destruction
Windows with keyboard focus may have an active key repeat timer.
This should be reset when the window is closed, or key repeat events
could be sent to a NULL window were it not for the quickfix in PR #2732.

Fixes #2741
Probably the source of #2727
2025-07-17 17:24:19 +02:00
Mike Gorchak
098a8c1697 Add OpenGL ES variant of Boing demo. 2024-07-17 11:05:30 -04:00
5 changed files with 995 additions and 185 deletions

View File

@ -68,6 +68,7 @@ video tutorials.
- Jan Ekström
- Siavash Eliasi
- er-azh
- Jan Hendrik Farr
- Ahmad Fatoum
- Nikita Fediuchin
- Felipe Ferreira

View File

@ -132,6 +132,13 @@ information on what to include when reporting a bug.
- [Wayland] Bugfix: `glfwInit` would segfault on compositor with no seat (#2517)
- [Wayland] Bugfix: A drag entering a non-GLFW surface could cause a segfault
- [Wayland] Bugfix: Ignore key repeat events when no window has keyboard focus (#2727)
- [Wayland] Bugfix: Reset key repeat timer when window destroyed (#2741,#2727)
- [Wayland] Bugfix: Memory would leak if reading a data offer failed midway
- [Wayland] Bugfix: Keyboard leave event handler now processes key repeats (#2736)
- [Wayland] Bugfix: Retrieved cursor position would be incorrect when hovering over
fallback decorations
- [Wayland] Bugfix: Fallback decorations would report scroll events
- [Wayland] Bugfix: Keyboard repeat events halted when any key is released (#2568)
- [X11] Bugfix: Running without a WM could trigger an assert (#2593,#2601,#2631)
- [Null] Added Vulkan 'window' surface creation via `VK_EXT_headless_surface`
- [Null] Added EGL context creation on Mesa via `EGL_MESA_platform_surfaceless`

775
examples/boing-opengles.c Normal file
View File

@ -0,0 +1,775 @@
/*****************************************************************************
* Title: OpenGL ES Boing
* Desc: Tribute to Amiga Boing.
* Author: Jim Brooks <gfx@jimbrooks.org>
* Original Amiga authors were R.J. Mical and Dale Luck.
* GLFW conversion by Marcus Geelnard
* OpenGL ES port by Mike Gorchak
* Notes: - 360' = 2*PI [radian]
*
* - Distances between objects are created by doing a relative
* Z translations.
*
* - Although OpenGL ES enticingly supports alpha-blending,
* the shadow of the original Boing didn't affect the color
* of the grid.
*
* - [Marcus] Changed timing scheme from interval driven to frame-
* time based animation steps (which results in much smoother
* movement)
*
* History of Amiga Boing:
*
* Boing was demonstrated on the prototype Amiga (codenamed "Lorraine") in
* 1985. According to legend, it was written ad-hoc in one night by
* R. J. Mical and Dale Luck. Because the bouncing ball animation was so fast
* and smooth, attendees did not believe the Amiga prototype was really doing
* the rendering. Suspecting a trick, they began looking around the booth for
* a hidden computer or VCR.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#define GLAD_GLES2_IMPLEMENTATION
#include <glad/gles2.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <linmath.h>
/*****************************************************************************
* Various declarations and macros
*****************************************************************************/
/* Draw ball, or its shadow */
typedef enum
{
DRAW_BALL,
DRAW_BALL_SHADOW
} DRAW_BALL_ENUM;
/* Prototypes */
void init(void);
void display(void);
void reshape(GLFWwindow* window, int w, int h);
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
void cursor_position_callback(GLFWwindow* window, double x, double y);
void GenerateBoingBall(void);
void GenerateGrid(void);
void DrawBoingBall(DRAW_BALL_ENUM drawBallHow);
void DrawGrid(void);
void BounceBall(float dt);
#define RADIUS 70.0f
#define STEP_LONGITUDE 22.5f /* 22.5 makes 8 bands like original Boing */
#define STEP_LATITUDE 22.5f
#define DIST_BALL (RADIUS * 2.0f + RADIUS * 0.1f)
#define VIEW_SCENE_DIST (DIST_BALL * 3.0f + 200.0f) /* distance from viewer to middle of boing area */
#define GRID_SIZE (RADIUS * 4.5f) /* length (width) of grid */
#define BOUNCE_HEIGHT (RADIUS * 2.1f)
#define BOUNCE_WIDTH (RADIUS * 2.1f)
#define SHADOW_OFFSET_X -20.0f
#define SHADOW_OFFSET_Y 10.f
#define SHADOW_OFFSET_Z 0.0f
#define WALL_L_OFFSET 0.0f
#define WALL_R_OFFSET 5.0f
/* Animation speed (50.0 mimics the original GLUT demo speed) */
#define ANIMATION_SPEED 50.0f
/* Maximum allowed delta time per physics iteration */
#define MAX_DELTA_T 0.02f
/* Global vars */
int windowed_xpos, windowed_ypos, windowed_width, windowed_height;
int width, height;
int override_pos = GLFW_FALSE;
float deg_rot_y = 0.0f;
float deg_rot_y_inc = 2.0f;
float cursor_x = 0.0f;
float cursor_y = 0.0f;
float ball_x = -RADIUS;
float ball_y = -RADIUS;
float ball_x_inc = 1.0f;
float ball_y_inc = 2.0f;
float t;
float t_old = 0.0f;
float dt;
/* Random number generator */
#ifndef RAND_MAX
#define RAND_MAX 4095
#endif
static mat4x4 projection;
static mat4x4 modelview_orig;
static mat4x4 modelview;
static mat4x4 mvp;
static vec3* ball_color1_vertices = NULL;
static vec3* ball_color2_vertices = NULL;
static vec3* grid_vertices = NULL;
static vec3 color1 = {0.8f, 0.1f, 0.1f}; /* reddish */
static vec3 color2 = {0.95f, 0.95f, 0.95f}; /* almost white */
static vec3 color_shadow = {0.35f, 0.35f, 0.35f}; /* dark grey */
static vec3 grid_color = {0.6f, 0.1f, 0.6f}; /* purple */
static int facet_array_size = 0;
static int grid_array_size = 0;
static GLuint buffer;
static GLuint program;
static GLuint mvp_matrix_loc = -1;
static GLuint vertex_color_loc = -1;
static GLuint vertex_attr_index = -1;
/*****************************************************************************
* Truncate a degree.
*****************************************************************************/
float TruncateDeg(float deg)
{
if (deg >= 360.0f)
return (deg - 360.0f);
else
return deg;
}
/*****************************************************************************
* Convert a degree (360-based) into a radian.
* 360' = 2 * PI
*****************************************************************************/
float deg2rad(float deg)
{
return deg / 360.0f * (2.0f * M_PI);
}
/*****************************************************************************
* 360' sin().
*****************************************************************************/
float sin_deg(float deg)
{
return sin(deg2rad(deg));
}
/*****************************************************************************
* 360' cos().
*****************************************************************************/
float cos_deg(float deg)
{
return cos(deg2rad(deg));
}
/*****************************************************************************
* init_shaders()
*****************************************************************************/
static int init_shaders(void)
{
GLenum error;
GLuint vertex_shader, fragment_shader;
char log[8192];
GLsizei len;
GLint processing_done = GL_TRUE;
error = glGetError();
const char *vtx_shdr_src =
"attribute vec3 vertex_position; \n"
" \n"
"uniform mat4 mvp_matrix; \n"
"uniform vec4 vertex_color; \n"
" \n"
"varying vec4 fs_color; \n"
" \n"
"void main() \n"
"{ \n"
" fs_color = vertex_color; \n"
" gl_Position = mvp_matrix * vec4(vertex_position, 1.0); \n"
"} \n";
const char *frg_shdr_src =
"varying vec4 fs_color; \n"
" \n"
"void main(void) \n"
"{ \n"
" gl_FragColor = fs_color; \n"
"} \n";
program = glCreateProgram();
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glAttachShader(program, vertex_shader);
glShaderSource(vertex_shader, 1, (const char**)&vtx_shdr_src, NULL);
glCompileShader(vertex_shader);
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &processing_done);
if (processing_done != GL_TRUE)
{
glGetShaderInfoLog(vertex_shader, sizeof(log), &len, log);
fprintf(stderr, "Vertex Shader: %s", log);
return -1;
}
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glAttachShader(program, fragment_shader);
glShaderSource(fragment_shader, 1, (const char**)&frg_shdr_src, NULL);
glCompileShader(fragment_shader);
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &processing_done);
if (processing_done != GL_TRUE)
{
glGetShaderInfoLog(fragment_shader, sizeof(log), &len, log);
fprintf(stderr, "Fragment Shader: %s", log);
return -1;
}
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &processing_done);
if (processing_done != GL_TRUE)
{
glGetProgramInfoLog(program, sizeof(log), &len, log);
fprintf(stderr, "Linker: %s", log);
return -1;
}
glUseProgram(program);
mvp_matrix_loc = glGetUniformLocation(program, "mvp_matrix");
if ((mvp_matrix_loc == -1))
{
fprintf(stderr, "Can't locate location of MVP matrix uniform!\n");
return -1;
}
vertex_color_loc = glGetUniformLocation(program, "vertex_color");
if (vertex_color_loc == -1)
{
fprintf(stderr, "Can't locate location of fog color uniform!\n");
return -1;
}
vertex_attr_index = glGetAttribLocation(program, "vertex_position");
if (vertex_attr_index == -1)
{
fprintf(stderr, "Can't locate location of vertex attribute!\n");
return -1;
}
/* We do not change VBO and use index offsets to the array elements */
glEnableVertexAttribArray(vertex_attr_index);
glVertexAttribPointer(vertex_attr_index, 3, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)(uintptr_t)0);
error = glGetError();
if (error != GL_NO_ERROR)
{
fprintf(stderr, "GL Error: 0x%04X in shaders setup\n", error);
return -1;
}
return 0;
}
/*****************************************************************************
* init()
*****************************************************************************/
void init(void)
{
int offset = 0;
/* Clear background. */
glClearColor(0.55f, 0.55f, 0.55f, 0.0f);
/* Generate initial modelview matrix */
vec3 eye = {0.0f, 0.0f, VIEW_SCENE_DIST};
vec3 center = {0.0f, 0.0f, 0.0f};
vec3 up = {0.0f, -1.0f, 0.0f};
mat4x4_look_at(modelview_orig, eye, center, up);
GenerateBoingBall();
GenerateGrid();
/* Setup VBO */
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, facet_array_size + facet_array_size + grid_array_size,
NULL, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, offset, facet_array_size, ball_color1_vertices);
offset += facet_array_size;
glBufferSubData(GL_ARRAY_BUFFER, offset, facet_array_size, ball_color2_vertices);
offset += facet_array_size;
glBufferSubData(GL_ARRAY_BUFFER, offset, grid_array_size, grid_vertices);
offset += grid_array_size;
/* We don't need vertices in a heap, they were uploaded as VBO */
free(ball_color1_vertices);
free(ball_color2_vertices);
free(grid_vertices);
init_shaders();
}
/*****************************************************************************
* display()
*****************************************************************************/
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
DrawBoingBall(DRAW_BALL_SHADOW);
DrawGrid();
DrawBoingBall(DRAW_BALL);
}
/*****************************************************************************
* reshape()
*****************************************************************************/
void reshape(GLFWwindow* window, int w, int h)
{
width = w;
height = h;
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
mat4x4_perspective(projection, 2.0f * (float)atan2(RADIUS, 200.0f), (float)w / (float)h, 1.0f, VIEW_SCENE_DIST);
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (action != GLFW_PRESS)
return;
if (key == GLFW_KEY_ESCAPE && mods == 0)
glfwSetWindowShouldClose(window, GLFW_TRUE);
if ((key == GLFW_KEY_ENTER && mods == GLFW_MOD_ALT) ||
(key == GLFW_KEY_F11 && mods == GLFW_MOD_ALT))
{
if (glfwGetWindowMonitor(window))
{
glfwSetWindowMonitor(window, NULL,
windowed_xpos, windowed_ypos,
windowed_width, windowed_height, 0);
}
else
{
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
if (monitor)
{
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwGetWindowPos(window, &windowed_xpos, &windowed_ypos);
glfwGetWindowSize(window, &windowed_width, &windowed_height);
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
}
}
}
}
static void set_ball_pos(float x, float y)
{
ball_x = (width / 2) - x;
ball_y = y - (height / 2);
}
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
if (button != GLFW_MOUSE_BUTTON_LEFT)
return;
if (action == GLFW_PRESS)
{
override_pos = GLFW_TRUE;
set_ball_pos(cursor_x, cursor_y);
}
else
{
override_pos = GLFW_FALSE;
}
}
void cursor_position_callback(GLFWwindow* window, double x, double y)
{
cursor_x = (float)x;
cursor_y = (float)y;
if (override_pos)
set_ball_pos(cursor_x, cursor_y);
}
/*****************************************************************************
* Generate the Boing ball.
*
* The Boing ball is sphere in which each facet is a rectangle.
* Facet colors alternate between red and white.
* The ball is built by stacking latitudinal circles. Each circle is composed
* of a widely-separated set of points, so that each facet is noticeably large.
*****************************************************************************/
void GenerateBoingBall(void)
{
/* degree of longitude */
float lon_deg;
float lat_deg;
int vertex_idx_c1 = 0;
int vertex_idx_c2 = 0;
bool colorToggle = 0;
/* "ne" means south-east, so on */
vec3 vert_ne;
vec3 vert_nw;
vec3 vert_sw;
vec3 vert_se;
/* "/ 2" - half facets of one color */
/* "* 6" - 6 vertices per facet (two triangles) */
facet_array_size = (int)((180.0f / STEP_LONGITUDE) * (360.0f / STEP_LATITUDE) / 2) * 6 * sizeof(vec3);
ball_color1_vertices = calloc(1, facet_array_size);
ball_color2_vertices = calloc(1, facet_array_size);
if ((ball_color1_vertices == NULL) || (ball_color2_vertices == NULL))
{
fprintf(stderr, "Can't allocate memory for ball vertices!\n");
glfwTerminate();
exit(EXIT_FAILURE);
}
/*
* Build a faceted latitude slice of the Boing ball,
* stepping same-sized vertical bands of the sphere.
*/
for (lon_deg = 0; lon_deg < 180; lon_deg += STEP_LONGITUDE)
{
/*
* Iterate through the points of a latitude circle.
* A latitude circle is a 2D set of X,Z points.
*/
for (lat_deg = 0; lat_deg <= (360 - STEP_LATITUDE); lat_deg += STEP_LATITUDE)
{
/* Assign each Y. */
vert_ne[1] = vert_nw[1] = (float) cos_deg((lon_deg + STEP_LONGITUDE)) * RADIUS;
vert_sw[1] = vert_se[1] = (float) cos_deg(lon_deg) * RADIUS;
/*
* Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude.
* Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude),
* while long=90 (sin(90)=1) is at equator.
*/
vert_ne[0] = (float)cos_deg(lat_deg ) * (RADIUS * (float)sin_deg(lon_deg + STEP_LONGITUDE));
vert_se[0] = (float)cos_deg(lat_deg ) * (RADIUS * (float)sin_deg(lon_deg ));
vert_nw[0] = (float)cos_deg(lat_deg + STEP_LATITUDE) * (RADIUS * (float)sin_deg(lon_deg + STEP_LONGITUDE));
vert_sw[0] = (float)cos_deg(lat_deg + STEP_LATITUDE) * (RADIUS * (float)sin_deg(lon_deg ));
vert_ne[2] = (float)sin_deg(lat_deg ) * (RADIUS * (float)sin_deg(lon_deg + STEP_LONGITUDE));
vert_se[2] = (float)sin_deg(lat_deg ) * (RADIUS * (float)sin_deg(lon_deg ));
vert_nw[2] = (float)sin_deg(lat_deg + STEP_LATITUDE) * (RADIUS * (float)sin_deg(lon_deg + STEP_LONGITUDE));
vert_sw[2] = (float)sin_deg(lat_deg + STEP_LATITUDE) * (RADIUS * (float)sin_deg(lon_deg ));
/*
* Color this polygon with red or white. Replace polygons with
* triangles to perform a batch draw operation.
*/
if (colorToggle)
{
memcpy(ball_color1_vertices[vertex_idx_c1 + 0], vert_ne, sizeof(vec3)); /* V0 */
memcpy(ball_color1_vertices[vertex_idx_c1 + 1], vert_nw, sizeof(vec3)); /* V1 */
memcpy(ball_color1_vertices[vertex_idx_c1 + 2], vert_sw, sizeof(vec3)); /* V2 */
memcpy(ball_color1_vertices[vertex_idx_c1 + 3], vert_ne, sizeof(vec3)); /* V0 */
memcpy(ball_color1_vertices[vertex_idx_c1 + 4], vert_sw, sizeof(vec3)); /* V2 */
memcpy(ball_color1_vertices[vertex_idx_c1 + 5], vert_se, sizeof(vec3)); /* V3 */
vertex_idx_c1 += 6;
}
else
{
memcpy(ball_color2_vertices[vertex_idx_c2 + 0], vert_ne, sizeof(vec3)); /* V0 */
memcpy(ball_color2_vertices[vertex_idx_c2 + 1], vert_nw, sizeof(vec3)); /* V1 */
memcpy(ball_color2_vertices[vertex_idx_c2 + 2], vert_sw, sizeof(vec3)); /* V2 */
memcpy(ball_color2_vertices[vertex_idx_c2 + 3], vert_ne, sizeof(vec3)); /* V0 */
memcpy(ball_color2_vertices[vertex_idx_c2 + 4], vert_sw, sizeof(vec3)); /* V2 */
memcpy(ball_color2_vertices[vertex_idx_c2 + 5], vert_se, sizeof(vec3)); /* V3 */
vertex_idx_c2 += 6;
}
colorToggle = ! colorToggle;
}
/* Toggle color so that next band will opposite red/white colors than this one. */
colorToggle = ! colorToggle;
}
}
/*****************************************************************************
* Bounce the ball.
*****************************************************************************/
void BounceBall(float delta_t)
{
float sign;
float deg;
if (override_pos)
return;
/* Bounce on walls */
if (ball_x > (BOUNCE_WIDTH / 2 + WALL_R_OFFSET))
{
ball_x_inc = -0.5f - 0.75f * (float)rand() / (float)RAND_MAX;
deg_rot_y_inc = -deg_rot_y_inc;
}
if (ball_x < -(BOUNCE_HEIGHT / 2 + WALL_L_OFFSET))
{
ball_x_inc = 0.5f + 0.75f * (float)rand() / (float)RAND_MAX;
deg_rot_y_inc = -deg_rot_y_inc;
}
/* Bounce on floor / roof */
if (ball_y > BOUNCE_HEIGHT / 2)
{
ball_y_inc = -0.75f - 1.0f * (float)rand() / (float)RAND_MAX;
}
if (ball_y < -BOUNCE_HEIGHT / 2 * 0.85)
{
ball_y_inc = 0.75f + 1.0f * (float)rand() / (float)RAND_MAX;
}
/* Update ball position */
ball_x += ball_x_inc * ((float)delta_t * ANIMATION_SPEED);
ball_y += ball_y_inc * ((float)delta_t * ANIMATION_SPEED);
/* Simulate the effects of gravity on Y movement. */
if (ball_y_inc < 0) sign = -1.0; else sign = 1.0;
deg = (ball_y + BOUNCE_HEIGHT / 2) * 90 / BOUNCE_HEIGHT;
if (deg > 80) deg = 80;
if (deg < 10) deg = 10;
ball_y_inc = sign * 4.0f * (float)sin_deg(deg);
}
/*****************************************************************************
* Draw the ball.
*****************************************************************************/
void DrawBoingBall(DRAW_BALL_ENUM drawBallHow)
{
float dt_total, dt2;
GLenum error;
/* Reset error */
error = glGetError();
memcpy(modelview, modelview_orig, sizeof(modelview));
/* Another relative Z translation to separate objects. */
mat4x4_translate_in_place(modelview, 0.0f, 0.0f, DIST_BALL);
/* Update ball position and rotation (iterate if necessary) */
dt_total = dt;
while (dt_total > 0.0)
{
dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total;
dt_total -= dt2;
BounceBall(dt2);
deg_rot_y = TruncateDeg(deg_rot_y + deg_rot_y_inc * ((float)dt2 * ANIMATION_SPEED));
}
/* Set ball position */
mat4x4_translate_in_place(modelview, ball_x, ball_y, 0.0f);
/* Offset the shadow. */
if (drawBallHow == DRAW_BALL_SHADOW)
{
mat4x4_translate_in_place(modelview, SHADOW_OFFSET_X, SHADOW_OFFSET_Y, SHADOW_OFFSET_Z);
}
/* Tilt the ball. */
mat4x4_rotate(modelview, modelview, 0.0f, 0.0f, 1.0f, deg2rad(-20.0f));
/* Continually rotate ball around Y axis. */
mat4x4_rotate(modelview, modelview, 0.0f, 1.0f, 0.0f, deg2rad(deg_rot_y));
/* Set OpenGL state for Boing ball. */
glCullFace(GL_FRONT);
glEnable(GL_CULL_FACE);
mat4x4_mul(mvp, projection, modelview);
glUniformMatrix4fv(mvp_matrix_loc, 1, GL_FALSE, (GLfloat*)mvp);
if (drawBallHow == DRAW_BALL_SHADOW)
{
glUniform4fv(vertex_color_loc, 1, color_shadow);
}
else
{
glUniform4fv(vertex_color_loc, 1, color1);
}
glDrawArrays(GL_TRIANGLES, 0, facet_array_size / sizeof(vec3));
if (drawBallHow == DRAW_BALL_SHADOW)
{
glUniform4fv(vertex_color_loc, 1, color_shadow);
}
else
{
glUniform4fv(vertex_color_loc, 1, color2);
}
glDrawArrays(GL_TRIANGLES, facet_array_size / sizeof(vec3), facet_array_size / sizeof(vec3));
error = glGetError();
if (error != GL_NO_ERROR)
{
fprintf(stderr, "GL Error: 0x%04X in ball draw function\n", error);
}
return;
}
/*****************************************************************************
* Generate grid of lines
*****************************************************************************/
void GenerateGrid(void)
{
const int rowTotal = 12; /* must be divisible by 2 */
const int colTotal = rowTotal; /* must be same as rowTotal */
const float widthLine = 2.0f; /* should be divisible by 2 */
const float sizeCell = GRID_SIZE / rowTotal;
const float z_offset = -40.0f;
int row, col;
float xl, xr;
float yt, yb;
int grid_idx = 0;
/* "* 6" - 6 vertices per line (two triangles) */
grid_array_size = ((rowTotal + 1) + (colTotal + 1)) * 6 * sizeof(vec3);
grid_vertices = calloc(1, facet_array_size);
if (grid_vertices == NULL)
{
fprintf(stderr, "Can't allocate memory for grid vertices!\n");
glfwTerminate();
exit(EXIT_FAILURE);
}
/* Generate vertical lines (as skinny 3D rectangles). */
for (col = 0; col <= colTotal; col++)
{
/* Compute coords of line. */
xl = -GRID_SIZE / 2 + col * sizeCell;
xr = xl + widthLine;
yt = GRID_SIZE / 2;
yb = -GRID_SIZE / 2 - widthLine;
memcpy(grid_vertices[grid_idx + 0], (vec3){xr, yt, z_offset}, sizeof(vec3)); /* NE, V0 */
memcpy(grid_vertices[grid_idx + 1], (vec3){xl, yt, z_offset}, sizeof(vec3)); /* NW, V1 */
memcpy(grid_vertices[grid_idx + 2], (vec3){xl, yb, z_offset}, sizeof(vec3)); /* SW, V2 */
memcpy(grid_vertices[grid_idx + 3], (vec3){xr, yt, z_offset}, sizeof(vec3)); /* NE, V0 */
memcpy(grid_vertices[grid_idx + 4], (vec3){xl, yb, z_offset}, sizeof(vec3)); /* SW, V2 */
memcpy(grid_vertices[grid_idx + 5], (vec3){xr, yb, z_offset}, sizeof(vec3)); /* SE, V3 */
grid_idx += 6;
}
/* Generate horizontal lines (as skinny 3D rectangles). */
for (row = 0; row <= rowTotal; row++)
{
/* Compute coords of line. */
yt = GRID_SIZE / 2 - row * sizeCell;
yb = yt - widthLine;
xl = -GRID_SIZE / 2;
xr = GRID_SIZE / 2 + widthLine;
memcpy(grid_vertices[grid_idx + 0], (vec3){xr, yt, z_offset}, sizeof(vec3)); /* NE, V0 */
memcpy(grid_vertices[grid_idx + 1], (vec3){xl, yt, z_offset}, sizeof(vec3)); /* NW, V1 */
memcpy(grid_vertices[grid_idx + 2], (vec3){xl, yb, z_offset}, sizeof(vec3)); /* SW, V2 */
memcpy(grid_vertices[grid_idx + 3], (vec3){xr, yt, z_offset}, sizeof(vec3)); /* NE, V0 */
memcpy(grid_vertices[grid_idx + 4], (vec3){xl, yb, z_offset}, sizeof(vec3)); /* SW, V2 */
memcpy(grid_vertices[grid_idx + 5], (vec3){xr, yb, z_offset}, sizeof(vec3)); /* SE, V3 */
grid_idx += 6;
}
}
/*****************************************************************************
* Draw the purple grid of lines, behind the Boing ball.
* When the Workbench is dropped to the bottom, Boing shows 12 rows.
*****************************************************************************/
void DrawGrid(void)
{
memcpy(modelview, modelview_orig, sizeof(modelview));
glDisable(GL_CULL_FACE);
/* Another relative Z translation to separate objects. */
mat4x4_translate_in_place(modelview, 0.0f, 0.0f, DIST_BALL);
mat4x4_mul(mvp, projection, modelview);
glUniformMatrix4fv(mvp_matrix_loc, 1, GL_FALSE, (GLfloat*)mvp);
glUniform4fv(vertex_color_loc, 1, grid_color);
glDrawArrays(GL_TRIANGLES, (facet_array_size + facet_array_size) / sizeof(vec3),
(facet_array_size + facet_array_size) / sizeof(vec3));
return;
}
/*======================================================================*
* main()
*======================================================================*/
int main(void)
{
GLFWwindow* window;
/* Init GLFW */
if (!glfwInit())
exit(EXIT_FAILURE);
window = glfwCreateWindow(400, 400, "Boing OpenGL ES 2.x (classic Amiga demo)", NULL, NULL);
if (!window)
{
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSetWindowAspectRatio(window, 1, 1);
glfwSetFramebufferSizeCallback(window, reshape);
glfwSetKeyCallback(window, key_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetCursorPosCallback(window, cursor_position_callback);
glfwMakeContextCurrent(window);
gladLoadGLES2(glfwGetProcAddress);
glfwSwapInterval(1);
glfwGetFramebufferSize(window, &width, &height);
reshape(window, width, height);
glfwSetTime(0.0);
init();
/* Main loop */
for (;;)
{
/* Timing */
t = glfwGetTime();
dt = t - t_old;
t_old = t;
/* Draw one frame */
display();
/* Swap buffers */
glfwSwapBuffers(window);
glfwPollEvents();
/* Check if we are still running */
if (glfwWindowShouldClose(window))
break;
}
glfwTerminate();
exit(EXIT_SUCCESS);
}

View File

@ -413,6 +413,8 @@ typedef struct _GLFWwindowWayland
struct wl_buffer* buffer;
_GLFWfallbackEdgeWayland top, left, right, bottom;
struct wl_surface* focus;
wl_fixed_t pointerX, pointerY;
const char* cursorName;
} fallback;
} _GLFWwindowWayland;
@ -454,7 +456,6 @@ typedef struct _GLFWlibraryWayland
struct wl_cursor_theme* cursorTheme;
struct wl_cursor_theme* cursorThemeHiDPI;
struct wl_surface* cursorSurface;
const char* cursorPreviousName;
int cursorTimerfd;
uint32_t serial;
uint32_t pointerEnterSerial;

View File

@ -275,6 +275,146 @@ static void destroyFallbackDecorations(_GLFWwindow* window)
destroyFallbackEdge(&window->wl.fallback.bottom);
}
static void updateFallbackDecorationCursor(_GLFWwindow* window,
wl_fixed_t sx,
wl_fixed_t sy)
{
window->wl.fallback.pointerX = sx;
window->wl.fallback.pointerY = sy;
const double xpos = wl_fixed_to_double(sx);
const double ypos = wl_fixed_to_double(sy);
const char* cursorName = "left_ptr";
if (window->resizable)
{
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "n-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "nw-resize";
else
cursorName = "w-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "ne-resize";
else
cursorName = "e-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
cursorName = "sw-resize";
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
cursorName = "se-resize";
else
cursorName = "s-resize";
}
}
if (window->wl.fallback.cursorName != cursorName)
{
struct wl_surface* surface = _glfw.wl.cursorSurface;
struct wl_cursor_theme* theme = _glfw.wl.cursorTheme;
int scale = 1;
if (window->wl.bufferScale > 1 && _glfw.wl.cursorThemeHiDPI)
{
// We only support up to scale=2 for now, since libwayland-cursor
// requires us to load a different theme for each size.
scale = 2;
theme = _glfw.wl.cursorThemeHiDPI;
}
struct wl_cursor* cursor = wl_cursor_theme_get_cursor(theme, cursorName);
if (!cursor)
return;
// TODO: handle animated cursors too.
struct wl_cursor_image* image = cursor->images[0];
if (!image)
return;
struct wl_buffer* buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial,
surface,
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, image->width, image->height);
wl_surface_commit(surface);
window->wl.fallback.cursorName = cursorName;
}
}
static void handleFallbackDecorationButton(_GLFWwindow* window,
uint32_t serial,
uint32_t button)
{
const double xpos = wl_fixed_to_double(window->wl.fallback.pointerX);
const double ypos = wl_fixed_to_double(window->wl.fallback.pointerY);
if (button == BTN_LEFT)
{
uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
else
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
}
if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE)
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat, serial, edges);
}
else if (button == BTN_RIGHT)
{
if (window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
}
}
}
static void xdgDecorationHandleConfigure(void* userData,
struct zxdg_toplevel_decoration_v1* decoration,
uint32_t mode)
@ -1333,6 +1473,7 @@ static char* readDataOfferAsString(struct wl_data_offer* offer, const char* mime
if (!longer)
{
_glfwInputError(GLFW_OUT_OF_MEMORY, NULL);
_glfw_free(string);
close(fds[0]);
return NULL;
}
@ -1352,6 +1493,7 @@ static char* readDataOfferAsString(struct wl_data_offer* offer, const char* mime
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to read from data offer pipe: %s",
strerror(errno));
_glfw_free(string);
close(fds[0]);
return NULL;
}
@ -1415,7 +1557,6 @@ static void pointerHandleLeave(void* userData,
_glfw.wl.serial = serial;
_glfw.wl.pointerFocus = NULL;
_glfw.wl.cursorPreviousName = NULL;
if (window->wl.hovered)
{
@ -1425,7 +1566,10 @@ static void pointerHandleLeave(void* userData,
else
{
if (window->wl.fallback.decorations)
{
window->wl.fallback.focus = NULL;
window->wl.fallback.cursorName = NULL;
}
}
}
@ -1442,92 +1586,16 @@ static void pointerHandleMotion(void* userData,
if (window->cursorMode == GLFW_CURSOR_DISABLED)
return;
const double xpos = wl_fixed_to_double(sx);
const double ypos = wl_fixed_to_double(sy);
window->wl.cursorPosX = xpos;
window->wl.cursorPosY = ypos;
if (window->wl.hovered)
{
_glfw.wl.cursorPreviousName = NULL;
_glfwInputCursorPos(window, xpos, ypos);
return;
window->wl.cursorPosX = wl_fixed_to_double(sx);
window->wl.cursorPosY = wl_fixed_to_double(sy);
_glfwInputCursorPos(window, window->wl.cursorPosX, window->wl.cursorPosY);
}
else
{
if (window->wl.fallback.decorations)
{
const char* cursorName = "left_ptr";
if (window->resizable)
{
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "n-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "nw-resize";
else
cursorName = "w-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (ypos < GLFW_BORDER_SIZE)
cursorName = "ne-resize";
else
cursorName = "e-resize";
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (xpos < GLFW_BORDER_SIZE)
cursorName = "sw-resize";
else if (xpos > window->wl.width + GLFW_BORDER_SIZE)
cursorName = "se-resize";
else
cursorName = "s-resize";
}
}
if (_glfw.wl.cursorPreviousName != cursorName)
{
struct wl_surface* surface = _glfw.wl.cursorSurface;
struct wl_cursor_theme* theme = _glfw.wl.cursorTheme;
int scale = 1;
if (window->wl.bufferScale > 1 && _glfw.wl.cursorThemeHiDPI)
{
// We only support up to scale=2 for now, since libwayland-cursor
// requires us to load a different theme for each size.
scale = 2;
theme = _glfw.wl.cursorThemeHiDPI;
}
struct wl_cursor* cursor = wl_cursor_theme_get_cursor(theme, cursorName);
if (!cursor)
return;
// TODO: handle animated cursors too.
struct wl_cursor_image* image = cursor->images[0];
if (!image)
return;
struct wl_buffer* buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerEnterSerial,
surface,
image->hotspot_x / scale,
image->hotspot_y / scale);
wl_surface_set_buffer_scale(surface, scale);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, image->width, image->height);
wl_surface_commit(surface);
_glfw.wl.cursorPreviousName = cursorName;
}
updateFallbackDecorationCursor(window, sx, sy);
}
}
@ -1550,62 +1618,11 @@ static void pointerHandleButton(void* userData,
button - BTN_LEFT,
state == WL_POINTER_BUTTON_STATE_PRESSED,
_glfw.wl.xkb.modifiers);
return;
}
else
{
if (window->wl.fallback.decorations)
{
if (button == BTN_LEFT)
{
uint32_t edges = XDG_TOPLEVEL_RESIZE_EDGE_NONE;
if (window->wl.fallback.focus == window->wl.fallback.top.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
else
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
}
else if (window->wl.fallback.focus == window->wl.fallback.left.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
}
else if (window->wl.fallback.focus == window->wl.fallback.right.surface)
{
if (window->wl.cursorPosY < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
}
else if (window->wl.fallback.focus == window->wl.fallback.bottom.surface)
{
if (window->wl.cursorPosX < GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (window->wl.cursorPosX > window->wl.width + GLFW_BORDER_SIZE)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
}
if (edges != XDG_TOPLEVEL_RESIZE_EDGE_NONE)
{
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat,
serial, edges);
}
}
else if (button == BTN_RIGHT)
{
if (window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
}
}
handleFallbackDecorationButton(window, serial, button);
}
}
@ -1619,12 +1636,15 @@ static void pointerHandleAxis(void* userData,
if (!window)
return;
if (window->wl.hovered)
{
// NOTE: 10 units of motion per mouse wheel step seems to be a common ratio
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
_glfwInputScroll(window, -wl_fixed_to_double(value) / 10.0, 0.0);
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
_glfwInputScroll(window, 0.0, -wl_fixed_to_double(value) / 10.0);
}
}
static const struct wl_pointer_listener pointerListener =
{
@ -1800,10 +1820,11 @@ static void keyboardHandleKey(void* userData,
timer.it_value.tv_sec = _glfw.wl.keyRepeatDelay / 1000;
timer.it_value.tv_nsec = (_glfw.wl.keyRepeatDelay % 1000) * 1000000;
}
}
timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL);
}
} else if (scancode == _glfw.wl.keyRepeatScancode) {
timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL);
}
_glfwInputKey(window, key, scancode, action, _glfw.wl.xkb.modifiers);
@ -2187,7 +2208,12 @@ void _glfwDestroyWindowWayland(_GLFWwindow* window)
_glfw.wl.pointerFocus = NULL;
if (window == _glfw.wl.keyboardFocus)
{
struct itimerspec timer = {0};
timerfd_settime(_glfw.wl.keyRepeatTimerfd, 0, &timer, NULL);
_glfw.wl.keyboardFocus = NULL;
}
if (window->wl.fractionalScale)
wp_fractional_scale_v1_destroy(window->wl.fractionalScale);