From 4a53c52d5296fd37576354014abeace5f70c3a61 Mon Sep 17 00:00:00 2001 From: Matthew Roush Date: Sat, 15 Aug 2026 03:24:24 -0400 Subject: [PATCH] [examples] Add shapes_outlines_testbed This example stress tests the previously existing and newly added shape outline functions. --- examples/shapes/shapes_outlines_testbed.c | 407 ++++++++++++++++++++ examples/shapes/shapes_outlines_testbed.png | Bin 0 -> 24683 bytes 2 files changed, 407 insertions(+) create mode 100644 examples/shapes/shapes_outlines_testbed.c create mode 100644 examples/shapes/shapes_outlines_testbed.png diff --git a/examples/shapes/shapes_outlines_testbed.c b/examples/shapes/shapes_outlines_testbed.c new file mode 100644 index 000000000..22dcc0b5d --- /dev/null +++ b/examples/shapes/shapes_outlines_testbed.c @@ -0,0 +1,407 @@ +/******************************************************************************************* +* +* raylib [shapes] example - outlines testbed +* +* Example complexity rating: [★★★☆] 3/4 +* +* Example originally created with raylib 6.1, last time updated with raylib 6.1 +* +* Example contributed by Matthew Roush (@MatthewRoush) and reviewed by Ramon Santamaria (@raysan5) +* +* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software +* +* Copyright (c) 2026 Matthew Roush (@MatthewRoush) +* +********************************************************************************************/ + +#include "raylib.h" + +#include "raymath.h" + +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" // Required for GUI controls + +#include + +#define COLOR_FILLED DARKBLUE +#define COLOR_OUTLINE YELLOW + +#define SHAPE_SPACING 14.0f +#define SHAPE_SIZE 46.0f +#define BOX_SPACING 10.0f + +#define MOUSE_CAMERA_ZOOM_SPEED 0.3f +#define KEYBOARD_CAMERA_MOVE_SPEED 10.0f +#define KEYBOARD_CAMERA_ZOOM_SPEED 0.1f + +#define CAMERA_ZOOM_MIN 0.1f +#define CAMERA_ZOOM_MAX 1000.0f + +// The shapes are ordered according to their order in this enum, left to right +enum { + ORDER_RECTANGLE = 0, + ORDER_RECTANGLE_ROUNDED, + ORDER_CIRCLE, + ORDER_ELLIPSE, + ORDER_CIRCLE_SECTOR, + ORDER_RING, + ORDER_TRIANGLE, + ORDER_POLYGON, + COUNT_SHAPES +}; + +// The line style groups are ordered according to their order in this enum, top to bottom +enum { + ORDER_LINES = 0, + ORDER_LINES_EX_WORLD, + ORDER_LINES_EX_SCREEN, +}; + +#define BOX_WIDTH (SHAPE_SIZE*COUNT_SHAPES + SHAPE_SPACING*(COUNT_SHAPES - 1) + BOX_SPACING*2.0f) +#define BOX_HEIGHT (SHAPE_SIZE + BOX_SPACING*2.0f) + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [shapes] example - outlines testbed"); + + Camera2D camera = { .zoom = 1 }; + + // User configurable options while running the program. + float lineOpacity = 130; + float lineThickness = 4.0f; + float rectangleRoundness = 0.4f; + float rectangleSegments = 9; + float ellipseRadiusY = 0.5f; + float circleStartAngle = 20.0f; + float circleEndAngle = 270.0f; + float circleSegments = 36; + float ringInnerRadiusScale = 0.3f; + float polygonSides = 6; + + bool disableMouseControl = false; + + const Rectangle optionsBackground = { 510, 0, 290, 450 }; + + const Vector2 zoomPoint = {((float)screenWidth - optionsBackground.width)/2.0f, (float)screenHeight/2.0f}; + + GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(WHITE)); + + SetTargetFPS(60); + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + const Vector2 mousePosScreen = GetMousePosition(); + const Vector2 mouseWheelVec = GetMouseWheelMoveV(); + + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mousePosScreen, optionsBackground)) disableMouseControl = true; + + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) disableMouseControl = false; + + if (!disableMouseControl) { + if (mouseWheelVec.y != 0.0f) { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(MOUSE_CAMERA_ZOOM_SPEED*mouseWheelVec.y); // Constant zoom rate + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { + const Vector2 mouseDelta = GetMouseDelta(); + camera.target.x -= mouseDelta.x/camera.zoom; + camera.target.y -= mouseDelta.y/camera.zoom; + + const int mouseMaxX = (int)optionsBackground.x; + const int mouseMaxY = screenHeight; + + int newX = (int)mousePosScreen.x; + int newY = (int)mousePosScreen.y; + + // In C, the '%' operator computes the remainder, we want the modulus + newX = (newX%mouseMaxX + mouseMaxX)%mouseMaxX; + newY = (newY%mouseMaxY + mouseMaxY)%mouseMaxY; + + if ((newX != (int)mousePosScreen.x) || (newY != (int)mousePosScreen.y)) SetMousePosition(newX, newY); + } + } + + if (IsKeyDown(KEY_A)) camera.target.x -= KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_D)) camera.target.x += KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_W)) camera.target.y -= KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + if (IsKeyDown(KEY_S)) camera.target.y += KEYBOARD_CAMERA_MOVE_SPEED/camera.zoom; + + if (IsKeyDown(KEY_UP)) + { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(KEYBOARD_CAMERA_ZOOM_SPEED); + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + if (IsKeyDown(KEY_DOWN)) + { + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom *= exp2f(-KEYBOARD_CAMERA_ZOOM_SPEED); + camera.zoom = Clamp(camera.zoom, CAMERA_ZOOM_MIN, CAMERA_ZOOM_MAX); + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsKeyPressed(KEY_Z)) + { + const Vector2 zoomPoint = {((float)screenWidth - optionsBackground.width)/2.0f, (float)screenHeight/2.0f}; + const Vector2 prevWorldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + + camera.zoom = 1.0f; + + const Vector2 worldZoomPoint = GetScreenToWorld2D(zoomPoint, camera); + camera.target = Vector2Add(camera.target, Vector2Subtract(prevWorldZoomPoint, worldZoomPoint)); + } + + if (IsKeyPressed(KEY_C)) camera.target = (Vector2){ 0 }; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground((Color){ 50, 50, 55, 255 }); + + Color colorOutline = COLOR_OUTLINE; + colorOutline.a = (unsigned char)lineOpacity; + + BeginMode2D(camera); + + const float shapeOffset = BOX_SPACING*2.0f; + const float shapePaddingX = SHAPE_SIZE + SHAPE_SPACING; + const float shapePaddingY = SHAPE_SIZE + SHAPE_SPACING + BOX_SPACING*2.0f; + + const float radiusX = SHAPE_SIZE/2.0f; + const float radiusY = radiusX*ellipseRadiusY; + + const float ringOuterRadius = SHAPE_SIZE/2.0f; + const float ringInnerRadius = ringOuterRadius*ringInnerRadiusScale; + + const Vector2 triangleVertex0Offset = { 0.0f, SHAPE_SIZE*0.8f }; + const Vector2 triangleVertex1Offset = { SHAPE_SIZE*0.6f, SHAPE_SIZE }; + const Vector2 triangleVertex2Offset = { SHAPE_SIZE, 0.0f }; + + // ---------------------------------------- + // Draw*Lines() + float posY = shapeOffset + shapePaddingY*ORDER_LINES; + + // Rectangle + float posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLines(posX, posY, SHAPE_SIZE, SHAPE_SIZE, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLines((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLines((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLines((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*Lines()"); + // ---------------------------------------- + + // ---------------------------------------- + // Draw*LinesEx() with world space pixel thickness + posY = shapeOffset + shapePaddingY*ORDER_LINES_EX_WORLD; + + // Rectangle + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, lineThickness, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, lineThickness, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, lineThickness, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, lineThickness, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, lineThickness, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, lineThickness, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLinesEx((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + lineThickness, colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, lineThickness, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*LinesEx() with *world space* pixel thickness"); + // ---------------------------------------- + + // ---------------------------------------- + // Draw*LinesEx() with screen space pixel thickness + posY = shapeOffset + shapePaddingY*ORDER_LINES_EX_SCREEN; + + float constantThickness = lineThickness/camera.zoom; + + // Rectangle + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE; + DrawRectangleRec((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, COLOR_FILLED); + DrawRectangleLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, constantThickness, colorOutline); + + // Rectangle Rounded + posX = shapeOffset + shapePaddingX*ORDER_RECTANGLE_ROUNDED; + DrawRectangleRounded((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, COLOR_FILLED); + DrawRectangleRoundedLinesEx((Rectangle){ posX, posY, SHAPE_SIZE, SHAPE_SIZE }, rectangleRoundness, (int)rectangleSegments, constantThickness, colorOutline); + + // Circle + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE; + DrawCircleV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, COLOR_FILLED); + DrawCircleLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, constantThickness, colorOutline); + + // Ellipse + posX = shapeOffset + shapePaddingX*ORDER_ELLIPSE; + DrawEllipseV((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, COLOR_FILLED); + DrawEllipseLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, radiusY, constantThickness, colorOutline); + + // Circle Sector + posX = shapeOffset + shapePaddingX*ORDER_CIRCLE_SECTOR; + DrawCircleSector((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawCircleSectorLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, radiusX, circleStartAngle, circleEndAngle, (int)circleSegments, constantThickness, colorOutline); + + // Ring + posX = shapeOffset + shapePaddingX*ORDER_RING; + DrawRing((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, COLOR_FILLED); + DrawRingLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, ringInnerRadius, ringOuterRadius, circleStartAngle, circleEndAngle, (int)circleSegments, constantThickness, colorOutline); + + // Triangle + posX = shapeOffset + shapePaddingX*ORDER_TRIANGLE; + DrawTriangle((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + COLOR_FILLED); + DrawTriangleLinesEx((Vector2){ posX + triangleVertex0Offset.x, posY + triangleVertex0Offset.y }, + (Vector2){ posX + triangleVertex1Offset.x, posY + triangleVertex1Offset.y }, + (Vector2){ posX + triangleVertex2Offset.x, posY + triangleVertex2Offset.y }, + constantThickness, colorOutline); + + // Polygon + posX = shapeOffset + shapePaddingX*ORDER_POLYGON; + DrawPoly((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, COLOR_FILLED); + DrawPolyLinesEx((Vector2){ posX + SHAPE_SIZE/2.0f, posY + SHAPE_SIZE/2.0f }, (int)polygonSides, radiusX, 0.0f, constantThickness, colorOutline); + + // Group the shapes + GuiGroupBox((Rectangle){ shapeOffset - BOX_SPACING, posY - BOX_SPACING, BOX_WIDTH, BOX_HEIGHT }, "Draw*LinesEx() with *screen space* pixel thickness"); + // ---------------------------------------- + + EndMode2D(); + + DrawRectangleRec(optionsBackground, Fade(DARKGRAY, 0.75f)); + + DrawRectangleRec((Rectangle){ optionsBackground.x, 360, optionsBackground.width, 90 }, Fade(BLACK, 0.4f)); + DrawLineEx((Vector2){ optionsBackground.x, 360 }, (Vector2){ optionsBackground.x + optionsBackground.width, 360 }, 1.0f, BLACK); + + DrawText("Move with the mouse or WASD keys", (int)optionsBackground.x + 10, 370, 10, ORANGE); + DrawText("Zoom with the mouse or UP and DOWN keys", (int)optionsBackground.x + 10, 390, 10, ORANGE); + DrawText("Press C to reset position", (int)optionsBackground.x + 10, 410, 10, ORANGE); + DrawText("Press Z to reset zoom", (int)optionsBackground.x + 10, 430, 10, ORANGE); + + DrawLineEx((Vector2){ optionsBackground.x, optionsBackground.y }, (Vector2){ optionsBackground.x, optionsBackground.y + optionsBackground.height }, 1.0f, BLACK); + + GuiSliderBar((Rectangle){ 605, 10, 150, 25 }, "Line Opacity" , TextFormat("%d" , (int)lineOpacity) , &lineOpacity , 0 , 255); + GuiSliderBar((Rectangle){ 605, 45, 150, 25 }, "Thickness" , TextFormat("%.2f", lineThickness) , &lineThickness , -20.0f , 40.0f); + GuiSliderBar((Rectangle){ 605, 80, 150, 25 }, "Rect Roundness" , TextFormat("%.2f", rectangleRoundness) , &rectangleRoundness , -1.0f , 2.0f); + GuiSliderBar((Rectangle){ 605, 115, 150, 25 }, "Rect Segments" , TextFormat("%d" , (int)rectangleSegments), &rectangleSegments , -1 , 100); + GuiSliderBar((Rectangle){ 605, 150, 150, 25 }, "Ellipse Radius Y", TextFormat("%.2f", ellipseRadiusY) , &ellipseRadiusY , 0.0f , 1.25f); + GuiSliderBar((Rectangle){ 605, 185, 150, 25 }, "Start Angle" , TextFormat("%.2f", circleStartAngle) , &circleStartAngle , -360.0f, 360.0f); + GuiSliderBar((Rectangle){ 605, 220, 150, 25 }, "End Angle" , TextFormat("%.2f", circleEndAngle) , &circleEndAngle , -360.0f, 360.0f); + GuiSliderBar((Rectangle){ 605, 255, 150, 25 }, "Circle Segments" , TextFormat("%d" , (int)circleSegments) , &circleSegments , -1 , 100); + GuiSliderBar((Rectangle){ 605, 290, 150, 25 }, "Ring Radius" , TextFormat("%.2f", ringInnerRadiusScale) , &ringInnerRadiusScale, -1.0f , 2.0f); + GuiSliderBar((Rectangle){ 605, 325, 150, 25 }, "Poly Sides" , TextFormat("%d" , (int)polygonSides) , &polygonSides , -1 , 100); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} diff --git a/examples/shapes/shapes_outlines_testbed.png b/examples/shapes/shapes_outlines_testbed.png new file mode 100644 index 0000000000000000000000000000000000000000..02d58449eda279ccbea5fdb3ccab033ebfd748a2 GIT binary patch literal 24683 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 I&MBb@05>f2ZU6uP literal 0 HcmV?d00001