From a42eb2f79b807c9a7fb0d91228fc6b90b509af6e Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 9 Sep 2025 17:32:13 +0200 Subject: [PATCH] REDESIGNED: example: `shapes_digital_clock` --- examples/shapes/shapes_digital_clock.c | 217 ++++++++++++++++++----- examples/shapes/shapes_digital_clock.png | Bin 3651 -> 18697 bytes 2 files changed, 174 insertions(+), 43 deletions(-) diff --git a/examples/shapes/shapes_digital_clock.c b/examples/shapes/shapes_digital_clock.c index aad933be0..48e19e2be 100644 --- a/examples/shapes/shapes_digital_clock.c +++ b/examples/shapes/shapes_digital_clock.c @@ -11,7 +11,7 @@ * 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) 2025 Hamza RAHAL (@hmz-rhl) +* Copyright (c) 2025 Hamza RAHAL (@hmz-rhl) and Ramon Santamaria (@raysan5) * ********************************************************************************************/ @@ -20,37 +20,40 @@ #include // Required for: cosf(), sinf() #include // Required for: time(), localtime() -#define DIGIT_SIZE 30 +#define CLOCK_ANALOG 0 +#define CLOCK_DIGITAL 1 //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef enum { - MODE_NORMAL = 0, - MODE_HANDS_FREE, -} ClockMode; - +// Clock hand type typedef struct { - int value; - Vector2 origin; - float angle; - int length; - int thickness; - Color color; + int value; // Time value + + // Visual elements + float angle; // Hand angle + int length; // Hand length + int thickness; // Hand thickness + Color color; // Hand color } ClockHand; +// Clock hands typedef struct { - ClockMode mode; - ClockHand second; - ClockHand minute; - ClockHand hour; + ClockHand second; // Clock hand for seconds + ClockHand minute; // Clock hand for minutes + ClockHand hour; // Clock hand for hours } Clock; //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- static void UpdateClock(Clock *clock); // Update clock time -static void DrawClock(Clock clock, Vector2 centerPos); // Draw clock at desired position +static void DrawClockAnalog(Clock clock, Vector2 position); // Draw analog clock at desired center position +static void DrawClockDigital(Clock clock, Vector2 position); // Draw digital clock at desired position + +static void DrawDisplayValue(Vector2 position, int value, Color colorOn, Color colorOff); +static void Draw7SDisplay(Vector2 position, char segments, Color colorOn, Color colorOff); +static void DrawDisplaySegment(Vector2 center, int length, int thick, bool vertical, Color color); //------------------------------------------------------------------------------------ // Program main entry point @@ -62,16 +65,18 @@ int main(void) const int screenWidth = 800; const int screenHeight = 450; + SetConfigFlags(FLAG_MSAA_4X_HINT); InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock"); - // Initialize clock - Clock myClock = { - .mode = MODE_NORMAL, + int clockMode = CLOCK_DIGITAL; + // Initialize clock + // NOTE: Includes visual info for anlaog clock + Clock clock = { .second.angle = 45, .second.length = 140, .second.thickness = 3, - .second.color = BEIGE, + .second.color = MAROON, .minute.angle = 10, .minute.length = 130, @@ -94,11 +99,12 @@ int main(void) //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_SPACE)) { - if (myClock.mode == MODE_HANDS_FREE) myClock.mode = MODE_NORMAL; - else if (myClock.mode == MODE_NORMAL) myClock.mode = MODE_HANDS_FREE; + // Toggle clock mode + if (clockMode == CLOCK_DIGITAL) clockMode = CLOCK_ANALOG; + else if (clockMode == CLOCK_ANALOG) clockMode = CLOCK_DIGITAL; } - UpdateClock(&myClock); + UpdateClock(&clock); // Update clock required data: value and angle //---------------------------------------------------------------------------------- // Draw @@ -107,11 +113,21 @@ int main(void) ClearBackground(RAYWHITE); - DrawCircle(400, 225, 5, BLACK); // Clock center dot + // Draw clock in selected mode + if (clockMode == CLOCK_ANALOG) DrawClockAnalog(clock, (Vector2){ 400, 240 }); + else if (clockMode == CLOCK_DIGITAL) + { + DrawClockDigital(clock, (Vector2){ 30, 60 }); - DrawClock(myClock, (Vector2){ 400, 225 }); // Clock in selected mode + // Draw clock using default raylib font + // Get pointer to formated clock time string + // WARNING: Pointing to an internal static string that is reused between TextFormat() calls + const char *clockTime = TextFormat("%02i:%02i:%02i", clock.hour.value, clock.minute.value, clock.second.value); + DrawText(clockTime, GetScreenWidth()/2 - MeasureText(clockTime, 150)/2, 300, 150, BLACK); + } - DrawText("Press [SPACE] to switch clock mode", 10, 10, 20, DARKGRAY); + DrawText(TextFormat("Press [SPACE] to switch clock mode: %s", + (clockMode == CLOCK_DIGITAL)? "DIGITAL CLOCK" : "ANALOGUE CLOCK"), 10, 10, 20, DARKGRAY); EndDrawing(); //---------------------------------------------------------------------------------- @@ -154,31 +170,146 @@ static void UpdateClock(Clock *clock) clock->second.angle -= 90; } -// Draw clock -static void DrawClock(Clock clock, Vector2 centerPosition) +// Draw analog clock +// Parameter: position, refers to center position +static void DrawClockAnalog(Clock clock, Vector2 position) { - if (clock.mode == MODE_HANDS_FREE) - { - DrawCircleLinesV(centerPosition, clock.minute.length, LIGHTGRAY); + // Draw clock base + DrawCircleV(position, clock.second.length + 40, LIGHTGRAY); + DrawCircleV(position, 12, GRAY); - DrawText(TextFormat("%i", clock.second.value), centerPosition.x + (clock.second.length - 10)*cosf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.second.length*sinf(clock.second.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GRAY); + // Draw clock minutes/seconds lines + for (int i = 0; i < 60; i++) + { + DrawLineEx((Vector2){ position.x + (clock.second.length + ((i%5)? 10 : 6))*cosf((6.0f*i - 90.0f)*DEG2RAD), + position.y + (clock.second.length + ((i%5)? 10 : 6))*sinf((6.0f*i - 90.0f)*DEG2RAD) }, + (Vector2){ position.x + (clock.second.length + 20)*cosf((6.0f*i - 90.0f)*DEG2RAD), + position.y + (clock.second.length + 20)*sinf((6.0f*i - 90.0f)*DEG2RAD) }, ((i%5)? 1.0f : 3.0f), DARKGRAY); + + // Draw seconds numbers + //DrawText(TextFormat("%02i", i), centerPosition.x + (clock.second.length + 50)*cosf((6.0f*i - 90.0f)*DEG2RAD) - 10/2, + // centerPosition.y + (clock.second.length + 50)*sinf((6.0f*i - 90.0f)*DEG2RAD) - 10/2, 10, GRAY); + } - DrawText(TextFormat("%i", clock.minute.value), centerPosition.x + clock.minute.length*cosf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.minute.length*sinf(clock.minute.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, RED); - - DrawText(TextFormat("%i", clock.hour.value), centerPosition.x + clock.hour.length*cosf(clock.hour.angle*(float)(PI/180)) - DIGIT_SIZE/2, centerPosition.y + clock.hour.length*sinf(clock.hour.angle*(float)(PI/180)) - DIGIT_SIZE/2, DIGIT_SIZE, GOLD); - } - else if (clock.mode == MODE_NORMAL) - { // Draw hand seconds - DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.second.length, clock.second.thickness }, + DrawRectanglePro((Rectangle){ position.x, position.y, clock.second.length, clock.second.thickness }, (Vector2){ 0.0f, clock.second.thickness/2.0f }, clock.second.angle, clock.second.color); // Draw hand minutes - DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.minute.length, clock.minute.thickness }, + DrawRectanglePro((Rectangle){ position.x, position.y, clock.minute.length, clock.minute.thickness }, (Vector2){ 0.0f, clock.minute.thickness/2.0f }, clock.minute.angle, clock.minute.color); // Draw hand hours - DrawRectanglePro((Rectangle){ centerPosition.x, centerPosition.y, clock.hour.length, clock.hour.thickness }, + DrawRectanglePro((Rectangle){ position.x, position.y, clock.hour.length, clock.hour.thickness }, (Vector2){ 0.0f, clock.hour.thickness/2.0f }, clock.hour.angle, clock.hour.color); +} + +// Draw digital clock +// PARAM: position, refers to top-left corner +static void DrawClockDigital(Clock clock, Vector2 position) +{ + // Draw clock using custom 7-segments display (made of shapes) + DrawDisplayValue((Vector2){ position.x, position.y }, clock.hour.value/10, RED, Fade(LIGHTGRAY, 0.3f)); + DrawDisplayValue((Vector2){ position.x + 120, position.y }, clock.hour.value%10, RED, Fade(LIGHTGRAY, 0.3f)); + + DrawCircle(position.x + 240, position.y + 70, 12, (clock.second.value%2)? RED : Fade(LIGHTGRAY, 0.3f)); + DrawCircle(position.x + 240, position.y + 150, 12, (clock.second.value%2)? RED : Fade(LIGHTGRAY, 0.3f)); + + DrawDisplayValue((Vector2){ position.x + 260, position.y }, clock.minute.value/10, RED, Fade(LIGHTGRAY, 0.3f)); + DrawDisplayValue((Vector2){ position.x + 380, position.y }, clock.minute.value%10, RED, Fade(LIGHTGRAY, 0.3f)); + + DrawCircle(position.x + 500, position.y + 70, 12, (clock.second.value%2)? RED : Fade(LIGHTGRAY, 0.3f)); + DrawCircle(position.x + 500, position.y + 150, 12, (clock.second.value%2)? RED : Fade(LIGHTGRAY, 0.3f)); + + DrawDisplayValue((Vector2){ position.x + 520, position.y }, clock.second.value/10, RED, Fade(LIGHTGRAY, 0.3f)); + DrawDisplayValue((Vector2){ position.x + 640, position.y }, clock.second.value%10, RED, Fade(LIGHTGRAY, 0.3f)); +} + +// Draw 7-segment display with value +static void DrawDisplayValue(Vector2 position, int value, Color colorOn, Color colorOff) +{ + switch (value) + { + case 0: Draw7SDisplay(position, 0b00111111, colorOn, colorOff); break; + case 1: Draw7SDisplay(position, 0b00000110, colorOn, colorOff); break; + case 2: Draw7SDisplay(position, 0b01011011, colorOn, colorOff); break; + case 3: Draw7SDisplay(position, 0b01001111, colorOn, colorOff); break; + case 4: Draw7SDisplay(position, 0b01100110, colorOn, colorOff); break; + case 5: Draw7SDisplay(position, 0b01101101, colorOn, colorOff); break; + case 6: Draw7SDisplay(position, 0b01111101, colorOn, colorOff); break; + case 7: Draw7SDisplay(position, 0b00000111, colorOn, colorOff); break; + case 8: Draw7SDisplay(position, 0b01111111, colorOn, colorOff); break; + case 9: Draw7SDisplay(position, 0b01101111, colorOn, colorOff); break; + default: break; + } +} + +// Draw seven segments display +// Parameter: position, refers to top-left corner of display +// Parameter: segments, defines in binary the segments to be activated +static void Draw7SDisplay(Vector2 position, char segments, Color colorOn, Color colorOff) +{ + int segmentLen = 60; + int segmentThick = 20; + float offsetYAdjust = segmentThick*0.3f; // HACK: Adjust gap space between segment limits + + // Segment A + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + segmentThick }, + segmentLen, segmentThick, false, (segments & 0b00000001)? colorOn : colorOff); + // Segment B + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, + segmentLen, segmentThick, true, (segments & 0b00000010)? colorOn : colorOff); + // Segment C + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, + segmentLen, segmentThick, true, (segments & 0b00000100)? colorOn : colorOff); + // Segment D + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 5*segmentThick + 2*segmentLen - 4*offsetYAdjust }, + segmentLen, segmentThick, false, (segments & 0b00001000)? colorOn : colorOff); + // Segment E + DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 4*segmentThick + segmentLen + segmentLen/2.0f - 3*offsetYAdjust }, + segmentLen, segmentThick, true, (segments & 0b00010000)? colorOn : colorOff); + // Segment F + DrawDisplaySegment((Vector2){ position.x + segmentThick/2.0f, position.y + 2*segmentThick + segmentLen/2.0f - offsetYAdjust }, + segmentLen, segmentThick, true, (segments & 0b00100000)? colorOn : colorOff); + // Segment G + DrawDisplaySegment((Vector2){ position.x + segmentThick + segmentLen/2.0f, position.y + 3*segmentThick + segmentLen - 2*offsetYAdjust }, + segmentLen, segmentThick, false, (segments & 0b01000000)? colorOn : colorOff); +} + +// Draw one 7-segment display segment, horizontal or vertical +static void DrawDisplaySegment(Vector2 center, int length, int thick, bool vertical, Color color) +{ + if (!vertical) + { + // Horizontal segment points + // 3___________________________5 + // / \ + // /1 x 6\ + // \ / + // \2___________________________4/ + Vector2 segmentPointsH[6] = { + (Vector2){ center.x - length/2.0f - thick/2.0f, center.y }, // Point 1 + (Vector2){ center.x - length/2.0f, center.y + thick/2.0f }, // Point 2 + (Vector2){ center.x - length/2.0f, center.y - thick/2.0f }, // Point 3 + (Vector2){ center.x + length/2.0f, center.y + thick/2.0f }, // Point 4 + (Vector2){ center.x + length/2.0f, center.y - thick/2.0f }, // Point 5 + (Vector2){ center.x + length/2.0f + thick/2.0f, center.y }, // Point 6 + }; + + DrawTriangleStrip(segmentPointsH, 6, color); + } + else + { + // Vertical segment points + Vector2 segmentPointsV[6] = { + (Vector2){ center.x, center.y - length/2.0f - thick/2.0f }, // Point 1 + (Vector2){ center.x - thick/2.0f, center.y - length/2.0f }, // Point 2 + (Vector2){ center.x + thick/2.0f, center.y - length/2.0f }, // Point 3 + (Vector2){ center.x - thick/2.0f, center.y + length/2.0f }, // Point 4 + (Vector2){ center.x + thick/2.0f, center.y + length/2.0f }, // Point 5 + (Vector2){ center.x, center.y + length/2 + thick/2.0f }, // Point 6 + }; + + DrawTriangleStrip(segmentPointsV, 6, color); } } diff --git a/examples/shapes/shapes_digital_clock.png b/examples/shapes/shapes_digital_clock.png index 22939253176be52b334d781d99a2e7c23a750ddd..35ac56b93ba45b3d2b725c19f345a48305c50bce 100644 GIT binary patch literal 18697 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 literal 3651 zcmeHKYfzI{8h$^b35r3~ibA*qC|gB9z(5fMQ$(&SQe<|iS`8o&7KBIu5k)=)(bkGc zYu5`4$yP9ojJ8%jMG*ucD~^Jf0#(t<#eLGw{@9=FuboaandHqm@8x;k z%Q;^@iHryqPPLp$M8eRJz$hY~B@xqL%*RUUWpcsSBxOKov@u@Wj5qATZ;Q<3F`28< z6Eio*W+YHjTKdKW7iD}#LPDBSo}M|tYxIZGJcH7*jD*n34gl)Vzz?Ex%AVb;Io#<*MX^5neK#KPk3Y6~z$GIq zDDv-v?{qk>(PV$xWa|Mzl5#7PrUQU{KZnoz zg&<<~o`i_?Rx?CRQ-nm~2sySk1webxEYOvf{igbVlz~@6PZ?5_HI9$=j#T82zpR@T zY$SbhO-`8`Ea$ZL5i6NcB{}!q!;EH*n&A%j_v^_ZO`s1rb-y|2k{GoDbFTiq4>Pa|;2%^Sg-{|n``ONxKq zyKZAdlI3!`H)3uP$D@owK3A)&Hxp5borG+<(%!$wkZ6ifwMOY~=S4C z+W}$6no}hI3xrSFNtF=5(+7I$idru+?E!Kst9l5mwS|1?WMGu0Kv-@N7C=bo3Y#o7 zfv^if{)oX zDH8A%AA-Ir5j*#>;|bV8@AzOR%E7rRFU%6rtw1?R0-;29m08J0?#_u#O`5!utu zgpty*X6U=hp2oKw>OA_w%#xhTI}}=5QWo;*l3V@0sfG^JZa(fCmG|aF@?5@2Gn2DI zPJ;!GXqA?2b9F;cItobT-;%hXyt(<$3)dIVHKjT6K|I#JI?>HPe|+0u2-O?G$Ew!O z%7f3K2r0k?%Cw3g8ZHl3JV=p1^&)TVPH%I!&IG$>{ zc>zx`RWQPw|Nb*RRcgjuq@Bo6C0g_u*pwl$=dW4{>Fa;Vma^H0Zjj0n1(w5H-#&*)lnCOAvEOiiG-#3dX3|P{OqK15@?hFh2wH zc;x1SgBJ}aA-5kZ+xS|4RE+b)Kt45? zlClR=^GybiaKs9m_*y00dZD$Jo(HY31A)}_F~)ch+{ZCR{stv zfpI+fK~yr5L1&zHhvV7c$XVjdJ~(fmaFL~UHiDnLvNmwfG`$l_#fJHH+dF!&3kzh9T*w&?;3b@Zud~ZYTaaQu5F7AkuG=ZR#8!G&X(A> z!^7FH;|Kh* z!*Inh<$0^$K3+BY;7&@(TwPtyKtso&r0VD0Bc5a9NsBDP*F7Jqag3jDJE%gV{6}}*KTlD zE=~F2AIiG-S1fycyXD@GS!^)wh^XTgP78mE&ii5Lrb%b%%KPE#mFX$H51nk1))nXa zv3NRAMLAZSY`k!#sy}Y<+O8(g#)b1+xh41W(p+w_W4QGToExs>4Tp}FZhh3+8#>xm z`|9=5qsj9v@~_5(b+^`@3sUxOF`p8iboNC4`dOmzw<~M?mrZ)`pW8+Lu}f!xP2SNJ zr+&^R&IzBWC%d+t?lqG6VTMWC^(**OQs2n57yR{ZR>lqw{C%R(