From f76e3714364fdd96756cb2fcaee3be5c48f8b35f Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 9 Nov 2025 19:14:46 +0100 Subject: [PATCH] REDESIGNED: `core_clipboard_text`, based on #5248 --- examples/README.md | 2 +- examples/core/core_clipboard_text.c | 216 +++++++++++--------------- examples/core/core_clipboard_text.png | Bin 15878 -> 17783 bytes examples/examples_list.txt | 2 +- 4 files changed, 94 insertions(+), 126 deletions(-) diff --git a/examples/README.md b/examples/README.md index 9a208f599..29cea47fb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,7 +69,7 @@ Examples using raylib[core](../src/rcore.c) platform functionality like window c | [core_directory_files](core/core_directory_files.c) | core_directory_files | ⭐☆☆☆ | 5.5 | 5.6 | [Hugo ARNAL](https://github.com/hugoarnal) | | [core_highdpi_testbed](core/core_highdpi_testbed.c) | core_highdpi_testbed | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | | [core_screen_recording](core/core_screen_recording.c) | core_screen_recording | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | -| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Robin](https://github.com/RobinsAviary) | +| [core_clipboard_text](core/core_clipboard_text.c) | core_clipboard_text | ⭐☆☆☆ | 5.6-dev | 5.6-dev | [Ananth S](https://github.com/Ananth1839) | | [core_text_file_loading](core/core_text_file_loading.c) | core_text_file_loading | ⭐☆☆☆ | 5.5 | 5.6 | [Aanjishnu Bhattacharyya](https://github.com/NimComPoo-04) | | [core_compute_hash](core/core_compute_hash.c) | core_compute_hash | ⭐⭐☆☆ | 5.6-dev | 5.6-dev | [Ramon Santamaria](https://github.com/raysan5) | diff --git a/examples/core/core_clipboard_text.c b/examples/core/core_clipboard_text.c index c9d22c54a..895c235da 100644 --- a/examples/core/core_clipboard_text.c +++ b/examples/core/core_clipboard_text.c @@ -2,22 +2,23 @@ * * raylib [core] example - clipboard text * -* Example complexity rating: [★☆☆☆] 1/4 -* * Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev * -* Example contributed by Robin (@RobinsAviary) and reviewed by Ramon Santamaria (@raysan5) +* Example contributed by Ananth S (@Ananth1839) 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) 2025 Robin (@RobinsAviary) +* Copyright (c) 2025 Ananth S (@Ananth1839) * ********************************************************************************************/ #include "raylib.h" -#include +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +#define MAX_TEXT_SAMPLES 5 //------------------------------------------------------------------------------------ // Program main entry point @@ -31,30 +32,32 @@ int main(void) InitWindow(screenWidth, screenHeight, "raylib [core] example - clipboard text"); - const char *clipboardText = NULL; + // Define some sample texts + const char *sampleTexts[MAX_TEXT_SAMPLES] = { + "Hello from raylib!", + "The quick brown fox jumps over the lazy dog", + "Clipboard operations are useful!", + "raylib is a simple and easy-to-use library", + "Copy and paste me!" + }; - // List of text the user can switch through and copy - const char *copyableText[] = { "raylib is fun", "hello, clipboard!", "potato chips" }; + char *clipboardText = NULL; + char inputBuffer[256] = "Hello from raylib!"; // Random initial string - unsigned int textIndex = 0; + // UI required variables + bool textBoxEditMode = false; - const char *popupText = NULL; + bool btnCutPressed = false; + bool btnCopyPressed = false; + bool btnPastePressed = false; + bool btnClearPressed = false; + bool btnRandomPressed = false; - // Initialize timers - // The amount of time the pop-up text is on screen, before fading - const float maxTime = 3.0f; - float textTimer = 0.0f; - // The length of time text is offset - const float animMaxTime = 0.1f; - float pasteAnim = 0.0f; - float copyAnim = 0.0f; - int copyAnimMult = 1; - float textAnim = 0.0f; - float textAlpha = 0.0f; - // Offset amount for animations - const int offsetAmount = -4; + // Set UI style + GuiSetStyle(DEFAULT, TEXT_SIZE, 20); + GuiSetIconScale(2); - SetTargetFPS(60); + SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- // Main game loop @@ -62,83 +65,56 @@ int main(void) { // Update //---------------------------------------------------------------------------------- - // Check if the user has pressed the copy/paste key combinations - bool pastePressed = (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)); - bool copyPressed = (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)); - - // Update animation timers - if (textTimer > 0) textTimer -= GetFrameTime(); - if (pasteAnim > 0) pasteAnim -= GetFrameTime(); - if (copyAnim > 0) copyAnim -= GetFrameTime(); - if (textAnim > 0) textAnim -= GetFrameTime(); - - if (pastePressed) + // Handle button interactions + if (btnCutPressed) { - // Most operating systems hide this information until the user presses Ctrl-V on the window. + SetClipboardText(inputBuffer); + clipboardText = GetClipboardText(); + inputBuffer[0] = '\0'; // Quick solution to clear text + //memset(inputBuffer, 0, 256); // Clear full buffer properly + } - // Check to see if the clipboard contains an image - // This function does nothing outside of Windows, as it directly calls the Windows API - Image image = GetClipboardImage(); - - if (IsImageValid(image)) + if (btnCopyPressed) + { + SetClipboardText(inputBuffer); // Copy text to clipboard + clipboardText = GetClipboardText(); // Get text from clipboard + } + + if (btnPastePressed) + { + // Paste text from clipboard + clipboardText = GetClipboardText(); + if (clipboardText != NULL) TextCopy(inputBuffer, clipboardText); + } + + if (btnClearPressed) + { + inputBuffer[0] = '\0'; // Quick solution to clear text + //memset(inputBuffer, 0, 256); // Clear full buffer properly + } + + if (btnRandomPressed) + { + // Get random text from sample list + TextCopy(inputBuffer, sampleTexts[GetRandomValue(0, MAX_TEXT_SAMPLES - 1)]); + } + + // Quick cut/copy/paste with keyboard shortcuts + if (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) + { + if (IsKeyPressed(KEY_X)) { - UnloadImage(image); - popupText = "clipboard contains image"; + SetClipboardText(inputBuffer); + inputBuffer[0] = '\0'; // Quick solution to clear text } - else + + if (IsKeyPressed(KEY_C)) SetClipboardText(inputBuffer); + + if (IsKeyPressed(KEY_V)) { clipboardText = GetClipboardText(); - - popupText = "text pasted"; - pasteAnim = animMaxTime; + if (clipboardText != NULL) TextCopy(inputBuffer, clipboardText); } - - // Reset animation values - textTimer = maxTime; - textAnim = animMaxTime; - textAlpha = 1; - } - - // React to the user pressing copy - if (copyPressed) - { - // Set the text on the user's clipboard - SetClipboardText(copyableText[textIndex]); - - // Reset values - textTimer = maxTime; - textAnim = animMaxTime; - copyAnim = animMaxTime; - copyAnimMult = 1; - textAlpha = 1; - popupText = "text copied"; - } - - // Switch to the next item in the list when the user presses up - if (IsKeyPressed(KEY_UP)) - { - // Reset animation - copyAnim = animMaxTime; - copyAnimMult = 1; - - textIndex += 1; - - if (textIndex >= sizeof(copyableText) / sizeof(const char*)) // Length of array - { - // Loop back to the other end - textIndex = 0; - } - } - - // Switch to the previous item in the list when the user presses down - if (IsKeyPressed(KEY_DOWN)) - { - // Reset animation - copyAnim = animMaxTime; - copyAnimMult = -1; - - if (textIndex == 0) textIndex = (sizeof(copyableText)/sizeof(const char*)) - 1; - else textIndex -= 1; } //---------------------------------------------------------------------------------- @@ -146,40 +122,32 @@ int main(void) //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(RAYWHITE); + ClearBackground(RAYWHITE); - // Draw the user's pasted text, if there is any yet - if (clipboardText) - { - // Offset animation - int offset = 0; - if (pasteAnim > 0) offset = offsetAmount; + // Draw instructions + GuiLabel((Rectangle){ 50, 20, 700, 36 }, "Use the BUTTONS or KEY SHORTCUTS:"); + DrawText("[CTRL+X] - CUT | [CTRL+C] COPY | [CTRL+V] | PASTE", 50, 60, 20, MAROON); - // Draw the pasted text - DrawText("pasted clipboard:", 10, 10 + offset, 20, DARKGREEN); - DrawText(clipboardText, 10, 30 + offset, 20, DARKGRAY); - } + // Draw text box + if (GuiTextBox((Rectangle){ 50, 120, 652, 40 }, inputBuffer, 256, textBoxEditMode)) textBoxEditMode = !textBoxEditMode; - // Offset animation - int textOffset = 0; - if (copyAnim > 0) textOffset = offsetAmount; + // Random text button + btnRandomPressed = GuiButton((Rectangle){ 50 + 652 + 8, 120, 40, 40 }, "#77#"); - // Draw copyable text and controls - DrawText(copyableText[textIndex], 10, 330 + (textOffset * copyAnimMult), 20, MAROON); - DrawText("up/down to change string, ctrl-c to copy, ctrl-v to paste", 10, 355, 20, DARKGRAY); + // Draw buttons + btnCutPressed = GuiButton((Rectangle){ 50, 180, 158, 40 }, "#17#CUT"); + btnCopyPressed = GuiButton((Rectangle){ 50 + 165, 180, 158, 40 }, "#16#COPY"); + btnPastePressed = GuiButton((Rectangle){ 50 + 165*2, 180, 158, 40 }, "#18#PASTE"); + btnClearPressed = GuiButton((Rectangle){ 50 + 165*3, 180, 158, 40 }, "#143#CLEAR"); - // Alpha / Offset animation - if (textAlpha > 0) - { - // Offset animation - int offset = 0; - if (textAnim > 0) offset = offsetAmount; - // Draw pop up text - DrawText(popupText, 10, 425 + offset, 20, ColorAlpha(DARKGREEN, textAlpha)); - - // Fade-out animation - if (textTimer < 0) textAlpha -= GetFrameTime(); - } + // Draw clipboard status + GuiSetState(STATE_DISABLED); + GuiLabel((Rectangle){ 50, 260, 700, 40 }, "Clipboard current text data:"); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiTextBox((Rectangle){ 50, 300, 700, 40 }, clipboardText, 256, false); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + GuiLabel((Rectangle){ 50, 360, 700, 40 }, "Try copying text from other applications and pasting here!"); + GuiSetState(STATE_NORMAL); EndDrawing(); //---------------------------------------------------------------------------------- @@ -191,4 +159,4 @@ int main(void) //-------------------------------------------------------------------------------------- return 0; -} \ No newline at end of file +} diff --git a/examples/core/core_clipboard_text.png b/examples/core/core_clipboard_text.png index caa9b314a694bdb9316f9f8d07161887d53887e6..96a7379c7d956fad97ce1a17eeec7df011d5f422 100644 GIT binary patch literal 17783 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 delta 2729 zcmey~#n@IeL7B<+>_*jTj8=-)%!lqKoMSljrB5j8cK7KTueY)*J}e6V!&CmhS8U&6 zMFS}&6e7kcp@j2VMwHy-LPu%d4LxmNFK{%j=T&!KoHdzIT8y);hNI_PLyy{{$s0AL zO&Bg3D;#;Zy`lHYum7>QbkBnJ z>lrhRCLd%J;k0l{C}B%6QSF=D$S7fWL6^nj9&=N+KGVXP93}D(r5M#q*E4T?)mdtO zX6}+z>LSToZXI^auIHWlL(TMW?=-{7g%)(5IjTIJa=1+d8DlO}h;tl}`-uoA)fb10JbnpQ?ec5D?(=8azzGqzcRgNjiIqQoY@2@4A zS?}j6Ucb6{c|z~BMJPc64j~I?u!B><4)z5(cpjr->00}ZuU76&XQ@@0cV(~Ym3x;R z*8Ocs%gYdz-yq_V{n^v^<&jxbzQ}VQ zu4Q!vOHW<=A)%`}`6Gu2XIm1))wd^a)U=wsUq*%9;fvgaix!fTFY-xp@iKZr9L77j zfl*TGf-Vc2FT0AB)hx!Am2ZoqW?El(C!_UG>g?&s6RpKK8)U#%zDxvJ$$P*^>YB4L z%QU@ME2+}S6RpKLpFm>eBG{8U7j#)V<}oUk)wAWK%nSKyAiwyvv;0f>oM(@^G_!UJ z->v!bLDhWE$X>Q;+zrxdaQF6k?FD8=@GRg>S`rZ&wz5bYockz>VOC`_F27A)M6B1zaLD8(t zaMD;|$-9FKrd1WaXKrj&*|SyFf5t;P&Jyp;f1Zj;)(W? z8>J+W*vQ`k(Ec9B3uZkqQaC z^`OAh2W9UTw`YlZZM_#Jua>*cVSmQP$(UismB8#Y#=A8^e?4=~OnU7<Fc=3own^*n1nz3^7LMc&B8HmZUARkExU(jXAsN+9TaKG4L^Q-CilsX5~7m}RM@tPP}21^c9{w(OQzz87lE{@}#kk)zVn#t+2`h(P)pY!8>N`~)sTrp=^=wE|?r^Xwf@^3wy zy=3{}gHrc-7AKgnv;}~zSGx`_^(JrA6su=A`@KOxs#5VuaFF{JkFEVN-vwXmdHwB| zvpaH5f_KVZgYQDw_KN=>NChq}wc)?^LD}5)!Kw5wr}URh%kcKR^Vl_WpTf1;7PlW4 z?{1uOs`_eM^gs2jdlr=6PS~-;IjjA4f_HmMTFQmL{#kpSvVMBzSo*%{d6+S~!i1$Q znWN_%OGD4H*ONDD$~Y~sW$lj2u5`bA=>cEkK`SPg-13Q5MRv3D*wZ<5WNmBnO)~kK z1SEXhe<(NJ`7}H1>6aHi$1^q>N-@oVq#3QrfeJE?&3SAoCC`#mmepmw-?_j#LQQ&QskMx3JXG)+*U_)^vAg21rR4e01_bDe3x#Cz1j| z*Nuc$&x=wv+EOI1<96Zf^6$6&V!kcx-@vogmS+o}d)9L9B=;>RzKU=B=<|Nlq&o#F zr7ROB*dAF{?&U70sXN`{EBnG9GFm_9-}=dG8{>9aG1F3h8o!3``5mtuXZ&!u?)+q# zaMbk6SM=E3Zl74JXaLViZ3iYdGm3%ImBjwKzrW^-Z41m^l4_#c)5f6doM19HNWJ9Z z;X;!b_s_b9iVkl}jArq*r|**1^>;fxd9tCoy0OmUL|ZAQ!&WeJ|{)bRn~!NG9x(U)G6w=*y>FnGH9xvX