diff --git a/examples/playdate-project-example/clay-video-demo-playdate.c b/examples/playdate-project-example/clay-video-demo-playdate.c index 23fd699..e96dfb3 100644 --- a/examples/playdate-project-example/clay-video-demo-playdate.c +++ b/examples/playdate-project-example/clay-video-demo-playdate.c @@ -116,7 +116,7 @@ Clay_RenderCommandArray ClayVideoDemo_CreateLayout(int selectedDocumentIndex) { CLAY({ .id = CLAY_ID("Sidebar"), .border = contentBorders, - .cornerRadius = 4, + .cornerRadius = CLAY_CORNER_RADIUS(4), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = CLAY_PADDING_ALL(8), @@ -138,7 +138,7 @@ Clay_RenderCommandArray ClayVideoDemo_CreateLayout(int selectedDocumentIndex) { CLAY({ .layout = sidebarButtonLayout, .backgroundColor = COLOR_BLACK, - .cornerRadius = CLAY_CORNER_RADIUS(4) + .cornerRadius = { .topLeft = 10, .topRight = 60, .bottomLeft = 30, .bottomRight = 60 } }) { CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, @@ -149,7 +149,7 @@ Clay_RenderCommandArray ClayVideoDemo_CreateLayout(int selectedDocumentIndex) { CLAY({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 0, 0, 0, Clay_Hovered() ? 120 : 0 }, - .cornerRadius = CLAY_CORNER_RADIUS(4), + .cornerRadius = { .topLeft = 10, .topRight = 60, .bottomLeft = 30, .bottomRight = 60 }, .border = contentBorders }) { CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ diff --git a/examples/playdate-project-example/main.c b/examples/playdate-project-example/main.c index 909d461..83d8a48 100644 --- a/examples/playdate-project-example/main.c +++ b/examples/playdate-project-example/main.c @@ -1,6 +1,4 @@ - #include "pd_api.h" -#include "pd_api/pd_api_gfx.h" #define CLAY_IMPLEMENTATION #include "../../clay.h" @@ -17,7 +15,7 @@ struct TextUserData { PlaydateAPI *pd; }; -static struct TextUserData textUserData = {.font = {NULL, NULL}, .pd = NULL}; +static struct TextUserData textUserData = {.font = {NULL}, .pd = NULL}; static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { struct TextUserData *textUserData = userData; diff --git a/renderers/playdate/clay_renderer_playdate.c b/renderers/playdate/clay_renderer_playdate.c index 77a3417..ae829db 100644 --- a/renderers/playdate/clay_renderer_playdate.c +++ b/renderers/playdate/clay_renderer_playdate.c @@ -1,7 +1,5 @@ #include "pd_api.h" -#include "../../clay.h" - struct Clay_Playdate_Rect { float x; float y; @@ -43,6 +41,18 @@ static LCDBitmapDrawMode clayColorToDrawMode(Clay_Color color) { return kDrawModeCopy; } +static float clampCornerRadius(float yAxisSize, float radius) { + if (radius < 1.0f) { + return 0.0f; + } + if (radius > yAxisSize / 2) { + return yAxisSize / 2; + } + // Trying to draw a 2x2 ellipse seems to result in just a dot, so if + // there is a corner radius at minimum it must be 2 + return radius + 1; +} + 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); @@ -50,37 +60,93 @@ static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray render switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; - struct Clay_Playdate_Rect rect = (struct Clay_Playdate_Rect){ - .x = boundingBox.x, - .y = boundingBox.y, - .w = boundingBox.width, - .h = boundingBox.height, - }; - // TODO: support different radius for each corner like clay API allows - 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) - ); - } + + float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); + float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); + float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); + float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); + + pd->graphics->fillEllipse( + boundingBox.x, boundingBox.y, + radiusTl * 2, radiusTl * 2, + -90.0f, 0.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, + radiusTr * 2, radiusTr * 2, + 0.0f, 90.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x + boundingBox.width - radiusBr * 2, + boundingBox.y + boundingBox.height - radiusBr * 2, + radiusBr * 2, radiusBr * 2, + 90.0f, 180.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x, + boundingBox.y + boundingBox.height - radiusBl * 2, + radiusBl * 2, radiusBl * 2, + 180.0f, 270.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + // Top chunk + pd->graphics->fillRect( + boundingBox.x + radiusTl, boundingBox.y, + boundingBox.width - radiusTl - radiusTr, + CLAY__MAX(radiusTl, radiusTr), + clayColorToLCDColor(config->backgroundColor) + ); + + // bottom chunk + int bottomChunkHeight = CLAY__MAX(radiusBl, radiusBr); + pd->graphics->fillRect( + boundingBox.x + radiusBl, boundingBox.y + boundingBox.height - bottomChunkHeight, + boundingBox.width - radiusBl - radiusBr, + bottomChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Middle chunk + int middleChunkHeight = boundingBox.height - CLAY__MIN(radiusBr, radiusBl) - CLAY__MIN(radiusTr, radiusTl); + pd->graphics->fillRect( + boundingBox.x + CLAY__MIN(radiusTl, radiusBl), boundingBox.y + CLAY__MIN(radiusTr, radiusTl), + boundingBox.width - radiusBl - radiusBr, + middleChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Left chunk + int leftChunkHeight = boundingBox.height - radiusTl - radiusBl; + int leftChunkWidth = CLAY__MAX(radiusTl, radiusBl); + pd->graphics->fillRect( + boundingBox.x, boundingBox.y + radiusTl, + leftChunkWidth, + leftChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Right chunk + int rightChunkHeight = boundingBox.height - radiusTr - radiusBr; + int rightChunkWidth = CLAY__MAX(radiusTr, radiusBr); + pd->graphics->fillRect( + boundingBox.x + boundingBox.width - rightChunkWidth, boundingBox.y + radiusTr, + rightChunkWidth, + rightChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); break; } case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *config = &renderCommand->renderData.text; LCDFont *font = fonts[config->fontId]; pd->graphics->setFont(font); - struct Clay_Playdate_Rect destination = (struct Clay_Playdate_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, @@ -89,22 +155,16 @@ static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray render renderCommand->renderData.text.stringContents.length ), kUTF8Encoding, - destination.x, - destination.y + boundingBox.x, + boundingBox.y ); pd->graphics->setDrawMode(kDrawModeCopy); break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { - struct Clay_Playdate_Rect currentClippingRectangle = (struct Clay_Playdate_Rect){ - .x = boundingBox.x, - .y = boundingBox.y, - .w = boundingBox.width, - .h = boundingBox.height, - }; pd->graphics->setClipRect( - currentClippingRectangle.x, currentClippingRectangle.y, - currentClippingRectangle.w, currentClippingRectangle.h + boundingBox.x,boundingBox.y, + boundingBox.width, boundingBox.height ); break; } @@ -115,36 +175,101 @@ static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray render case CLAY_RENDER_COMMAND_TYPE_IMAGE: { Clay_ImageRenderData *config = &renderCommand->renderData.image; LCDBitmap *texture = config->imageData; - struct Clay_Playdate_Rect destination = (struct Clay_Playdate_Rect){ - .x = boundingBox.x, - .y = boundingBox.y, - .w = boundingBox.width, - .h = boundingBox.height, - }; - pd->graphics->drawBitmap(texture, destination.x, destination.y, kBitmapUnflipped); + int texWidth; + int texHeight; + pd->graphics->getBitmapData(texture, &texWidth, &texHeight, NULL, NULL, NULL); + if (texWidth != boundingBox.width || texHeight != boundingBox.height) { + pd->graphics->drawScaledBitmap( + texture, + boundingBox.x, boundingBox.y, + boundingBox.width / texWidth, + boundingBox.height / texHeight + ); + } else { + pd->graphics->drawBitmap(texture, boundingBox.x, boundingBox.y, kBitmapUnflipped); + } break; } case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData *config = &renderCommand->renderData.border; - struct Clay_Playdate_Rect rect = (struct Clay_Playdate_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, + float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); + float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); + float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); + float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); + + if (config->width.top > 0) { + pd->graphics->drawEllipse( + boundingBox.x, boundingBox.y, + radiusTl * 2, radiusTl * 2, + config->width.top, + -90.0f, 0.0f, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawLine( + boundingBox.x + radiusTl, boundingBox.y, + boundingBox.x + boundingBox.width - radiusTr - config->width.right, boundingBox.y, config->width.top, clayColorToLCDColor(config->color) ); - } else { - pd->graphics->drawRect( - rect.x, rect.y, rect.w, rect.h, + + pd->graphics->drawEllipse( + boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, + radiusTr * 2, radiusTr * 2, + config->width.top, + 0.0f, 90.0f, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.right > 0 && radiusTr + radiusBr <= boundingBox.height) { + + pd->graphics->drawLine( + boundingBox.x + boundingBox.width - config->width.right, + boundingBox.y + radiusTr, + boundingBox.x + boundingBox.width - config->width.right, + boundingBox.y + boundingBox.height - radiusBr - config->width.bottom, + config->width.right, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.bottom > 0) { + + pd->graphics->drawEllipse( + boundingBox.x + boundingBox.width - radiusBr * 2, + boundingBox.y + boundingBox.height - radiusBr * 2, + radiusBr * 2, radiusBr * 2, + config->width.bottom, + 90.0f, 180.0f, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawLine( + boundingBox.x + boundingBox.width - radiusBr - config->width.right, + boundingBox.y + boundingBox.height - config->width.bottom, + boundingBox.x + radiusBl, + boundingBox.y + boundingBox.height - config->width.bottom, + config->width.bottom, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawEllipse( + boundingBox.x, + boundingBox.y + boundingBox.height - radiusBl * 2, + radiusBl * 2, radiusBl * 2, + config->width.bottom, + 180.0f, 270.0f, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.left > 0 && radiusBl + radiusTl < boundingBox.height) { + pd->graphics->drawLine( + boundingBox.x, boundingBox.y + boundingBox.height - radiusBl - config->width.bottom, + boundingBox.x, boundingBox.y + radiusTl, + config->width.left, clayColorToLCDColor(config->color) ); }