|
|
|
@ -0,0 +1,400 @@ |
|
|
|
/******************************************************************************************* |
|
|
|
* |
|
|
|
* raylib [text] example - strings management |
|
|
|
* |
|
|
|
* Example complexity rating: [★★★☆] 3/4 |
|
|
|
* |
|
|
|
* Example originally created with raylib 5.6-dev, last time updated with raylib 5.6-dev |
|
|
|
* |
|
|
|
* Example contributed by David Buzatto (@davidbuzatto) 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 David Buzatto (@davidbuzatto) |
|
|
|
* |
|
|
|
********************************************************************************************/ |
|
|
|
|
|
|
|
#include "raylib.h" |
|
|
|
|
|
|
|
#include <stdlib.h> |
|
|
|
|
|
|
|
#define MAX_TEXT_LENGTH 100 |
|
|
|
#define MAX_TEXT_PARTICLES 100 |
|
|
|
#define FONT_SIZE 30 |
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
// Types and Structures Definition |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
typedef struct TextParticle { |
|
|
|
char text[MAX_TEXT_LENGTH]; |
|
|
|
Rectangle rect; // Boundary |
|
|
|
Vector2 vel; // Velocity |
|
|
|
Vector2 ppos; // Previous position |
|
|
|
float padding; |
|
|
|
float borderWidth; |
|
|
|
float friction; |
|
|
|
float elasticity; |
|
|
|
Color color; |
|
|
|
bool grabbed; |
|
|
|
} TextParticle; |
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
// Module Functions Declaration |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
void PrepareFirstTextParticle(const char* text, TextParticle *tps, int *particleCount); |
|
|
|
TextParticle CreateTextParticle(const char *text, float x, float y, Color color); |
|
|
|
void SliceTextParticle(TextParticle *tp, int particlePos, int sliceLength, TextParticle *tps, int *particleCount); |
|
|
|
void SliceTextParticleByChar(TextParticle *tp, char charToSlice, TextParticle *tps, int *particleCount); |
|
|
|
void ShatterTextParticle(TextParticle *tp, int particlePos, TextParticle *tps, int *particleCount); |
|
|
|
void GlueTextParticles(TextParticle *grabbed, TextParticle *target, TextParticle *tps, int *particleCount); |
|
|
|
void RealocateTextParticles(TextParticle *tps, int particlePos, int *particleCount); |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------------ |
|
|
|
// Program main entry point |
|
|
|
//------------------------------------------------------------------------------------ |
|
|
|
int main(void) |
|
|
|
{ |
|
|
|
// Initialization |
|
|
|
//-------------------------------------------------------------------------------------- |
|
|
|
const int screenWidth = 800; |
|
|
|
const int screenHeight = 450; |
|
|
|
|
|
|
|
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - strings management"); |
|
|
|
|
|
|
|
TextParticle textParticles[MAX_TEXT_PARTICLES] = { 0 }; |
|
|
|
int particleCount = 0; |
|
|
|
TextParticle *grabbedTextParticle = NULL; |
|
|
|
Vector2 pressOffset = {0}; |
|
|
|
|
|
|
|
PrepareFirstTextParticle("raylib => fun videogames programming!", textParticles, &particleCount); |
|
|
|
|
|
|
|
SetTargetFPS(60); // Set our game to run at 60 frames-per-second |
|
|
|
//--------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
// Main game loop |
|
|
|
while (!WindowShouldClose()) // Detect window close button or ESC key |
|
|
|
{ |
|
|
|
// Update |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
float delta = GetFrameTime(); |
|
|
|
Vector2 mousePos = GetMousePosition(); |
|
|
|
|
|
|
|
// Checks if a text particle was grabbed |
|
|
|
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) |
|
|
|
{ |
|
|
|
for (int i = particleCount - 1; i >= 0; i--) |
|
|
|
{ |
|
|
|
TextParticle *tp = &textParticles[i]; |
|
|
|
pressOffset.x = mousePos.x - tp->rect.x; |
|
|
|
pressOffset.y = mousePos.y - tp->rect.y; |
|
|
|
if (CheckCollisionPointRec(mousePos, tp->rect)) |
|
|
|
{ |
|
|
|
tp->grabbed = true; |
|
|
|
grabbedTextParticle = tp; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Releases any text particle the was grabbed |
|
|
|
if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) |
|
|
|
{ |
|
|
|
if (grabbedTextParticle != NULL) |
|
|
|
{ |
|
|
|
grabbedTextParticle->grabbed = false; |
|
|
|
grabbedTextParticle = NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Slice os shatter a text particle |
|
|
|
if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) |
|
|
|
{ |
|
|
|
for (int i = particleCount - 1; i >= 0; i--) |
|
|
|
{ |
|
|
|
TextParticle *tp = &textParticles[i]; |
|
|
|
if (CheckCollisionPointRec(mousePos, tp->rect)) |
|
|
|
{ |
|
|
|
if (IsKeyDown(KEY_LEFT_SHIFT)) |
|
|
|
{ |
|
|
|
ShatterTextParticle(tp, i, textParticles, &particleCount); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
SliceTextParticle(tp, i, TextLength(tp->text)/2, textParticles, &particleCount); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Shake text particles |
|
|
|
if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) |
|
|
|
{ |
|
|
|
for (int i = 0; i < particleCount; i++) |
|
|
|
{ |
|
|
|
if (!textParticles[i].grabbed) textParticles[i].vel = (Vector2){ GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000) }; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Reset using TextTo* functions |
|
|
|
if (IsKeyPressed(KEY_ONE)) PrepareFirstTextParticle("raylib => fun videogames programming!", textParticles, &particleCount); |
|
|
|
if (IsKeyPressed(KEY_TWO)) PrepareFirstTextParticle(TextToUpper("raylib => fun videogames programming!"), textParticles, &particleCount); |
|
|
|
if (IsKeyPressed(KEY_THREE)) PrepareFirstTextParticle(TextToLower("raylib => fun videogames programming!"), textParticles, &particleCount); |
|
|
|
if (IsKeyPressed(KEY_FOUR)) PrepareFirstTextParticle(TextToPascal("raylib_fun_videogames_programming"), textParticles, &particleCount); |
|
|
|
if (IsKeyPressed(KEY_FIVE)) PrepareFirstTextParticle(TextToSnake("RaylibFunVideogamesProgramming"), textParticles, &particleCount); |
|
|
|
if (IsKeyPressed(KEY_SIX)) PrepareFirstTextParticle(TextToCamel("raylib_fun_videogames_programming"), textParticles, &particleCount); |
|
|
|
|
|
|
|
// Slice by char pressed only when we have one text particle |
|
|
|
char charPressed = GetCharPressed(); |
|
|
|
if ((charPressed >= 'A') && (charPressed <= 'z') && (particleCount == 1)) |
|
|
|
{ |
|
|
|
SliceTextParticleByChar(&textParticles[0], charPressed, textParticles, &particleCount); |
|
|
|
} |
|
|
|
|
|
|
|
// Updates each text particle state |
|
|
|
for (int i = 0; i < particleCount; i++) |
|
|
|
{ |
|
|
|
TextParticle *tp = &textParticles[i]; |
|
|
|
|
|
|
|
// The text particle is not grabbed |
|
|
|
if (!tp->grabbed) |
|
|
|
{ |
|
|
|
// text particle repositioning using the velocity |
|
|
|
tp->rect.x += tp->vel.x * delta; |
|
|
|
tp->rect.y += tp->vel.y * delta; |
|
|
|
|
|
|
|
// Does the text particle hit the screen right boundary? |
|
|
|
if ((tp->rect.x + tp->rect.width) >= screenWidth) |
|
|
|
{ |
|
|
|
tp->rect.x = screenWidth - tp->rect.width; // Text particle repositioning |
|
|
|
tp->vel.x = -tp->vel.x*tp->elasticity; // Elasticity makes the text particle lose 10% of its velocity on hit |
|
|
|
} |
|
|
|
// Does the text particle hit the screen left boundary? |
|
|
|
else if (tp->rect.x <= 0) |
|
|
|
{ |
|
|
|
tp->rect.x = 0.0f; |
|
|
|
tp->vel.x = -tp->vel.x*tp->elasticity; |
|
|
|
} |
|
|
|
|
|
|
|
// The same for y axis |
|
|
|
if ((tp->rect.y + tp->rect.height) >= screenHeight) |
|
|
|
{ |
|
|
|
tp->rect.y = screenHeight - tp->rect.height; |
|
|
|
tp->vel.y = -tp->vel.y*tp->elasticity; |
|
|
|
} |
|
|
|
else if (tp->rect.y <= 0) |
|
|
|
{ |
|
|
|
tp->rect.y = 0.0f; |
|
|
|
tp->vel.y = -tp->vel.y*tp->elasticity; |
|
|
|
} |
|
|
|
|
|
|
|
// Friction makes the text particle lose 1% of its velocity each frame |
|
|
|
tp->vel.x = tp->vel.x*tp->friction; |
|
|
|
tp->vel.y = tp->vel.y*tp->friction; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
// Text particle repositioning using the mouse position |
|
|
|
tp->rect.x = mousePos.x - pressOffset.x; |
|
|
|
tp->rect.y = mousePos.y - pressOffset.y; |
|
|
|
|
|
|
|
// While the text particle is grabbed, recalculates its velocity |
|
|
|
tp->vel.x = (tp->rect.x - tp->ppos.x)/delta; |
|
|
|
tp->vel.y = (tp->rect.y - tp->ppos.y)/delta; |
|
|
|
tp->ppos.x = tp->rect.x; |
|
|
|
tp->ppos.y = tp->rect.y; |
|
|
|
|
|
|
|
// Glue text particles when dragging and pressing left ctrl |
|
|
|
if (IsKeyDown(KEY_LEFT_CONTROL)) |
|
|
|
{ |
|
|
|
for (int i = 0; i < particleCount; i++) |
|
|
|
{ |
|
|
|
if (&textParticles[i] != grabbedTextParticle && grabbedTextParticle->grabbed) |
|
|
|
{ |
|
|
|
if (CheckCollisionRecs(grabbedTextParticle->rect, textParticles[i].rect)) |
|
|
|
{ |
|
|
|
GlueTextParticles(grabbedTextParticle, &textParticles[i], textParticles, &particleCount); |
|
|
|
grabbedTextParticle = &textParticles[particleCount-1]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
// Draw |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
BeginDrawing(); |
|
|
|
|
|
|
|
ClearBackground(RAYWHITE); |
|
|
|
|
|
|
|
for (int i = 0; i < particleCount; i++) |
|
|
|
{ |
|
|
|
TextParticle *tp = &textParticles[i]; |
|
|
|
DrawRectangle(tp->rect.x-tp->borderWidth, tp->rect.y-tp->borderWidth, tp->rect.width+tp->borderWidth*2, tp->rect.height+tp->borderWidth*2, BLACK); |
|
|
|
DrawRectangleRec(tp->rect, tp->color); |
|
|
|
DrawText(tp->text, tp->rect.x+tp->padding, tp->rect.y+tp->padding, FONT_SIZE, BLACK); |
|
|
|
} |
|
|
|
|
|
|
|
DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, DARKGRAY); |
|
|
|
DrawText("slice a text particle by pressing it with the mouse right button", 10, 30, 10, DARKGRAY); |
|
|
|
DrawText("shatter a text particle keeping left shift pressed and pressing it with the mouse right button", 10, 50, 10, DARKGRAY); |
|
|
|
DrawText("glue text particles by grabbing than and keeping left control pressed", 10, 70, 10, DARKGRAY); |
|
|
|
DrawText("1 to 6 to reset", 10, 90, 10, DARKGRAY); |
|
|
|
DrawText("when you have only one text particle, you can slice it by pressing a char", 10, 110, 10, DARKGRAY); |
|
|
|
DrawText(TextFormat("TEXT PARTICLE COUNT: %d", particleCount), 10, GetScreenHeight() - 30, 20, BLACK); |
|
|
|
|
|
|
|
EndDrawing(); |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
} |
|
|
|
|
|
|
|
// De-Initialization |
|
|
|
//-------------------------------------------------------------------------------------- |
|
|
|
CloseWindow(); // Close window and OpenGL context |
|
|
|
//-------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
// Module Functions Definition |
|
|
|
//---------------------------------------------------------------------------------- |
|
|
|
void PrepareFirstTextParticle(const char* text, TextParticle *tps, int *particleCount) |
|
|
|
{ |
|
|
|
tps[0] = CreateTextParticle( |
|
|
|
text, |
|
|
|
GetScreenWidth()/2, |
|
|
|
GetScreenHeight()/2, |
|
|
|
RAYWHITE |
|
|
|
); |
|
|
|
*particleCount = 1; |
|
|
|
} |
|
|
|
|
|
|
|
TextParticle CreateTextParticle(const char *text, float x, float y, Color color) |
|
|
|
{ |
|
|
|
TextParticle tp = { |
|
|
|
.text = "", |
|
|
|
.rect = { x, y, 30, 30 }, |
|
|
|
.vel = { GetRandomValue(-200, 200), GetRandomValue(-200, 200) }, |
|
|
|
.ppos = { 0 }, |
|
|
|
.padding = 5.0f, |
|
|
|
.borderWidth = 5.0f, |
|
|
|
.friction = 0.99, |
|
|
|
.elasticity = 0.9, |
|
|
|
.color = color, |
|
|
|
.grabbed = false |
|
|
|
}; |
|
|
|
|
|
|
|
TextCopy(tp.text, text); |
|
|
|
tp.rect.width = MeasureText(tp.text, FONT_SIZE)+tp.padding*2; |
|
|
|
tp.rect.height = FONT_SIZE+tp.padding*2; |
|
|
|
return tp; |
|
|
|
} |
|
|
|
|
|
|
|
void SliceTextParticle(TextParticle *tp, int particlePos, int sliceLength, TextParticle *tps, int *particleCount) |
|
|
|
{ |
|
|
|
int length = TextLength(tp->text); |
|
|
|
|
|
|
|
if((length > 1) && ((*particleCount+length) < MAX_TEXT_PARTICLES)) |
|
|
|
{ |
|
|
|
for (int i = 0; i < length; i += sliceLength) |
|
|
|
{ |
|
|
|
const char *text = sliceLength == 1 ? TextFormat("%c", tp->text[i]) : TextSubtext(tp->text, i, sliceLength); |
|
|
|
tps[(*particleCount)++] = CreateTextParticle( |
|
|
|
text, |
|
|
|
tp->rect.x + i * tp->rect.width/length, |
|
|
|
tp->rect.y, |
|
|
|
(Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } |
|
|
|
); |
|
|
|
} |
|
|
|
RealocateTextParticles(tps, particlePos, particleCount); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void SliceTextParticleByChar(TextParticle *tp, char charToSlice, TextParticle *tps, int *particleCount) |
|
|
|
{ |
|
|
|
int tokenCount = 0; |
|
|
|
const char **tokens = TextSplit(tp->text, charToSlice, &tokenCount); |
|
|
|
|
|
|
|
if (tokenCount > 1) |
|
|
|
{ |
|
|
|
int textLength = TextLength(tp->text); |
|
|
|
for (int i = 0; i < textLength; i++) |
|
|
|
{ |
|
|
|
if (tp->text[i] == charToSlice) |
|
|
|
{ |
|
|
|
tps[(*particleCount)++] = CreateTextParticle( |
|
|
|
TextFormat("%c", charToSlice), |
|
|
|
tp->rect.x, |
|
|
|
tp->rect.y, |
|
|
|
(Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
for (int i = 0; i < tokenCount; i++) |
|
|
|
{ |
|
|
|
int tokenLength = TextLength(tokens[i]); |
|
|
|
tps[(*particleCount)++] = CreateTextParticle( |
|
|
|
TextFormat("%s", tokens[i]), |
|
|
|
tp->rect.x + i * tp->rect.width/tokenLength, |
|
|
|
tp->rect.y, |
|
|
|
(Color) { GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255 } |
|
|
|
); |
|
|
|
} |
|
|
|
if (tokenCount) |
|
|
|
{ |
|
|
|
RealocateTextParticles(tps, 0, particleCount); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void ShatterTextParticle(TextParticle *tp, int particlePos, TextParticle *tps, int *particleCount) |
|
|
|
{ |
|
|
|
SliceTextParticle(tp, particlePos, 1, tps, particleCount); |
|
|
|
} |
|
|
|
|
|
|
|
void GlueTextParticles(TextParticle *grabbed, TextParticle *target, TextParticle *tps, int *particleCount) |
|
|
|
{ |
|
|
|
int p1 = -1; |
|
|
|
int p2 = -1; |
|
|
|
|
|
|
|
for (int i = 0; i < *particleCount; i++) |
|
|
|
{ |
|
|
|
if (&tps[i] == grabbed) p1 = i; |
|
|
|
if (&tps[i] == target) p2 = i; |
|
|
|
} |
|
|
|
|
|
|
|
if ((p1 != -1) && (p2 != -1)) |
|
|
|
{ |
|
|
|
TextParticle tp = CreateTextParticle( |
|
|
|
TextFormat( "%s%s", grabbed->text, target->text), |
|
|
|
grabbed->rect.x, |
|
|
|
grabbed->rect.y, |
|
|
|
RAYWHITE |
|
|
|
); |
|
|
|
tp.grabbed = true; |
|
|
|
tps[(*particleCount)++] = tp; |
|
|
|
grabbed->grabbed = false; |
|
|
|
if (p1 < p2) |
|
|
|
{ |
|
|
|
RealocateTextParticles(tps, p2, particleCount); |
|
|
|
RealocateTextParticles(tps, p1, particleCount); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
RealocateTextParticles(tps, p1, particleCount); |
|
|
|
RealocateTextParticles(tps, p2, particleCount); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void RealocateTextParticles(TextParticle *tps, int particlePos, int *particleCount) |
|
|
|
{ |
|
|
|
for (int i = particlePos+1; i < *particleCount; i++) |
|
|
|
{ |
|
|
|
tps[i-1] = tps[i]; |
|
|
|
} |
|
|
|
(*particleCount)--; |
|
|
|
} |