mirror of
https://github.com/nicbarker/clay.git
synced 2025-09-18 20:46:17 +00:00
basics working on the playdate
This commit is contained in:
parent
52759cd028
commit
bed2d5b328
|
@ -11,6 +11,7 @@ option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF)
|
||||||
option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
|
option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
|
||||||
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
|
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
|
||||||
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
|
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
|
||||||
|
option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF)
|
||||||
|
|
||||||
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
|
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
|
||||||
|
|
||||||
|
@ -42,6 +43,9 @@ if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES)
|
||||||
add_subdirectory("examples/sokol-video-demo")
|
add_subdirectory("examples/sokol-video-demo")
|
||||||
add_subdirectory("examples/sokol-corner-radius")
|
add_subdirectory("examples/sokol-corner-radius")
|
||||||
endif()
|
endif()
|
||||||
|
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_PLAYDATE_EXAMPLES)
|
||||||
|
add_subdirectory("examples/playdate-project-example")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32) # Build only for Win or Wine
|
if(WIN32) # Build only for Win or Wine
|
||||||
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES)
|
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES)
|
||||||
|
|
2
examples/playdate-project-example/.gitignore
vendored
Normal file
2
examples/playdate-project-example/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
clay_playdate_example.pdx
|
||||||
|
Source/pdex.dylib
|
40
examples/playdate-project-example/CmakeLists.txt
Normal file
40
examples/playdate-project-example/CmakeLists.txt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
|
||||||
|
set(ENVSDK $ENV{PLAYDATE_SDK_PATH})
|
||||||
|
|
||||||
|
if (NOT ${ENVSDK} STREQUAL "")
|
||||||
|
# Convert path from Windows
|
||||||
|
file(TO_CMAKE_PATH ${ENVSDK} SDK)
|
||||||
|
else()
|
||||||
|
execute_process(
|
||||||
|
COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config"
|
||||||
|
COMMAND head -n 1
|
||||||
|
COMMAND cut -c9-
|
||||||
|
OUTPUT_VARIABLE SDK
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT EXISTS ${SDK})
|
||||||
|
message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Release")
|
||||||
|
set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
|
||||||
|
|
||||||
|
# Game Name Customization
|
||||||
|
set(PLAYDATE_GAME_NAME clay_playdate_example)
|
||||||
|
set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE)
|
||||||
|
|
||||||
|
project(${PLAYDATE_GAME_NAME} C ASM)
|
||||||
|
|
||||||
|
if (TOOLCHAIN STREQUAL "armgcc")
|
||||||
|
add_executable(${PLAYDATE_GAME_DEVICE} main.c)
|
||||||
|
else()
|
||||||
|
add_library(${PLAYDATE_GAME_NAME} SHARED main.c)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(${SDK}/C_API/buildsupport/playdate_game.cmake)
|
||||||
|
|
5
examples/playdate-project-example/Source/pdxinfo
Normal file
5
examples/playdate-project-example/Source/pdxinfo
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name=Clay Playdate Example
|
||||||
|
author=Matthew Jennings
|
||||||
|
description=A small demo of Clay running on the Playdate
|
||||||
|
bundleID=dev.mattahj.clay_example
|
||||||
|
imagePath=
|
103
examples/playdate-project-example/main.c
Normal file
103
examples/playdate-project-example/main.c
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
|
||||||
|
#include "pd_api.h"
|
||||||
|
#define CLAY_IMPLEMENTATION
|
||||||
|
#include "../../clay.h"
|
||||||
|
|
||||||
|
#include "../../renderers/playdate/clay_renderer_playdate.c"
|
||||||
|
#include "../shared-layouts/clay-video-demo.c"
|
||||||
|
|
||||||
|
static int update(void *userdata);
|
||||||
|
const char *fontpath = "/System/Fonts/Asheville-Sans-14-Bold.pft";
|
||||||
|
LCDFont *font = NULL;
|
||||||
|
|
||||||
|
void HandleClayErrors(Clay_ErrorData errorData) {}
|
||||||
|
|
||||||
|
struct TextUserData {
|
||||||
|
LCDFont *font;
|
||||||
|
PlaydateAPI *pd;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct TextUserData gTextUserData = {.font = NULL, .pd = NULL};
|
||||||
|
static ClayVideoDemo_Data demoData;
|
||||||
|
|
||||||
|
static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text,
|
||||||
|
Clay_TextElementConfig *config,
|
||||||
|
void *userData) {
|
||||||
|
// TODO: playdate needs to load fonts at the given size, so need to do that
|
||||||
|
// before we can use different font sizes!
|
||||||
|
struct TextUserData *textUserData = userData;
|
||||||
|
int width = textUserData->pd->graphics->getTextWidth(
|
||||||
|
textUserData->font, text.chars,
|
||||||
|
utf8_count_codepoints(text.chars, text.length), kUTF8Encoding, 0);
|
||||||
|
int height = textUserData->pd->graphics->getFontHeight(textUserData->font);
|
||||||
|
return (Clay_Dimensions){
|
||||||
|
.width = (float)width,
|
||||||
|
.height = (float)height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WINDLL
|
||||||
|
__declspec(dllexport)
|
||||||
|
#endif
|
||||||
|
int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t arg)
|
||||||
|
{
|
||||||
|
(void)arg; // arg is currently only used for event = kEventKeyPressed
|
||||||
|
|
||||||
|
if (event == kEventInit) {
|
||||||
|
const char *err;
|
||||||
|
font = pd->graphics->loadFont(fontpath, &err);
|
||||||
|
|
||||||
|
if (font == NULL)
|
||||||
|
pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__,
|
||||||
|
fontpath, err);
|
||||||
|
|
||||||
|
gTextUserData.pd = pd;
|
||||||
|
gTextUserData.font = font;
|
||||||
|
|
||||||
|
// Note: If you set an update callback in the kEventInit handler, the system
|
||||||
|
// assumes the game is pure C and doesn't run any Lua code in the game
|
||||||
|
pd->system->setUpdateCallback(update, pd);
|
||||||
|
|
||||||
|
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||||
|
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(
|
||||||
|
totalMemorySize, pd->system->realloc(NULL, totalMemorySize));
|
||||||
|
Clay_Initialize(clayMemory,
|
||||||
|
(Clay_Dimensions){(float)pd->display->getWidth(),
|
||||||
|
(float)pd->display->getHeight()},
|
||||||
|
(Clay_ErrorHandler){HandleClayErrors});
|
||||||
|
Clay_SetMeasureTextFunction(PlayDate_MeasureText, &gTextUserData);
|
||||||
|
demoData = ClayVideoDemo_Initialize(pd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEXT_WIDTH 86
|
||||||
|
#define TEXT_HEIGHT 16
|
||||||
|
|
||||||
|
int x = (400 - TEXT_WIDTH) / 2;
|
||||||
|
int y = (240 - TEXT_HEIGHT) / 2;
|
||||||
|
int dx = 1;
|
||||||
|
int dy = 2;
|
||||||
|
|
||||||
|
static int update(void *userdata) {
|
||||||
|
PlaydateAPI *pd = userdata;
|
||||||
|
|
||||||
|
pd->graphics->clear(kColorWhite);
|
||||||
|
pd->graphics->setFont(font);
|
||||||
|
|
||||||
|
Clay_SetPointerState((Clay_Vector2){.x = pd->display->getWidth() / 2.0f,
|
||||||
|
.y = pd->display->getHeight() / 2.0f},
|
||||||
|
false);
|
||||||
|
float crankDelta = pd->system->getCrankChange();
|
||||||
|
Clay_UpdateScrollContainers(true, (Clay_Vector2){0, crankDelta * 0.25f},
|
||||||
|
pd->system->getElapsedTime());
|
||||||
|
Clay_RenderCommandArray renderCommands =
|
||||||
|
ClayVideoDemo_CreateLayout(&demoData);
|
||||||
|
|
||||||
|
Clay_Playdate_Render(pd, renderCommands, font);
|
||||||
|
|
||||||
|
pd->system->drawFPS(0, 0);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
156
renderers/playdate/clay_renderer_playdate.c
Normal file
156
renderers/playdate/clay_renderer_playdate.c
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
#include "../../clay.h"
|
||||||
|
#include "pd_api.h"
|
||||||
|
|
||||||
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
|
|
||||||
|
struct Rect {
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
float w;
|
||||||
|
float h;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t utf8_count_codepoints(const char *str, size_t byte_len) {
|
||||||
|
size_t count = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < byte_len) {
|
||||||
|
uint8_t c = (uint8_t)str[i];
|
||||||
|
|
||||||
|
// Skip continuation bytes (10xxxxxx)
|
||||||
|
if ((c & 0xC0) != 0x80) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LCDColor clayColorToLCDColor(Clay_Color color) {
|
||||||
|
if (color.r == 255 && color.g == 255 && color.b == 255) {
|
||||||
|
return kColorWhite;
|
||||||
|
}
|
||||||
|
return kColorBlack;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LCDBitmapDrawMode clayColorToDrawMode(Clay_Color color) {
|
||||||
|
|
||||||
|
if (color.r == 255 && color.g == 255 && color.b == 255) {
|
||||||
|
return kDrawModeFillWhite;
|
||||||
|
}
|
||||||
|
return kDrawModeCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Clay_Playdate_Render(PlaydateAPI *pd,
|
||||||
|
Clay_RenderCommandArray renderCommands,
|
||||||
|
LCDFont *fonts) {
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < renderCommands.length; i++) {
|
||||||
|
Clay_RenderCommand *renderCommand =
|
||||||
|
Clay_RenderCommandArray_Get(&renderCommands, i);
|
||||||
|
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
|
||||||
|
switch (renderCommand->commandType) {
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||||
|
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
|
||||||
|
struct Rect rect = (struct Rect){
|
||||||
|
.x = boundingBox.x,
|
||||||
|
.y = boundingBox.y,
|
||||||
|
.w = boundingBox.width,
|
||||||
|
.h = boundingBox.height,
|
||||||
|
};
|
||||||
|
if (config->cornerRadius.topLeft > 0) {
|
||||||
|
pd->graphics->fillRoundRect(
|
||||||
|
rect.x, rect.y, rect.w, rect.h, config->cornerRadius.topLeft,
|
||||||
|
clayColorToLCDColor(config->backgroundColor));
|
||||||
|
} else {
|
||||||
|
pd->graphics->fillRect(rect.x, rect.y, rect.w, rect.h,
|
||||||
|
clayColorToLCDColor(config->backgroundColor));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||||
|
Clay_TextRenderData *config = &renderCommand->renderData.text;
|
||||||
|
// LCDFont *font = fonts[config->fontId];
|
||||||
|
// TODO: support loading more than 1 font and use the fonts that clay
|
||||||
|
// layout has..
|
||||||
|
LCDFont *font = fonts;
|
||||||
|
struct Rect destination = (struct Rect){
|
||||||
|
.x = boundingBox.x,
|
||||||
|
.y = boundingBox.y,
|
||||||
|
.w = boundingBox.width,
|
||||||
|
.h = boundingBox.height,
|
||||||
|
};
|
||||||
|
pd->graphics->setDrawMode(clayColorToDrawMode(config->textColor));
|
||||||
|
pd->graphics->drawText(
|
||||||
|
renderCommand->renderData.text.stringContents.chars,
|
||||||
|
utf8_count_codepoints(
|
||||||
|
renderCommand->renderData.text.stringContents.chars,
|
||||||
|
renderCommand->renderData.text.stringContents.length),
|
||||||
|
kUTF8Encoding, destination.x, destination.y);
|
||||||
|
pd->graphics->setDrawMode(kDrawModeCopy);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||||
|
struct Rect currentClippingRectangle = (struct Rect){
|
||||||
|
.x = boundingBox.x,
|
||||||
|
.y = boundingBox.y,
|
||||||
|
.w = boundingBox.width,
|
||||||
|
.h = boundingBox.height,
|
||||||
|
};
|
||||||
|
pd->graphics->setClipRect(
|
||||||
|
currentClippingRectangle.x, currentClippingRectangle.y,
|
||||||
|
currentClippingRectangle.w, currentClippingRectangle.h);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||||
|
pd->graphics->clearClipRect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||||
|
Clay_ImageRenderData *config = &renderCommand->renderData.image;
|
||||||
|
LCDBitmap *texture = config->imageData;
|
||||||
|
|
||||||
|
struct Rect destination = (struct Rect){
|
||||||
|
.x = boundingBox.x,
|
||||||
|
.y = boundingBox.y,
|
||||||
|
.w = boundingBox.width,
|
||||||
|
.h = boundingBox.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
pd->graphics->drawBitmap(texture, destination.x, destination.y,
|
||||||
|
kBitmapUnflipped);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||||
|
Clay_BorderRenderData *config = &renderCommand->renderData.border;
|
||||||
|
|
||||||
|
struct Rect rect = (struct Rect){
|
||||||
|
.x = boundingBox.x,
|
||||||
|
.y = boundingBox.y,
|
||||||
|
.w = boundingBox.width,
|
||||||
|
.h = boundingBox.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: properly support the different border thickness and radius
|
||||||
|
// instead of just using topLeft corner /top thickness as a global setting
|
||||||
|
if (config->cornerRadius.topLeft > 0) {
|
||||||
|
pd->graphics->drawRoundRect(
|
||||||
|
rect.x, rect.y, rect.w, rect.h, config->cornerRadius.topLeft,
|
||||||
|
config->width.top, clayColorToLCDColor(config->color));
|
||||||
|
} else {
|
||||||
|
pd->graphics->drawRect(rect.x, rect.y, rect.w, rect.h,
|
||||||
|
clayColorToLCDColor(config->color));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
pd->system->logToConsole("Error: unhandled render command: %d\n",
|
||||||
|
renderCommand->commandType);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue