531 lines
18 KiB
C
531 lines
18 KiB
C
/*
|
|
Simple DirectMedia Layer
|
|
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
warranty. In no event will the authors be held liable for any damages
|
|
arising from the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software
|
|
in a product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3/SDL_main.h>
|
|
#include <SDL3/SDL_test.h>
|
|
#include <SDL3/SDL_test_common.h>
|
|
|
|
#define WIDTH 1600
|
|
#define HEIGHT 1200
|
|
|
|
#define VERBOSE 0
|
|
|
|
#define ALWAYS_SHOW_PRESSURE_BOX 1
|
|
|
|
static SDLTest_CommonState *state;
|
|
static int quitting = 0;
|
|
|
|
static float last_x, last_y;
|
|
static float last_xtilt, last_ytilt, last_pressure, last_distance, last_rotation;
|
|
static int last_button;
|
|
static int last_touching; /* tip touches surface */
|
|
static int last_was_eraser;
|
|
|
|
static SDL_Texture *offscreen_texture = NULL;
|
|
|
|
static void DrawScreen(SDL_Renderer *renderer)
|
|
{
|
|
float xdelta, ydelta, endx, endy;
|
|
/* off-screen texture to render into */
|
|
SDL_Texture *window_texture;
|
|
const float X = 128.0f, Y = 128.0f; /* mid-point in the off-screen texture */
|
|
SDL_FRect dest_rect;
|
|
float tilt_vec_x = SDL_sinf(last_xtilt * SDL_PI_F / 180.0f);
|
|
float tilt_vec_y = SDL_sinf(last_ytilt * SDL_PI_F / 180.0f);
|
|
int color = last_button + 1;
|
|
|
|
if (!renderer) {
|
|
return;
|
|
}
|
|
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
|
|
SDL_RenderClear(renderer);
|
|
|
|
if (offscreen_texture == NULL) {
|
|
offscreen_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, (int)(X * 2.0f), (int)(Y * 2.0f));
|
|
}
|
|
|
|
/* Render into off-screen texture so we can do pixel-precise rendering later */
|
|
window_texture = SDL_GetRenderTarget(renderer);
|
|
SDL_SetRenderTarget(renderer, offscreen_texture);
|
|
|
|
/* Rendering starts here */
|
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
SDL_SetRenderDrawColor(renderer, 0x40, 0x40, 0x40, 0xff);
|
|
SDL_RenderClear(renderer);
|
|
|
|
SDL_SetRenderDrawColor(renderer, 0xa0, 0xa0, 0xa0, 0xff);
|
|
if (last_touching) {
|
|
SDL_FRect rect;
|
|
|
|
rect.x = 0;
|
|
rect.y = 0;
|
|
rect.w = 2.0f * X - 1.0f;
|
|
rect.h = 2.0f * Y - 1.0f;
|
|
|
|
SDL_RenderRect(renderer, &rect);
|
|
} else {
|
|
/* Show where the pen is rotating when it isn't touching the surface.
|
|
Otherwise we draw the rotation angle below together with pressure information. */
|
|
float rot_vecx = SDL_sinf(last_rotation / 180.0f * SDL_PI_F);
|
|
float rot_vecy = -SDL_cosf(last_rotation / 180.0f * SDL_PI_F);
|
|
float px = X + rot_vecx * 100.0f;
|
|
float py = Y + rot_vecy * 100.0f;
|
|
float px2 = X + rot_vecx * 80.0f;
|
|
float py2 = Y + rot_vecy * 80.0f;
|
|
|
|
SDL_RenderLine(renderer,
|
|
px, py,
|
|
px2 + rot_vecy * 20.0f,
|
|
py2 - rot_vecx * 20.0f);
|
|
SDL_RenderLine(renderer,
|
|
px, py,
|
|
px2 - rot_vecy * 20.0f,
|
|
py2 + rot_vecx * 20.0f);
|
|
}
|
|
|
|
if (last_was_eraser) {
|
|
SDL_FRect rect;
|
|
|
|
rect.x = X - 10.0f;
|
|
rect.y = Y - 10.0f;
|
|
rect.w = 21.0f;
|
|
rect.h = 21.0f;
|
|
|
|
SDL_SetRenderDrawColor(renderer, 0x00, 0xff, 0xff, 0xff);
|
|
SDL_RenderFillRect(renderer, &rect);
|
|
} else {
|
|
float distance = last_distance * 50.0f;
|
|
|
|
SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff);
|
|
SDL_RenderLine(renderer,
|
|
X - 10.0f - distance, Y,
|
|
X - distance, Y);
|
|
SDL_RenderLine(renderer,
|
|
X + 10.0f + distance, Y,
|
|
X + distance, Y);
|
|
SDL_RenderLine(renderer,
|
|
X, Y - 10.0f - distance,
|
|
X, Y - distance);
|
|
SDL_RenderLine(renderer,
|
|
X, Y + 10.0f + distance,
|
|
X, Y + distance);
|
|
|
|
}
|
|
|
|
/* Draw a cone based on the direction the pen is leaning as if it were shining a light. */
|
|
/* Colour derived from pens, intensity based on pressure: */
|
|
SDL_SetRenderDrawColor(renderer,
|
|
(color & 0x01) ? 0xff : 0,
|
|
(color & 0x02) ? 0xff : 0,
|
|
(color & 0x04) ? 0xff : 0,
|
|
(int)(0xff));
|
|
|
|
xdelta = -tilt_vec_x * 100.0f;
|
|
ydelta = -tilt_vec_y * 100.0f;
|
|
endx = X + xdelta;
|
|
endy = Y + ydelta;
|
|
SDL_RenderLine(renderer, X, Y, endx, endy);
|
|
|
|
SDL_SetRenderDrawColor(renderer,
|
|
(color & 0x01) ? 0xff : 0,
|
|
(color & 0x02) ? 0xff : 0,
|
|
(color & 0x04) ? 0xff : 0,
|
|
(int)(0xff * last_pressure));
|
|
/* Cone base width based on pressure: */
|
|
SDL_RenderLine(renderer, X, Y, endx + (ydelta * last_pressure / 3.0f), endy - (xdelta * last_pressure / 3.0f));
|
|
SDL_RenderLine(renderer, X, Y, endx - (ydelta * last_pressure / 3.0f), endy + (xdelta * last_pressure / 3.0f));
|
|
|
|
/* If tilt is very small (or zero, for pens that don't have tilt), add some extra lines, rotated by the current rotation value */
|
|
if (ALWAYS_SHOW_PRESSURE_BOX || (fabs(tilt_vec_x) < 0.2f && fabs(tilt_vec_y) < 0.2f)) {
|
|
int rot;
|
|
float pressure = last_pressure * 80.0f;
|
|
|
|
/* Four times, rotated 90 degrees, so that we get a box */
|
|
for (rot = 0; rot < 4; ++rot) {
|
|
|
|
float vecx = SDL_cosf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
|
|
float vecy = SDL_sinf((last_rotation + (rot * 90.0f)) / 180.0f * SDL_PI_F);
|
|
|
|
float px = X + vecx * pressure;
|
|
float py = Y + vecy * pressure;
|
|
|
|
SDL_RenderLine(renderer,
|
|
px + vecy * 10.0f, py - vecx * 10.0f,
|
|
px - vecy * 10.0f, py + vecx * 10.0f);
|
|
|
|
if (rot == 3) {
|
|
int r = 0;
|
|
for (; r >= 0; r -= 2) {
|
|
float delta = 10.0f - ((float) r);
|
|
|
|
SDL_RenderLine(renderer,
|
|
px + vecy * delta, py - vecx * delta,
|
|
px + (vecx * pressure * 0.4f),
|
|
py + (vecy * pressure * 0.4f));
|
|
SDL_RenderLine(renderer,
|
|
px - vecy * delta, py + vecx * delta,
|
|
px + (vecx * pressure * 0.4f),
|
|
py + (vecy * pressure * 0.4f));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_SetRenderTarget(renderer, window_texture);
|
|
/* Now render to pixel-precise position */
|
|
dest_rect.x = last_x - X;
|
|
dest_rect.y = last_y - Y;
|
|
dest_rect.w = X * 2.0f;
|
|
dest_rect.h = Y * 2.0f;
|
|
SDL_RenderTexture(renderer, offscreen_texture, NULL, &dest_rect);
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
static void dump_state(void)
|
|
{
|
|
int i;
|
|
int pens_nr;
|
|
|
|
/* Make sure this also works with a NULL parameter */
|
|
SDL_PenID* pens = SDL_GetPens(NULL);
|
|
if (pens) {
|
|
SDL_free(pens);
|
|
}
|
|
|
|
pens = SDL_GetPens(&pens_nr);
|
|
if (!pens) {
|
|
SDL_Log("Couldn't get pens: %s\n", SDL_GetError());
|
|
return;
|
|
}
|
|
SDL_Log("Found %d pens (terminated by %u)\n", pens_nr, (unsigned) pens[pens_nr]);
|
|
|
|
for (i = 0; i < pens_nr; ++i) {
|
|
SDL_PenID penid = pens[i];
|
|
SDL_GUID guid = SDL_GetPenGUID(penid);
|
|
char guid_str[33];
|
|
float axes[SDL_PEN_NUM_AXES];
|
|
float x, y;
|
|
int k;
|
|
SDL_PenCapabilityInfo info;
|
|
Uint32 status = SDL_GetPenStatus(penid, &x, &y, axes, SDL_PEN_NUM_AXES);
|
|
Uint32 capabilities = SDL_GetPenCapabilities(penid, &info);
|
|
char *type;
|
|
char *buttons_str;
|
|
|
|
SDL_GUIDToString(guid, guid_str, 33);
|
|
|
|
switch (SDL_GetPenType(penid)) {
|
|
case SDL_PEN_TYPE_ERASER:
|
|
type = "Eraser";
|
|
break;
|
|
case SDL_PEN_TYPE_PEN:
|
|
type = "Pen";
|
|
break;
|
|
case SDL_PEN_TYPE_PENCIL:
|
|
type = "Pencil";
|
|
break;
|
|
case SDL_PEN_TYPE_BRUSH:
|
|
type = "Brush";
|
|
break;
|
|
case SDL_PEN_TYPE_AIRBRUSH:
|
|
type = "Airbrush";
|
|
break;
|
|
default:
|
|
type = "Unknown (bug?)";
|
|
}
|
|
|
|
switch (info.num_buttons) {
|
|
case SDL_PEN_INFO_UNKNOWN:
|
|
SDL_asprintf(&buttons_str, "? buttons");
|
|
break;
|
|
case 1:
|
|
SDL_asprintf(&buttons_str, "1 button");
|
|
break;
|
|
default:
|
|
SDL_asprintf(&buttons_str, "%d button", info.num_buttons);
|
|
break;
|
|
}
|
|
|
|
SDL_Log("%s %lu: [%s] attached=%d, %s [cap= %08lx:%08lx =status] '%s'\n",
|
|
type,
|
|
(unsigned long) penid, guid_str,
|
|
SDL_PenConnected(penid), /* should always be SDL_TRUE during iteration */
|
|
buttons_str,
|
|
(unsigned long) capabilities,
|
|
(unsigned long) status,
|
|
SDL_GetPenName(penid));
|
|
SDL_free(buttons_str);
|
|
SDL_Log(" pos=(%.2f, %.2f)", x, y);
|
|
for (k = 0; k < SDL_PEN_NUM_AXES; ++k) {
|
|
SDL_bool supported = capabilities & SDL_PEN_AXIS_CAPABILITY(k);
|
|
if (supported) {
|
|
if (k == SDL_PEN_AXIS_XTILT || k == SDL_PEN_AXIS_YTILT) {
|
|
if (info.max_tilt == SDL_PEN_INFO_UNKNOWN) {
|
|
SDL_Log(" axis %d: %.3f (max tilt unknown)", k, axes[k]);
|
|
} else {
|
|
SDL_Log(" axis %d: %.3f (tilt -%.1f..%.1f)", k, axes[k],
|
|
info.max_tilt, info.max_tilt);
|
|
}
|
|
} else {
|
|
SDL_Log(" axis %d: %.3f", k, axes[k]);
|
|
}
|
|
} else {
|
|
SDL_Log(" axis %d: unsupported (%.3f)", k, axes[k]);
|
|
}
|
|
}
|
|
}
|
|
SDL_free(pens);
|
|
}
|
|
|
|
static void update_axes(float *axes)
|
|
{
|
|
last_xtilt = axes[SDL_PEN_AXIS_XTILT];
|
|
last_ytilt = axes[SDL_PEN_AXIS_YTILT];
|
|
last_pressure = axes[SDL_PEN_AXIS_PRESSURE];
|
|
last_distance = axes[SDL_PEN_AXIS_DISTANCE];
|
|
last_rotation = axes[SDL_PEN_AXIS_ROTATION];
|
|
}
|
|
|
|
static void update_axes_from_touch(const float pressure)
|
|
{
|
|
last_xtilt = 0;
|
|
last_ytilt = 0;
|
|
last_pressure = pressure;
|
|
last_distance = 0;
|
|
last_rotation = 0;
|
|
}
|
|
|
|
static void process_event(SDL_Event event)
|
|
{
|
|
SDLTest_CommonEvent(state, &event, &quitting);
|
|
|
|
switch (event.type) {
|
|
case SDL_EVENT_KEY_DOWN:
|
|
{
|
|
dump_state();
|
|
break;
|
|
}
|
|
case SDL_EVENT_MOUSE_MOTION:
|
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
|
#if VERBOSE
|
|
{
|
|
float x, y;
|
|
SDL_GetMouseState(&x, &y);
|
|
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
|
SDL_Log("[%lu] mouse motion: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f) delta (%.2f, %.2f)\n",
|
|
event.motion.timestamp,
|
|
event.motion.which,
|
|
event.motion.x, event.motion.y,
|
|
event.motion.xrel, event.motion.yrel,
|
|
x, y);
|
|
} else {
|
|
SDL_Log("[%lu] mouse button: mouse ID %d is at (%.2f, %.2f) (state: %.2f,%.2f)\n",
|
|
event.button.timestamp,
|
|
event.button.which,
|
|
event.button.x, event.button.y,
|
|
x, y);
|
|
}
|
|
}
|
|
#endif
|
|
if (event.motion.which != SDL_PEN_MOUSEID && event.motion.which != SDL_TOUCH_MOUSEID) {
|
|
SDL_ShowCursor();
|
|
} break;
|
|
|
|
case SDL_EVENT_PEN_MOTION:
|
|
{
|
|
SDL_PenMotionEvent *ev = &event.pmotion;
|
|
|
|
SDL_HideCursor();
|
|
last_x = ev->x;
|
|
last_y = ev->y;
|
|
update_axes(ev->axes);
|
|
last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK;
|
|
#if VERBOSE
|
|
SDL_Log("[%lu] pen motion: %s %u at (%.4f, %.4f); pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f [buttons=%02x]\n",
|
|
(unsigned long) ev->timestamp,
|
|
last_was_eraser ? "eraser" : "pen",
|
|
(unsigned int)ev->which, ev->x, ev->y, last_pressure, last_xtilt, last_ytilt, last_distance,
|
|
ev->pen_state);
|
|
#endif
|
|
} break;
|
|
|
|
case SDL_EVENT_PEN_UP:
|
|
case SDL_EVENT_PEN_DOWN: {
|
|
SDL_PenTipEvent *ev = &event.ptip;
|
|
last_x = ev->x;
|
|
last_y = ev->y;
|
|
update_axes(ev->axes);
|
|
last_was_eraser = ev->tip == SDL_PEN_TIP_ERASER;
|
|
last_button = ev->pen_state & 0xf; /* button mask */
|
|
last_touching = (event.type == SDL_EVENT_PEN_DOWN);
|
|
} break;
|
|
|
|
case SDL_EVENT_PEN_BUTTON_UP:
|
|
case SDL_EVENT_PEN_BUTTON_DOWN:
|
|
{
|
|
SDL_PenButtonEvent *ev = &event.pbutton;
|
|
|
|
SDL_HideCursor();
|
|
last_x = ev->x;
|
|
last_y = ev->y;
|
|
update_axes(ev->axes);
|
|
if (last_pressure > 0.0f && !last_touching) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
|
|
"[%lu] : reported pressure %.5f even though pen is not touching surface",
|
|
(unsigned long) ev->timestamp, last_pressure);
|
|
|
|
}
|
|
last_was_eraser = ev->pen_state & SDL_PEN_ERASER_MASK;
|
|
last_button = ev->pen_state & 0xf; /* button mask */
|
|
if ((ev->pen_state & SDL_PEN_DOWN_MASK) && !last_touching) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
|
|
"[%lu] : reported flags %x (SDL_PEN_FLAG_DOWN_MASK) despite not receiving SDL_EVENT_PEN_DOWN",
|
|
(unsigned long) ev->timestamp, ev->pen_state);
|
|
|
|
}
|
|
if (!(ev->pen_state & SDL_PEN_DOWN_MASK) && last_touching) {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_TEST,
|
|
"[%lu] : reported flags %x (no SDL_PEN_FLAG_DOWN_MASK) despite receiving SDL_EVENT_PEN_DOWN without SDL_EVENT_PEN_UP afterwards",
|
|
(unsigned long) ev->timestamp, ev->pen_state);
|
|
|
|
}
|
|
#if VERBOSE
|
|
SDL_Log("[%lu] pen button: %s %u at (%.4f, %.4f); BUTTON %d reported %s with event %s [pressure=%.3f, tilt=%.3f/%.3f, dist=%.3f]\n",
|
|
(unsigned long) ev->timestamp,
|
|
last_was_eraser ? "eraser" : "pen",
|
|
(unsigned int)ev->which, ev->x, ev->y,
|
|
ev->button,
|
|
(ev->state == SDL_PRESSED) ? "PRESSED"
|
|
: ((ev->state == SDL_RELEASED) ? "RELEASED" : "--invalid--"),
|
|
event.type == SDL_EVENT_PEN_BUTTON_UP ? "PENBUTTONUP" : "PENBUTTONDOWN",
|
|
last_pressure, last_xtilt, last_ytilt, last_distance);
|
|
#endif
|
|
} break;
|
|
|
|
case SDL_EVENT_WINDOW_PEN_ENTER:
|
|
SDL_Log("[%lu] Pen %lu entered window %lx",
|
|
(unsigned long) event.window.timestamp,
|
|
(unsigned long) event.window.data1,
|
|
(unsigned long) event.window.windowID);
|
|
break;
|
|
|
|
case SDL_EVENT_WINDOW_PEN_LEAVE:
|
|
SDL_Log("[%lu] Pen %lu left window %lx",
|
|
(unsigned long) event.window.timestamp,
|
|
(unsigned long) event.window.data1,
|
|
(unsigned long) event.window.windowID);
|
|
break;
|
|
|
|
#if VERBOSE
|
|
case SDL_EVENT_WINDOW_MOUSE_ENTER:
|
|
SDL_Log("[%lu] Mouse entered window %lx",
|
|
(unsigned long) event.window.timestamp,
|
|
(unsigned long) event.window.windowID);
|
|
break;
|
|
|
|
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
|
|
SDL_Log("[%lu] Mouse left window %lx",
|
|
(unsigned long) event.window.timestamp,
|
|
(unsigned long) event.window.windowID);
|
|
break;
|
|
#endif
|
|
|
|
case SDL_EVENT_FINGER_DOWN:
|
|
case SDL_EVENT_FINGER_MOTION:
|
|
case SDL_EVENT_FINGER_UP:
|
|
{
|
|
SDL_TouchFingerEvent *ev = &event.tfinger;
|
|
int w, h;
|
|
SDL_HideCursor();
|
|
SDL_GetWindowSize(SDL_GetWindowFromID(ev->windowID), &w, &h);
|
|
last_x = ev->x * w;
|
|
last_y = ev->y * h;
|
|
update_axes_from_touch(ev->pressure);
|
|
last_was_eraser = SDL_FALSE;
|
|
last_button = 0;
|
|
last_touching = (ev->type != SDL_EVENT_FINGER_UP);
|
|
#if VERBOSE
|
|
SDL_Log("[%lu] finger %s: %s (touchId: %" SDL_PRIs64 ", fingerId: %" SDL_PRIs64 ") at (%.4f, %.4f); pressure=%.3f\n",
|
|
(unsigned long) ev->timestamp,
|
|
ev->type == SDL_EVENT_FINGER_DOWN ? "down" : (ev->type == SDL_EVENT_FINGER_MOTION ? "motion" : "up"),
|
|
SDL_GetTouchDeviceName(ev->touchId),
|
|
ev->touchId,
|
|
ev->fingerId,
|
|
last_x, last_y, last_pressure);
|
|
#endif
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void loop(void)
|
|
{
|
|
SDL_Event event;
|
|
int i;
|
|
|
|
for (i = 0; i < state->num_windows; ++i) {
|
|
if (state->renderers[i]) {
|
|
DrawScreen(state->renderers[i]);
|
|
}
|
|
}
|
|
|
|
if (SDL_WaitEventTimeout(&event, 10)) {
|
|
process_event(event);
|
|
}
|
|
while (SDL_PollEvent(&event)) {
|
|
process_event(event);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
|
|
if (!state) {
|
|
return 1;
|
|
}
|
|
|
|
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
|
|
|
|
state->window_title = "Pressure-Sensitive Pen Test";
|
|
state->window_w = WIDTH;
|
|
state->window_h = HEIGHT;
|
|
state->skip_renderer = SDL_FALSE;
|
|
|
|
if (!SDLTest_CommonDefaultArgs(state, argc, argv) || !SDLTest_CommonInit(state)) {
|
|
SDLTest_CommonQuit(state);
|
|
return 1;
|
|
}
|
|
|
|
while (!quitting) {
|
|
loop();
|
|
}
|
|
|
|
SDLTest_CommonQuit(state);
|
|
return 0;
|
|
}
|