From 8407557c4dae89d2e3ee3b318fd94951357d1f90 Mon Sep 17 00:00:00 2001 From: Pat Thoyts Date: Wed, 19 Aug 2020 07:13:59 +0100 Subject: [PATCH 1/1] Initial version of SDL2 Scribble application. --- .gitignore | 2 + CMakeLists.txt | 14 +++ LICENSE | 19 ++++ README.md | 4 + scribble.c | 238 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 277 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 scribble.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f31401 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2b5c176 --- /dev/null +++ b/CMakeLists.txt @@ -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 index 0000000..a8a4616 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Pat Thoyts + +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 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 index 0000000..395ec91 --- /dev/null +++ b/scribble.c @@ -0,0 +1,238 @@ +// scribble.c - Copyright (c) 2020 Pat Thoyts + +#include +#include +#include + +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 -- 2.23.0