--- /dev/null
+// scribble.c - Copyright (c) 2020 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <SDL2/SDL.h>
+
+const int SCREEN_WIDTH = 640;
+const int SCREEN_HEIGHT = 480;
+
+typedef enum { APP_EVENT_TICK, APP_EVENT_DRAWLINES } AppEventID;
+
+const int FLAG_IS_DRAWING = (1<<0);
+
+typedef struct {
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_Texture *texture;
+ uint32_t counter;
+ uint32_t tick;
+ uint32_t flags;
+ uint32_t points_size; // most recent point added
+ uint32_t points_reserved; // size of points array
+ uint32_t points_drawn; // last point drawn
+ SDL_Point *points;
+} ApplicationState;
+
+static void DrawLines(ApplicationState *app);
+static void EraseScene(ApplicationState *app);
+static void DrawScene(ApplicationState *app);
+
+static void PostAppEvent(AppEventID id, void *data1, void *data2)
+{
+ SDL_Event ev = {0};
+ ev.type = SDL_USEREVENT;
+ ev.user.type = SDL_USEREVENT;
+ ev.user.code = id;
+ ev.user.data1 = data1;
+ ev.user.data2 = data2;
+ SDL_PushEvent(&ev);
+}
+
+// on the worker timer, report back to the main loop using user events
+// in this example, every 100 game ticks (1s) we signal the main loop
+//
+static uint32_t on_worker_timer(uint32_t interval, void *param)
+{
+ ApplicationState *app = (ApplicationState *)param;
+ if (++(app->counter) > 100)
+ {
+ PostAppEvent(APP_EVENT_TICK, param, NULL);
+ app->counter = 0;
+ }
+ if ((app->counter % 5) == 0)
+ {
+ if (app->points_size - app->points_drawn > 0)
+ PostAppEvent(APP_EVENT_DRAWLINES, NULL, NULL);
+ }
+ return interval;
+}
+
+// test the keyboard for key combinations
+static void keyboard_handler(ApplicationState *app)
+{
+ int count = 0;
+ char buf[80];
+ const uint8_t *state = SDL_GetKeyboardState(&count);
+ buf[0] = 0;
+ if (state[SDL_SCANCODE_RIGHT]) strcat(buf, "right ");
+ if (state[SDL_SCANCODE_LEFT]) strcat(buf, "left ");
+ if (state[SDL_SCANCODE_UP]) strcat(buf, "up ");
+ if (state[SDL_SCANCODE_DOWN]) strcat(buf, "down ");
+ if (buf[0] != 0)
+ printf("%s\n", buf);
+}
+
+static void OnMouseButtonDown(ApplicationState *app, SDL_MouseButtonEvent *event)
+{
+ app->flags |= FLAG_IS_DRAWING;
+ if (app->points_reserved == 0)
+ {
+ app->points_reserved = 4096;
+ app->points_drawn = 0;
+ app->points_size = 0;
+ app->points = (SDL_Point *)malloc(sizeof(SDL_Point) * app->points_reserved);
+ }
+ int count = 0;
+ const uint8_t *state = SDL_GetKeyboardState(&count);
+ if (!(state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT]))
+ {
+ app->points_size = 0;
+ EraseScene(app);
+ }
+ SDL_Point point = {event->x, event->y };
+ app->points[app->points_size++] = point;
+}
+
+static void DrawLines(ApplicationState *app)
+{
+ uint32_t count = app->points_size - app->points_drawn;
+ if (count > 0)
+ {
+ SDL_LogDebug("Draw lines %u to %d", app->points_drawn, app->points_size);
+ SDL_SetRenderTarget(app->renderer, app->texture);
+ SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 255); // RGBA
+ uint32_t last = app->points_drawn;
+ if (last > 0)
+ {
+ --last;
+ ++count;
+ }
+ SDL_RenderDrawLines(app->renderer, app->points + last, count);
+ app->points_drawn = app->points_size;
+ SDL_SetRenderTarget(app->renderer, NULL);
+ }
+}
+
+static void EraseScene(ApplicationState *app)
+{
+ SDL_SetRenderTarget(app->renderer, app->texture);
+ SDL_SetRenderDrawColor(app->renderer, 255, 255, 255, 255); // RGBA
+ SDL_RenderClear(app->renderer);
+}
+
+static void DrawScene(ApplicationState *app)
+{
+ EraseScene(app);
+ SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 255); // RGBA
+ SDL_RenderDrawLines(app->renderer, app->points, app->points_size);
+ SDL_SetRenderTarget(app->renderer, NULL);
+}
+
+static void OnMouseButtonUp(ApplicationState *app, SDL_MouseButtonEvent *event)
+{
+ app->flags &= ~FLAG_IS_DRAWING;
+ DrawScene(app);
+}
+
+// Add point to array. The render surface is updated on a timer to add the new points
+// to collate points added quickly together.
+static void OnMouseMotion(ApplicationState *app, SDL_MouseMotionEvent *event)
+{
+ if (app->flags & FLAG_IS_DRAWING)
+ {
+ SDL_Point lastpoint = app->points[app->points_size-1];
+ SDL_Point point = { event->x, event->y };
+ app->points[app->points_size++] = point;
+ if (app->points_size > app->points_reserved)
+ {
+ app->points_reserved = app->points_reserved * 2;
+ app->points = (SDL_Point *)realloc(app->points, app->points_reserved * sizeof(SDL_Point));
+ }
+ }
+}
+
+int main(int argc, const char *argv[])
+{
+ int r = SDL_Init(SDL_INIT_EVERYTHING);
+ if (0 == r)
+ {
+ ApplicationState app = {0};
+
+ app.window = SDL_CreateWindow("SDL Scribble",
+ SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
+ if (app.window == NULL)
+ {
+ SDL_Log("Could not create a window: %s", SDL_GetError());
+ return -1;
+ }
+
+ app.renderer = SDL_CreateRenderer(app.window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
+ if (app.renderer == NULL)
+ {
+ SDL_Log("Could not create a renderer: %s", SDL_GetError());
+ SDL_DestroyWindow(app.window);
+ return -1;
+ }
+
+ app.texture = SDL_CreateTexture(app.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT);
+ if (app.texture == NULL)
+ {
+ SDL_Log("Failed to create texture: %s", SDL_GetError());
+ SDL_DestroyRenderer(app.renderer);
+ SDL_DestroyWindow(app.window);
+ return -1;
+ }
+
+ DrawScene(&app);
+
+ // low CPU usage game loop.
+ // using SDL_PollEvent causes the system to busy wait consuming cpu.
+ {
+ // use a timer for anything that must happen regularly.
+ SDL_AddTimer(10, on_worker_timer, &app);
+
+ SDL_SetRenderTarget(app.renderer, NULL);
+ SDL_RenderCopy(app.renderer, app.texture, NULL, NULL);
+ SDL_RenderPresent(app.renderer);
+
+ SDL_Event event;
+ while (SDL_WaitEvent(&event))
+ {
+ if (event.type == SDL_QUIT)
+ {
+ break;
+ }
+ if (event.type == SDL_MOUSEBUTTONDOWN)
+ OnMouseButtonDown(&app, (SDL_MouseButtonEvent *)&event);
+ if (event.type == SDL_MOUSEBUTTONUP)
+ OnMouseButtonUp(&app, (SDL_MouseButtonEvent *)&event);
+ if (event.type == SDL_MOUSEMOTION)
+ OnMouseMotion(&app, (SDL_MouseMotionEvent *)&event);
+ if (event.type == SDL_KEYUP || event.type == SDL_KEYDOWN)
+ keyboard_handler(&app);
+ if (event.type == SDL_USEREVENT)
+ {
+ SDL_UserEvent *uev = (SDL_UserEvent *)&event;
+ switch (uev->code)
+ {
+ case APP_EVENT_TICK:
+ printf("tick %d\n", app.tick++);
+ break;
+ case APP_EVENT_DRAWLINES:
+ DrawLines(&app);
+ SDL_SetRenderTarget(app.renderer, NULL);
+ SDL_RenderCopy(app.renderer, app.texture, NULL, NULL);
+ SDL_RenderPresent(app.renderer);
+ break;
+ }
+ }
+ }
+ }
+ SDL_DestroyTexture(app.texture);
+ SDL_DestroyRenderer(app.renderer);
+ SDL_DestroyWindow(app.window);
+ SDL_Quit();
+ }
+ return r;
+}
\ No newline at end of file