Initial version of SDL2 Scribble application. master
authorPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 19 Aug 2020 06:13:59 +0000 (07:13 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Wed, 19 Aug 2020 06:13:59 +0000 (07:13 +0100)
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
scribble.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..6f31401
--- /dev/null
@@ -0,0 +1,2 @@
+build/
+.vscode/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..2b5c176
--- /dev/null
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.10)
+
+project(scribble VERSION 1.0.0)
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(SDL2 REQUIRED sdl2)
+
+set(SOURCES ${PROJECT_NAME}.c)
+
+add_executable(${PROJECT_NAME} ${SOURCES} "README.md")
+include_directories(${PROJECT_NAME} ${SDL2_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${SDL2_LIBRARIES})
+
+install(TARGETS ${PROJECT} RUNTIME DESTINATION ${BIN_DIR})
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..a8a4616
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2020 Pat Thoyts <patthoyts@users.sourceforge.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..141ebe6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+# SDL Scribble
+
+Sample application using the SDL 2 library to paint lines using the mouse
+onto the client area of the application.
diff --git a/scribble.c b/scribble.c
new file mode 100644 (file)
index 0000000..395ec91
--- /dev/null
@@ -0,0 +1,238 @@
+// 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