Showing preview only (8,908K chars total). Download the full file or copy to clipboard to get everything.
Repository: EmilDimov93/Rapid-Engine
Branch: main
Commit: e56b8c08801d
Files: 34
Total size: 8.5 MB
Directory structure:
gitextract__bogz74p/
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── Engine/
│ ├── CGEditor.c
│ ├── CGEditor.h
│ ├── Engine.c
│ ├── Engine.h
│ ├── HitboxEditor.c
│ ├── HitboxEditor.h
│ ├── InfoByType.c
│ ├── InfoByType.h
│ ├── Interpreter.c
│ ├── Interpreter.h
│ ├── Nodes.c
│ ├── Nodes.h
│ ├── ProjectManager.c
│ ├── ProjectManager.h
│ ├── TextEditor.c
│ ├── TextEditor.h
│ ├── definitions.c
│ ├── definitions.h
│ ├── resources/
│ │ ├── fonts.c
│ │ ├── resources.h
│ │ ├── sound.c
│ │ └── textures.c
│ └── version.h
├── LICENSE
├── Projects/
│ └── Example/
│ ├── Example.cg
│ ├── Example.config
│ ├── ex.txt
│ ├── random.abc
│ └── textdocument.txt
├── RAYLIB_LICENSE
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.vscode/
RE_resources/
re_early_dev/
build/
v1.0.0/
RapidEngine.exe
engine_log.txt
error_codes.txt
lines.ps1
push.ps1
run.ps1
desktop.ini
================================================
FILE: .gitmodules
================================================
[submodule "Engine/raylib"]
path = Engine/raylib
url = https://github.com/raysan5/raylib.git
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.10)
project(RapidEngine C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
add_subdirectory(Engine/raylib)
add_executable(RapidEngine
Engine/Engine.c
Engine/CGEditor.c
Engine/Interpreter.c
Engine/HitboxEditor.c
Engine/ProjectManager.c
Engine/TextEditor.c
Engine/Nodes.c
Engine/InfoByType.c
Engine/definitions.c
Engine/resources/fonts.c
Engine/resources/sound.c
Engine/resources/textures.c
)
target_link_libraries(RapidEngine raylib)
if(WIN32)
target_link_libraries(RapidEngine opengl32 gdi32 winmm)
endif()
set(STACK_SIZE 8388608)
if(MSVC)
target_link_options(RapidEngine PRIVATE /STACK:${STACK_SIZE})
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
if(WIN32)
target_link_options(RapidEngine PRIVATE "-Wl,--stack,${STACK_SIZE}")
target_link_options(RapidEngine PRIVATE "-mwindows")
endif()
endif()
================================================
FILE: Engine/CGEditor.c
================================================
// Copyright 2025 Emil Dimov
// Licensed under the Apache License, Version 2.0
#include "CGEditor.h"
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include "raymath.h"
#define MENU_WIDTH 270
#define MENU_ITEM_HEIGHT 40
#define MENU_VISIBLE_ITEMS 5.5
#define MENU_BORDER_THICKNESS 3
#define SUBMENU_WIDTH 250
void AddToLogFromCGEditor(CGEditorContext *cgEd, char *message, int level);
CGEditorContext InitEditorContext()
{
CGEditorContext cgEd = {0};
cgEd.hasFatalErrorOccurred = false;
cgEd.lastClickedPin = INVALID_PIN;
cgEd.scrollIndexNodeMenu = 0;
cgEd.hoveredItem = 0;
cgEd.mousePos = (Vector2){0, 0};
cgEd.isDraggingScreen = false;
cgEd.isDraggingSelectedNodes = false;
cgEd.isLMBPressed = false;
cgEd.delayFrames = true;
cgEd.isFirstFrame = true;
cgEd.engineDelayFrames = false;
cgEd.isNodeCreateMenuOpen = false;
Vector2 menuPosition = {0, 0};
Vector2 submenuPosition = {0, 0};
cgEd.isNodeOptionsMenuOpen = false;
cgEd.openedOptionsMenuNode = -1;
Image tempImg;
tempImg = LoadImageFromMemory(".png", node_gear_png, node_gear_png_len);
cgEd.gearTxt = LoadTextureFromImage(tempImg);
UnloadImage(tempImg);
if (cgEd.gearTxt.id == 0)
{
cgEd.hasFatalErrorOccurred = true;
AddToLogFromCGEditor(&cgEd, "Failed to load texture{C220}", LOG_LEVEL_ERROR);
return cgEd;
}
cgEd.focusedDropdownPin = -1;
cgEd.focusedFieldPin = -1;
cgEd.font = LoadFontFromMemory(".ttf", arialbd_ttf, arialbd_ttf_len, FONT_GLYPHS, NULL, 0);
if (cgEd.font.texture.id == 0)
{
cgEd.hasFatalErrorOccurred = true;
AddToLogFromCGEditor(&cgEd, "Failed to load font{C221}", LOG_LEVEL_ERROR);
return cgEd;
}
cgEd.newLogMessage = false;
cgEd.editingNodeNameIndex = -1;
cgEd.hasChanged = false;
cgEd.hasChangedInLastFrame = false;
cgEd.createNodeMenuFirstFrame = true;
cgEd.zoom = 1.0f;
cgEd.nodeGlareTime = 0;
cgEd.copiedNodesCount = 0;
cgEd.isLowSpecModeOn = false;
cgEd.hasDroppedFile = false;
cgEd.isSelecting = false;
cgEd.selectedNodesCount = 0;
cgEd.hoveredNodeIndex = -1;
cgEd.hoveredPinIndex = -1;
return cgEd;
}
void FreeEditorContext(CGEditorContext *cgEd)
{
UnloadTexture(cgEd->gearTxt);
UnloadFont(cgEd->font);
if (cgEd->graph)
{
FreeGraphContext(cgEd->graph);
}
}
void AddToLogFromCGEditor(CGEditorContext *cgEd, char *message, int level)
{
if (cgEd->logMessageCount >= MAX_LOG_MESSAGES)
{
return;
}
strmac(cgEd->logMessages[cgEd->logMessageCount], MAX_LOG_MESSAGE_SIZE, "%s", message);
cgEd->logMessageLevels[cgEd->logMessageCount] = level;
cgEd->logMessageCount++;
cgEd->newLogMessage = true;
}
void DrawBackgroundGrid(CGEditorContext *cgEd, int gridSpacing, RenderTexture2D dot)
{
static Vector2 offset;
if (cgEd->isDraggingScreen)
{
offset = Vector2Subtract(offset, Vector2Scale(GetMouseDelta(), 1.0f / (cgEd->zoom * cgEd->zoom)));
}
gridSpacing = (gridSpacing / cgEd->zoom > 1) ? gridSpacing / cgEd->zoom : 1;
float worldLeft = offset.x;
float worldTop = offset.y;
float worldRight = offset.x + cgEd->screenWidth / cgEd->zoom;
float worldBottom = offset.y + cgEd->screenHeight / cgEd->zoom;
int startX = ((int)worldLeft / gridSpacing) * gridSpacing - gridSpacing;
int startY = ((int)worldTop / gridSpacing) * gridSpacing - gridSpacing;
int endX = ((int)worldRight / gridSpacing) * gridSpacing + gridSpacing;
int endY = ((int)worldBottom / gridSpacing) * gridSpacing + gridSpacing;
for (int y = startY; y <= endY; y += gridSpacing)
{
int row = y / gridSpacing;
for (int x = startX; x <= endX; x += gridSpacing)
{
float drawX = x + (row % 2) * (gridSpacing / 2);
float drawY = (float)y;
float screenX = (drawX - offset.x) * cgEd->zoom;
float screenY = (drawY - offset.y) * cgEd->zoom;
DrawTextureRec(dot.texture, (Rectangle){0, 0, (float)dot.texture.width, (float)-dot.texture.height}, (Vector2){screenX, screenY}, COLOR_CGED_BACKGROUND_DOT);
}
}
}
void DrawCurvedWire(Vector2 outputPos, Vector2 inputPos, float thickness, Color color, bool isLowSpecModeOn)
{
const float inputOffset = 12.0f;
const float outputOffset = 17.0f;
float distance = fabsf(inputPos.x - outputPos.x);
float controlOffset = distance * 0.5f;
DrawLineEx(outputPos, (Vector2){outputPos.x + outputOffset, outputPos.y}, thickness, color);
inputPos.x -= inputOffset;
outputPos.x += outputOffset;
Vector2 p0 = outputPos;
Vector2 p1 = {outputPos.x + controlOffset, outputPos.y};
Vector2 p2 = {inputPos.x - controlOffset, inputPos.y};
Vector2 p3 = inputPos;
const int segments = isLowSpecModeOn ? 12 : Clamp((int)(distance / 8.0f), 12, 64);
Vector2 points[65];
points[0] = p0;
for (int i = 1; i <= segments; i++)
{
float t = (float)i / segments;
float u = 1.0f - t;
points[i] = (Vector2){
u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x,
u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y};
}
if (!isLowSpecModeOn)
{
for (int glow = 3; glow > 0; glow--)
{
float glowThickness = thickness + glow * 1.2f;
Color glowColor = color;
glowColor.a = 100;
for (int i = 1; i <= segments; i++)
{
DrawLineEx(points[i - 1], points[i], glowThickness, glowColor);
}
}
}
for (int i = 1; i <= segments; i++)
{
DrawLineEx(points[i - 1], points[i], thickness, color);
}
DrawLineEx(inputPos, (Vector2){inputPos.x + inputOffset, inputPos.y}, thickness, color);
}
void HandleVarNameTextBox(CGEditorContext *cgEd, Rectangle bounds, char *text, int index, GraphContext *graph)
{
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V))
{
cgEd->hasChangedInLastFrame = true;
const char *clipboard = GetClipboardText();
if (clipboard)
{
strmac(text, MAX_VARIABLE_NAME_SIZE, "%s%s", text, clipboard);
}
}
bounds.width = MeasureTextEx(cgEd->font, text, 16, 2).x + 25;
DrawRectangleRounded((Rectangle){bounds.x, bounds.y - 20, 56, bounds.height}, 0.4f, 4, DARKGRAY);
DrawTextEx(cgEd->font, "Name:", (Vector2){bounds.x + 4, bounds.y - 16}, 14, 2, WHITE);
DrawRectangleRounded(bounds, 0.6f, 4, GRAY);
DrawRectangleRoundedLinesEx(bounds, 0.6f, 4, 2, DARKGRAY);
bool showCursor = ((int)(GetTime() * 2) % 2) == 0;
char buffer[MAX_VARIABLE_NAME_SIZE];
strmac(buffer, MAX_VARIABLE_NAME_SIZE, "%s%s", text, showCursor ? "_" : " ");
DrawTextEx(cgEd->font, buffer, (Vector2){bounds.x + 5, bounds.y + 8}, 16, 2, BLACK);
int key = GetCharPressed();
bool hasNameChanged = false;
if (key > 0)
{
int len = strlen(text);
if (len < MAX_VARIABLE_NAME_SIZE - 1 && key >= 32 && key <= 125)
{
text[len] = (char)key;
text[len + 1] = '\0';
hasNameChanged = true;
cgEd->hasChangedInLastFrame = true;
}
}
static float backspaceTimer = 0;
static bool backspaceHeld = false;
if (IsKeyDown(KEY_BACKSPACE))
{
if (!backspaceHeld)
{
int len = strlen(text);
if (len > 0)
{
text[len - 1] = '\0';
hasNameChanged = true;
cgEd->hasChangedInLastFrame = true;
}
backspaceTimer = 0.5f;
backspaceHeld = true;
}
else
{
backspaceTimer -= GetFrameTime();
if (backspaceTimer <= 0)
{
int len = strlen(text);
if (len > 0)
{
text[len - 1] = '\0';
hasNameChanged = true;
cgEd->hasChangedInLastFrame = true;
}
backspaceTimer = 0.05f;
}
}
}
else
{
backspaceHeld = false;
}
if (hasNameChanged)
{
for (int i = 0; i < graph->variablesCount; i++)
{
free(graph->variables[i]);
}
free(graph->variables);
free(graph->variableTypes);
int newCount = 1;
for (int i = 0; i < graph->nodeCount; i++)
{
if (graph->nodes[i].type == NODE_CREATE_NUMBER ||
graph->nodes[i].type == NODE_CREATE_STRING ||
graph->nodes[i].type == NODE_CREATE_BOOL ||
graph->nodes[i].type == NODE_CREATE_COLOR ||
graph->nodes[i].type == NODE_CREATE_SPRITE)
{
newCount++;
}
}
graph->variables = malloc(sizeof(char *) * newCount);
graph->variableTypes = malloc(sizeof(NodeType) * newCount);
graph->variables[0] = strmac(NULL, 5, "NONE");
graph->variableTypes[0] = NODE_UNKNOWN;
int idx = 1;
for (int i = 0; i < graph->nodeCount; i++)
{
if (graph->nodes[i].type == NODE_CREATE_NUMBER || graph->nodes[i].type == NODE_CREATE_STRING || graph->nodes[i].type == NODE_CREATE_BOOL || graph->nodes[i].type == NODE_CREATE_COLOR || graph->nodes[i].type == NODE_CREATE_SPRITE)
{
graph->variables[idx] = strmac(NULL, MAX_VARIABLE_NAME_SIZE, "%s", graph->nodes[i].name);
graph->variableTypes[idx] = graph->nodes[i].type;
idx++;
}
}
graph->variablesCount = newCount;
}
}
void HandleLiteralNodeField(CGEditorContext *cgEd, GraphContext *graph, int currPinIndex)
{
PinType type = graph->pins[currPinIndex].type;
int limit = 0;
switch (type)
{
case PIN_FIELD_NUM:
limit = 90;
break;
case PIN_FIELD_STRING:
limit = 150;
break;
case PIN_FIELD_BOOL:
limit = 100;
break;
case PIN_FIELD_COLOR:
limit = 120;
break;
default:
break;
}
Rectangle textbox = {
graph->pins[currPinIndex].position.x - 6,
graph->pins[currPinIndex].position.y - 10,
limit,
24};
float textWidth = MeasureTextEx(cgEd->font, graph->pins[currPinIndex].textFieldValue, 20, 0).x;
if (cgEd->focusedFieldPin == currPinIndex)
{
textWidth += MeasureTextEx(cgEd->font, "_", 20, 0).x;
}
float boxWidth = (textWidth + 10 > limit) ? limit : textWidth + 10;
if (boxWidth < 25)
{
boxWidth = 25;
}
textbox.width = boxWidth;
bool isFieldHovered = CheckCollisionPointRec(cgEd->mousePos, textbox);
if (isFieldHovered && type != PIN_FIELD_BOOL)
{
cgEd->cursor = MOUSE_CURSOR_IBEAM;
}
if (isFieldHovered && type == PIN_FIELD_STRING && IsMouseButtonReleased(MOUSE_LEFT_BUTTON))
{
if (cgEd->hasDroppedFile)
{
cgEd->hasDroppedFile = false;
cgEd->hasChangedInLastFrame = true;
if (cgEd->droppedFilePath[0] != '\0')
{
strmac(graph->pins[currPinIndex].textFieldValue, MAX_LITERAL_NODE_FIELD_SIZE, "%s", cgEd->droppedFilePath);
}
}
}
if (cgEd->isLMBPressed)
{
if (isFieldHovered)
{
if (type == PIN_FIELD_BOOL)
{
cgEd->hasChangedInLastFrame = true;
if (strcmp(graph->pins[currPinIndex].textFieldValue, "false") == 0 || graph->pins[currPinIndex].textFieldValue[0] == '\0')
{
strmac(graph->pins[currPinIndex].textFieldValue, 5, "true");
}
else
{
strmac(graph->pins[currPinIndex].textFieldValue, 6, "false");
}
}
else
{
cgEd->focusedFieldPin = currPinIndex;
}
}
else if (cgEd->focusedFieldPin == currPinIndex)
{
switch (type)
{
case PIN_FIELD_NUM:
if (graph->pins[currPinIndex].textFieldValue[0] == '\0')
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 2, "0");
}
break;
case PIN_FIELD_STRING:
if (graph->pins[currPinIndex].textFieldValue[0] == '\0')
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 1, "");
}
break;
case PIN_FIELD_COLOR:
if (strlen(graph->pins[currPinIndex].textFieldValue) != 8)
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 9, "00000000");
}
break;
default:
break;
}
cgEd->focusedFieldPin = -1;
}
}
Color textboxColor = (cgEd->focusedFieldPin == currPinIndex) ? LIGHTGRAY : (isFieldHovered ? WHITE : GRAY);
DrawRectangleRec(textbox, textboxColor);
DrawRectangleLinesEx(textbox, 1, WHITE);
const char *originalText = graph->pins[currPinIndex].textFieldValue;
const char *text = originalText;
if (boxWidth == limit)
{
text = AddEllipsis(cgEd->font, originalText, 20, limit - 10, cgEd->focusedFieldPin == currPinIndex);
}
if (cgEd->focusedFieldPin == currPinIndex)
{
static float blinkTime = 0;
blinkTime += GetFrameTime();
char blinking[MAX_LITERAL_NODE_FIELD_SIZE];
strmac(blinking, MAX_LITERAL_NODE_FIELD_SIZE, "%s%c", text, (fmodf(blinkTime, 1.0f) < 0.5f) ? '_' : '\0');
DrawTextEx(cgEd->font, cgEd->focusedFieldPin == currPinIndex ? blinking : text, (Vector2){textbox.x + 5, textbox.y + 4}, 20, 0, BLACK);
}
else
{
DrawTextEx(cgEd->font, text, (Vector2){textbox.x + 5, textbox.y + 4}, 20, 0, BLACK);
}
if (cgEd->focusedFieldPin == currPinIndex)
{
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V))
{
const char *clipboard = GetClipboardText();
if (clipboard)
{
bool validClipboard = true;
for (int i = 0; i < strlen(clipboard); i++)
{
bool validSymbol =
(type == PIN_FIELD_NUM && clipboard[i] >= '0' && clipboard[i] <= '9') ||
(type == PIN_FIELD_STRING && clipboard[i] >= 32 && clipboard[i] <= 126) ||
(type == PIN_FIELD_COLOR &&
((clipboard[i] >= '0' && clipboard[i] <= '9') || (clipboard[i] >= 'a' && clipboard[i] <= 'f') || (clipboard[i] >= 'A' && clipboard[i] <= 'F')));
if (!validSymbol)
{
validClipboard = false;
break;
}
}
if (validClipboard)
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, MAX_VARIABLE_NAME_SIZE, "%s%s", graph->pins[currPinIndex].textFieldValue, clipboard);
}
}
}
int key = GetCharPressed();
while (key > 0)
{
int len = strlen(graph->pins[currPinIndex].textFieldValue);
bool validKey =
(type == PIN_FIELD_NUM && key >= '0' && key <= '9') ||
(type == PIN_FIELD_STRING && key >= 32 && key <= 126) ||
(type == PIN_FIELD_COLOR &&
((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f') || (key >= 'A' && key <= 'F')));
if (validKey)
{
cgEd->hasChangedInLastFrame = true;
if (type == PIN_FIELD_COLOR && len >= 8)
{
key = GetCharPressed();
continue;
}
graph->pins[currPinIndex].textFieldValue[len] = (char)key;
graph->pins[currPinIndex].textFieldValue[len + 1] = '\0';
}
else if (type == PIN_FIELD_NUM)
{
if (key == KEY_PERIOD && !graph->pins[currPinIndex].isNumFloat)
{
cgEd->hasChangedInLastFrame = true;
graph->pins[currPinIndex].textFieldValue[len] = (char)key;
graph->pins[currPinIndex].textFieldValue[len + 1] = '\0';
graph->pins[currPinIndex].isNumFloat = true;
}
if (key == KEY_MINUS && len == 0)
{
graph->pins[currPinIndex].textFieldValue[len] = (char)key;
}
}
key = GetCharPressed();
}
static float backspaceTimer = 0;
static bool backspaceHeld = false;
if (IsKeyDown(KEY_BACKSPACE))
{
cgEd->hasChangedInLastFrame = true;
if (!backspaceHeld)
{
size_t len = strlen(graph->pins[currPinIndex].textFieldValue);
if (len > 0)
{
if (type == PIN_FIELD_NUM &&
graph->pins[currPinIndex].textFieldValue[len - 1] == 46)
{
graph->pins[currPinIndex].isNumFloat = false;
}
graph->pins[currPinIndex].textFieldValue[len - 1] = '\0';
}
backspaceTimer = 0.5f;
backspaceHeld = true;
}
else
{
backspaceTimer -= GetFrameTime();
if (backspaceTimer <= 0)
{
size_t len = strlen(graph->pins[currPinIndex].textFieldValue);
if (len > 0)
{
if (type == PIN_FIELD_NUM &&
graph->pins[currPinIndex].textFieldValue[len - 1] == 46)
{
graph->pins[currPinIndex].isNumFloat = false;
}
graph->pins[currPinIndex].textFieldValue[len - 1] = '\0';
}
backspaceTimer = 0.05f;
}
}
}
else
{
backspaceHeld = false;
}
if (IsKeyPressed(KEY_ENTER))
{
switch (type)
{
case PIN_FIELD_NUM:
if (graph->pins[currPinIndex].textFieldValue[0] == '\0')
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 2, "0");
}
break;
case PIN_FIELD_STRING:
if (graph->pins[currPinIndex].textFieldValue[0] == '\0')
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 1, "");
}
break;
case PIN_FIELD_COLOR:
if (strlen(graph->pins[currPinIndex].textFieldValue) != 8)
{
cgEd->hasChangedInLastFrame = true;
strmac(graph->pins[currPinIndex].textFieldValue, 9, "00000000");
}
break;
default:
break;
}
cgEd->focusedFieldPin = -1;
}
}
}
void HandleKeyNodeField(CGEditorContext *cgEd, GraphContext *graph, int currPinIndex)
{
Rectangle textbox = {
graph->pins[currPinIndex].position.x - 6,
graph->pins[currPinIndex].position.y - 10,
cgEd->focusedFieldPin == currPinIndex ? 110 : MeasureTextEx(cgEd->font, GetKeyboardKeyName(graph->pins[currPinIndex].pickedOption), 20, 0).x + 10,
24};
if (cgEd->isLMBPressed)
{
if (CheckCollisionPointRec(cgEd->mousePos, textbox))
{
cgEd->focusedFieldPin = currPinIndex;
}
else if (cgEd->focusedFieldPin == currPinIndex)
{
cgEd->focusedFieldPin = -1;
}
}
DrawRectangleRec(textbox, (cgEd->focusedFieldPin == currPinIndex) ? LIGHTGRAY : GRAY);
DrawRectangleLinesEx(textbox, 1, WHITE);
if (cgEd->focusedFieldPin == currPinIndex)
{
static float blinkTime = 0;
blinkTime += GetFrameTime();
if (fmodf(blinkTime, 1.0f) < 0.6f)
{
char blinking[MAX_KEY_NAME_SIZE];
strmac(blinking, MAX_KEY_NAME_SIZE, "Press a key");
DrawTextEx(cgEd->font, blinking, (Vector2){textbox.x + 5, textbox.y + 4}, 20, 0, BLACK);
}
}
else
{
DrawTextEx(cgEd->font, GetKeyboardKeyName(graph->pins[currPinIndex].pickedOption), (Vector2){textbox.x + 5, textbox.y + 4}, 20, 0, BLACK);
}
if (cgEd->focusedFieldPin == currPinIndex)
{
for (int key = 0; key <= KEY_KB_MENU; key++)
{
if (IsKeyPressed(key))
{
cgEd->hasChangedInLastFrame = true;
graph->pins[currPinIndex].pickedOption = key;
cgEd->focusedFieldPin = -1;
break;
}
}
}
}
void HandleDropdownMenu(GraphContext *graph, int currPinIndex, int hoveredNodeIndex, int currNodeIndex, CGEditorContext *cgEd)
{
static bool menuJustOpened = false;
DropdownOptionsByPinType options;
if (graph->pins[currPinIndex].type == PIN_VARIABLE || graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE)
{
options.boxWidth = 100;
options.optionsCount = graph->variablesCount;
options.options = graph->variables;
if (options.optionsCount == 0)
{
static char *noVars[] = {"No variables"};
options.options = noVars;
options.optionsCount = 1;
}
}
else
{
options = getPinDropdownOptionsByType(graph->pins[currPinIndex].type);
}
if (graph->pins[currPinIndex].pickedOption >= options.optionsCount)
{
graph->pins[currPinIndex].pickedOption = 0;
}
Rectangle dropdown = {graph->pins[currPinIndex].position.x - 6, graph->pins[currPinIndex].position.y - 10, options.boxWidth, 24};
DrawRectangleRec(dropdown, GRAY);
const char *text = AddEllipsis(cgEd->font, options.options[graph->pins[currPinIndex].pickedOption], 20, options.boxWidth - 15, false);
DrawTextEx(cgEd->font, text, (Vector2){(graph->pins[currPinIndex].type == PIN_VARIABLE || graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE) ? dropdown.x + 20 : dropdown.x + 3, dropdown.y + 3}, 20, 0, BLACK);
DrawRectangleLinesEx(dropdown, 1, WHITE);
if (graph->pins[currPinIndex].type == PIN_VARIABLE || graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE)
{
Color varTypeColor;
PinType varType = PIN_UNKNOWN_VALUE;
switch (graph->variableTypes[graph->pins[currPinIndex].pickedOption])
{
case NODE_CREATE_NUMBER:
varTypeColor = COLOR_VAR_NUMBER;
varType = PIN_NUM;
break;
case NODE_CREATE_STRING:
varTypeColor = COLOR_VAR_STRING;
varType = PIN_STRING;
break;
case NODE_CREATE_BOOL:
varTypeColor = COLOR_VAR_BOOL;
varType = PIN_BOOL;
break;
case NODE_CREATE_COLOR:
varTypeColor = COLOR_VAR_COLOR;
varType = PIN_COLOR;
break;
case NODE_CREATE_SPRITE:
varTypeColor = COLOR_VAR_SPRITE;
varType = PIN_SPRITE;
break;
default:
varTypeColor = LIGHTGRAY;
}
if (graph->nodes[currNodeIndex].type == NODE_GET_VARIABLE)
{
graph->pins[FindPinIndexByID(graph, graph->nodes[currNodeIndex].outputPins[0])].type = varType;
}
else if (graph->nodes[currNodeIndex].type == NODE_SET_VARIABLE)
{
graph->pins[FindPinIndexByID(graph, graph->nodes[currNodeIndex].inputPins[2])].type = varType;
}
DrawCircle(graph->pins[currPinIndex].position.x + 4, graph->pins[currPinIndex].position.y, 6, varTypeColor);
}
bool mouseOnDropdown = CheckCollisionPointRec(cgEd->mousePos, dropdown);
bool mouseOnOptions = false;
if (cgEd->focusedDropdownPin == currPinIndex)
{
for (int j = 0; j < options.optionsCount; j++)
{
Rectangle option = {dropdown.x, dropdown.y - (j + 1) * 30, dropdown.width, 30};
if (CheckCollisionPointRec(cgEd->mousePos, option))
{
mouseOnOptions = true;
break;
}
}
}
if (cgEd->isLMBPressed)
{
if (cgEd->focusedDropdownPin == currPinIndex && menuJustOpened)
{
menuJustOpened = false;
}
else if (cgEd->focusedDropdownPin == currPinIndex && !menuJustOpened)
{
if (mouseOnDropdown)
{
cgEd->focusedDropdownPin = -1;
}
else if (!mouseOnOptions)
{
cgEd->focusedDropdownPin = -1;
}
}
else if (mouseOnDropdown)
{
cgEd->focusedDropdownPin = currPinIndex;
menuJustOpened = true;
}
}
if (cgEd->focusedDropdownPin == currPinIndex)
{
cgEd->delayFrames = true;
cgEd->hoveredNodeIndex = -1;
int displayedVarsCounter = 0;
for (int j = 0; j < options.optionsCount; j++)
{
displayedVarsCounter++;
if ((graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE && graph->variableTypes[j] != NODE_CREATE_SPRITE) && j != 0)
{
displayedVarsCounter--;
continue;
}
Rectangle option = {dropdown.x, dropdown.y - displayedVarsCounter * 30, dropdown.width, 30};
DrawRectangleRec(option, RAYWHITE);
const char *text = AddEllipsis(cgEd->font, options.options[j], 20, options.boxWidth - 15, false);
DrawTextEx(cgEd->font, text, (Vector2){(graph->pins[currPinIndex].type == PIN_VARIABLE || graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE) ? option.x + 20 : option.x + 3, option.y + 3}, 20, 0, BLACK);
DrawRectangleLinesEx(option, 1, DARKGRAY);
if (graph->pins[currPinIndex].type == PIN_VARIABLE || graph->pins[currPinIndex].type == PIN_SPRITE_VARIABLE)
{
Color varTypeColor;
switch (graph->variableTypes[j])
{
case NODE_CREATE_NUMBER:
varTypeColor = COLOR_VAR_NUMBER;
break;
case NODE_CREATE_STRING:
varTypeColor = COLOR_VAR_STRING;
break;
case NODE_CREATE_BOOL:
varTypeColor = COLOR_VAR_BOOL;
break;
case NODE_CREATE_COLOR:
varTypeColor = COLOR_VAR_COLOR;
break;
case NODE_CREATE_SPRITE:
varTypeColor = COLOR_VAR_SPRITE;
break;
default:
varTypeColor = LIGHTGRAY;
}
DrawCircle(option.x + 10, option.y + 12, 6, varTypeColor);
}
if (CheckCollisionPointRec(cgEd->mousePos, option) && cgEd->isLMBPressed)
{
graph->pins[currPinIndex].pickedOption = j;
cgEd->focusedDropdownPin = -1;
cgEd->hasChangedInLastFrame = true;
}
}
}
}
void DrawNodes(CGEditorContext *cgEd, GraphContext *graph)
{
if (graph->nodeCount == 0)
{
return;
}
for (int i = 0; i < graph->linkCount; i++)
{
Vector2 inputPinPosition;
Vector2 outputPinPosition;
bool isInputPosSet = false;
bool isOutputPosSet = false;
bool isInputFlow = false;
bool isOutputFlow = false;
PinType linkType;
for (int j = 0; j < graph->pinCount; j++)
{
if (graph->links[i].inputPinID == graph->pins[j].id)
{
inputPinPosition = graph->pins[j].position;
isInputPosSet = true;
isInputFlow = (graph->pins[j].type == PIN_FLOW);
if (graph->pins[j].type != PIN_ANY_VALUE)
{
linkType = graph->pins[j].type;
}
}
else if (graph->links[i].outputPinID == graph->pins[j].id)
{
outputPinPosition = graph->pins[j].position;
isOutputPosSet = true;
isOutputFlow = (graph->pins[j].type == PIN_FLOW);
if (graph->pins[j].type != PIN_ANY_VALUE)
{
linkType = graph->pins[j].type;
}
}
}
bool isFlowLink = isInputFlow && isOutputFlow;
if (isInputPosSet && isOutputPosSet)
{
Color wireColor;
switch (linkType)
{
case PIN_FLOW:
wireColor = COLOR_CGED_WIRE_FLOW;
break;
case PIN_NUM:
wireColor = COLOR_CGED_WIRE_NUM;
break;
case PIN_STRING:
wireColor = COLOR_CGED_WIRE_STRING;
break;
case PIN_BOOL:
wireColor = COLOR_CGED_WIRE_BOOL;
break;
case PIN_COLOR:
wireColor = COLOR_CGED_WIRE_COLOR;
break;
case PIN_SPRITE:
wireColor = COLOR_CGED_WIRE_SPRITE;
break;
default:
wireColor = COLOR_CGED_WIRE_UNKNOWN;
}
DrawCurvedWire(outputPinPosition, inputPinPosition, 2.0f + 1.0f / cgEd->zoom + isFlowLink, wireColor, cgEd->isLowSpecModeOn);
}
else
{
AddToLogFromCGEditor(cgEd, "Error drawing connection{C110}", LOG_LEVEL_WARNING);
}
}
static Rectangle textBoxRect = {0};
bool isAnyMenuOpen = cgEd->isNodeCreateMenuOpen || cgEd->isNodeOptionsMenuOpen;
cgEd->hoveredNodeIndex = -1;
for (int i = 0; i < graph->nodeCount; i++)
{
float x = graph->nodes[i].position.x;
float y = graph->nodes[i].position.y;
float width = getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH);
float height = getNodeInfoByType(graph->nodes[i].type, INFO_NODE_HEIGHT);
float roundness = 0.2f;
float segments = 8;
int glareOffset = 0;
if (!CheckCollisionRecs(cgEd->viewportBoundary, (Rectangle){x, y, width, height}))
{
continue;
}
if (!isAnyMenuOpen && CheckCollisionPointRec(cgEd->mousePos, (Rectangle){graph->nodes[i].position.x, graph->nodes[i].position.y, getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH), getNodeInfoByType(graph->nodes[i].type, INFO_NODE_HEIGHT)}))
{
if (!cgEd->isDraggingSelectedNodes)
{
cgEd->cursor = MOUSE_CURSOR_POINTING_HAND;
}
cgEd->hoveredNodeIndex = i;
cgEd->nodeGlareTime += GetFrameTime();
glareOffset = (int)(sinf(cgEd->nodeGlareTime * 6.0f) * 30);
}
Color nodeColor = getNodeColorByType(graph->nodes[i].type);
Color nodeLeftGradientColor = {
(unsigned char)Clamp((int)nodeColor.r + 40 + glareOffset, 0, 255),
(unsigned char)Clamp((int)nodeColor.g + 40 + glareOffset, 0, 255),
(unsigned char)Clamp((int)nodeColor.b + 40 + glareOffset, 0, 255),
nodeColor.a};
Color nodeRightGradientColor = {
(unsigned char)Clamp((int)nodeColor.r - 60 + glareOffset, 0, 255),
(unsigned char)Clamp((int)nodeColor.g - 60 + glareOffset, 0, 255),
(unsigned char)Clamp((int)nodeColor.b - 60 + glareOffset, 0, 255),
nodeColor.a};
float fullRadius = roundness * fminf(width, height) / 2.0f;
Color nodeBackgroundColor = {
(unsigned char)Clamp((int)glareOffset + 5, 0, 255),
(unsigned char)Clamp((int)glareOffset + 5, 0, 255),
(unsigned char)Clamp((int)glareOffset + 5, 0, 255),
120};
DrawRectangleRounded((Rectangle){x, y, width, height}, roundness, segments, nodeBackgroundColor);
if (cgEd->isLowSpecModeOn)
{
DrawCircleSector((Vector2){x + fullRadius - 2, y + fullRadius - 2}, fullRadius, 180, 270, segments, nodeColor);
DrawCircleSector((Vector2){x + width - fullRadius + 2, y + fullRadius - 2}, fullRadius, 270, 360, segments, nodeColor);
DrawRectangle(x + fullRadius - 2, y - 2, width - 2 * fullRadius + 4, fullRadius, nodeColor);
DrawRectangle(x - 2, y + fullRadius - 2, width + 4, 38 - fullRadius, nodeColor);
}
else
{
DrawCircleSector((Vector2){x + fullRadius - 2, y + fullRadius - 2}, fullRadius, 180, 270, segments, nodeLeftGradientColor);
DrawCircleSector((Vector2){x + width - fullRadius + 2, y + fullRadius - 2}, fullRadius, 270, 360, segments, nodeRightGradientColor);
DrawRectangleGradientH(x + fullRadius - 2, y - 2, width - 2 * fullRadius + 4, fullRadius, nodeLeftGradientColor, nodeRightGradientColor);
DrawRectangleGradientH(x - 2, y + fullRadius - 2, width + 4, 38 - fullRadius, nodeLeftGradientColor, nodeRightGradientColor);
}
DrawRectangleRoundedLinesEx((Rectangle){x - 1, y - 1, width + 2, height + 2}, roundness, segments, 2.0f + 1.0f / round(cgEd->zoom), COLOR_CGED_NODE_BORDER);
DrawTextEx(cgEd->font, NodeTypeToString(graph->nodes[i].type), (Vector2){x + 8, y + 6}, 28, 1, WHITE);
if (getIsEditableByType(graph->nodes[i].type))
{
Rectangle gearRect = {graph->nodes[i].position.x + getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH) - 18 - fullRadius / 5, graph->nodes[i].position.y + 5 + fullRadius / 5, 16, 16};
Rectangle src = {0, 0, cgEd->gearTxt.width, cgEd->gearTxt.height};
Rectangle dst = {gearRect.x, gearRect.y, 15, 15};
Vector2 origin = {0, 0};
DrawTexturePro(cgEd->gearTxt, src, dst, origin, 0.0f, WHITE);
if (CheckCollisionPointRec(cgEd->mousePos, gearRect) && cgEd->isLMBPressed)
{
cgEd->editingNodeNameIndex = i;
textBoxRect = (Rectangle){graph->nodes[i].position.x + getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH) + 10, graph->nodes[i].position.y, MeasureTextEx(cgEd->font, graph->nodes[i].name, 16, 2).x + 25, 30};
}
else if (cgEd->editingNodeNameIndex == i)
{
HandleVarNameTextBox(cgEd, textBoxRect, graph->nodes[cgEd->editingNodeNameIndex].name, cgEd->editingNodeNameIndex, graph);
cgEd->delayFrames = true;
if (CheckCollisionPointRec(cgEd->mousePos, textBoxRect))
cgEd->isDraggingScreen = false;
if (IsKeyPressed(KEY_ENTER) || (cgEd->isLMBPressed && !CheckCollisionPointRec(cgEd->mousePos, textBoxRect)))
{
cgEd->editingNodeNameIndex = -1;
cgEd->engineDelayFrames = true;
}
}
}
}
cgEd->hoveredPinIndex = -1;
for (int i = 0; i < graph->pinCount; i++)
{
int currNodeIndex = -1;
for (int j = 0; j < graph->nodeCount; j++)
{
if (graph->nodes[j].id == graph->pins[i].nodeID)
{
currNodeIndex = j;
break;
}
}
if (currNodeIndex == -1)
{
TraceLog(LOG_WARNING, "Pin %d has no matching node (ID %d)", i, graph->pins[i].nodeID);
continue;
}
Vector2 nodePos = graph->nodes[currNodeIndex].position;
int xOffset = graph->pins[i].isInput ? 5 : (getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH) - 20);
int yOffset = 52 + graph->pins[i].posInNode * 30;
graph->pins[i].position = (Vector2){nodePos.x + xOffset + 5, nodePos.y + yOffset};
if (graph->pins[i].type == PIN_NONE)
{
continue;
}
else if (graph->pins[i].type == PIN_FLOW)
{
DrawTriangle((Vector2){nodePos.x + xOffset, nodePos.y + yOffset - 8}, (Vector2){nodePos.x + xOffset, nodePos.y + yOffset + 8}, (Vector2){nodePos.x + xOffset + 15, nodePos.y + yOffset}, WHITE);
if (!isAnyMenuOpen && CheckCollisionPointRec(cgEd->mousePos, (Rectangle){nodePos.x + xOffset - 5, nodePos.y + yOffset - 15, 25, 31}))
{
if (graph->pins[i].isInput)
{
DrawTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], (Vector2){(2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2, nodePos.y + yOffset - 8}, 18, 0, WHITE);
DrawLine(graph->pins[i].position.x, graph->pins[i].position.y, (2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2 - 5, graph->pins[i].position.y, WHITE);
}
else
{
DrawTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], (Vector2){(2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2 - 5, nodePos.y + yOffset - 8}, 18, 0, WHITE);
DrawLine(graph->pins[i].position.x, graph->pins[i].position.y, (2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 + MeasureTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2, graph->pins[i].position.y, WHITE);
}
DrawTriangle((Vector2){nodePos.x + xOffset - 2, nodePos.y + yOffset - 10}, (Vector2){nodePos.x + xOffset - 2, nodePos.y + yOffset + 10}, (Vector2){nodePos.x + xOffset + 17, nodePos.y + yOffset}, WHITE);
cgEd->hoveredPinIndex = i;
}
}
else if (graph->pins[i].type == PIN_DROPDOWN_COMPARISON_OPERATOR || graph->pins[i].type == PIN_DROPDOWN_GATE || graph->pins[i].type == PIN_DROPDOWN_ARITHMETIC || graph->pins[i].type == PIN_DROPDOWN_KEY_ACTION || graph->pins[i].type == PIN_DROPDOWN_LAYER || graph->pins[i].type == PIN_VARIABLE || graph->pins[i].type == PIN_SPRITE_VARIABLE)
{
if (cgEd->focusedDropdownPin != i)
{
HandleDropdownMenu(graph, i, cgEd->hoveredNodeIndex, currNodeIndex, cgEd);
}
}
else if (graph->pins[i].type == PIN_FIELD_NUM || graph->pins[i].type == PIN_FIELD_STRING || graph->pins[i].type == PIN_FIELD_BOOL || graph->pins[i].type == PIN_FIELD_COLOR)
{
HandleLiteralNodeField(cgEd, graph, i);
}
else if (graph->pins[i].type == PIN_FIELD_KEY)
{
HandleKeyNodeField(cgEd, graph, i);
}
else if (graph->pins[i].type == PIN_EDIT_HITBOX)
{
DrawRectangleRounded((Rectangle){graph->pins[i].position.x - 6, graph->pins[i].position.y - 10, 96, 24}, 0.4f, 4, DARKPURPLE);
if (CheckCollisionPointRec(cgEd->mousePos, (Rectangle){graph->pins[i].position.x - 6, graph->pins[i].position.y - 10, 96, 24}))
{
DrawRectangleRounded((Rectangle){graph->pins[i].position.x - 6, graph->pins[i].position.y - 10, 96, 24}, 0.4f, 4, COLOR_CGED_EDIT_HITBOX_BTN_HOVER);
if (cgEd->isLMBPressed)
{
for (int b = 0; b < graph->nodeCount; b++)
{
if (graph->nodes[b].id == graph->pins[i].nodeID)
{
for (int c = 0; c < graph->pinCount; c++)
{
if (graph->nodes[b].inputPins[1] == graph->pins[c].id)
{
for (int k = 0; k < graph->linkCount; k++)
{
if (graph->links[k].inputPinID == graph->pins[c].id)
{
for (int d = 0; d < graph->nodeCount; d++)
{
if (graph->nodes[d].outputPins[0] == graph->links[k].outputPinID)
{
if (graph->nodes[d].type == NODE_LITERAL_STRING)
{
for (int e = 0; e < graph->pinCount; e++)
{
if (graph->pins[e].id == graph->nodes[d].inputPins[0])
{
cgEd->shouldOpenHitboxEditor = true;
strmac(cgEd->hitboxEditorFileName, MAX_FILE_NAME, "%s", graph->pins[e].textFieldValue);
cgEd->hitboxEditingPinID = graph->pins[i].id;
return;
}
}
}
break;
}
}
break;
}
}
break;
}
}
break;
}
}
AddToLogFromCGEditor(cgEd, "Couldn't find sprite texture{H100}", LOG_LEVEL_WARNING);
}
}
DrawTextEx(cgEd->font, "Edit Hitbox", (Vector2){graph->pins[i].position.x - 2, graph->pins[i].position.y - 6}, 18, 0.2f, WHITE);
}
else
{
DrawCircle(nodePos.x + xOffset + 5, nodePos.y + yOffset, 5, WHITE);
if (!isAnyMenuOpen && CheckCollisionPointCircle(cgEd->mousePos, (Vector2){nodePos.x + xOffset + 5, nodePos.y + yOffset}, 12))
{
if (graph->pins[i].isInput)
{
DrawTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], (Vector2){(2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2, nodePos.y + yOffset - 8}, 18, 0, WHITE);
DrawLine(graph->pins[i].position.x, graph->pins[i].position.y, (2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeInputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2 - 5, graph->pins[i].position.y, WHITE);
}
else if (graph->nodes[currNodeIndex].type == NODE_COMPARISON || graph->nodes[currNodeIndex].type == NODE_GATE || graph->nodes[currNodeIndex].type == NODE_LITERAL_NUMBER || graph->nodes[currNodeIndex].type == NODE_LITERAL_STRING || graph->nodes[currNodeIndex].type == NODE_LITERAL_BOOL || graph->nodes[currNodeIndex].type == NODE_LITERAL_COLOR || graph->nodes[currNodeIndex].type == NODE_GET_SPRITE_POSITION)
{
const char *label = getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode];
Vector2 textSize = MeasureTextEx(cgEd->font, label, 18, 0);
DrawTextEx(cgEd->font, label, (Vector2){graph->pins[i].position.x - textSize.x - 12, graph->pins[i].position.y - textSize.y / 2}, 18, 0, WHITE);
DrawLine(graph->pins[i].position.x, graph->pins[i].position.y, graph->pins[i].position.x - 10, graph->pins[i].position.y, WHITE);
}
else
{
DrawTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], (Vector2){(2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 - MeasureTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2 - 5, nodePos.y + yOffset - 8}, 18, 0, WHITE);
DrawLine(graph->pins[i].position.x, graph->pins[i].position.y, (2 * nodePos.x + getNodeInfoByType(graph->nodes[currNodeIndex].type, INFO_NODE_WIDTH)) / 2 + MeasureTextEx(cgEd->font, getNodeOutputNamesByType(graph->nodes[currNodeIndex].type)[graph->pins[i].posInNode], 18, 0).x / 2, graph->pins[i].position.y, WHITE);
}
DrawCircle(nodePos.x + xOffset + 5, nodePos.y + yOffset, 7, WHITE);
cgEd->hoveredPinIndex = i;
}
}
}
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
Rectangle nodeRect = (Rectangle){graph->nodes[cgEd->selectedNodes[i]].position.x, graph->nodes[cgEd->selectedNodes[i]].position.y, getNodeInfoByType(graph->nodes[cgEd->selectedNodes[i]].type, INFO_NODE_WIDTH), getNodeInfoByType(graph->nodes[cgEd->selectedNodes[i]].type, INFO_NODE_HEIGHT)};
if (cgEd->focusedDropdownPin == -1 && cgEd->focusedFieldPin == -1)
{
DrawRectangleRounded(nodeRect, 0.2f, 8, COLOR_CGED_NODE_SELECTED);
}
DrawRectangleRoundedLinesEx(nodeRect, 0.2f, 8, 5.0f, WHITE);
}
if (cgEd->focusedDropdownPin != -1)
{
for (int j = 0; j < graph->nodeCount; j++)
{
if (graph->nodes[j].id == graph->pins[cgEd->focusedDropdownPin].nodeID)
{
HandleDropdownMenu(graph, cgEd->focusedDropdownPin, cgEd->hoveredNodeIndex, j, cgEd);
break;
}
}
}
if (cgEd->hoveredPinIndex != -1 && cgEd->isLMBPressed)
{
cgEd->isDraggingSelectedNodes = false;
if (cgEd->lastClickedPin.id == -1)
{
cgEd->lastClickedPin = graph->pins[cgEd->hoveredPinIndex];
}
else
{
CreateLink(graph, cgEd->lastClickedPin, graph->pins[cgEd->hoveredPinIndex]);
cgEd->lastClickedPin = INVALID_PIN;
cgEd->hasChangedInLastFrame = true;
}
}
else if (cgEd->hoveredPinIndex == -1 && (cgEd->isLMBPressed || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)))
{
cgEd->lastClickedPin = INVALID_PIN;
}
else if (cgEd->hoveredPinIndex != -1 && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))
{
RemoveConnections(graph, graph->pins[cgEd->hoveredPinIndex].id);
cgEd->isNodeCreateMenuOpen = false;
cgEd->hasChangedInLastFrame = true;
}
if (cgEd->hoveredPinIndex == -1 && cgEd->hoveredNodeIndex != -1)
{
DrawRectangleRoundedLinesEx((Rectangle){graph->nodes[cgEd->hoveredNodeIndex].position.x - 1, graph->nodes[cgEd->hoveredNodeIndex].position.y - 1, getNodeInfoByType(graph->nodes[cgEd->hoveredNodeIndex].type, INFO_NODE_WIDTH) + 2, getNodeInfoByType(graph->nodes[cgEd->hoveredNodeIndex].type, INFO_NODE_HEIGHT) + 2}, 0.2f, 8, 5.0f, WHITE);
cgEd->delayFrames = true;
}
if (cgEd->selectedNodesCount != 0 && IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C))
{
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
cgEd->copiedNodes[i] = graph->nodes[cgEd->selectedNodes[i]];
}
cgEd->copiedNodesCount = cgEd->selectedNodesCount;
SetClipboardText(cgEd->copiedNodesCount == 1 ? TextFormat("CoreGraph node of type %s", NodeTypeToString(cgEd->copiedNodes[0].type)) : "CoreGraph nodes");
}
if (cgEd->lastClickedPin.id != -1)
{
cgEd->cursor = MOUSE_CURSOR_CROSSHAIR;
Vector2 p1 = cgEd->lastClickedPin.isInput ? cgEd->mousePos : cgEd->lastClickedPin.position;
Vector2 p2 = cgEd->lastClickedPin.isInput ? cgEd->lastClickedPin.position : cgEd->mousePos;
DrawCurvedWire(p1, p2, 2.0f + 1.0f / cgEd->zoom, cgEd->lastClickedPin.type == PIN_FLOW ? COLOR_CGED_WIRE_FLOW : COLOR_CGED_WIRE_NEON_BLUE, cgEd->isLowSpecModeOn);
}
if (cgEd->hoveredNodeIndex == -1 && cgEd->hasDroppedFile)
{
cgEd->hasDroppedFile = false;
if (!CreateNode(graph, NODE_LITERAL_STRING, (Vector2){cgEd->mousePos.x - getNodeInfoByType(NODE_LITERAL_STRING, INFO_NODE_WIDTH) / 2, cgEd->mousePos.y - getNodeInfoByType(NODE_LITERAL_STRING, INFO_NODE_HEIGHT) / 2}))
{
cgEd->hasFatalErrorOccurred = true;
AddToLogFromCGEditor(cgEd, "Error creating node{C230}", LOG_LEVEL_ERROR);
return;
}
else
{
cgEd->hasChangedInLastFrame = true;
cgEd->delayFrames = true;
strmac(graph->pins[FindPinIndexByID(graph, graph->nodes[graph->nodeCount - 1].inputPins[0])].textFieldValue, MAX_LITERAL_NODE_FIELD_SIZE, "%s", cgEd->droppedFilePath);
}
}
}
bool CheckNodeCollisions(CGEditorContext *cgEd, GraphContext *graph)
{
for (int i = 0; i < graph->nodeCount; i++)
{
if (CheckCollisionPointRec(cgEd->mousePos, (Rectangle){graph->nodes[i].position.x, graph->nodes[i].position.y, getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH), getNodeInfoByType(graph->nodes[i].type, INFO_NODE_HEIGHT)}))
{
return true;
}
}
return false;
}
const char *Search(const char *haystack, const char *needle)
{
if (!*needle)
{
return haystack;
}
while (*haystack)
{
if (tolower((unsigned char)*haystack) == tolower((unsigned char)*needle))
{
const char *h = haystack + 1;
const char *n = needle + 1;
while (*n && tolower((unsigned char)*h) == tolower((unsigned char)*n))
{
h++;
n++;
}
if (!*n)
{
return haystack;
}
}
haystack++;
}
return NULL;
}
const char *DrawNodeMenu(CGEditorContext *cgEd, RenderTexture2D view)
{
if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON))
{
cgEd->createNodeMenuFirstFrame = true;
}
float searchBarHeight = 30.0f;
float menuHeight = MENU_ITEM_HEIGHT * MENU_VISIBLE_ITEMS + searchBarHeight + 10;
int len = strlen(cgEd->nodeMenuSearch);
int key = GetCharPressed();
while (key > 0 && len < MAX_SEARCH_BAR_FIELD_SIZE - 1)
{
if (key >= 32 && key <= 125)
{
cgEd->nodeMenuSearch[len] = (char)key;
cgEd->nodeMenuSearch[len + 1] = '\0';
len++;
}
key = GetCharPressed();
}
if (IsKeyPressed(KEY_BACKSPACE))
{
if (len > 0)
{
cgEd->nodeMenuSearch[len - 1] = '\0';
len--;
}
}
int filteredItemCategory[subMenuItemCount * menuItemCount];
int filteredItem[subMenuItemCount * menuItemCount];
int filteredCount = 0;
for (int i = 0; i < menuItemCount; i++)
{
for (int j = 0; j < subMenuCounts[i]; j++)
{
if (strlen(cgEd->nodeMenuSearch) == 0 || (subMenuItems[i][j][0] != '\0' && Search(subMenuItems[i][j], cgEd->nodeMenuSearch)))
{
if (filteredCount < subMenuItemCount * menuItemCount)
{
filteredItemCategory[filteredCount] = i;
filteredItem[filteredCount] = j;
filteredCount++;
}
}
}
}
if (cgEd->nodeMenuSearch[0] == '\0')
{
filteredCount = menuItemCount;
}
int wheelMove = GetMouseWheelMove();
if (wheelMove < 0 && cgEd->scrollIndexNodeMenu < filteredCount - MENU_VISIBLE_ITEMS)
{
cgEd->scrollIndexNodeMenu++;
}
else if (wheelMove > 0 && cgEd->scrollIndexNodeMenu > 0)
{
cgEd->scrollIndexNodeMenu--;
}
if (cgEd->createNodeMenuFirstFrame)
{
cgEd->menuPosition = cgEd->rightClickPos;
if (cgEd->menuPosition.y + menuHeight > cgEd->viewportBoundary.y + cgEd->viewportBoundary.height)
{
cgEd->menuPosition.y = cgEd->viewportBoundary.y + cgEd->viewportBoundary.height - menuHeight;
}
if (cgEd->menuPosition.x + MENU_WIDTH + SUBMENU_WIDTH > cgEd->viewportBoundary.x + cgEd->viewportBoundary.width)
{
cgEd->menuPosition.x = cgEd->viewportBoundary.x + cgEd->viewportBoundary.width - MENU_WIDTH - SUBMENU_WIDTH;
}
Rectangle menuRect = {cgEd->menuPosition.x, cgEd->menuPosition.y + searchBarHeight + 10, MENU_WIDTH, menuHeight - searchBarHeight - 10};
cgEd->submenuPosition.x = (cgEd->menuPosition.x + MENU_WIDTH + SUBMENU_WIDTH > cgEd->screenWidth)
? (cgEd->menuPosition.x - SUBMENU_WIDTH)
: (cgEd->menuPosition.x + MENU_WIDTH - 15);
cgEd->submenuPosition.y = cgEd->menuPosition.y + searchBarHeight + 7;
cgEd->hoveredItem = 0;
cgEd->createNodeMenuFirstFrame = false;
}
DrawRectangleRounded((Rectangle){cgEd->menuPosition.x, cgEd->menuPosition.y, MENU_WIDTH, menuHeight}, 0.1f, 8, GRAY_50);
DrawRectangleRoundedLinesEx((Rectangle){cgEd->menuPosition.x, cgEd->menuPosition.y, MENU_WIDTH, menuHeight}, 0.1f, 8, MENU_BORDER_THICKNESS, WHITE);
if (cgEd->nodeMenuSearch[0] != '\0')
{
for (int i = 0; i < (int)MENU_VISIBLE_ITEMS; i++)
{
int listIndex = i + cgEd->scrollIndexNodeMenu;
if (listIndex >= filteredCount)
break;
const char *pickedItemName = subMenuItems[filteredItemCategory[listIndex]][filteredItem[listIndex]];
Rectangle itemRect = {cgEd->menuPosition.x, cgEd->menuPosition.y + searchBarHeight + 10 + i * MENU_ITEM_HEIGHT, MENU_WIDTH, MENU_ITEM_HEIGHT};
if (CheckCollisionPointRec(cgEd->mousePos, itemRect))
{
cgEd->cursor = MOUSE_CURSOR_POINTING_HAND;
DrawRectangleRec(itemRect, GRAY_80);
if (cgEd->isLMBPressed)
{
cgEd->delayFrames = true;
cgEd->hasChangedInLastFrame = true;
cgEd->isNodeCreateMenuOpen = false;
cgEd->nodeMenuSearch[0] = '\0';
return pickedItemName;
}
}
DrawTextEx(cgEd->font, pickedItemName, (Vector2){itemRect.x + 20, itemRect.y + 12}, 25, 1, WHITE);
DrawLine(itemRect.x, itemRect.y + MENU_ITEM_HEIGHT - 1, itemRect.x + MENU_WIDTH, itemRect.y + MENU_ITEM_HEIGHT - 1, DARKGRAY);
}
}
else
{
for (int i = 0; i < (int)MENU_VISIBLE_ITEMS; i++)
{
int listIndex = i + cgEd->scrollIndexNodeMenu;
if (listIndex >= menuItemCount)
{
break;
}
Rectangle itemRect = {cgEd->menuPosition.x, cgEd->menuPosition.y + searchBarHeight + 10 + i * MENU_ITEM_HEIGHT, MENU_WIDTH, MENU_ITEM_HEIGHT};
if (CheckCollisionPointRec(cgEd->mousePos, itemRect))
{
cgEd->hoveredItem = listIndex;
cgEd->submenuPosition.x = (cgEd->menuPosition.x + MENU_WIDTH + SUBMENU_WIDTH > cgEd->screenWidth)
? (cgEd->menuPosition.x - SUBMENU_WIDTH)
: (cgEd->menuPosition.x + MENU_WIDTH - 15);
cgEd->submenuPosition.y = itemRect.y - 3;
}
if (listIndex == cgEd->hoveredItem)
DrawRectangleRec(itemRect, GRAY_80);
DrawTextEx(cgEd->font, menuItems[listIndex], (Vector2){itemRect.x + 20, itemRect.y + 12}, 25, 1, WHITE);
DrawLine(itemRect.x, itemRect.y + MENU_ITEM_HEIGHT - 1, itemRect.x + MENU_WIDTH, itemRect.y + MENU_ITEM_HEIGHT - 1, DARKGRAY);
}
}
int maxScroll = (filteredCount > 0 ? filteredCount : menuItemCount) - MENU_VISIBLE_ITEMS;
if (maxScroll < 1)
{
maxScroll = 1;
}
float scrollTrackHeight = MENU_ITEM_HEIGHT * MENU_VISIBLE_ITEMS - 16;
float scrollBarHeight = scrollTrackHeight * ((float)MENU_VISIBLE_ITEMS / ((filteredCount > 5) ? filteredCount : 1));
if (scrollBarHeight < 20.0f)
{
scrollBarHeight = 20.0f;
}
if (scrollBarHeight > scrollTrackHeight)
{
scrollBarHeight = scrollTrackHeight;
}
DrawRectangleRounded((Rectangle){cgEd->menuPosition.x + 2, cgEd->menuPosition.y + searchBarHeight + cgEd->scrollIndexNodeMenu * ((scrollTrackHeight - scrollBarHeight) / (maxScroll + 1)) + 16, 8, scrollBarHeight}, 0.8f, 4, GRAY_150);
if (cgEd->nodeMenuSearch[0] == '\0' && cgEd->hoveredItem >= 0 && cgEd->hoveredItem < menuItemCount)
{
DrawRectangleRounded((Rectangle){cgEd->submenuPosition.x, cgEd->submenuPosition.y, SUBMENU_WIDTH, subMenuCounts[cgEd->hoveredItem] * MENU_ITEM_HEIGHT}, 0.1f, 2, GRAY_50);
DrawRectangleRoundedLinesEx((Rectangle){cgEd->submenuPosition.x, cgEd->submenuPosition.y, SUBMENU_WIDTH, subMenuCounts[cgEd->hoveredItem] * MENU_ITEM_HEIGHT}, 0.1f, 2, MENU_BORDER_THICKNESS, WHITE);
DrawRectangleGradientH(cgEd->submenuPosition.x - 5, cgEd->submenuPosition.y + 3, 20, MENU_ITEM_HEIGHT, GRAY_80, GRAY_50);
for (int j = 0; j < subMenuCounts[cgEd->hoveredItem]; j++)
{
Rectangle subItemRect = {cgEd->submenuPosition.x, cgEd->submenuPosition.y + j * MENU_ITEM_HEIGHT, SUBMENU_WIDTH, MENU_ITEM_HEIGHT};
if (CheckCollisionPointRec(cgEd->mousePos, subItemRect))
{
cgEd->cursor = MOUSE_CURSOR_POINTING_HAND;
DrawRectangleRounded(subItemRect, 0.2f, 2, GRAY_80);
if (cgEd->isLMBPressed)
{
cgEd->delayFrames = true;
cgEd->hasChangedInLastFrame = true;
cgEd->isNodeCreateMenuOpen = false;
return subMenuItems[cgEd->hoveredItem][j];
}
}
DrawTextEx(cgEd->font, subMenuItems[cgEd->hoveredItem][j], (Vector2){cgEd->submenuPosition.x + 20, cgEd->submenuPosition.y + j * MENU_ITEM_HEIGHT + 10}, 25, 0, WHITE);
}
}
if (cgEd->isLMBPressed)
{
cgEd->nodeMenuSearch[0] = '\0';
cgEd->isNodeCreateMenuOpen = false;
}
DrawRectangle(cgEd->menuPosition.x + 5, cgEd->menuPosition.y + 5, MENU_WIDTH - 10, searchBarHeight, DARKGRAY);
DrawTextEx(cgEd->font, cgEd->nodeMenuSearch[0] == '\0' ? "Type to search" : cgEd->nodeMenuSearch, (Vector2){cgEd->menuPosition.x + 10, cgEd->menuPosition.y + 10}, 20, 0, WHITE);
return NULL;
}
void HandleDragging(CGEditorContext *cgEd, GraphContext *graph)
{
if (cgEd->isLMBPressed && !cgEd->isDraggingSelectedNodes && cgEd->focusedFieldPin == -1 && cgEd->focusedDropdownPin == -1)
{
for (int i = 0; i < graph->nodeCount; i++)
{
if (CheckCollisionPointRec(cgEd->mousePos, (Rectangle){graph->nodes[i].position.x, graph->nodes[i].position.y, getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH), getNodeInfoByType(graph->nodes[i].type, INFO_NODE_HEIGHT)}))
{
cgEd->isDraggingSelectedNodes = true;
bool nodeAlreadySelected = false;
for (int j = 0; j < cgEd->selectedNodesCount; j++)
{
if (cgEd->selectedNodes[j] == i)
{
if (IsKeyDown(KEY_LEFT_CONTROL))
{
for (int k = j; k < cgEd->selectedNodesCount - 1; k++)
{
cgEd->selectedNodes[k] = cgEd->selectedNodes[k + 1];
}
cgEd->selectedNodesCount--;
}
nodeAlreadySelected = true;
break;
}
}
if (!nodeAlreadySelected)
{
if (!IsKeyDown(KEY_LEFT_CONTROL))
{
cgEd->selectedNodesCount = 0;
}
cgEd->selectedNodes[cgEd->selectedNodesCount] = i;
cgEd->selectedNodesCount++;
}
return;
}
}
if (!cgEd->isNodeOptionsMenuOpen)
{
cgEd->selectedNodesCount = 0;
cgEd->isDraggingScreen = true;
}
return;
}
else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && cgEd->isDraggingSelectedNodes && cgEd->focusedFieldPin == -1)
{
cgEd->cursor = MOUSE_CURSOR_RESIZE_ALL;
Vector2 delta = GetMouseDelta();
if (delta.x != 0 || delta.y != 0)
{
cgEd->hasChangedInLastFrame = true;
}
if (cgEd->selectedNodesCount != 0)
{
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
graph->nodes[cgEd->selectedNodes[i]].position.x += delta.x / cgEd->zoom;
graph->nodes[cgEd->selectedNodes[i]].position.y += delta.y / cgEd->zoom;
}
}
}
else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && cgEd->isDraggingScreen)
{
cgEd->cursor = MOUSE_CURSOR_RESIZE_ALL;
Vector2 delta = Vector2Scale(GetMouseDelta(), 1.0f / cgEd->zoom);
for (int i = 0; i < graph->nodeCount; i++)
{
graph->nodes[i].position.x += delta.x;
graph->nodes[i].position.y += delta.y;
}
}
else if (IsMouseButtonUp(MOUSE_LEFT_BUTTON))
{
cgEd->isDraggingSelectedNodes = false;
cgEd->isDraggingScreen = false;
}
}
void SetCGEditorFPS(CGEditorContext *cgEd)
{
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) || IsMouseButtonDown(MOUSE_RIGHT_BUTTON))
{
cgEd->fps = FPS_HIGH;
}
else
{
cgEd->fps = FPS_DEFAULT;
}
}
void DrawFullTexture(CGEditorContext *cgEd, GraphContext *graph, RenderTexture2D view, RenderTexture2D dot, bool draggingDisabled)
{
BeginTextureMode(view);
ClearBackground(COLOR_CGED_BACKGROUND);
if (!draggingDisabled)
{
HandleDragging(cgEd, graph);
}
DrawBackgroundGrid(cgEd, 40, dot);
DrawNodes(cgEd, graph);
if (cgEd->isSelecting)
{
Rectangle selectorRect = (Rectangle){fminf(cgEd->rightClickPos.x, cgEd->mousePos.x), fminf(cgEd->rightClickPos.y, cgEd->mousePos.y), fabsf(cgEd->mousePos.x - cgEd->rightClickPos.x), fabsf(cgEd->mousePos.y - cgEd->rightClickPos.y)};
DrawRectangleRec(selectorRect, COLOR_CGED_SELECTOR);
DrawRectangleLinesEx(selectorRect, 2.0f, COLOR_CGED_SELECTOR_OUTLINE);
cgEd->selectedNodesCount = 0;
for (int i = 0; i < graph->nodeCount; i++)
{
Rectangle nodeRect = (Rectangle){graph->nodes[i].position.x, graph->nodes[i].position.y, getNodeInfoByType(graph->nodes[i].type, INFO_NODE_WIDTH), getNodeInfoByType(graph->nodes[i].type, INFO_NODE_HEIGHT)};
if (CheckCollisionRecs(nodeRect, selectorRect))
{
cgEd->selectedNodes[cgEd->selectedNodesCount] = i;
cgEd->selectedNodesCount++;
}
}
}
if (cgEd->isNodeCreateMenuOpen)
{
const char *createdNode = DrawNodeMenu(cgEd, view);
if (createdNode != NULL)
{
NodeType newNodeType = StringToNodeType(createdNode);
if (!CreateNode(graph, newNodeType, cgEd->rightClickPos))
{
cgEd->hasFatalErrorOccurred = true;
AddToLogFromCGEditor(cgEd, "Error creating node{C230}", LOG_LEVEL_ERROR);
return;
}
else if (newNodeType == NODE_CREATE_NUMBER || newNodeType == NODE_CREATE_STRING || newNodeType == NODE_CREATE_BOOL || newNodeType == NODE_CREATE_COLOR || newNodeType == NODE_CREATE_SPRITE)
{
graph->variables = realloc(graph->variables, sizeof(char *) * (graph->variablesCount + 1));
graph->variables[graph->variablesCount] = strmac(NULL, MAX_VARIABLE_NAME_SIZE, "%s", graph->nodes[graph->nodeCount - 1].name);
graph->variableTypes = realloc(graph->variableTypes, sizeof(int) * (graph->variablesCount + 1));
graph->variableTypes[graph->variablesCount] = graph->nodes[graph->nodeCount - 1].type;
graph->variablesCount++;
}
cgEd->rightClickPos = (Vector2){0, 0};
}
}
else if (cgEd->isNodeOptionsMenuOpen)
{
int boxWidth = 80 + (cgEd->selectedNodesCount != 1) * 60;
DrawRectangleRounded((Rectangle){cgEd->rightClickPos.x - 5, cgEd->rightClickPos.y - 65, boxWidth + 10, 70}, 0.2f, 4, DARKGRAY);
DrawRectangle(cgEd->rightClickPos.x, cgEd->rightClickPos.y - 60, boxWidth, 30, DARKGRAY);
DrawRectangle(cgEd->rightClickPos.x, cgEd->rightClickPos.y - 30, boxWidth, 30, DARKGRAY);
DrawTextEx(cgEd->font, cgEd->selectedNodesCount == 1 ? "Copy" : TextFormat("Copy All(%d)", cgEd->selectedNodesCount), (Vector2){cgEd->rightClickPos.x + 5, cgEd->rightClickPos.y - 55}, 20, 1, WHITE);
DrawTextEx(cgEd->font, cgEd->selectedNodesCount == 1 ? "Delete" : TextFormat("Delete All(%d)", cgEd->selectedNodesCount), (Vector2){cgEd->rightClickPos.x + 5, cgEd->rightClickPos.y - 25}, 20, 1, WHITE);
if (CheckCollisionPointRec(cgEd->mousePos, (Rectangle){cgEd->rightClickPos.x, cgEd->rightClickPos.y - 60, boxWidth, 30}))
{
cgEd->cursor = MOUSE_CURSOR_POINTING_HAND;
DrawRectangle(cgEd->rightClickPos.x, cgEd->rightClickPos.y - 60, boxWidth, 30, COLOR_CGED_NODE_OPTIONS_MENU_HOVER);
if (cgEd->isLMBPressed)
{
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
cgEd->copiedNodes[i] = graph->nodes[cgEd->selectedNodes[i]];
}
cgEd->copiedNodesCount = cgEd->selectedNodesCount;
SetClipboardText(cgEd->copiedNodesCount == 1 ? TextFormat("CoreGraph node of type %s", NodeTypeToString(cgEd->copiedNodes[0].type)) : "CoreGraph nodes");
cgEd->isDraggingSelectedNodes = false;
}
}
else if (CheckCollisionPointRec(cgEd->mousePos, (Rectangle){cgEd->rightClickPos.x, cgEd->rightClickPos.y - 30, boxWidth, 30}))
{
cgEd->cursor = MOUSE_CURSOR_POINTING_HAND;
DrawRectangle(cgEd->rightClickPos.x, cgEd->rightClickPos.y - 30, boxWidth, 30, COLOR_CGED_NODE_OPTIONS_MENU_HOVER);
if (cgEd->isLMBPressed)
{
int selectedNodeIds[MAX_SELECTED_NODES];
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
selectedNodeIds[i] = graph->nodes[cgEd->selectedNodes[i]].id;
}
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
DeleteNode(graph, selectedNodeIds[i]);
}
cgEd->hasChangedInLastFrame = true;
cgEd->isDraggingSelectedNodes = false;
cgEd->selectedNodesCount = 0;
}
}
if (cgEd->isLMBPressed || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))
{
cgEd->isNodeOptionsMenuOpen = false;
cgEd->openedOptionsMenuNode = -1;
}
}
EndTextureMode();
}
bool CheckOpenMenus(CGEditorContext *cgEd)
{
return cgEd->isDraggingSelectedNodes || cgEd->lastClickedPin.id != -1 || cgEd->isNodeCreateMenuOpen || cgEd->focusedDropdownPin != -1 || cgEd->focusedFieldPin != -1 || cgEd->editingNodeNameIndex != -1;
}
void HandleEditor(CGEditorContext *cgEd, GraphContext *graph, RenderTexture2D *viewport, Vector2 mousePos, bool draggingDisabled)
{
cgEd->newLogMessage = false;
cgEd->cursor = MOUSE_CURSOR_ARROW;
cgEd->screenWidth = viewport->texture.width;
cgEd->screenHeight = viewport->texture.height;
cgEd->mousePos = mousePos;
cgEd->isLMBPressed = IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && !draggingDisabled;
static RenderTexture2D dot;
SetCGEditorFPS(cgEd);
if (cgEd->isFirstFrame)
{
dot = LoadRenderTexture(15, 15);
SetTextureFilter(dot.texture, TEXTURE_FILTER_BILINEAR);
BeginTextureMode(dot);
ClearBackground(BLANK);
DrawRectangleRounded((Rectangle){0, 0, 15, 15}, 1.0f, 8, GRAY_128);
EndTextureMode();
cgEd->isFirstFrame = false;
}
static float deltaSinceRightClick = 0;
if (IsMouseButtonDown(MOUSE_RIGHT_BUTTON) && cgEd->hoveredNodeIndex == -1)
{
cgEd->delayFrames = true;
cgEd->isNodeCreateMenuOpen = false;
deltaSinceRightClick += fabsf(GetMouseDelta().x + GetMouseDelta().y);
cgEd->isSelecting = true;
if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))
{
cgEd->rightClickPos = cgEd->mousePos;
}
}
else if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON) && deltaSinceRightClick <= 20.0f)
{
if (cgEd->hoveredNodeIndex != -1 && cgEd->hoveredPinIndex == -1)
{
cgEd->isNodeOptionsMenuOpen = true;
cgEd->openedOptionsMenuNode = cgEd->hoveredNodeIndex;
cgEd->isNodeCreateMenuOpen = false;
if (cgEd->selectedNodesCount == 0)
{
cgEd->selectedNodes[cgEd->selectedNodesCount] = cgEd->openedOptionsMenuNode;
cgEd->selectedNodesCount++;
}
else
{
bool isAlreadySelected = false;
for (int i = 0; i < cgEd->selectedNodesCount; i++)
{
if (cgEd->selectedNodes[i] == cgEd->openedOptionsMenuNode)
{
isAlreadySelected = true;
break;
}
}
if (!isAlreadySelected)
{
cgEd->selectedNodesCount = 0;
cgEd->selectedNodes[cgEd->selectedNodesCount] = cgEd->openedOptionsMenuNode;
cgEd->selectedNodesCount++;
}
}
}
else if (cgEd->hoveredNodeIndex == -1)
{
cgEd->isNodeCreateMenuOpen = true;
cgEd->scrollIndexNodeMenu = 0;
cgEd->isNodeOptionsMenuOpen = false;
}
cgEd->rightClickPos = cgEd->mousePos;
cgEd->delayFrames = true;
cgEd->isSelecting = false;
}
if (IsMouseButtonUp(MOUSE_RIGHT_BUTTON))
{
deltaSinceRightClick = 0;
cgEd->isSelecting = false;
cgEd->delayFrames = true;
}
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V) && cgEd->copiedNodesCount > 0 && cgEd->focusedFieldPin == -1 && cgEd->editingNodeNameIndex == -1)
{
int nodeY = 0;
cgEd->selectedNodesCount = 0;
for (int i = 0; i < cgEd->copiedNodesCount; i++)
{
if (!DuplicateNode(graph, &cgEd->copiedNodes[i], cgEd->mousePos, nodeY))
{
cgEd->hasFatalErrorOccurred = true;
AddToLogFromCGEditor(cgEd, "Error duplicating node{C231}", LOG_LEVEL_ERROR);
return;
}
else if (cgEd->copiedNodes[i].type == NODE_CREATE_NUMBER || cgEd->copiedNodes[i].type == NODE_CREATE_STRING || cgEd->copiedNodes[i].type == NODE_CREATE_BOOL || cgEd->copiedNodes[i].type == NODE_CREATE_COLOR || cgEd->copiedNodes[i].type == NODE_CREATE_SPRITE)
{
strmac(graph->nodes[graph->nodeCount - 1].name, MAX_VARIABLE_NAME_SIZE, "%s", AssignAvailableVarName(graph, graph->nodes[graph->nodeCount - 1].name));
graph->variables = realloc(graph->variables, sizeof(char *) * (graph->variablesCount + 1));
graph->variables[graph->variablesCount] = strmac(NULL, MAX_VARIABLE_NAME_SIZE, "%s", graph->nodes[graph->nodeCount - 1].name);
graph->variableTypes = realloc(graph->variableTypes, sizeof(int) * (graph->variablesCount + 1));
graph->variableTypes[graph->variablesCount] = graph->nodes[graph->nodeCount - 1].type;
graph->variablesCount++;
}
nodeY += getNodeInfoByType(cgEd->copiedNodes[i].type, INFO_NODE_HEIGHT) + 15;
cgEd->selectedNodes[cgEd->selectedNodesCount] = graph->nodeCount - 1;
cgEd->selectedNodesCount++;
}
cgEd->hasChangedInLastFrame = true;
cgEd->delayFrames = true;
cgEd->engineDelayFrames = true;
}
if (CheckNodeCollisions(cgEd, graph) || IsMouseButtonDown(MOUSE_LEFT_BUTTON) || IsKeyDown(KEY_LEFT_CONTROL) || CheckOpenMenus(cgEd))
{
DrawFullTexture(cgEd, graph, *viewport, dot, draggingDisabled);
cgEd->delayFrames = true;
}
else if (cgEd->delayFrames == true)
{
DrawFullTexture(cgEd, graph, *viewport, dot, draggingDisabled);
cgEd->delayFrames = false;
}
}
================================================
FILE: Engine/CGEditor.h
================================================
// Copyright 2025 Emil Dimov
// Licensed under the Apache License, Version 2.0
#pragma once
#include <stdio.h>
#include "raylib.h"
#include "Nodes.h"
#include "definitions.h"
#define MAX_KEY_NAME_SIZE 12
#define MAX_SEARCH_BAR_FIELD_SIZE 26
#define MAX_SELECTED_NODES 1000
typedef struct
{
int screenWidth;
int screenHeight;
bool delayFrames;
bool isFirstFrame;
bool engineDelayFrames;
bool isLMBPressed;
Vector2 mousePos;
Vector2 rightClickPos;
Texture2D gearTxt;
bool isDraggingScreen;
bool isDraggingSelectedNodes;
bool isNodeCreateMenuOpen;
Vector2 menuPosition;
Vector2 submenuPosition;
int scrollIndexNodeMenu;
int hoveredItem;
bool isNodeOptionsMenuOpen;
int openedOptionsMenuNode;
Pin lastClickedPin;
Font font;
int focusedDropdownPin;
int focusedFieldPin;
bool newLogMessage;
char logMessages[MAX_LOG_MESSAGES][MAX_LOG_MESSAGE_SIZE];
LogLevel logMessageLevels[MAX_LOG_MESSAGES];
int logMessageCount;
int editingNodeNameIndex;
MouseCursor cursor;
bool hasChanged;
bool hasChangedInLastFrame;
int fps;
float zoom;
Rectangle viewportBoundary;
bool createNodeMenuFirstFrame;
char nodeMenuSearch[MAX_SEARCH_BAR_FIELD_SIZE];
bool shouldOpenHitboxEditor;
char hitboxEditorFileName[MAX_FILE_NAME];
int hitboxEditingPinID;
float nodeGlareTime;
Node copiedNodes[MAX_SELECTED_NODES];
int copiedNodesCount;
bool hasFatalErrorOccurred;
bool isLowSpecModeOn;
bool hasDroppedFile;
char droppedFilePath[MAX_FILE_PATH];
bool isSelecting;
int selectedNodes[MAX_SELECTED_NODES];
int selectedNodesCount;
int hoveredNodeIndex;
int hoveredPinIndex;
GraphContext *graph;
} CGEditorContext;
CGEditorContext InitEditorContext(void);
void FreeEditorContext(CGEditorContext *editor);
void HandleEditor(CGEditorContext *editor, GraphContext *graph, RenderTexture2D *viewport, Vector2 mousePos, bool draggingDisabled);
================================================
FILE: Engine/Engine.c
================================================
// Copyright 2025 Emil Dimov
// Licensed under the Apache License, Version 2.0
#include <stdio.h>
#include <raylib.h>
#include <stdlib.h>
#include <time.h>
#include "version.h"
#include "CGEditor.h"
#include "ProjectManager.h"
#include "Engine.h"
#include "Interpreter.h"
#include "HitboxEditor.h"
#include "TextEditor.h"
bool STRING_ALLOCATION_FAILURE = false;
Logs InitLogs()
{
Logs logs;
logs.count = 0;
logs.capacity = 100;
logs.entries = malloc(sizeof(LogEntry) * logs.capacity);
logs.hasNewLogMessage = false;
return logs;
}
void AddToLog(EngineContext *eng, const char *newLine, int level);
void EmergencyExit(EngineContext *eng, CGEditorContext *cgEd, InterpreterContext *intp, TextEditorContext *txEd);
EngineContext InitEngineContext()
{
EngineContext eng = {0};
eng.logs = InitLogs();
eng.screenWidth = GetScreenWidth();
eng.screenHeight = GetScreenHeight();
eng.sideBarWidth = eng.screenWidth * 0.2;
eng.bottomBarHeight = eng.screenHeight * 0.25;
eng.maxScreenWidth = eng.screenWidth;
eng.maxScreenHeight = eng.screenHeight;
eng.prevScreenWidth = eng.screenWidth;
eng.prevScreenHeight = eng.screenHeight;
eng.viewportWidth = eng.screenWidth - eng.sideBarWidth;
eng.viewportHeight = eng.screenHeight - eng.bottomBarHeight;
eng.sideBarMiddleY = (eng.screenHeight - eng.bottomBarHeight) / 2 + 20;
eng.mousePos = GetMousePosition();
eng.viewportTex = LoadRenderTexture(eng.screenWidth * 2, eng.screenHeight * 2);
eng.uiTex = LoadRenderTexture(eng.screenWidth, eng.screenHeight);
Image tempImg;
tempImg = LoadImageFromMemory(".png", resize_btn_png, resize_btn_png_len);
eng.resizeButton = LoadTextureFromImage(tempImg);
UnloadImage(tempImg);
tempImg = LoadImageFromMemory(".png", viewport_fullscreen_png, viewport_fullscreen_png_len);
eng.viewportFullscreenButton = LoadTextureFromImage(tempImg);
UnloadImage(tempImg);
tempImg = LoadImageFromMemory(".png", settings_gear_png, settings_gear_png_len);
eng.settingsGear = LoadTextureFromImage(tempImg);
UnloadImage(tempImg);
if (eng.uiTex.id == 0 || eng.viewportTex.id == 0 || eng.resizeButton.id == 0 || eng.viewportFullscreenButton.id == 0)
{
AddToLog(&eng, "Failed to load texture{E223}", LOG_LEVEL_ERROR);
EmergencyExit(&eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
eng.delayFrames = true;
eng.menuResizeButton = RESIZING_MENU_NONE;
eng.font = LoadFontFromMemory(".ttf", arialbd_ttf, arialbd_ttf_len, FONT_GLYPHS, NULL, 0);
if (eng.font.texture.id == 0)
{
AddToLog(&eng, "Failed to load font{E224}", LOG_LEVEL_ERROR);
EmergencyExit(&eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
eng.wasViewportFocusedLastFrame = false;
eng.CGFilePath = malloc(MAX_FILE_PATH);
eng.CGFilePath[0] = '\0';
eng.hoveredUIElementIndex = -1;
eng.viewportMode = VIEWPORT_CG_EDITOR;
eng.isGameRunning = false;
eng.saveSound = LoadSoundFromWave(LoadWaveFromMemory(".wav", save_wav, save_wav_len));
if (eng.saveSound.frameCount == 0)
{
AddToLog(&eng, "Failed to load audio{E225}", LOG_LEVEL_ERROR);
EmergencyExit(&eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
eng.isSoundOn = true;
eng.sideBarHalfSnap = false;
eng.zoom = 1.0f;
eng.wasBuilt = false;
eng.showSaveWarning = 0;
eng.showSettingsMenu = false;
eng.varsFilter = 0;
eng.isViewportFullscreen = false;
eng.isSaveButtonHovered = false;
eng.isBuildButtonHovered = false;
eng.isAutoSaveON = false;
eng.autoSaveTimer = 0.0f;
eng.fpsLimit = FPS_HIGH;
eng.shouldShowFPS = false;
eng.isAnyMenuOpen = false;
eng.shouldCloseWindow = false;
eng.windowResizeButton = RESIZING_WINDOW_NONE;
eng.isWindowMoving = false;
eng.shouldHideCursorInGameFullscreen = true;
eng.isSettingsButtonHovered = false;
eng.isVarHovered = false;
eng.draggedFileIndex = -1;
eng.openFilesWithRapidEditor = true;
eng.isLogMessageHovered = false;
eng.isTopBarHovered = false;
eng.isKeyboardShortcutActivated = false;
return eng;
}
void FreeEngineContext(EngineContext *eng)
{
if (eng->currentPath)
{
free(eng->currentPath);
eng->currentPath = NULL;
}
if (eng->projectPath)
{
free(eng->projectPath);
eng->projectPath = NULL;
}
if (eng->CGFilePath)
free(eng->CGFilePath);
if (eng->logs.entries)
{
free(eng->logs.entries);
eng->logs.entries = NULL;
}
UnloadDirectoryFiles(eng->files);
UnloadRenderTexture(eng->viewportTex);
UnloadRenderTexture(eng->uiTex);
UnloadTexture(eng->resizeButton);
UnloadTexture(eng->viewportFullscreenButton);
UnloadTexture(eng->settingsGear);
UnloadFont(eng->font);
UnloadSound(eng->saveSound);
}
void AddUIElement(EngineContext *eng, UIElement element)
{
if (eng->uiElementCount < MAX_UI_ELEMENTS)
{
eng->uiElements[eng->uiElementCount++] = element;
}
else
{
AddToLog(eng, "UIElement limit reached{E212}", LOG_LEVEL_ERROR);
EmergencyExit(eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
}
void AddToLog(EngineContext *eng, const char *newLine, int level)
{
if (eng->logs.count >= eng->logs.capacity)
{
eng->logs.capacity += 100;
eng->logs.entries = realloc(eng->logs.entries, sizeof(LogEntry) * eng->logs.capacity);
if (!eng->logs.entries)
{
exit(1);
}
}
eng->logs.hasNewLogMessage = true;
time_t timestamp = time(NULL);
struct tm *tm_info = localtime(×tamp);
strmac(eng->logs.entries[eng->logs.count].message, MAX_LOG_MESSAGE_SIZE, "%02d:%02d:%02d %s", tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, newLine);
eng->logs.entries[eng->logs.count].level = level;
eng->logs.count++;
eng->delayFrames = true;
}
char *LogLevelToString(LogLevel level)
{
switch (level)
{
case LOG_LEVEL_NORMAL:
return "INFO";
case LOG_LEVEL_WARNING:
return "WARNING";
case LOG_LEVEL_ERROR:
return "ERROR";
case LOG_LEVEL_SUCCESS:
return "SAVE";
case LOG_LEVEL_DEBUG:
return "DEBUG";
default:
return "UNKNOWN";
}
}
void EmergencyExit(EngineContext *eng, CGEditorContext *cgEd, InterpreterContext *intp, TextEditorContext *txEd)
{
time_t timestamp = time(NULL);
struct tm *tm_info = localtime(×tamp);
FILE *logFile = fopen("engine_log.txt", "w");
if (logFile)
{
fprintf(logFile, "Crash Report - Date: %02d-%02d-%04d - Version: v1.0.0(%d)\n\n", tm_info->tm_mday, tm_info->tm_mon + 1, tm_info->tm_year + 1900, RAPID_ENGINE_VERSION);
for (int i = 0; i < eng->logs.count; i++)
{
fprintf(logFile, "[ENGINE %s] %s\n", LogLevelToString(eng->logs.entries[i].level), eng->logs.entries[i].message);
}
for (int i = 0; i < cgEd->logMessageCount; i++)
{
fprintf(logFile, "[CGEDITOR %s] %02d:%02d:%02d %s\n", LogLevelToString(cgEd->logMessageLevels[i]), tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, cgEd->logMessages[i]);
}
for (int i = 0; i < intp->logMessageCount; i++)
{
fprintf(logFile, "[INTERPRETER %s] %02d:%02d:%02d %s\n", LogLevelToString(intp->logMessageLevels[i]), tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, intp->logMessages[i]);
}
for (int i = 0; i < txEd->logMessageCount; i++)
{
fprintf(logFile, "[TEXTEDITOR %s] %02d:%02d:%02d %s\n", LogLevelToString(intp->logMessageLevels[i]), tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, intp->logMessages[i]);
}
fprintf(logFile, "\nTo submit a crash report, please email support@rapidengine.eu");
fclose(logFile);
}
OpenFile("engine_log.txt");
FreeEngineContext(eng);
FreeEditorContext(cgEd);
FreeInterpreterContext(intp);
FreeTextEditorContext(txEd);
free(intp->projectPath);
intp->projectPath = NULL;
CloseAudioDevice();
CloseWindow();
exit(EXIT_FAILURE);
}
void PrepareCGFilePath(EngineContext *eng, const char *projectPath)
{
char cwd[MAX_FILE_PATH];
if (!GetCWD(cwd, sizeof(cwd)))
{
AddToLog(eng, "Failed to get working directory{E226}", LOG_LEVEL_ERROR);
EmergencyExit(eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
if (!GetFileExtension(projectPath))
{
strmac(eng->CGFilePath, MAX_FILE_PATH, "%s%c%s%c%s.cg", cwd, PATH_SEPARATOR, projectPath, PATH_SEPARATOR, GetFileName(projectPath));
}
else
{
strmac(eng->CGFilePath, MAX_FILE_PATH, "%s", projectPath);
}
if (!FileExists(eng->CGFilePath))
{
FILE *f = fopen(eng->CGFilePath, "w");
if (f)
{
fclose(f);
}
}
}
void SetProjectFolderPath(EngineContext *eng, const char *filePath)
{
if(filePath == NULL){
AddToLog(eng, "Failed to open project{E228}", LOG_LEVEL_ERROR);
EmergencyExit(eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
char cwd[MAX_FILE_PATH];
if (!GetCWD(cwd, sizeof(cwd)))
{
AddToLog(eng, "Failed to get working directory{E226}", LOG_LEVEL_ERROR);
EmergencyExit(eng, &(CGEditorContext){0}, &(InterpreterContext){0}, &(TextEditorContext){0});
}
if (!GetFileExtension(filePath))
{
eng->currentPath = strmac(NULL, MAX_FILE_PATH, "%s%c%s", cwd, PATH_SEPARATOR, filePath);
}
else
{
char buff[MAX_FILE_PATH];
strmac(buff, MAX_FILE_PATH, "%s", filePath);
for (int i = strlen(buff) - 1; i >= 0; i--)
{
if (buff[i] == '/' || buff[i] == '\\')
{
buff[i] = '\0';
break;
}
}
eng->currentPath = strmac(NULL, MAX_FILE_PATH, "%s", buff);
}
eng->projectPath = strmac(NULL, MAX_FILE_PATH, "%s", eng->currentPath);
PrepareCGFilePath(eng, filePath);
}
FileType GetFileType(const char *folderPath, const char *fileName)
{
char fullPath[MAX_FILE_PATH];
strmac(fullPath, MAX_FILE_PATH, "%s%c%s", folderPath, PATH_SEPARATOR, fileName);
const char *ext = GetFileExtension(fileName);
if (!ext || *(ext + 1) == '\0')
{
if (DirectoryExists(fullPath))
{
return FILE_TYPE_FOLDER;
}
else
{
return FILE_TYPE_OTHER;
}
}
if (strcmp(ext + 1, "cg") == 0)
{
return FILE_TYPE_CG;
}
else if (strcmp(ext + 1, "png") == 0 || strcmp(ext + 1, "jpg") == 0 || strcmp(ext + 1, "jpeg") == 0)
{
return FILE_TYPE_IMAGE;
}
else if (strcmp(ext + 1, "config") == 0)
{
return FILE_TYPE_CONFIG;
}
return FILE_TYPE_OTHER;
}
int DrawSaveWarning(EngineContext *eng, GraphContext *graph, CGEditorContext *cgEd)
{
eng->isViewportFocused = false;
int popupWidth = 500;
int popupHeight = 150;
int popupX = (eng->screenWidth - popupWidth) / 2;
int popupY = (eng->screenHeight - popupHeight) / 2 - 100;
const char *message = "Save changes before exiting?";
int textWidth = MeasureTextEx(eng->font, message, 30, 0).x;
int btnWidth = 120;
int btnHeight = 30;
int btnSpacing = 10;
int btnY = popupY + popupHeight - btnHeight - 20;
int totalBtnWidth = btnWidth * 3 + btnSpacing * 2 + 30;
int btnStartX = popupX + (popupWidth + 5 - totalBtnWidth) / 2;
Rectangle saveBtn = {btnStartX, btnY, btnWidth, btnHeight};
Rectangle closeBtn = {btnStartX + btnWidth + btnSpacing, btnY, btnWidth + 20, btnHeight};
Rectangle cancelBtn = {btnStartX + 2 * (btnWidth + btnSpacing) + 20, btnY, btnWidth, btnHeight};
DrawRectangle(0, 0, eng->screenWidth, eng->screenHeight, COLOR_BACKGROUND_BLUR);
DrawRectangleRounded((Rectangle){popupX, popupY, popupWidth, popupHeight}, 0.4f, 8, GRAY_30);
DrawRectangleRoundedLines((Rectangle){popupX, popupY, popupWidth, popupHeight}, 0.4f, 8, WHITE);
DrawTextEx(eng->font, message, (Vector2){popupX + (popupWidth - textWidth) / 2, popupY + 20}, 30, 0, WHITE);
DrawRectangleRounded(saveBtn, 0.2f, 2, DARKGREEN);
DrawTextEx(eng->font, "Save", (Vector2){saveBtn.x + 35, saveBtn.y + 4}, 24, 0, WHITE);
DrawRectangleRounded(closeBtn, 0.2f, 2, COLOR_SAVE_MENU_DONT_SAVE_BTN);
DrawTextEx(eng->font, "Don't save", (Vector2){closeBtn.x + 18, closeBtn.y + 4}, 24, 0, WHITE);
DrawRectangleRounded(cancelBtn, 0.2f, 2, GRAY_80);
DrawTextEx(eng->font, "Cancel", (Vector2){cancelBtn.x + 25, cancelBtn.y + 4}, 24, 0, WHITE);
if (CheckCollisionPointRec(eng->mousePos, saveBtn))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
if (SaveGraphToFile(eng->CGFilePath, graph) == 0)
{
AddToLog(eng, "Saved successfully{C300}", LOG_LEVEL_SUCCESS);
cgEd->hasChanged = false;
}
else
{
AddToLog(eng, "Error saving changes!{C101}", LOG_LEVEL_WARNING);
}
return 2;
}
}
else if (CheckCollisionPointRec(eng->mousePos, closeBtn))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
return 2;
}
}
else if (CheckCollisionPointRec(eng->mousePos, cancelBtn))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
return 0;
}
}
else
{
SetMouseCursor(MOUSE_CURSOR_ARROW);
}
return 1;
}
void DrawSlider(Vector2 pos, bool *value, Vector2 mousePos, bool *hasChanged)
{
if (CheckCollisionPointRec(mousePos, (Rectangle){pos.x, pos.y, 40, 25}))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
*value = !*value;
*hasChanged = true;
DrawRectangleRounded((Rectangle){pos.x, pos.y, 40, 24}, 1.0f, 8, COLOR_SETTINGS_MENU_SLIDER_MID_GREEN);
DrawCircle(pos.x + 20, pos.y + 12, 10, WHITE);
return;
}
}
if (*value)
{
DrawRectangleRounded((Rectangle){pos.x, pos.y, 40, 24}, 1.0f, 8, COLOR_SETTINGS_MENU_SLIDER_FULL_GREEN);
DrawCircle(pos.x + 28, pos.y + 12, 10, WHITE);
}
else
{
DrawRectangleRounded((Rectangle){pos.x, pos.y, 40, 24}, 1.0f, 8, GRAY);
DrawCircle(pos.x + 12, pos.y + 12, 10, WHITE);
}
}
void DrawFPSLimitDropdown(Vector2 pos, int *limit, Vector2 mousePos, Font font, bool *hasChanged)
{
static bool dropdownOpen = false;
int fpsOptions[] = {240, 90, 60, 30};
int fpsCount = sizeof(fpsOptions) / sizeof(fpsOptions[0]);
float blockHeight = 30;
DrawRectangle(pos.x, pos.y, 90, blockHeight, GRAY_60);
DrawTextEx(font, TextFormat("%d FPS", *limit), (Vector2){pos.x + 14 - 4 * (*limit) / 100, pos.y + 4}, 20, 1, WHITE);
DrawRectangleLines(pos.x, pos.y, 90, blockHeight, WHITE);
if (dropdownOpen)
{
for (int i = 0; i < fpsCount; i++)
{
Rectangle optionBox = {pos.x - (i + 1) * 40, pos.y, 40, blockHeight};
DrawRectangle(pos.x - (i + 1) * 40 - 2, pos.y, 40, blockHeight, (*limit == fpsOptions[i]) ? COLOR_SETTINGS_MENU_DROPDOWN_SELECTED_OPTION : GRAY_60);
DrawTextEx(font, TextFormat("%d", fpsOptions[i]), (Vector2){optionBox.x + 10 - 4 * fpsOptions[i] / 100, optionBox.y + 4}, 20, 1, WHITE);
if (CheckCollisionPointRec(mousePos, optionBox))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
*limit = fpsOptions[i];
dropdownOpen = false;
*hasChanged = true;
}
}
}
}
if (CheckCollisionPointRec(mousePos, (Rectangle){pos.x, pos.y, 90, blockHeight}))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
dropdownOpen = !dropdownOpen;
}
}
else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
dropdownOpen = false;
}
}
bool SaveSettings(EngineContext *eng, InterpreterContext *intp, CGEditorContext *cgEd)
{
FILE *fptr = fopen(TextFormat("%s%c%s.config", eng->projectPath, PATH_SEPARATOR, GetFileName(eng->projectPath)), "w");
if (!fptr)
{
return false;
}
fprintf(fptr, "Engine:\n\n");
fprintf(fptr, "Sound=%s\n", eng->isSoundOn ? "true" : "false");
fprintf(fptr, "FPSLimit=%d\n", eng->fpsLimit);
fprintf(fptr, "ShowFPS=%s\n", eng->shouldShowFPS ? "true" : "false");
fprintf(fptr, "AutoSave=%s\n", eng->isAutoSaveON ? "true" : "false");
fprintf(fptr, "HideCursorinFullscreen=%s\n", eng->shouldHideCursorInGameFullscreen ? "true" : "false");
fprintf(fptr, "LowSpecMode=%s\n", eng->isLowSpecModeOn ? "true" : "false");
fprintf(fptr, "OpenFilesWithRapidEditor=%s\n", eng->openFilesWithRapidEditor ? "true" : "false");
fprintf(fptr, "\nInterpreter:\n\n");
fprintf(fptr, "InfiniteLoopProtection=%s\n", intp->isInfiniteLoopProtectionOn ? "true" : "false");
fprintf(fptr, "ShowHitboxes=%s\n", intp->shouldShowHitboxes ? "true" : "false");
fclose(fptr);
return true;
}
bool LoadSettingsConfig(EngineContext *eng, InterpreterContext *intp, CGEditorContext *cgEd)
{
FILE *fptr = fopen(TextFormat("%s%c%s.config", eng->projectPath, PATH_SEPARATOR, GetFileName(eng->projectPath)), "r");
if (!fptr)
{
if (!SaveSettings(eng, intp, cgEd))
{
return false;
}
fptr = fopen(TextFormat("%s%c%s.config", eng->projectPath, PATH_SEPARATOR, GetFileName(eng->projectPath)), "r");
if (!fptr)
{
return false;
}
}
char line[MAX_SETTINGS_LINE];
while (fgets(line, sizeof(line), fptr))
{
char *eq = strchr(line, '=');
if (!eq)
{
continue;
}
*eq = '\0';
char *key = line;
char *value = eq + 1;
value[strcspn(value, "\r\n")] = '\0';
if (strcmp(key, "Sound") == 0)
{
eng->isSoundOn = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "FPSLimit") == 0)
{
eng->fpsLimit = atoi(value);
}
else if (strcmp(key, "ShowFPS") == 0)
{
eng->shouldShowFPS = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "AutoSave") == 0)
{
eng->isAutoSaveON = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "HideCursorinFullscreen") == 0)
{
eng->shouldHideCursorInGameFullscreen = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "LowSpecMode") == 0)
{
eng->isLowSpecModeOn = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "OpenFilesWithRapidEditor") == 0)
{
eng->openFilesWithRapidEditor = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "InfiniteLoopProtection") == 0)
{
intp->isInfiniteLoopProtectionOn = strcmp(value, "true") == 0 ? true : false;
}
else if (strcmp(key, "ShowHitboxes") == 0)
{
intp->shouldShowHitboxes = strcmp(value, "true") == 0 ? true : false;
}
}
fclose(fptr);
return true;
}
bool DrawSettingsMenu(EngineContext *eng, InterpreterContext *intp, CGEditorContext *cgEd)
{
DrawRectangle(0, 0, eng->screenWidth, eng->screenHeight, COLOR_BACKGROUND_BLUR);
static SettingsMode settingsMode = SETTINGS_MODE_ENGINE;
DrawRectangleRounded((Rectangle){eng->screenWidth / 4, 100, eng->screenWidth * 2 / 4, eng->screenHeight - 200}, 0.08f, 4, GRAY_30);
DrawRectangleRoundedLines((Rectangle){eng->screenWidth / 4, 100, eng->screenWidth * 2 / 4, eng->screenHeight - 200}, 0.08f, 4, WHITE);
static bool skipClickOnFirstFrame = true;
DrawLineEx((Vector2){eng->screenWidth * 3 / 4 - 50, 140}, (Vector2){eng->screenWidth * 3 / 4 - 30, 160}, 3, WHITE);
DrawLineEx((Vector2){eng->screenWidth * 3 / 4 - 50, 160}, (Vector2){eng->screenWidth * 3 / 4 - 30, 140}, 3, WHITE);
if (CheckCollisionPointRec(GetMousePosition(), (Rectangle){eng->screenWidth * 3 / 4 - 50, 140, 20, 20}))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
skipClickOnFirstFrame = true;
return false;
}
}
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && !CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4, 100, eng->screenWidth * 2 / 4, eng->screenHeight - 200}))
{
if (skipClickOnFirstFrame)
{
skipClickOnFirstFrame = false;
}
else
{
skipClickOnFirstFrame = true;
return false;
}
}
DrawTextEx(eng->font, "Settings", (Vector2){eng->screenWidth / 2 - MeasureTextEx(eng->font, "Settings", 50, 1).x / 2, 130}, 50, 1, WHITE);
DrawTextEx(eng->font, "Engine", (Vector2){eng->screenWidth / 4 + 30, 300}, 30, 1, settingsMode == SETTINGS_MODE_ENGINE ? WHITE : GRAY);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4 + 20, 290, MeasureTextEx(eng->font, "eng", 30, 1).x + 20, 50}) && settingsMode != SETTINGS_MODE_ENGINE)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
settingsMode = SETTINGS_MODE_ENGINE;
}
}
DrawTextEx(eng->font, "Game", (Vector2){eng->screenWidth / 4 + 30, 350}, 30, 1, settingsMode == SETTINGS_MODE_GAME ? WHITE : GRAY);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4 + 20, 340, MeasureTextEx(eng->font, "Game", 30, 1).x + 20, 50}) && settingsMode != SETTINGS_MODE_GAME)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
settingsMode = SETTINGS_MODE_GAME;
}
}
DrawTextEx(eng->font, "Keybinds", (Vector2){eng->screenWidth / 4 + 30, 400}, 30, 1, settingsMode == SETTINGS_MODE_KEYBINDS ? WHITE : GRAY);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4 + 20, 390, MeasureTextEx(eng->font, "Keybinds", 30, 1).x + 20, 50}) && settingsMode != SETTINGS_MODE_KEYBINDS)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
settingsMode = SETTINGS_MODE_KEYBINDS;
}
}
DrawTextEx(eng->font, "Export", (Vector2){eng->screenWidth / 4 + 30, 450}, 30, 1, settingsMode == SETTINGS_MODE_EXPORT ? WHITE : GRAY);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4 + 20, 440, MeasureTextEx(eng->font, "Export", 30, 1).x + 20, 50}) && settingsMode != SETTINGS_MODE_EXPORT)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
settingsMode = SETTINGS_MODE_EXPORT;
}
}
DrawTextEx(eng->font, "About", (Vector2){eng->screenWidth / 4 + 30, 500}, 30, 1, settingsMode == SETTINGS_MODE_ABOUT ? WHITE : GRAY);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth / 4 + 20, 490, MeasureTextEx(eng->font, "About", 30, 1).x + 20, 50}) && settingsMode != SETTINGS_MODE_ABOUT)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
settingsMode = SETTINGS_MODE_ABOUT;
}
}
DrawRectangleGradientV(eng->screenWidth / 4 + 180, 300, 2, eng->screenHeight - 400, GRAY, GRAY_30);
static bool hasChanged = false;
float glowOffset = (sinf(GetTime() * 5.0f) + 1.0f) * 50.0f;
DrawRectangleRounded((Rectangle){eng->screenWidth * 3 / 4 - 140, 135, 64, 30}, 0.6f, 4, hasChanged ? (Color){COLOR_WARNING_ORANGE.r + glowOffset, COLOR_WARNING_ORANGE.g + glowOffset, COLOR_WARNING_ORANGE.b + glowOffset, COLOR_WARNING_ORANGE.a} : DARKGRAY);
DrawTextEx(eng->font, hasChanged ? "Save*" : "Save", (Vector2){eng->screenWidth * 3 / 4 - 133 - hasChanged * 3, 139}, 22, 1.0f, WHITE);
if (CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->screenWidth * 3 / 4 - 140, 135, 64, 30}))
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
DrawRectangleRounded((Rectangle){eng->screenWidth * 3 / 4 - 140, 135, 64, 30}, 0.6f, 4, COLOR_SETTINGS_MENU_SAVE_BTN_HOVER);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
if (SaveSettings(eng, intp, cgEd))
{
if (eng->isSoundOn)
{
PlaySound(eng->saveSound);
}
AddToLog(eng, "Settings saved successfully{E300}", LOG_LEVEL_SUCCESS);
hasChanged = false;
}
else
{
AddToLog(eng, "Error saving settings{E100}", LOG_LEVEL_ERROR);
}
}
}
switch (settingsMode)
{
case SETTINGS_MODE_ENGINE:
DrawTextEx(eng->font, "Sound", (Vector2){eng->screenWidth / 4 + 200, 300}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 303}, &eng->isSoundOn, eng->mousePos, &hasChanged);
if (intp->isSoundOn != eng->isSoundOn)
{
intp->isSoundOn = eng->isSoundOn;
intp->hasSoundOnChanged = true;
}
DrawLine(eng->screenWidth / 4 + 182, 340, eng->screenWidth * 3 / 4, 340, GRAY_50);
DrawTextEx(eng->font, "Auto Save Every 2 Minutes", (Vector2){eng->screenWidth / 4 + 200, 350}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 353}, &eng->isAutoSaveON, eng->mousePos, &hasChanged);
DrawLine(eng->screenWidth / 4 + 182, 390, eng->screenWidth * 3 / 4, 390, GRAY_50);
DrawTextEx(eng->font, "Show FPS", (Vector2){eng->screenWidth / 4 + 200, 400}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 403}, &eng->shouldShowFPS, eng->mousePos, &hasChanged);
DrawLine(eng->screenWidth / 4 + 182, 440, eng->screenWidth * 3 / 4, 440, GRAY_50);
DrawTextEx(eng->font, "FPS Limit", (Vector2){eng->screenWidth / 4 + 200, 450}, 28, 1, WHITE);
DrawFPSLimitDropdown((Vector2){eng->screenWidth * 3 / 4 - 100, 450}, &eng->fpsLimit, eng->mousePos, eng->font, &hasChanged);
DrawLine(eng->screenWidth / 4 + 182, 490, eng->screenWidth * 3 / 4, 490, GRAY_50);
DrawTextEx(eng->font, "Low-spec mode", (Vector2){eng->screenWidth / 4 + 200, 500}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 503}, &eng->isLowSpecModeOn, eng->mousePos, &hasChanged);
if (cgEd->isLowSpecModeOn != eng->isLowSpecModeOn)
{
cgEd->isLowSpecModeOn = eng->isLowSpecModeOn;
cgEd->delayFrames = true;
}
DrawLine(eng->screenWidth / 4 + 182, 540, eng->screenWidth * 3 / 4, 540, GRAY_50);
DrawTextEx(eng->font, "Open files with Rapid Editor", (Vector2){eng->screenWidth / 4 + 200, 550}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 553}, &eng->openFilesWithRapidEditor, eng->mousePos, &hasChanged);
break;
case SETTINGS_MODE_GAME:
DrawTextEx(eng->font, "Infinite Loop Protection", (Vector2){eng->screenWidth / 4 + 200, 300}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 303}, &intp->isInfiniteLoopProtectionOn, eng->mousePos, &hasChanged);
DrawLine(eng->screenWidth / 4 + 182, 340, eng->screenWidth * 3 / 4, 340, GRAY_50);
DrawTextEx(eng->font, "Show Hitboxes", (Vector2){eng->screenWidth / 4 + 200, 350}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 353}, &intp->shouldShowHitboxes, eng->mousePos, &hasChanged);
DrawLine(eng->screenWidth / 4 + 182, 390, eng->screenWidth * 3 / 4, 390, GRAY_50);
DrawTextEx(eng->font, "Hide Mouse Cursor in Fullscreen", (Vector2){eng->screenWidth / 4 + 200, 400}, 28, 1, WHITE);
DrawSlider((Vector2){eng->screenWidth * 3 / 4 - 70, 403}, &eng->shouldHideCursorInGameFullscreen, eng->mousePos, &hasChanged);
break;
case SETTINGS_MODE_KEYBINDS:
DrawTextEx(eng->font, "No Keybind settings yet!", (Vector2){eng->screenWidth / 4 + 200, 300}, 28, 1, RED);
break;
case SETTINGS_MODE_EXPORT:
DrawTextEx(eng->font, "No Export settings yet!", (Vector2){eng->screenWidth / 4 + 200, 300}, 28, 1, RED);
break;
case SETTINGS_MODE_ABOUT:
DrawTextEx(eng->font, "Release version: v1.0.0", (Vector2){eng->screenWidth / 4 + 200, 300}, 28, 1, WHITE);
DrawTextEx(eng->font, TextFormat("Debug version: %d", RAPID_ENGINE_VERSION), (Vector2){eng->screenWidth / 4 + 200, 350}, 28, 1, WHITE);
break;
default:
settingsMode = SETTINGS_MODE_ENGINE;
}
return true;
}
FilePathList LoadAndSortFiles(const char *path)
{
FilePathList files = LoadDirectoryFilesEx(path, NULL, false);
if (files.count <= 0)
{
return files;
}
for (int i = 0; i < files.count - 1; i++)
{
FileType ti = GetFileType(path, GetFileName(files.paths[i]));
for (int j = i + 1; j < files.count; j++)
{
FileType tj = GetFileType(path, GetFileName(files.paths[j]));
if (ti > tj || (ti == tj && strcmp(files.paths[i], files.paths[j]) > 0))
{
char *tmp = files.paths[i];
files.paths[i] = files.paths[j];
files.paths[j] = tmp;
}
}
}
return files;
}
void CountingSortByLayer(EngineContext *eng)
{
int **elements = malloc(UI_LAYER_COUNT * sizeof(int *));
int *layerCount = calloc(UI_LAYER_COUNT, sizeof(int));
for (int i = 0; i < UI_LAYER_COUNT; i++)
{
elements[i] = malloc(eng->uiElementCount * sizeof(int));
}
for (int i = 0; i < eng->uiElementCount; i++)
{
if (eng->uiElements[i].layer < UI_LAYER_COUNT)
{
elements[eng->uiElements[i].layer][layerCount[eng->uiElements[i].layer]] = i;
layerCount[eng->uiElements[i].layer]++;
}
}
UIElement *sorted = malloc(sizeof(UIElement) * eng->uiElementCount);
int sortedCount = 0;
for (int i = 0; i < UI_LAYER_COUNT; i++)
{
for (int j = 0; j < layerCount[i]; j++)
{
sorted[sortedCount++] = eng->uiElements[elements[i][j]];
}
}
memcpy(eng->uiElements, sorted, sizeof(UIElement) * sortedCount);
free(sorted);
free(layerCount);
for (int i = 0; i < UI_LAYER_COUNT; i++)
{
free(elements[i]);
}
free(elements);
}
void DrawUIElements(EngineContext *eng, GraphContext *graph, CGEditorContext *cgEd, InterpreterContext *intp, RuntimeGraphContext *runtimeGraph, TextEditorContext *txEd)
{
BeginTextureMode(eng->uiTex);
ClearBackground(COLOR_TRANSPARENT);
DrawCircleSector((Vector2){eng->screenWidth - 150, 1}, 50, 90, 180, 8, GRAY_40);
DrawRing((Vector2){eng->screenWidth - 150, 2.5f}, 47, 50, 0, 360, 64, WHITE);
DrawRectangle(eng->screenWidth - 150, 0, 150, 50, GRAY_40);
DrawLineEx((Vector2){eng->screenWidth - 150, 50}, (Vector2){eng->screenWidth, 50}, 3, WHITE);
eng->isSaveButtonHovered = false;
eng->isBuildButtonHovered = false;
eng->isSettingsButtonHovered = false;
eng->isLogMessageHovered = false;
eng->isTopBarHovered = false;
eng->isVarHovered = false;
if (eng->hoveredUIElementIndex != -1 && !eng->isAnyMenuOpen && eng->draggedFileIndex == -1)
{
switch (eng->uiElements[eng->hoveredUIElementIndex].action)
{
case UI_ACTION_NONE:
break;
case UI_ACTION_SAVE_CG_FILE:
eng->isSaveButtonHovered = true;
if (eng->viewportMode != VIEWPORT_CG_EDITOR)
{
break;
}
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
{
if (eng->isSoundOn)
{
PlaySound(eng->saveSound);
}
if (SaveGraphToFile(eng->CGFilePath, graph) == 0)
{
AddToLog(eng, "Saved successfully{C300}", LOG_LEVEL_SUCCESS);
cgEd->hasChanged = false;
}
else
AddToLog(eng, "Error saving changes!{C101}", LOG_LEVEL_WARNING);
}
break;
case UI_ACTION_STOP_GAME:
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
eng->viewportMode = VIEWPORT_CG_EDITOR;
cgEd->isFirstFrame = true;
eng->isGameRunning = false;
eng->wasBuilt = false;
FreeInterpreterContext(intp);
}
break;
case UI_ACTION_RUN_GAME:
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
{
if (cgEd->hasChanged)
{
AddToLog(eng, "Project not saved!{I102}", LOG_LEVEL_WARNING);
break;
}
else if (!eng->wasBuilt)
{
AddToLog(eng, "Project has not been built{I103}", LOG_LEVEL_WARNING);
break;
}
eng->viewportMode = VIEWPORT_GAME_SCREEN;
eng->isGameRunning = true;
intp->isFirstFrame = true;
}
break;
case UI_ACTION_BUILD_GRAPH:
eng->isBuildButtonHovered = true;
if (cgEd->hasChanged || eng->viewportMode != VIEWPORT_CG_EDITOR)
{
break;
}
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
if (cgEd->hasChanged)
{
AddToLog(eng, "Project not saved!{I102}", LOG_LEVEL_WARNING);
break;
}
if (eng->viewportMode != VIEWPORT_CG_EDITOR)
{
break;
}
*runtimeGraph = ConvertToRuntimeGraph(graph, intp);
intp->runtimeGraph = runtimeGraph;
if (intp->buildFailed)
{
EmergencyExit(eng, cgEd, intp, txEd);
}
else if (intp->buildErrorOccured || runtimeGraph == NULL)
{
if (intp->newLogMessage)
{
for (int i = 0; i < intp->logMessageCount; i++)
{
AddToLog(eng, intp->logMessages[i], intp->logMessageLevels[i]);
}
intp->newLogMessage = false;
intp->logMessageCount = 0;
eng->delayFrames = true;
}
AddToLog(eng, "Build failed{I100}", LOG_LEVEL_WARNING);
intp->buildErrorOccured = false;
}
else
{
AddToLog(eng, "Build successful{I300}", LOG_LEVEL_NORMAL);
eng->wasBuilt = true;
}
eng->delayFrames = true;
}
break;
case UI_ACTION_CURRENT_PATH_BACK:
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
char *lastSlash = strrchr(eng->currentPath, PATH_SEPARATOR);
if (lastSlash && lastSlash != eng->currentPath)
{
*lastSlash = '\0';
}
UnloadDirectoryFiles(eng->files);
eng->files = LoadAndSortFiles(eng->currentPath);
if (!eng->files.paths || eng->files.count < 0)
{
AddToLog(eng, "Error loading files{E201}", LOG_LEVEL_ERROR);
EmergencyExit(eng, cgEd, intp, txEd);
}
eng->uiElementCount = 0;
eng->delayFrames = true;
return;
}
break;
case UI_ACTION_REFRESH_FILES:
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
UnloadDirectoryFiles(eng->files);
eng->files = LoadAndSortFiles(eng->currentPath);
if (!eng->files.paths || eng->files.count < 0)
{
AddToLog(eng, "Error loading files{E201}", LOG_LEVEL_ERROR);
EmergencyExit(eng, cgEd, intp, txEd);
}
}
break;
case UI_ACTION_CLOSE_WINDOW:
eng->isViewportFocused = false;
eng->isTopBarHovered = true;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
if (cgEd->hasChanged)
{
eng->showSaveWarning = true;
}
else
{
eng->shouldCloseWindow = true;
break;
}
}
break;
case UI_ACTION_MINIMIZE_WINDOW:
eng->isViewportFocused = false;
eng->isTopBarHovered = true;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
MinimizeWindow();
}
break;
case UI_ACTION_OPEN_SETTINGS:
eng->isViewportFocused = false;
eng->isTopBarHovered = true;
eng->isSettingsButtonHovered = true;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
eng->showSettingsMenu = true;
}
break;
case UI_ACTION_MOVE_WINDOW:
eng->isViewportFocused = false;
eng->isTopBarHovered = true;
DrawCircleSector((Vector2){eng->screenWidth - 152, 7}, 38, 90, 180, 8, GRAY_150);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
eng->isWindowMoving = true;
}
break;
case UI_ACTION_RESIZE_BOTTOM_BAR:
eng->isViewportFocused = false;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && eng->menuResizeButton == RESIZING_MENU_NONE)
{
eng->menuResizeButton = RESIZING_MENU_BOTTOMBAR;
}
break;
case UI_ACTION_RESIZE_SIDE_BAR:
eng->isViewportFocused = false;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && eng->menuResizeButton == RESIZING_MENU_NONE)
{
eng->menuResizeButton = RESIZING_MENU_SIDEBAR;
}
break;
case UI_ACTION_RESIZE_SIDE_BAR_MIDDLE:
eng->isViewportFocused = false;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && eng->menuResizeButton == 0)
{
eng->menuResizeButton = RESIZING_MENU_SIDEBAR_MIDDLE;
}
break;
case UI_ACTION_OPEN_FILE:
if(eng->isGameRunning){
break;
}
char tooltipText[MAX_FILE_TOOLTIP_SIZE];
strmac(tooltipText, MAX_FILE_TOOLTIP_SIZE, "File: %s\nSize: %d bytes", GetFileName(eng->uiElements[eng->hoveredUIElementIndex].name), GetFileLength(eng->uiElements[eng->hoveredUIElementIndex].name));
Rectangle tooltipRect = {eng->uiElements[eng->hoveredUIElementIndex].rect.pos.x + 10, eng->uiElements[eng->hoveredUIElementIndex].rect.pos.y - 61, MeasureTextEx(eng->font, tooltipText, 20, 0).x + 20, 60};
AddUIElement(eng, (UIElement){
.name = "FileTooltip",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {tooltipRect.x, tooltipRect.y}, .recSize = {tooltipRect.width, tooltipRect.height}, .roundness = 0, .roundSegments = 0},
.color = DARKGRAY,
.layer = 1,
.text = {.string = "", .textPos = {tooltipRect.x + 10, tooltipRect.y + 10}, .textSize = 20, .textSpacing = 0, .textColor = WHITE}});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_FILE_TOOLTIP_SIZE, "%s", tooltipText);
float currentTime = GetTime();
static int lastClickedFileIndex = -1;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
static float lastClickTime = 0;
if (currentTime - lastClickTime <= DOUBLE_CLICK_THRESHOLD && lastClickedFileIndex == eng->uiElements[eng->hoveredUIElementIndex].fileIndex)
{
FileType fileType = GetFileType(eng->currentPath, GetFileName(eng->uiElements[eng->hoveredUIElementIndex].name));
if (fileType == FILE_TYPE_CG)
{
*cgEd = InitEditorContext();
*graph = InitGraphContext();
PrepareCGFilePath(eng, eng->uiElements[eng->hoveredUIElementIndex].name);
LoadGraphFromFile(eng->CGFilePath, graph);
cgEd->graph = graph;
eng->viewportMode = VIEWPORT_CG_EDITOR;
}
else if (fileType == FILE_TYPE_IMAGE)
{
OpenFile(eng->uiElements[eng->hoveredUIElementIndex].name);
}
else if (fileType != FILE_TYPE_FOLDER)
{
if (eng->openFilesWithRapidEditor)
{
eng->delayFrames = true;
eng->viewportMode = VIEWPORT_TEXT_EDITOR;
ClearTextEditorContext(txEd);
if (!LoadFileInTextEditor(eng->uiElements[eng->hoveredUIElementIndex].name, txEd))
{
AddToLog(eng, "Failed to load file{T200}", LOG_LEVEL_ERROR);
}
}
else
{
OpenFile(eng->uiElements[eng->hoveredUIElementIndex].name);
}
}
else
{
strmac(eng->currentPath, MAX_FILE_PATH, "%s", eng->uiElements[eng->hoveredUIElementIndex].name);
UnloadDirectoryFiles(eng->files);
eng->files = LoadAndSortFiles(eng->currentPath);
if (!eng->files.paths || eng->files.count < 0)
{
AddToLog(eng, "Error loading files{E201}", LOG_LEVEL_ERROR);
EmergencyExit(eng, cgEd, intp, txEd);
}
}
}
lastClickTime = currentTime;
lastClickedFileIndex = eng->uiElements[eng->hoveredUIElementIndex].fileIndex;
}
static float holdDelta = 0;
static bool startedDragging = false;
Vector2 mouseDelta = GetMouseDelta();
if (IsMouseButtonDown(MOUSE_LEFT_BUTTON))
{
holdDelta += fabs(mouseDelta.x) + fabs(mouseDelta.y);
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
holdDelta = 0;
startedDragging = true;
}
if (holdDelta > 15 && startedDragging && eng->draggedFileIndex == -1)
{
eng->draggedFileIndex = eng->uiElements[eng->hoveredUIElementIndex].fileIndex;
}
}
else if (IsMouseButtonUp(MOUSE_LEFT_BUTTON))
{
startedDragging = false;
}
break;
case UI_ACTION_SHOW_VAR_TOOLTIP:
eng->isVarHovered = true;
AddUIElement(eng, (UIElement){
.name = "VarTooltip",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {eng->sideBarWidth, eng->uiElements[eng->hoveredUIElementIndex].rect.pos.y}, .recSize = {0, 40}, .roundness = 0.4f, .roundSegments = 4},
.color = DARKGRAY,
.layer = 1,
.text = {.textPos = {eng->sideBarWidth + 10, eng->uiElements[eng->hoveredUIElementIndex].rect.pos.y + 10}, .textSize = 20, .textSpacing = 0, .textColor = WHITE}});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_VARIABLE_TOOLTIP_SIZE, "%s %s = %s", ValueTypeToString(intp->values[eng->uiElements[eng->hoveredUIElementIndex].valueIndex].type), intp->values[eng->uiElements[eng->hoveredUIElementIndex].valueIndex].name, ValueToString(intp->values[eng->uiElements[eng->hoveredUIElementIndex].valueIndex]));
eng->uiElements[eng->uiElementCount - 1].rect.recSize.x = MeasureTextEx(eng->font, eng->uiElements[eng->uiElementCount - 1].text.string, 20, 0).x + 20;
break;
case UI_ACTION_CHANGE_VARS_FILTER:
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
eng->varsFilter++;
if (eng->varsFilter > 5)
{
eng->varsFilter = 0;
}
}
break;
case UI_ACTION_ENABLE_VIEWPORT_FULLSCREEN:
eng->isViewportFocused = false;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON))
{
eng->isViewportFullscreen = true;
}
break;
case UI_ACTION_SHOW_ERROR_CODE:
eng->isLogMessageHovered = true;
AddUIElement(eng, (UIElement){
.name = "LogErrorCode",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = (Vector2){eng->mousePos.x, eng->mousePos.y - 24}, .recSize = {50, 24}, .roundness = 0, .roundSegments = 0},
.color = GRAY_30,
.layer = 1,
.text = {.string = "", .textPos = (Vector2){eng->mousePos.x + 5, eng->mousePos.y - 22}, .textSize = 20, .textSpacing = 0, .textColor = RAPID_PURPLE}});
if (eng->uiElements[eng->hoveredUIElementIndex].name[strlen(eng->uiElements[eng->hoveredUIElementIndex].name) - 1] == '}')
{
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, 5, "%s", eng->uiElements[eng->hoveredUIElementIndex].name + strlen(eng->uiElements[eng->hoveredUIElementIndex].name) - 5);
}
else
{
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, 5, "%s", "D400");
}
break;
}
}
bool hasDrawnSpecialElements = false;
for (int i = 0; i < eng->uiElementCount; i++)
{
if ((eng->uiElements[i].layer == UI_LAYER_COUNT - 1 || i == eng->uiElementCount - 1) && !hasDrawnSpecialElements)
{
hasDrawnSpecialElements = true;
DrawRectangleLinesEx((Rectangle){0, 0, eng->screenWidth, eng->screenHeight}, 4.0f, WHITE);
DrawLineEx((Vector2){eng->screenWidth - 35, 15}, (Vector2){eng->screenWidth - 15, 35}, 2, WHITE);
DrawLineEx((Vector2){eng->screenWidth - 35, 35}, (Vector2){eng->screenWidth - 15, 15}, 2, WHITE);
DrawLineEx((Vector2){eng->screenWidth - 85, 25}, (Vector2){eng->screenWidth - 65, 25}, 2, WHITE);
Rectangle dst = {eng->screenWidth - 125, 27, 30, 30};
Vector2 origin = {dst.width / 2.0f, dst.height / 2.0f};
float rotation = eng->isSettingsButtonHovered ? sinf(GetTime() * 3.0f) * 100.0f : 0.0f;
DrawTexturePro(eng->settingsGear, (Rectangle){0, 0, eng->settingsGear.width, eng->settingsGear.height}, dst, origin, rotation, WHITE);
DrawTexture(eng->resizeButton, eng->screenWidth / 2 - 10, eng->screenHeight - eng->bottomBarHeight - 10, WHITE);
DrawTexturePro(eng->resizeButton, (Rectangle){0, 0, 20, 20}, (Rectangle){eng->sideBarWidth, (eng->screenHeight - eng->bottomBarHeight) / 2, 20, 20}, (Vector2){10, 10}, 90.0f, WHITE);
if (eng->sideBarWidth > 150)
{
DrawTexture(eng->resizeButton, eng->sideBarWidth / 2 - 10, eng->sideBarMiddleY - 10, WHITE);
}
if (eng->isGameRunning)
{
DrawTexturePro(eng->viewportFullscreenButton, (Rectangle){0, 0, eng->viewportFullscreenButton.width, eng->viewportFullscreenButton.height}, (Rectangle){eng->sideBarWidth + 8, 10, 50, 50}, (Vector2){0, 0}, 0, WHITE);
}
}
UIElement *el = &eng->uiElements[i];
switch (el->shape)
{
case UIRectangle:
DrawRectangleRounded((Rectangle){el->rect.pos.x, el->rect.pos.y, el->rect.recSize.x, el->rect.recSize.y}, el->rect.roundness, el->rect.roundSegments, el->color);
break;
case UICircle:
DrawCircleV(el->circle.center, el->circle.radius, el->color);
break;
case UILine:
DrawLineEx(el->line.startPos, el->line.endPos, el->line.thickness, el->color);
break;
case UIText:
default:
break;
}
if (el->text.string[0] != '\0')
{
DrawTextEx(eng->font, el->text.string, el->text.textPos, el->text.textSize, el->text.textSpacing, el->text.textColor);
}
}
EndTextureMode();
}
void BuildUITexture(EngineContext *eng, GraphContext *graph, CGEditorContext *cgEd, InterpreterContext *intp, RuntimeGraphContext *runtimeGraph, TextEditorContext *txEd)
{
eng->uiElementCount = 0;
if (eng->uiElements[eng->hoveredUIElementIndex].shape == 0)
{
AddUIElement(eng, (UIElement){
.name = "HoverBlink",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {eng->uiElements[eng->hoveredUIElementIndex].rect.pos.x, eng->uiElements[eng->hoveredUIElementIndex].rect.pos.y}, .recSize = {eng->uiElements[eng->hoveredUIElementIndex].rect.recSize.x, eng->uiElements[eng->hoveredUIElementIndex].rect.recSize.y}, .roundness = eng->uiElements[eng->hoveredUIElementIndex].rect.roundness, .roundSegments = eng->uiElements[eng->hoveredUIElementIndex].rect.roundSegments},
.color = eng->uiElements[eng->hoveredUIElementIndex].rect.hoverColor,
.layer = 2});
}
AddUIElement(eng, (UIElement){
.name = "SideBarVars",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {0, 0}, .recSize = {eng->sideBarWidth, eng->sideBarMiddleY}, .roundness = 0.0f, .roundSegments = 0},
.color = GRAY_28,
.layer = 0,
});
AddUIElement(eng, (UIElement){
.name = "SideBarLog",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {0, eng->sideBarMiddleY}, .recSize = {eng->sideBarWidth, eng->screenHeight - eng->bottomBarHeight}, .roundness = 0.0f, .roundSegments = 0},
.color = GRAY_15,
.layer = 0,
});
AddUIElement(eng, (UIElement){
.name = "SideBarMiddleLine",
.shape = UILine,
.action = UI_ACTION_NONE,
.line = {.startPos = {eng->sideBarWidth, 0}, .endPos = {eng->sideBarWidth, eng->screenHeight - eng->bottomBarHeight}, .thickness = 2},
.color = WHITE,
.layer = 0,
});
AddUIElement(eng, (UIElement){
.name = "SideBarFromViewportDividerLine",
.shape = UILine,
.action = UI_ACTION_NONE,
.line = {.startPos = {0, eng->sideBarMiddleY}, .endPos = {eng->sideBarWidth, eng->sideBarMiddleY}, .thickness = 2},
.color = WHITE,
.layer = 0,
});
Vector2 saveButtonPos = {
eng->sideBarHalfSnap ? eng->sideBarWidth - 70 : eng->sideBarWidth - 145,
eng->sideBarHalfSnap ? eng->sideBarMiddleY + 60 : eng->sideBarMiddleY + 15};
AddUIElement(eng, (UIElement){
.name = "SaveButton",
.shape = UIRectangle,
.action = UI_ACTION_SAVE_CG_FILE,
.rect = {.pos = saveButtonPos, .recSize = {64, 30}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = (eng->viewportMode == VIEWPORT_CG_EDITOR ? Fade(WHITE, 0.2f) : COLOR_TRANSPARENT)},
.color = GRAY_50,
.layer = 1,
.text = {.textPos = {cgEd->hasChanged ? saveButtonPos.x + 5 : saveButtonPos.x + 8, saveButtonPos.y + 5}, .textSize = 20, .textSpacing = 2, .textColor = (eng->viewportMode == VIEWPORT_CG_EDITOR ? WHITE : GRAY)},
});
if (cgEd->hasChanged)
{
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, 6, "Save*");
}
else
{
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, 5, "Save");
}
if (eng->viewportMode == VIEWPORT_GAME_SCREEN)
{
AddUIElement(eng, (UIElement){
.name = "StopButton",
.shape = UIRectangle,
.action = UI_ACTION_STOP_GAME,
.rect = {.pos = {eng->sideBarWidth - 70, eng->sideBarMiddleY + 15}, .recSize = {64, 30}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = Fade(WHITE, 0.4f)},
.color = RED,
.layer = 1,
.text = {.string = "Stop", .textPos = {eng->sideBarWidth - 62, eng->sideBarMiddleY + 20}, .textSize = 20, .textSpacing = 2, .textColor = WHITE},
});
}
else if (eng->wasBuilt && eng->viewportMode == VIEWPORT_CG_EDITOR)
{
AddUIElement(eng, (UIElement){
.name = "RunButton",
.shape = UIRectangle,
.action = UI_ACTION_RUN_GAME,
.rect = {.pos = {eng->sideBarWidth - 70, eng->sideBarMiddleY + 15}, .recSize = {64, 30}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = Fade(WHITE, 0.4f)},
.color = DARKGREEN,
.layer = 1,
.text = {.string = "Run", .textPos = {eng->sideBarWidth - 56, eng->sideBarMiddleY + 20}, .textSize = 20, .textSpacing = 2, .textColor = WHITE},
});
}
else
{
AddUIElement(eng, (UIElement){
.name = "BuildButton",
.shape = UIRectangle,
.action = UI_ACTION_BUILD_GRAPH,
.rect = {.pos = {eng->sideBarWidth - 70, eng->sideBarMiddleY + 15}, .recSize = {64, 30}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = ((!cgEd->hasChanged && eng->viewportMode == VIEWPORT_CG_EDITOR) ? Fade(WHITE, 0.2f) : COLOR_TRANSPARENT)},
.color = GRAY_50,
.layer = 1,
.text = {.string = "Build", .textPos = {eng->sideBarWidth - 64, eng->sideBarMiddleY + 20}, .textSize = 20, .textSpacing = 2, .textColor = ((!cgEd->hasChanged && eng->viewportMode == VIEWPORT_CG_EDITOR) ? WHITE : GRAY)},
});
}
int logY = eng->screenHeight - eng->bottomBarHeight - 30;
for (int i = eng->logs.count - 1; i >= 0 && logY > eng->sideBarMiddleY + 60 + eng->sideBarHalfSnap * 40; i--)
{
int batchCount = 0;
for (int j = i - 1; j >= 0; j--)
{
if (strcmp(eng->logs.entries[i].message + 8, eng->logs.entries[j].message + 8) == 0)
{
batchCount++;
}
else
{
break;
}
}
char logMessage[MAX_LOG_MESSAGE_SIZE];
if (batchCount == 0)
{
strmac(logMessage, MAX_LOG_MESSAGE_SIZE, "%s", eng->logs.entries[i].message);
}
else
{
strmac(logMessage, MAX_LOG_MESSAGE_SIZE, "[%d]%s", batchCount + 1, eng->logs.entries[i].message);
i -= batchCount;
}
if (eng->sideBarHalfSnap)
{
logMessage[5] = '\0';
}
else
{
if (eng->logs.entries[i].level != LOG_LEVEL_DEBUG)
{
logMessage[strlen(logMessage) - 6] = '\0';
}
strmac(logMessage, MAX_LOG_MESSAGE_SIZE, "%s", AddEllipsis(eng->font, logMessage, 24, eng->sideBarWidth - 40, false));
}
Color logColor;
switch (eng->logs.entries[i].level)
{
case LOG_LEVEL_NORMAL:
logColor = WHITE;
break;
case LOG_LEVEL_WARNING:
logColor = YELLOW;
break;
case LOG_LEVEL_ERROR:
logColor = RED;
break;
case LOG_LEVEL_DEBUG:
logColor = PURPLE;
break;
case LOG_LEVEL_SUCCESS:
logColor = GREEN;
break;
default:
logColor = WHITE;
break;
}
AddUIElement(eng, (UIElement){
.name = "LogBackground",
.shape = UIRectangle,
.action = UI_ACTION_SHOW_ERROR_CODE,
.rect = {.pos = {10, logY}, .recSize = {MeasureTextEx(eng->font, logMessage, 20, 2.0f).x, 20}, .roundness = 0, .roundSegments = 0, .hoverColor = COLOR_LOG_HOVER},
.color = COLOR_TRANSPARENT,
.layer = 1,
});
strmac(eng->uiElements[eng->uiElementCount - 1].name, MAX_LOG_MESSAGE_SIZE, "%s", eng->logs.entries[i].message);
AddUIElement(eng, (UIElement){
.name = "LogText",
.shape = UIText,
.action = UI_ACTION_NONE,
.text = {.textPos = {10, logY}, .textSize = 20, .textSpacing = 2, .textColor = logColor},
.layer = 0});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_LOG_MESSAGE_SIZE, logMessage);
logY -= 25;
}
if (eng->sideBarMiddleY > 45)
{
AddUIElement(eng, (UIElement){
.name = "VarsFilterShowText",
.shape = UIText,
.action = UI_ACTION_NONE,
.text = {.string = "Show:", .textPos = {eng->sideBarWidth - 155, 20}, .textSize = 20, .textSpacing = 2, .textColor = WHITE},
.layer = 0});
char varsFilterText[10];
Color varFilterColor;
switch (eng->varsFilter)
{
case VAR_FILTER_ALL:
strmac(varsFilterText, 4, "All");
varFilterColor = WHITE;
break;
case VAR_FILTER_NUMBERS:
strmac(varsFilterText, 5, "Nums");
varFilterColor = COLOR_VARS_FILTER_NUMS;
break;
case VAR_FILTER_STRINGS:
strmac(varsFilterText, 8, "Strings");
varFilterColor = COLOR_VARS_FILTER_STRINGS;
break;
case VAR_FILTER_BOOLS:
strmac(varsFilterText, 6, "Bools");
varFilterColor = COLOR_VARS_FILTER_BOOLS;
break;
case VAR_FILTER_COLORS:
strmac(varsFilterText, 7, "Colors");
varFilterColor = COLOR_VARS_FILTER_COLORS;
break;
case VAR_FILTER_SPRITES:
strmac(varsFilterText, 8, "Sprites");
varFilterColor = COLOR_VARS_FILTER_SPRITES;
break;
default:
eng->varsFilter = 0;
strmac(varsFilterText, 4, "All");
varFilterColor = WHITE;
break;
}
AddUIElement(eng, (UIElement){
.name = "VarsFilterButton",
.shape = UIRectangle,
.action = UI_ACTION_CHANGE_VARS_FILTER,
.rect = {.pos = {eng->sideBarWidth - 85 + eng->sideBarHalfSnap * 15, 15}, .recSize = {78 - eng->sideBarHalfSnap * 15, 30}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = Fade(WHITE, 0.2f)},
.color = GRAY_50,
.layer = 1,
.text = {.textPos = {eng->sideBarWidth - 85 + (80 - MeasureTextEx(eng->font, varsFilterText, 20 - eng->sideBarHalfSnap * 3, 1).x) / 2 + eng->sideBarHalfSnap * 5, 20 + eng->sideBarHalfSnap * 2}, .textSize = 20 - eng->sideBarHalfSnap * 3, .textSpacing = 1, .textColor = varFilterColor},
});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, 10, varsFilterText);
}
int varsY = 60;
int ellipsisSize = MeasureTextEx(eng->font, "...", 24, 2).x;
for (int i = 0; i < (eng->isGameRunning ? intp->valueCount : graph->variablesCount) && varsY < eng->sideBarMiddleY - 40; i++)
{
if (eng->isGameRunning)
{
if (!intp->values[i].isVariable)
{
continue;
}
}
else
{
if (i == 0)
{
continue;
}
}
Color varColor;
switch (eng->isGameRunning ? intp->values[i].type : graph->variableTypes[i])
{
case VAL_NUMBER:
case NODE_CREATE_NUMBER:
varColor = COLOR_VAR_NUMBER;
if (eng->varsFilter != VAR_FILTER_NUMBERS && eng->varsFilter != VAR_FILTER_ALL)
{
continue;
}
break;
case VAL_STRING:
case NODE_CREATE_STRING:
varColor = COLOR_VAR_STRING;
if (eng->varsFilter != VAR_FILTER_STRINGS && eng->varsFilter != VAR_FILTER_ALL)
{
continue;
}
break;
case VAL_BOOL:
case NODE_CREATE_BOOL:
varColor = COLOR_VAR_BOOL;
if (eng->varsFilter != VAR_FILTER_BOOLS && eng->varsFilter != VAR_FILTER_ALL)
{
continue;
}
break;
case VAL_COLOR:
case NODE_CREATE_COLOR:
varColor = COLOR_VAR_COLOR;
if (eng->varsFilter != VAR_FILTER_COLORS && eng->varsFilter != VAR_FILTER_ALL)
{
continue;
}
break;
case VAL_SPRITE:
case NODE_CREATE_SPRITE:
varColor = COLOR_VAR_SPRITE;
if (eng->varsFilter != VAR_FILTER_SPRITES && eng->varsFilter != VAR_FILTER_ALL)
{
continue;
}
break;
default:
varColor = LIGHTGRAY;
}
AddUIElement(eng, (UIElement){
.name = "Variable Background",
.shape = UIRectangle,
.action = eng->isGameRunning ? UI_ACTION_SHOW_VAR_TOOLTIP : UI_ACTION_NONE,
.rect = {.pos = {15, varsY - 5}, .recSize = {eng->sideBarWidth - 25, 35}, .roundness = 0.6f, .roundSegments = 4, .hoverColor = Fade(WHITE, 0.2f)},
.color = GRAY_59,
.layer = 1,
.valueIndex = i});
char varName[MAX_VARIABLE_NAME_SIZE];
strmac(varName, MAX_VARIABLE_NAME_SIZE, "%s", eng->isGameRunning ? intp->values[i].name : graph->variables[i]);
bool textHidden = false;
if (eng->sideBarHalfSnap || ellipsisSize > eng->sideBarWidth - 80 - 20)
{
textHidden = true;
varName[0] = '\0';
}
else
{
strmac(varName, MAX_VARIABLE_NAME_SIZE, "%s", AddEllipsis(eng->font, varName, 24, eng->sideBarWidth - 80, false));
}
AddUIElement(eng, (UIElement){
.name = "Variable",
.shape = UICircle,
.action = UI_ACTION_NONE,
.circle = {.center = (Vector2){textHidden ? eng->sideBarWidth / 2 + 3 : eng->sideBarWidth - 25, varsY + 14}, .radius = 8},
.color = varColor,
.text = {.textPos = {25, varsY}, .textSize = 24, .textSpacing = 2, .textColor = WHITE},
.layer = 2});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_VARIABLE_NAME_SIZE, "%s", varName);
varsY += 40;
}
AddUIElement(eng, (UIElement){
.name = "BottomBar",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {0, eng->screenHeight - eng->bottomBarHeight}, .recSize = {eng->screenWidth, eng->bottomBarHeight}, .roundness = 0.0f, .roundSegments = 0},
.color = GRAY_28,
.layer = 0,
});
AddUIElement(eng, (UIElement){
.name = "BottomBarFromViewportDividerLine",
.shape = UILine,
.action = UI_ACTION_NONE,
.line = {.startPos = {0, eng->screenHeight - eng->bottomBarHeight}, .endPos = {eng->screenWidth, eng->screenHeight - eng->bottomBarHeight}, .thickness = 2},
.color = WHITE,
.layer = 0,
});
AddUIElement(eng, (UIElement){
.name = "BackButton",
.shape = UIRectangle,
.action = UI_ACTION_CURRENT_PATH_BACK,
.rect = {.pos = {30, eng->screenHeight - eng->bottomBarHeight + 10}, .recSize = {65, 30}, .roundness = 0, .roundSegments = 0, .hoverColor = Fade(WHITE, 0.2f)},
.color = GRAY_40,
.layer = 1,
.text = {.string = "Back", .textPos = {39, eng->screenHeight - eng->bottomBarHeight + 14}, .textSize = 22, .textSpacing = 0, .textColor = WHITE}});
AddUIElement(eng, (UIElement){
.name = "RefreshButton",
.shape = UIRectangle,
.action = UI_ACTION_REFRESH_FILES,
.rect = {.pos = {110, eng->screenHeight - eng->bottomBarHeight + 10}, .recSize = {100, 30}, .roundness = 0, .roundSegments = 0, .hoverColor = Fade(WHITE, 0.2f)},
.color = GRAY_40,
.layer = 1,
.text = {.string = "Refresh", .textPos = {123, eng->screenHeight - eng->bottomBarHeight + 14}, .textSize = 22, .textSpacing = 0, .textColor = WHITE}});
AddUIElement(eng, (UIElement){
.name = "CurrentPath",
.shape = UIText,
.action = UI_ACTION_NONE,
.color = COLOR_TRANSPARENT,
.layer = 0,
.text = {.string = "", .textPos = {230, eng->screenHeight - eng->bottomBarHeight + 15}, .textSize = 22, .textSpacing = 2, .textColor = WHITE}});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_FILE_PATH, "%s", eng->currentPath);
int xOffset = 50;
int yOffset = eng->screenHeight - eng->bottomBarHeight + 70;
for (int i = 0; i < eng->files.count; i++)
{
if (i > MAX_UI_ELEMENTS / 2)
{
break;
}
const char *fileName = GetFileName(eng->files.paths[i]);
if (fileName[0] == '.')
{
continue;
}
Color fileOutlineColor;
Color fileTextColor;
switch (GetFileType(eng->currentPath, fileName))
{
case FILE_TYPE_FOLDER:
fileOutlineColor = COLOR_FILE_TYPE_FOLDER_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_FOLDER_TEXT;
break;
case FILE_TYPE_CG:
fileOutlineColor = COLOR_FILE_TYPE_CG_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_CG_TEXT;
break;
case FILE_TYPE_CONFIG:
fileOutlineColor = COLOR_FILE_TYPE_CONFIG_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_CONFIG_TEXT;
break;
case FILE_TYPE_IMAGE:
fileOutlineColor = COLOR_FILE_TYPE_IMAGE_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_IMAGE_TEXT;
break;
case FILE_TYPE_OTHER:
fileOutlineColor = COLOR_FILE_TYPE_OTHER_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_OTHER_TEXT;
break;
default:
AddToLog(eng, "Out of bounds enum{O201}", LOG_LEVEL_ERROR);
fileOutlineColor = COLOR_FILE_UNKNOWN;
fileTextColor = COLOR_FILE_UNKNOWN;
break;
}
char buff[MAX_FILE_NAME];
strmac(buff, MAX_FILE_NAME, "%s", fileName);
int maxSize = 130;
const char *ext = GetFileExtension(fileName);
int extLen = ext ? strlen(ext) : 0;
char *namePart = buff;
if (ext)
{
buff[strlen(buff) - extLen] = '\0';
namePart = buff;
}
int nameLen = strlen(namePart);
int shortened = 0;
for (int j = nameLen; j >= 0; j--)
{
namePart[j] = '\0';
const char *displayStr;
if (shortened || j < nameLen)
{
if (ext)
displayStr = TextFormat("%s..%s", namePart, ext);
else
displayStr = TextFormat("%s..", namePart);
}
else
{
if (ext)
displayStr = TextFormat("%s%s", namePart, ext);
else
displayStr = namePart;
}
if (MeasureTextEx(eng->font, displayStr, 22, 0).x <= maxSize)
{
shortened = (j < nameLen);
break;
}
}
if (shortened)
{
if (ext)
strmac(buff, MAX_FILE_NAME, "%s..%s", namePart, ext);
else
strmac(buff, MAX_FILE_NAME, "%s..", namePart);
}
else if (ext)
{
strmac(buff, MAX_FILE_NAME, "%s%s", namePart, ext);
}
AddUIElement(eng, (UIElement){
.name = "FileOutline",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {xOffset - 2, yOffset - 2}, .recSize = {154, 64}, .roundness = 0.5f, .roundSegments = 8},
.color = fileOutlineColor,
.layer = 0});
AddUIElement(eng, (UIElement){
.name = "File",
.shape = UIRectangle,
.action = UI_ACTION_OPEN_FILE,
.rect = {.pos = {xOffset, yOffset}, .recSize = {150, 60}, .roundness = 0.4f, .roundSegments = 8, .hoverColor = Fade(WHITE, 0.2f)},
.color = GRAY_40,
.layer = 1,
.text = {.string = "", .textPos = {xOffset + 10, yOffset + 18}, .textSize = 22, .textSpacing = 0, .textColor = fileTextColor},
.fileIndex = i});
strmac(eng->uiElements[eng->uiElementCount - 1].name, MAX_FILE_PATH, "%s", eng->files.paths[i]);
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_FILE_PATH, "%s", buff);
xOffset += 180;
if (xOffset + 155 >= eng->screenWidth)
{
if (yOffset + 65 >= eng->screenHeight)
{
break;
}
xOffset = 50;
yOffset += 120;
}
}
if (eng->draggedFileIndex != -1)
{
Color fileOutlineColor;
Color fileTextColor;
switch (GetFileType(eng->currentPath, GetFileName(eng->files.paths[eng->draggedFileIndex])))
{
case FILE_TYPE_FOLDER:
fileOutlineColor = COLOR_FILE_TYPE_FOLDER_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_FOLDER_TEXT;
break;
case FILE_TYPE_CG:
fileOutlineColor = COLOR_FILE_TYPE_CG_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_CG_TEXT;
break;
case FILE_TYPE_CONFIG:
fileOutlineColor = COLOR_FILE_TYPE_CONFIG_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_CONFIG_TEXT;
break;
case FILE_TYPE_IMAGE:
fileOutlineColor = COLOR_FILE_TYPE_IMAGE_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_IMAGE_TEXT;
break;
case FILE_TYPE_OTHER:
fileOutlineColor = COLOR_FILE_TYPE_OTHER_OUTLINE;
fileTextColor = COLOR_FILE_TYPE_OTHER_TEXT;
break;
default:
AddToLog(eng, "Out of bounds enum{O201}", LOG_LEVEL_ERROR);
fileOutlineColor = COLOR_FILE_UNKNOWN;
fileTextColor = COLOR_FILE_UNKNOWN;
break;
}
char fileName[MAX_FILE_NAME];
strmac(fileName, MAX_FILE_NAME, "%s", GetFileName(eng->files.paths[eng->draggedFileIndex]));
int fileNameSize = MeasureTextEx(eng->font, fileName, 22, 0).x;
fileOutlineColor.a -= 50;
AddUIElement(eng, (UIElement){
.name = "DraggedFileOutline",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = {eng->mousePos.x - 73, eng->mousePos.y - 28}, .recSize = {154 > fileNameSize ? 154 : fileNameSize + 28, 64}, .roundness = 0.5f, .roundSegments = 8},
.color = fileOutlineColor,
.layer = 4});
AddUIElement(eng, (UIElement){
.name = "DraggedFile",
.shape = UIRectangle,
.action = UI_ACTION_NONE,
.rect = {.pos = (Vector2){eng->mousePos.x - 71, eng->mousePos.y - 26}, .recSize = {150 > fileNameSize ? 150 : fileNameSize + 24, 60}, .roundness = 0.4f, .roundSegments = 8, .hoverColor = COLOR_TRANSPARENT},
.color = COLOR_DRAGGED_FILE_BACKGROUND,
.layer = 4,
.text = {.string = "", .textPos = (Vector2){eng->mousePos.x - 61, eng->mousePos.y - 8}, .textSize = 22, .textSpacing = 0, .textColor = fileTextColor}});
strmac(eng->uiElements[eng->uiElementCount - 1].text.string, MAX_FILE_NAME, "%s", fileName);
}
AddUIElement(eng, (UIElement){
.name = "TopBarClose",
.shape = UIRectangle,
.action = UI_ACTION_CLOSE_WINDOW,
.rect = {.pos = {eng->screenWidth - 50, 0}, .recSize = {50, 50}, .roundness = 0.0f, .roundSegments = 0, .hoverColor = RED},
.color = COLOR_TRANSPARENT,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "TopBarMinimize",
.shape = UIRectangle,
.action = UI_ACTION_MINIMIZE_WINDOW,
.rect = {.pos = {eng->screenWidth - 100, 0}, .recSize = {50, 50}, .roundness = 0.0f, .roundSegments = 0, .hoverColor = GRAY},
.color = COLOR_TRANSPARENT,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "TopBarSettings",
.shape = UIRectangle,
.action = UI_ACTION_OPEN_SETTINGS,
.rect = {.pos = {eng->screenWidth - 150, 0}, .recSize = {50, 50}, .roundness = 0.0f, .roundSegments = 0, .hoverColor = GRAY},
.color = COLOR_TRANSPARENT,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "TopBarMoveWindow",
.shape = UIRectangle,
.action = UI_ACTION_MOVE_WINDOW,
.rect = {.pos = {eng->screenWidth - 200, 0}, .recSize = {50, 50}, .roundness = 0.0f, .roundSegments = 0, .hoverColor = COLOR_TRANSPARENT},
.color = COLOR_TRANSPARENT,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "BottomBarResizeButton",
.shape = UICircle,
.action = UI_ACTION_RESIZE_BOTTOM_BAR,
.circle = {.center = (Vector2){eng->screenWidth / 2, eng->screenHeight - eng->bottomBarHeight}, .radius = 10},
.color = COLOR_RESIZE_BUTTON,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "SideBarResizeButton",
.shape = UICircle,
.action = UI_ACTION_RESIZE_SIDE_BAR,
.circle = {.center = (Vector2){eng->sideBarWidth, (eng->screenHeight - eng->bottomBarHeight) / 2}, .radius = 10},
.color = COLOR_RESIZE_BUTTON,
.layer = 1,
});
AddUIElement(eng, (UIElement){
.name = "SideBarMiddleResizeButton",
.shape = UICircle,
.action = UI_ACTION_RESIZE_SIDE_BAR_MIDDLE,
.circle = {.center = (Vector2){eng->sideBarWidth / 2, eng->sideBarMiddleY}, .radius = 10},
.color = COLOR_RESIZE_BUTTON,
.layer = 1,
});
if (eng->isGameRunning)
{
AddUIElement(eng, (UIElement){
.name = "ViewportFullscreenButton",
.shape = UIRectangle,
.action = UI_ACTION_ENABLE_VIEWPORT_FULLSCREEN,
.rect = {.pos = {eng->sideBarWidth + 8, 10}, .recSize = {50, 50}, .roundness = 0.2f, .roundSegments = 4, .hoverColor = GRAY},
.color = GRAY_60,
.layer = 1,
});
}
CountingSortByLayer(eng);
DrawUIElements(eng, graph, cgEd, intp, runtimeGraph, txEd);
}
bool HandleUICollisions(EngineContext *eng, GraphContext *graph, InterpreterContext *intp, CGEditorContext *cgEd, RuntimeGraphContext *runtimeGraph, TextEditorContext *txEd)
{
if (eng->uiElementCount == 0)
{
eng->hoveredUIElementIndex = 0;
return true;
}
if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_S) && !IsKeyDown(KEY_LEFT_SHIFT) && eng->viewportMode == VIEWPORT_CG_EDITOR)
{
eng->isKeyboardShortcutActivated = true;
if (eng->isSoundOn)
{
PlaySound(eng->saveSound);
}
if (SaveGraphToFile(eng->CGFilePath, graph) == 0)
{
AddToLog(eng, "Saved successfully{C300}", LOG_LEVEL_SUCCESS);
cgEd->hasChanged = false;
}
else
{
AddToLog(eng, "Error saving changes!{C101}", LOG_LEVEL_WARNING);
}
}
else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_R))
{
eng->isKeyboardShortcutActivated = true;
if (cgEd->hasChanged)
{
AddToLog(eng, "Project not saved!{I102}", LOG_LEVEL_WARNING);
}
else if (!eng->wasBuilt)
{
AddToLog(eng, "Project has not been built!{I103}", LOG_LEVEL_WARNING);
}
else if (eng->isGameRunning)
{
AddToLog(eng, "Project already running!{I107}", LOG_LEVEL_WARNING);
}
else
{
eng->viewportMode = VIEWPORT_GAME_SCREEN;
eng->isGameRunning = true;
intp->isFirstFrame = true;
eng->delayFrames = true;
}
}
else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_E))
{
eng->isKeyboardShortcutActivated = true;
cgEd->delayFrames = true;
eng->delayFrames = true;
eng->viewportMode = VIEWPORT_CG_EDITOR;
cgEd->isFirstFrame = true;
eng->isGameRunning = false;
eng->wasBuilt = false;
eng->isViewportFullscreen = false;
FreeInterpreterContext(intp);
}
else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_T))
{
eng->isKeyboardShortcutActivated = true;
if (txEd->isFileOpened)
{
eng->viewportMode = VIEWPORT_TEXT_EDITOR;
eng->delayFrames = true;
}
}
else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_B))
{
eng->isKeyboardShortcutActivated = true;
if (cgEd->hasChanged)
{
AddToLog(eng, "Project not saved!{I102}", LOG_LEVEL_WARNING);
}
else if (eng->viewportMode == VIEWPORT_CG_EDITOR)
{
*runtimeGraph = ConvertToRuntimeGraph(graph, intp);
intp->runtimeGraph = runtimeGraph;
if (intp->buildFailed)
{
EmergencyExit(eng, cgEd, intp, txEd);
}
else if (intp->buildErrorOccured || runtimeGraph == NULL)
{
if (intp->newLogMessage)
{
for (int i = 0; i < intp->logMessageCount; i++)
{
AddToLog(eng, intp->logMessages[i], intp->logMessageLevels[i]);
}
intp->newLogMessage = false;
intp->logMessageCount = 0;
eng->delayFrames = true;
}
AddToLog(eng, "Build failed{I100}", LOG_LEVEL_WARNING);
intp->buildErrorOccured = false;
}
else
{
AddToLog(eng, "Build successful{I300}", LOG_LEVEL_NORMAL);
eng->wasBuilt = true;
}
eng->delayFrames = true;
}
}
else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_S) && IsKeyDown(KEY_LEFT_SHIFT))
{
eng->isKeyboardShortcutActivated = true;
cgEd->delayFrames = true;
eng->delayFrames = true;
eng->viewportMode = VIEWPORT_CG_EDITOR;
cgEd->isFirstFrame = true;
eng->isGameRunning = false;
eng->wasBuilt = false;
eng->isViewportFullscreen = false;
FreeInterpreterContext(intp);
}
if (IsKeyPressed(KEY_ESCAPE))
{
eng->isKeyboardShortcutActivated = true;
eng->isViewportFullscreen = false;
}
if (eng->draggedFileIndex != -1)
{
if (IsMouseButtonUp(MOUSE_LEFT_BUTTON))
{
size_t len = strlen(eng->projectPath);
if (strncmp(eng->projectPath, eng->files.paths[eng->draggedFileIndex], len) == 0)
{
const char *remainder = eng->files.paths[eng->draggedFileIndex] + len;
if (*remainder == PATH_SEPARATOR)
{
remainder++;
}
if (*remainder != '\0')
{
strmac(cgEd->droppedFilePath, MAX_FILE_PATH, "%s", remainder);
}
if (eng->isViewportFocused)
{
cgEd->hasDroppedFile = true;
}
}
else
{
cgEd->hasDroppedFile = false;
}
eng->draggedFileIndex = -1;
}
eng->delayFrames = true;
eng->hoveredUIElementIndex = -1;
return false;
}
if (eng->isWindowMoving)
{
Vector2 winPos = GetWindowPosition();
Vector2 screenMousePos = {winPos.x + eng->mousePos.x, winPos.y + eng->mousePos.y};
int newX = screenMousePos.x - eng->screenWidth + 175;
int newY = screenMousePos.y - 25;
if (IsMouseButtonUp(MOUSE_LEFT_BUTTON))
{
if (screenMousePos.y < 10)
{
newX = 0;
newY = 0;
eng->screenWidth = eng->maxScreenWidth;
eng->screenHeight = eng->maxScreenHeight;
}
else if (screenMousePos.x < 10)
{
newX = 0;
newY = 0;
eng->screenWidth = eng->maxScreenWidth / 3;
eng->screenHeight = eng->maxScreenHeight;
}
else if (screenMousePos.x > eng->maxScreenWidth - 10)
{
newX = eng->maxScreenWidth * 2 / 3;
newY = 0;
eng->screenWidth = eng->maxScreenWidth / 3;
eng->screenHeight = eng->maxScreenHeight;
}
else if (screenMousePos.y > eng->maxScreenHeight - 10)
{
newY = eng->maxScreenHeight - 51;
}
SetWindowPosition(newX, newY);
SetWindowSize(eng->screenWidth, eng->screenHeight);
eng->isWindowMoving = false;
}
else
{
SetWindowPosition(newX, newY);
}
}
static Vector2 totalWindowResizeDelta;
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && !eng->isWindowMoving && !eng->isTopBarHovered)
{
if (CheckCollisionPointLine(eng->mousePos, (Vector2){0, 5}, (Vector2){eng->screenWidth, 5}, 10.0f))
{
eng->windowResizeButton = RESIZING_WINDOW_NORTH;
}
else if (CheckCollisionPointLine(eng->mousePos, (Vector2){0, eng->screenHeight - 5}, (Vector2){eng->screenWidth, eng->screenHeight - 5}, 10.0f))
{
eng->windowResizeButton = RESIZING_WINDOW_SOUTH;
}
else if (CheckCollisionPointLine(eng->mousePos, (Vector2){eng->screenWidth - 5, 0}, (Vector2){eng->screenWidth - 5, eng->screenHeight}, 10.0f))
{
eng->windowResizeButton = RESIZING_WINDOW_EAST;
}
else if (CheckCollisionPointLine(eng->mousePos, (Vector2){5, 0}, (Vector2){5, eng->screenHeight}, 10.0f))
{
eng->windowResizeButton = RESIZING_WINDOW_WEST;
}
totalWindowResizeDelta = (Vector2){0, 0};
}
else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && eng->windowResizeButton != RESIZING_WINDOW_NONE)
{
totalWindowResizeDelta = Vector2Add(totalWindowResizeDelta, GetMouseDelta());
}
else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON) && eng->windowResizeButton != RESIZING_WINDOW_NONE)
{
Vector2 windowPosition = GetWindowPosition();
switch (eng->windowResizeButton)
{
case RESIZING_WINDOW_NORTH:
if (windowPosition.y + totalWindowResizeDelta.y < 0)
{
totalWindowResizeDelta.y = -windowPosition.y;
}
eng->screenHeight -= totalWindowResizeDelta.y;
if (eng->screenHeight > eng->maxScreenHeight)
{
eng->screenHeight = eng->maxScreenHeight;
}
else if (eng->screenHeight <= MIN_WINDOW_HEIGHT)
{
eng->screenHeight = MIN_WINDOW_HEIGHT;
}
SetWindowSize(eng->screenWidth, eng->screenHeight);
SetWindowPosition(GetWindowPosition().x, windowPosition.y + totalWindowResizeDelta.y);
break;
case RESIZING_WINDOW_SOUTH:
eng->screenHeight += totalWindowResizeDelta.y;
if (windowPosition.y + eng->screenHeight > eng->maxScreenHeight)
{
eng->screenHeight = eng->maxScreenHeight - windowPosition.y;
}
if (eng->screenHeight > eng->maxScreenHeight)
{
eng->screenHeight = eng->maxScreenHeight;
}
else if (eng->screenHeight <= MIN_WINDOW_HEIGHT)
{
eng->screenHeight = MIN_WINDOW_HEIGHT;
}
SetWindowSize(eng->screenWidth, eng->screenHeight);
break;
case RESIZING_WINDOW_EAST:
eng->screenWidth += totalWindowResizeDelta.x;
if (windowPosition.x + eng->screenWidth > eng->maxScreenWidth)
{
eng->screenWidth = eng->maxScreenWidth - windowPosition.x;
}
if (eng->screenWidth > eng->maxScreenWidth)
{
eng->screenWidth = eng->maxScreenWidth;
}
else if (eng->screenWidth <= MIN_WINDOW_WIDTH)
{
eng->screenWidth = MIN_WINDOW_WIDTH;
}
SetWindowSize(eng->screenWidth, eng->screenHeight);
break;
case RESIZING_WINDOW_WEST:
if (windowPosition.x + totalWindowResizeDelta.x < 0)
{
totalWindowResizeDelta.x = -windowPosition.x;
}
eng->screenWidth -= totalWindowResizeDelta.x;
if (eng->screenWidth > eng->maxScreenWidth)
{
eng->screenWidth = eng->maxScreenWidth;
}
else if (eng->screenWidth <= MIN_WINDOW_WIDTH)
{
eng->screenWidth = MIN_WINDOW_WIDTH;
}
SetWindowSize(eng->screenWidth, eng->screenHeight);
SetWindowPosition(windowPosition.x + totalWindowResizeDelta.x, windowPosition.y);
break;
default:
AddToLog(eng, "Out of bounds enum{O201}", LOG_ERROR);
}
eng->windowResizeButton = RESIZING_WINDOW_NONE;
}
if (eng->menuResizeButton != RESIZING_MENU_NONE)
{
if (IsMouseButtonUp(MOUSE_LEFT_BUTTON))
{
eng->menuResizeButton = RESIZING_MENU_NONE;
}
eng->hasResizedBar = true;
Vector2 mouseDelta = GetMouseDelta();
switch (eng->menuResizeButton)
{
case RESIZING_MENU_NONE:
break;
case RESIZING_MENU_BOTTOMBAR:
eng->bottomBarHeight -= mouseDelta.y;
if (eng->bottomBarHeight <= 150)
{
eng->bottomBarHeight = 150;
}
else if (eng->bottomBarHeight < 3 * eng->screenHeight / 4)
{
eng->sideBarMiddleY += mouseDelta.y / 2;
}
else
{
eng->bottomBarHeight = 3 * eng->screenHeight / 4;
}
break;
case RESIZING_MENU_SIDEBAR:
eng->sideBarWidth += mouseDelta.x;
if (eng->sideBarWidth < 160 && mouseDelta.x < 0)
{
eng->sideBarWidth = 80;
eng->sideBarHalfSnap = true;
}
else if (eng->sideBarWidth > 110)
{
eng->sideBarHalfSnap = false;
if (eng->sideBarWidth >= 3 * eng->screenWidth / 4)
{
eng->sideBarWidth = 3 * eng->screenWidth / 4;
}
}
break;
case RESIZING_MENU_SIDEBAR_MIDDLE:
eng->sideBarMiddleY += mouseDelta.y;
break;
default:
break;
}
}
if (eng->sideBarMiddleY >= eng->screenHeight - eng->bottomBarHeight - 60 - eng->sideBarHalfSnap * 40)
{
eng->sideBarMiddleY = eng->screenHeight - eng->bottomBarHeight - 60 - eng->sideBarHalfSnap * 40;
}
else if (eng->sideBarMiddleY <= 5)
{
eng->sideBarMiddleY = 5;
}
if (eng->screenWidth < eng->screenHeight || eng->screenWidth < 400)
{
eng->sideBarWidth = 80;
eng->sideBarHalfSnap = true;
}
if (eng->bottomBarHeight >= 3 * eng->screenHeight / 4)
{
eng->bottomBarHeight = 3 * eng->screenHeight / 4;
}
for (int i = 0; i < eng->uiElementCount; i++)
{
if (eng->uiElements[i].layer != 0)
{
if (eng->uiElements[i].shape == UIRectangle && CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->uiElements[i].rect.pos.x, eng->uiElements[i].rect.pos.y, eng->uiElements[i].rect.recSize.x, eng->uiElements[i].rect.recSize.y}))
{
eng->hoveredUIElementIndex = i;
return true;
}
else if (eng->uiElements[i].shape == UICircle && CheckCollisionPointCircle(eng->mousePos, eng->uiElements[i].circle.center, eng->uiElements[i].circle.radius))
{
eng->hoveredUIElementIndex = i;
return true;
}
}
}
eng->hoveredUIElementIndex = -1;
return false;
}
void ContextChangePerFrame(EngineContext *eng)
{
eng->mousePos = GetMousePosition();
eng->isViewportFocused = (eng->hoveredUIElementIndex == -1 || eng->uiElements[eng->hoveredUIElementIndex].action == UI_ACTION_NONE) && CheckCollisionPointRec(eng->mousePos, (Rectangle){eng->sideBarWidth, 0, eng->screenWidth - eng->sideBarWidth, eng->screenHeight - eng->bottomBarHeight});
eng->screenWidth = GetScreenWidth();
eng->screenHeight = GetScreenHeight();
eng->viewportWidth = eng->screenWidth - (eng->isViewportFullscreen ? 0 : eng->sideBarWidth);
eng->viewportHeight = eng->screenHeight - (eng->isViewportFullscreen ? 0 : eng->bottomBarHeight);
if (eng->prevScreenWidth != eng->screenWidth || eng->prevScreenHeight != eng->screenHeight || eng->hasResizedBar)
{
eng->prevScreenWidth = eng->screenWidth;
eng->prevScreenHeight = eng->screenHeight;
eng->hasResizedBar = false;
eng->delayFrames = true;
}
}
void SetEngineMouseCursor(EngineContext *eng, CGEditorContext *cgEd, TextEditorContext *txEd)
{
if (!(eng->viewportMode == VIEWPORT_GAME_SCREEN && eng->shouldHideCursorInGameFullscreen && eng->isViewportFullscreen))
{
ShowCursor();
}
if (eng->draggedFileIndex != -1)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
return;
}
if (eng->isAnyMenuOpen)
{
SetMouseCursor(MOUSE_CURSOR_ARROW);
return;
}
if (eng->windowResizeButton == RESIZING_WINDOW_EAST || eng->windowResizeButton == RESIZING_WINDOW_WEST)
{
SetMouseCursor(MOUSE_CURSOR_RESIZE_EW);
return;
}
else if (eng->windowResizeButton == RESIZING_WINDOW_NORTH || eng->windowResizeButton == RESIZING_WINDOW_SOUTH)
{
SetMouseCursor(MOUSE_CURSOR_RESIZE_NS);
return;
}
if (eng->menuResizeButton == RESIZING_MENU_BOTTOMBAR || eng->menuResizeButton == RESIZING_MENU_SIDEBAR_MIDDLE)
{
SetMouseCursor(MOUSE_CURSOR_RESIZE_NS);
return;
}
else if (eng->menuResizeButton == RESIZING_MENU_SIDEBAR)
{
SetMouseCursor(MOUSE_CURSOR_RESIZE_EW);
return;
}
if (eng->isViewportFocused || eng->isViewportFullscreen)
{
switch (eng->viewportMode)
{
case VIEWPORT_CG_EDITOR:
SetMouseCursor(cgEd->cursor);
return;
case VIEWPORT_GAME_SCREEN:
if (eng->shouldHideCursorInGameFullscreen && eng->isViewportFullscreen)
{
HideCursor();
return;
}
SetMouseCursor(MOUSE_CURSOR_ARROW);
return;
case VIEWPORT_HITBOX_EDITOR:
SetMouseCursor(MOUSE_CURSOR_CROSSHAIR);
return;
case VIEWPORT_TEXT_EDITOR:
SetMouseCursor(txEd->cursor);
return;
default:
eng->viewportMode = VIEWPORT_CG_EDITOR;
SetMouseCursor(cgEd->cursor);
return;
}
}
if ((eng->isSaveButtonHovered && eng->viewportMode != VIEWPORT_CG_EDITOR) || (eng->isBuildButtonHovered && (cgEd->hasChanged || eng->viewportMode != VIEWPORT_CG_EDITOR)))
{
SetMouseCursor(MOUSE_CURSOR_NOT_ALLOWED);
return;
}
if (eng->hoveredUIElementIndex != -1)
{
SetMouseCursor(MOUSE_CURSOR_POINTING_HAND);
return;
}
SetMouseCursor(MOUSE_CURSOR_ARROW);
return;
}
void SetEngineFPS(EngineContext *eng, CGEditorContext *cgEd, InterpreterContext *intp)
{
int fps;
if (eng->isViewportFocused)
{
switch (eng->viewportMode)
{
case VIEWPORT_CG_EDITOR:
fps = cgEd->fps;
break;
case VIEWPORT_GAME_SCREEN:
fps = intp->fps;
break;
case VIEWPORT_HITBOX_EDITOR:
fps = FPS_DEFAULT;
break;
case VIEWPORT_TEXT_EDITOR:
fps = FPS_HIGH;
break;
default:
fps = FPS_DEFAULT;
eng->viewportMode = VIEWPORT_CG_EDITOR;
break;
}
}
else
{
if (eng->draggedFileIndex != -1)
{
eng->fps = FPS_HIGH;
}
fps = eng->fps;
}
if (fps > eng->fpsLimit)
{
fps = eng->fpsLimit;
}
SetTargetFPS(fps);
}
void SetEngineZoom(EngineContext *eng, CGEditorContext *cgEd, InterpreterContext *intp)
{
if (eng->viewportMode == VIEWPORT_CG_EDITOR)
{
eng->zoom = cgEd->zoom;
float wheel = GetMouseWheelMove();
if (wheel != 0 && eng->isViewportFocused && !cgEd->isNodeCreateMenuOpen)
{
cgEd->delayFrames = true;
float zoom = eng->zoom;
if (wheel > 0 && zoom < 1.5f)
{
eng->zoom = zoom + 0.25f;
}
if (wheel < 0 && zoom > 0.5f)
{
eng->zoom = zoom - 0.25f;
}
cgEd->zoom = eng->zoom;
}
}
else if (eng->viewportMode == VIEWPORT_GAME_SCREEN)
{
eng->zoom = intp->zoom;
}
else
{
eng->zoom = 1.0f;
cgEd->zoom = 1.0f;
intp->zoom = 1.0f;
}
}
void DisplayLoadingScreen(int step)
{
BeginDrawing();
ClearBackground(GRAY_28);
int total_steps = 10;
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
float progress = (float)step / total_steps;
DrawText("Loading...", (screenWidth - 215) / 2, screenHeight / 3, 50, WHITE);
int barW = screenWidth * 3 / 4;
int barX = screenWidth * 1 / 8;
int barY = screenHeight / 2;
DrawRectangleRounded((Rectangle){barX, barY, barW, 100}, 0.3f, 8, GRAY_40);
DrawRectangleRounded((Rectangle){barX, barY, barW * progress, 100}, 0.3f, 8, RAPID_PURPLE);
DrawRectangleRoundedLines((Rectangle){barX, barY, barW, 100}, 0.3f, 4, WHITE);
DrawText(TextFormat("%d%%", (int)(progress * 100)), (screenWidth - 60) / 2, barY + 120, 30, WHITE);
EndDrawing();
}
int main(int argc, char **argv)
{
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_UNDECORATED);
SetTraceLogLevel(LOG_NONE);
InitWindow(PM_WINDOW_WIDTH, PM_WINDOW_HEIGHT, "RapidEngine");
SetTargetFPS(FPS_HIGH);
SetExitKey(KEY_NULL);
Image icon = LoadImageFromMemory(".png", icon_png, icon_png_len);
SetWindowIcon(icon);
UnloadImage(icon);
char filePath[MAX_FILE_PATH];
strmac(filePath, MAX_FILE_NAME, "%s", argc == 1 ? HandleProjectManager() : argv[1]);
MaximizeWindow();
DisplayLoadingScreen(1);
InitAudioDevice();
DisplayLoadingScreen(2);
EngineContext eng = InitEngineContext();
DisplayLoadingScreen(3);
CGEditorContext cgEd = InitEditorContext();
DisplayLoadingScreen(4);
GraphContext graph = InitGraphContext();
DisplayLoadingScreen(5);
InterpreterContext intp = InitInterpreterContext();
intp.isGameRunning = &eng.isGameRunning;
DisplayLoadingScreen(6);
RuntimeGraphContext runtimeGraph = {0};
TextEditorContext txEd = InitTextEditorContext();
SetProjectFolderPath(&eng, filePath);
intp.projectPath = eng.projectPath;
DisplayLoadingScreen(7);
eng.files = LoadAndSortFiles(eng.currentPath);
if (!eng.files.paths || eng.files.count <= 0)
{
AddToLog(&eng, "Error loading files{E201}", LOG_LEVEL_ERROR);
EmergencyExit(&eng, &cgEd, &intp, &txEd);
}
DisplayLoadingScreen(8);
if (!LoadGraphFromFile(eng.CGFilePath, &graph))
{
AddToLog(&eng, "Failed to load CoreGraph file! Continuing with empty graph{C223}", LOG_LEVEL_ERROR);
eng.CGFilePath[0] = '\0';
}
cgEd.graph = &graph;
DisplayLoadingScreen(9);
if (!LoadSettingsConfig(&eng, &intp, &cgEd))
{
AddToLog(&eng, "Failed to load settings file{E227}", LOG_LEVEL_ERROR);
}
SetTargetFPS(eng.fpsLimit > FPS_DEFAULT ? FPS_DEFAULT : eng.fpsLimit);
DisplayLoadingScreen(10);
AddToLog(&eng, "All resources loaded. Welcome!{E000}", LOG_LEVEL_NORMAL);
while (!WindowShouldClose())
{
if (!IsWindowReady())
{
EmergencyExit(&eng, &cgEd, &intp, &txEd);
}
if (STRING_ALLOCATION_FAILURE)
{
AddToLog(&eng, "String allocation failed{O200}", LOG_LEVEL_ERROR);
EmergencyExit(&eng, &cgEd, &intp, &txEd);
}
ContextChangePerFrame(&eng);
int prevHoveredUIIndex = eng.hoveredUIElementIndex;
eng.isAnyMenuOpen = eng.showSaveWarning == 1 || eng.showSettingsMenu;
if (HandleUICollisions(&eng, &graph, &intp, &cgEd, &runtimeGraph, &txEd) && !eng.isViewportFullscreen)
{
if ((prevHoveredUIIndex != eng.hoveredUIElementIndex || IsMouseButtonDown(MOUSE_LEFT_BUTTON) || eng.isSettingsButtonHovered || eng.isVarHovered || eng.draggedFileIndex != -1 || eng.isLogMessageHovered || eng.isKeyboardShortcutActivated || eng.logs.hasNewLogMessage) && eng.showSaveWarning != 1 && eng.showSettingsMenu == false)
{
BuildUITexture(&eng, &graph, &cgEd, &intp, &runtimeGraph, &txEd);
eng.fps = FPS_HIGH;
}
eng.delayFrames = true;
}
else if (eng.delayFrames && !eng.isViewportFullscreen)
{
BuildUITexture(&eng, &graph, &cgEd, &intp, &runtimeGraph, &txEd);
eng.fps = FPS_DEFAULT;
eng.delayFrames = false;
}
eng.logs.hasNewLogMessage = false;
SetEngineMouseCursor(&eng, &cgEd, &txEd);
SetEngineFPS(&eng, &cgEd, &intp);
SetEngineZoom(&eng, &cgEd, &intp);
Vector2 mouseInViewportTex = (Vector2){
(eng.mousePos.x - (eng.isViewportFullscreen ? 0 : eng.sideBarWidth)) / eng.zoom + (eng.viewportTex.texture.width - (eng.isViewportFullscreen ? eng.screenWidth : eng.viewportWidth / eng.zoom)) / 2.0f,
eng.mousePos.y / eng.zoom + (eng.viewportTex.texture.height - (eng.isViewportFullscreen ? eng.screenHeight : eng.viewportHeight / eng.zoom)) / 2.0f};
Rectangle viewportRecInViewportTex = (Rectangle){
(eng.viewportTex.texture.width - (eng.isViewportFullscreen ? eng.screenWidth : eng.viewportWidth) / eng.zoom) / 2.0f,
(eng.viewportTex.texture.height - (eng.isViewportFullscreen ? eng.screenHeight : eng.viewportHeight) / eng.zoom) / 2.0f,
(eng.screenWidth - (eng.isViewportFullscreen ? 0 : eng.sideBarWidth)) / eng.zoom,
(eng.screenHeight - (eng.isViewportFullscreen ? 0 : eng.bottomBarHeight)) / eng.zoom};
if (eng.showSaveWarning == 1 || eng.showSettingsMenu || eng.windowResizeButton != RESIZING_WINDOW_NONE)
{
eng.isViewportFocused = false;
}
BeginDrawing();
ClearBackground(BLACK);
switch (eng.viewportMode)
{
case VIEWPORT_CG_EDITOR:
{
if (eng.wasViewportFocusedLastFrame && !eng.isViewportFocused)
{
cgEd.isDraggingScreen = false;
cgEd.isDraggingSelectedNodes = false;
cgEd.nodeGlareTime = 0;
}
if (eng.CGFilePath[0] != '\0' && (eng.isViewportFocused || cgEd.isFirstFrame || eng.wasViewportFocusedLastFrame || eng.menuResizeButton != RESIZING_MENU_NONE))
{
cgEd.viewportBoundary = viewportRecInViewportTex;
HandleEditor(&cgEd, &graph, &eng.viewportTex, mouseInViewportTex, !eng.isViewportFocused);
}
if (eng.isAutoSaveON)
{
eng.autoSaveTimer += GetFrameTime();
if (eng.autoSaveTimer >= 120.0f)
{
if (SaveGraphToFile(eng.CGFilePath, &graph) == 0)
{
AddToLog(&eng, "Auto-saved successfully{C301}", LOG_LEVEL_SUCCESS);
cgEd.hasChanged = false;
}
else
{
AddToLog(&eng, "Error saving changes!{C101}", LOG_LEVEL_WARNING);
}
eng.autoSaveTimer = 0.0f;
}
}
if (cgEd.newLogMessage)
{
for (int i = 0; i < cgEd.logMessageCount; i++)
{
AddToLog(&eng, cgEd.logMessages[i], cgEd.logMessageLevels[i]);
}
cgEd.newLogMessage = false;
cgEd.logMessageCount = 0;
eng.delayFrames = true;
}
if (cgEd.engineDelayFrames)
{
cgEd.engineDelayFrames = false;
eng.delayFrames = true;
}
if (cgEd.hasChangedInLastFrame)
{
eng.delayFrames = true;
cgEd.hasChangedInLastFrame = false;
cgEd.hasChanged = true;
eng.wasBuilt = false;
}
if (cgEd.shouldOpenHitboxEditor)
{
cgEd.shouldOpenHitboxEditor = false;
eng.viewportMode = VIEWPORT_HITBOX_EDITOR;
}
if (cgEd.hasFatalErrorOccurred)
{
EmergencyExit(&eng, &cgEd, &intp, &txEd);
}
break;
}
case VIEWPORT_GAME_SCREEN:
{
BeginTextureMode(eng.viewportTex);
eng.isGameRunning = HandleGameScreen(&intp, &runtimeGraph, mouseInViewportTex, viewportRecInViewportTex);
EndTextureMode();
if (intp.newLogMessage)
{
for (int i = 0; i < intp.logMessageCount; i++)
{
AddToLog(&eng, intp.logMessages[i], intp.logMessageLevels[i]);
}
intp.newLogMessage = false;
intp.logMessageCount = 0;
eng.delayFrames = true;
}
if (!eng.isGameRunning)
{
eng.viewportMode = VIEWPORT_CG_EDITOR;
cgEd.isFirstFrame = true;
eng.wasBuilt = false;
FreeInterpreterContext(&intp);
}
break;
}
case VIEWPORT_HITBOX_EDITOR:
{
static HitboxEditorContext hbEd = {0};
if (hbEd.texture.id == 0)
{
eng.delayFrames = true;
char path[MAX_FILE_PATH];
strmac(path, MAX_FILE_PATH, "%s%c%s", eng.projectPath, PATH_SEPARATOR, cgEd.hitboxEditorFileName);
Image img = LoadImage(path);
if (img.data == NULL)
{
AddToLog(&eng, "Invalid texture file name{H200}", LOG_LEVEL_ERROR);
eng.viewportMode = VIEWPORT_CG_EDITOR;
}
else
{
int newWidth, newHeight;
if (img.width >= img.height)
{
newWidth = eng.viewportWidth * 2 / 5;
newHeight = (int)((float)img.height * newWidth / img.width);
}
else
{
newHeight = eng.viewportHeight * 2 / 5;
newWidth = (int)((float)img.width * newHeight / img.height);
}
float scaleX = (float)newWidth / (float)img.width;
float scaleY = (float)newHeight / (float)img.height;
ImageResize(&img, newWidth, newHeight);
Texture2D tex = LoadTextureFromImage(img);
UnloadImage(img);
hbEd = InitHitboxEditor(tex, (Vector2){eng.viewportTex.texture.width / 2.0f, eng.viewportTex.texture.height / 2.0f}, (Vector2){scaleX, scaleY});
for (int i = 0; i < graph.pinCount; i++)
{
if (graph.pins[i].id == cgEd.hitboxEditingPinID)
{
hbEd.poly = graph.pins[i].hitbox;
}
}
for (int i = 0; i < hbEd.poly.count; i++)
{
hbEd.poly.vertices[i].x *= hbEd.scale.x;
hbEd.poly.vertices[i].y *= hbEd.scale.y;
}
}
}
if (eng.isViewportFocused)
{
if (!UpdateHitboxEditor(&hbEd, mouseInViewportTex, &graph, cgEd.hitboxEditingPinID))
{
eng.viewportMode = VIEWPORT_CG_EDITOR;
eng.delayFrames = true;
break;
}
if (hbEd.hasChanged)
{
cgEd.hasChangedInLastFrame = true;
eng.wasBuilt = false;
hbEd.hasChanged = false;
eng.delayFrames = true;
}
}
BeginTextureMode(eng.viewportTex);
DrawHitboxEditor(&hbEd, mouseInViewportTex);
DrawTextEx(eng.font, "ESC - Save & Exit Hitbox Editor", (Vector2){viewportRecInViewportTex.x + 30, viewportRecInViewportTex.y + 30}, 30, 1, GRAY);
DrawTextEx(eng.font, "R - reset hitbox", (Vector2){viewportRecInViewportTex.x + 30, viewportRecInViewportTex.y + 70}, 30, 1, GRAY);
EndTextureMode();
break;
}
case VIEWPORT_TEXT_EDITOR:
{
HandleTextEditor(&txEd, mouseInViewportTex, viewportRecInViewportTex, &eng.viewportTex, eng.isViewportFocused, eng.font);
if (txEd.newLogMessage)
{
for (int i = 0; i < txEd.logMessageCount; i++)
{
AddToLog(&eng, txEd.logMessages[i], txEd.logMessageLevels[i]);
}
txEd.newLogMessage = false;
txEd.logMessageCount = 0;
eng.delayFrames = true;
}
break;
}
default:
{
AddToLog(&eng, "Out of bounds enum{O201}", LOG_ERROR);
eng.viewportMode = VIEWPORT_CG_EDITOR;
}
}
DrawTexturePro(eng.viewportTex.texture,
(Rectangle){(eng.viewportTex.texture.width - eng.viewportWidth / eng.zoom) / 2.0f,
(eng.viewportTex.texture.height - eng.viewportHeight / eng.zoom) / 2.0f,
eng.viewportWidth / eng.zoom,
-eng.viewportHeight / eng.zoom},
(Rectangle){eng.isViewportFullscreen ? 0 : eng.sideBarWidth,
0,
eng.screenWidth - (eng.isViewportFullscreen ? 0 : eng.sideBarWidth),
eng.screenHeight - (eng.isViewportFullscreen ? 0 : eng.bottomBarHeight)},
(Vector2){0, 0}, 0.0f, WHITE);
if (eng.uiTex.texture.id != 0 && !eng.isViewportFullscreen)
{
DrawTextureRec(eng.uiTex.texture, (Rectangle){0, 0, eng.uiTex.texture.width, -eng.uiTex.texture.height}, (Vector2){0, 0}, WHITE);
}
if (eng.viewportMode == VIEWPORT_CG_EDITOR && eng.screenHeight - eng.bottomBarHeight > 80 && eng.screenWidth > 550)
{
DrawTextEx(GetFontDefault(), "CoreGraph", (Vector2){eng.sideBarWidth + 20, 30}, 40, 4, COLOR_COREGRAPH_WATERMARK);
DrawTextEx(eng.font, "TM", (Vector2){eng.sideBarWidth + 240, 20}, 15, 1, COLOR_COREGRAPH_WATERMARK);
}
if (eng.showSaveWarning == 1)
{
eng.showSaveWarning = DrawSaveWarning(&eng, &graph, &cgEd);
if (eng.showSaveWarning == 2)
{
eng.shouldCloseWindow = true;
}
}
else if (eng.showSettingsMenu)
{
eng.showSettingsMenu = DrawSettingsMenu(&eng, &intp, &cgEd);
}
if (eng.shouldShowFPS)
{
DrawTextEx(eng.font, TextFormat("%d FPS", GetFPS()), (Vector2){eng.screenWidth / 2, 10}, 40, 1, RED);
}
EndDrawing();
eng.wasViewportFocusedLastFrame = eng.isViewportFocused;
if (eng.shouldCloseWindow)
{
break;
}
}
FreeEngineContext(&eng);
FreeEditorContext(&cgEd);
FreeInterpreterContext(&intp);
FreeTextEditorContext(&txEd);
CloseAudioDevice();
CloseWindow();
return 0;
}
================================================
FILE: Engine/Engine.h
================================================
// Copyright 2025 Emil Dimov
// Licensed under the Apache License, Version 2.0
#pragma once
#include "raylib.h"
#include "raymath.h"
#include "definitions.h"
#define MAX_UI_ELEMENTS 128
#define MAX_FILE_TOOLTIP_SIZE 512
#define MAX_VARIABLE_TOOLTIP_SIZE 256
#define DOUBLE_CLICK_THRESHOLD 0.3f
#define UI_LAYER_COUNT 5
#define MIN_WINDOW_WIDTH 300
#define MIN_WINDOW_HEIGHT 300
#define MAX_SETTINGS_LINE 128
typedef enum
{
UI_ACTION_NONE,
UI_ACTION_SAVE_CG_FILE,
UI_ACTION_STOP_GAME,
UI_ACTION_RUN_GAME,
UI_ACTION_BUILD_GRAPH,
UI_ACTION_CURRENT_PATH_BACK,
UI_ACTION_REFRESH_FILES,
UI_ACTION_CLOSE_WINDOW,
UI_ACTION_MINIMIZE_WINDOW,
UI_ACTION_OPEN_SETTINGS,
UI_ACTION_MOVE_WINDOW,
UI_ACTION_RESIZE_BOTTOM_BAR,
UI_ACTION_RESIZE_SIDE_BAR,
UI_ACTION_RESIZE_SIDE_BAR_MIDDLE,
UI_ACTION_OPEN_FILE,
UI_ACTION_CHANGE_VARS_FILTER,
UI_ACTION_SHOW_VAR_TOOLTIP,
UI_ACTION_ENABLE_VIEWPORT_FULLSCREEN,
UI_ACTION_SHOW_ERROR_CODE
} UIAction;
typedef enum
{
UIRectangle,
UICircle,
UILine,
UIText
} UIElementShape;
typedef enum
{
VAR_FILTER_ALL,
VAR_FILTER_NUMBERS,
VAR_FILTER_STRINGS,
VAR_FILTER_BOOLS,
VAR_FILTER_COLORS,
VAR_FILTER_SPRITES
} VarFilter;
typedef struct LogEntry
{
char message[MAX_LOG_MESSAGE_SIZE];
LogLevel level;
} LogEntry;
typedef struct Logs
{
LogEntry *entries;
int count;
int capacity;
bool hasNewLogMessage;
} Logs;
typedef struct UIElement
{
char name[MAX_FILE_PATH];
UIElementShape shape;
UIAction action;
union
{
struct
{
Vector2 pos;
Vector2 recSize;
float roundness;
int roundSegments;
Color hoverColor;
} rect;
struct
{
Vector2 center;
int radius;
} circle;
struct
{
Vector2 startPos;
Vector2 endPos;
int thickness;
} line;
};
Color color;
int layer;
struct
{
char string[MAX_FILE_PATH];
Vector2 textPos;
int textSize;
int textSpacing;
Color textColor;
} text;
int valueIndex;
int fileIndex;
} UIElement;
typedef enum
{
VIEWPORT_CG_EDITOR,
VIEWPORT_GAME_SCREEN,
VIEWPORT_HITBOX_EDITOR,
VIEWPORT_TEXT_EDITOR
} ViewportMode;
typedef enum
{
RESIZING_WINDOW_NONE,
RESIZING_WINDOW_NORTH,
RESIZING_WINDOW_SOUTH,
RESIZING_WINDOW_EAST,
RESIZING_WINDOW_WEST
} WindowResizingButton;
typedef enum
{
RESIZING_MENU_NONE,
RESIZING_MENU_BOTTOMBAR,
RESIZING_MENU_SIDEBAR,
RESIZING_MENU_SIDEBAR_MIDDLE
} MenuResizingButton;
typedef struct EngineContext
{
int screenWidth;
int screenHeight;
int prevScreenWidth;
int prevScreenHeight;
int viewportWidth;
int viewportHeight;
float zoom;
bool isViewportFullscreen;
bool sideBarHalfSnap;
int maxScreenWidth;
int maxScreenHeight;
int bottomBarHeight;
int sideBarWidth;
int sideBarMiddleY;
UIElement uiElements[MAX_UI_ELEMENTS];
int uiElementCount;
int hoveredUIElementIndex;
bool hasResizedBar;
bool isEditorOpened;
bool isViewportFocused;
bool wasViewportFocusedLastFrame;
bool isAnyMenuOpen;
bool isSaveButtonHovered;
bool isBuildButtonHovered;
int showSaveWarning;
bool showSettingsMenu;
bool shouldCloseWindow;
RenderTexture2D viewportTex;
RenderTexture2D uiTex;
Texture2D resizeButton;
Texture2D viewportFullscreenButton;
Texture2D settingsGear;
Font font;
ViewportMode viewportMode;
Vector2 mousePos;
MenuResizingButton menuResizeButton;
WindowResizingButton windowResizeButton;
bool isWindowMoving;
char *currentPath;
char *projectPath;
char *CGFilePath;
FilePathList files;
bool isGameRunning;
bool wasBuilt;
VarFilter varsFilter;
Sound saveSound;
int fps;
bool delayFrames;
float autoSaveTimer;
bool isSoundOn;
int fpsLimit;
bool shouldShowFPS;
bool isAutoSaveON;
bool shouldHideCursorInGameFullscreen;
bool isLowSpecModeOn;
bool isSettingsButtonHovered;
bool isVarHovered;
Logs logs;
int draggedFileIndex;
bool openFilesWithRapidEditor;
bool isLogMessageHovered;
bool isTopBarHovered;
bool isKeyboardShortcutActivated;
} EngineContext;
typedef enum
{
FILE_TYPE_FOLDER = 0,
FILE_TYPE_CG = 1,
FILE_TYPE_CONFIG = 2,
FILE_TYPE_IMAGE = 3,
FILE_TYPE_OTHER = 4
} FileType;
typedef enum
{
SETTINGS_MODE_ENGINE,
SETTINGS_MODE_GAME,
SETTINGS_MODE_KEYBINDS,
SETTINGS_MODE_EXPORT,
SETTINGS_MODE_ABOUT
} SettingsMode;
================================================
FILE: Engine/HitboxEditor.c
================================================
// Copyright 2025 Emil Dimov
// Licensed under the Apache License, Version 2.0
#include "raylib.h"
#include <stdio.h>
#include <math.h>
#include "HitboxEditor.h"
#define SNAP_DIST 10.0f
#define MAX_VERTICES 64
HitboxEditorContext InitHitboxEditor(Texture2D tex, Vector2 pos, Vector2 scale)
{
HitboxEditorContext hbEd = {0};
hbEd.texture = tex;
hbEd.position = pos;
hbEd.poly.count = 0;
hbEd.poly.isClosed = false;
hbEd.draggingVerticeIndex = -1;
hbEd.scale = scale;
hbEd.lastActionType = HBED_LAST_ACTION_TYPE_NONE;
return hbEd;
}
bool IsNear(Vector2 a, Vector2 b, float dist)
{
float dx = a.x - b.x;
float dy = a.y - b.y;
return (dx * dx + dy * dy) < (dist * dist);
}
bool UpdateHitboxEditor(HitboxEditorContext *hbEd, Vector2 mouseLocal, GraphContext *graph, int hitboxEditingPinID)
{
if (IsKeyPressed(KEY_ESCAPE))
{
if (hbEd->poly.isClosed)
{
for (int i = 0; i < hbEd->poly.count; i++)
{
hbEd->poly.vertices[i].x /= hbEd->scale.x;
hbEd->poly.vertices[i].y /= hbEd->scale.y;
}
for (int i = 0; i < graph->pinCount; i++)
{
if (graph->pins[i].id == hitboxEditingPinID)
{
graph->pins[i].hitbox = hbEd->poly;
}
}
UnloadTexture(hbE
gitextract__bogz74p/ ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Engine/ │ ├── CGEditor.c │ ├── CGEditor.h │ ├── Engine.c │ ├── Engine.h │ ├── HitboxEditor.c │ ├── HitboxEditor.h │ ├── InfoByType.c │ ├── InfoByType.h │ ├── Interpreter.c │ ├── Interpreter.h │ ├── Nodes.c │ ├── Nodes.h │ ├── ProjectManager.c │ ├── ProjectManager.h │ ├── TextEditor.c │ ├── TextEditor.h │ ├── definitions.c │ ├── definitions.h │ ├── resources/ │ │ ├── fonts.c │ │ ├── resources.h │ │ ├── sound.c │ │ └── textures.c │ └── version.h ├── LICENSE ├── Projects/ │ └── Example/ │ ├── Example.cg │ ├── Example.config │ ├── ex.txt │ ├── random.abc │ └── textdocument.txt ├── RAYLIB_LICENSE └── README.md
SYMBOL INDEX (162 symbols across 16 files) FILE: Engine/CGEditor.c function CGEditorContext (line 18) | CGEditorContext InitEditorContext() function FreeEditorContext (line 98) | void FreeEditorContext(CGEditorContext *cgEd) function AddToLogFromCGEditor (line 110) | void AddToLogFromCGEditor(CGEditorContext *cgEd, char *message, int level) function DrawBackgroundGrid (line 123) | void DrawBackgroundGrid(CGEditorContext *cgEd, int gridSpacing, RenderTe... function DrawCurvedWire (line 158) | void DrawCurvedWire(Vector2 outputPos, Vector2 inputPos, float thickness... function HandleVarNameTextBox (line 210) | void HandleVarNameTextBox(CGEditorContext *cgEd, Rectangle bounds, char ... function HandleLiteralNodeField (line 331) | void HandleLiteralNodeField(CGEditorContext *cgEd, GraphContext *graph, ... function HandleKeyNodeField (line 619) | void HandleKeyNodeField(CGEditorContext *cgEd, GraphContext *graph, int ... function HandleDropdownMenu (line 673) | void HandleDropdownMenu(GraphContext *graph, int currPinIndex, int hover... function DrawNodes (line 841) | void DrawNodes(CGEditorContext *cgEd, GraphContext *graph) function CheckNodeCollisions (line 1266) | bool CheckNodeCollisions(CGEditorContext *cgEd, GraphContext *graph) function HandleDragging (line 1513) | void HandleDragging(CGEditorContext *cgEd, GraphContext *graph) function SetCGEditorFPS (line 1595) | void SetCGEditorFPS(CGEditorContext *cgEd) function DrawFullTexture (line 1607) | void DrawFullTexture(CGEditorContext *cgEd, GraphContext *graph, RenderT... function CheckOpenMenus (line 1721) | bool CheckOpenMenus(CGEditorContext *cgEd) function HandleEditor (line 1726) | void HandleEditor(CGEditorContext *cgEd, GraphContext *graph, RenderText... FILE: Engine/CGEditor.h type CGEditorContext (line 16) | typedef struct FILE: Engine/Engine.c function Logs (line 18) | Logs InitLogs() function EngineContext (line 32) | EngineContext InitEngineContext() function FreeEngineContext (line 150) | void FreeEngineContext(EngineContext *eng) function AddUIElement (line 186) | void AddUIElement(EngineContext *eng, UIElement element) function AddToLog (line 199) | void AddToLog(EngineContext *eng, const char *newLine, int level) function EmergencyExit (line 243) | void EmergencyExit(EngineContext *eng, CGEditorContext *cgEd, Interprete... function PrepareCGFilePath (line 295) | void PrepareCGFilePath(EngineContext *eng, const char *projectPath) function SetProjectFolderPath (line 324) | void SetProjectFolderPath(EngineContext *eng, const char *filePath) function FileType (line 363) | FileType GetFileType(const char *folderPath, const char *fileName) function DrawSaveWarning (line 397) | int DrawSaveWarning(EngineContext *eng, GraphContext *graph, CGEditorCon... function DrawSlider (line 477) | void DrawSlider(Vector2 pos, bool *value, Vector2 mousePos, bool *hasCha... function DrawFPSLimitDropdown (line 504) | void DrawFPSLimitDropdown(Vector2 pos, int *limit, Vector2 mousePos, Fon... function SaveSettings (line 551) | bool SaveSettings(EngineContext *eng, InterpreterContext *intp, CGEditor... function LoadSettingsConfig (line 576) | bool LoadSettingsConfig(EngineContext *eng, InterpreterContext *intp, CG... function DrawSettingsMenu (line 649) | bool DrawSettingsMenu(EngineContext *eng, InterpreterContext *intp, CGEd... function FilePathList (line 842) | FilePathList LoadAndSortFiles(const char *path) function CountingSortByLayer (line 870) | void CountingSortByLayer(EngineContext *eng) function DrawUIElements (line 910) | void DrawUIElements(EngineContext *eng, GraphContext *graph, CGEditorCon... function BuildUITexture (line 1339) | void BuildUITexture(EngineContext *eng, GraphContext *graph, CGEditorCon... function HandleUICollisions (line 2007) | bool HandleUICollisions(EngineContext *eng, GraphContext *graph, Interpr... function ContextChangePerFrame (line 2413) | void ContextChangePerFrame(EngineContext *eng) function SetEngineMouseCursor (line 2433) | void SetEngineMouseCursor(EngineContext *eng, CGEditorContext *cgEd, Tex... function SetEngineFPS (line 2518) | void SetEngineFPS(EngineContext *eng, CGEditorContext *cgEd, Interpreter... function SetEngineZoom (line 2561) | void SetEngineZoom(EngineContext *eng, CGEditorContext *cgEd, Interprete... function DisplayLoadingScreen (line 2598) | void DisplayLoadingScreen(int step) function main (line 2625) | int main(int argc, char **argv) FILE: Engine/Engine.h type UIAction (line 23) | typedef enum type UIElementShape (line 46) | typedef enum type VarFilter (line 54) | typedef enum type LogEntry (line 64) | typedef struct LogEntry type Logs (line 70) | typedef struct Logs type UIElement (line 78) | typedef struct UIElement type ViewportMode (line 122) | typedef enum type WindowResizingButton (line 130) | typedef enum type MenuResizingButton (line 139) | typedef enum type EngineContext (line 147) | typedef struct EngineContext type FileType (line 233) | typedef enum type SettingsMode (line 242) | typedef enum FILE: Engine/HitboxEditor.c function HitboxEditorContext (line 12) | HitboxEditorContext InitHitboxEditor(Texture2D tex, Vector2 pos, Vector2... function IsNear (line 25) | bool IsNear(Vector2 a, Vector2 b, float dist) function UpdateHitboxEditor (line 32) | bool UpdateHitboxEditor(HitboxEditorContext *hbEd, Vector2 mouseLocal, G... function DrawHitboxEditor (line 184) | void DrawHitboxEditor(HitboxEditorContext *hbEd, Vector2 mouseLocal) FILE: Engine/HitboxEditor.h type HitboxEditorLastActionType (line 11) | typedef enum type HitboxEditorContext (line 19) | typedef struct FILE: Engine/InfoByType.h type NodeType (line 14) | typedef enum type PinType (line 90) | typedef enum type Comparison (line 116) | typedef enum type Gate (line 123) | typedef enum type Arithmetic (line 133) | typedef enum type KeyAction (line 142) | typedef enum type InfoByType (line 150) | typedef struct InfoByType function NodeTypeToIndex (line 245) | static inline int NodeTypeToIndex(NodeType type) type DropdownOptionsByPinType (line 266) | typedef struct DropdownOptionsByPinType function DropdownOptionsByPinType (line 287) | static inline DropdownOptionsByPinType getPinDropdownOptionsByType(PinTy... type RequestedInfo (line 300) | typedef enum function getNodeInfoByType (line 308) | static inline int getNodeInfoByType(NodeType type, RequestedInfo info) function getIsEditableByType (line 333) | static inline bool getIsEditableByType(NodeType type) function Color (line 381) | static inline Color getNodeColorByType(NodeType type) function PinType (line 397) | static inline PinType *getInputsByType(NodeType type) function PinType (line 413) | static inline PinType *getOutputsByType(NodeType type) function NodeType (line 573) | static inline NodeType StringToNodeType(const char strType[]) FILE: Engine/Interpreter.c function InterpreterContext (line 9) | InterpreterContext InitInterpreterContext() function FreeRuntimeGraphContext (line 52) | void FreeRuntimeGraphContext(RuntimeGraphContext *rg) function FreeInterpreterContext (line 82) | void FreeInterpreterContext(InterpreterContext *intp) function AddToLogFromInterpreter (line 182) | void AddToLogFromInterpreter(InterpreterContext *intp, Value message, in... function UpdateSpecialValues (line 195) | void UpdateSpecialValues(InterpreterContext *intp, Vector2 mousePos, Rec... function RuntimeGraphContext (line 205) | RuntimeGraphContext ConvertToRuntimeGraph(GraphContext *graph, Interpret... function DoesForceExist (line 849) | int DoesForceExist(InterpreterContext *intp, int id) function InterpretStringOfNodes (line 861) | void InterpretStringOfNodes(int lastNodeIndex, InterpreterContext *intp,... function DrawHitbox (line 1687) | void DrawHitbox(Hitbox *h, Vector2 centerPos, Vector2 spriteSize, Vector... function DrawComponents (line 1734) | void DrawComponents(InterpreterContext *intp) function CheckCollisionPolyPoly (line 1797) | bool CheckCollisionPolyPoly(Polygon *a, Vector2 aPos, Vector2 aSize, Vec... function CheckCollisionPolyCircle (line 1827) | bool CheckCollisionPolyCircle(Hitbox *h, Vector2 centerPos, Vector2 spri... function CheckCollisionPolyRect (line 1866) | bool CheckCollisionPolyRect(Polygon *poly, Vector2 polyPos, Vector2 poly... function CollisionResult (line 1921) | CollisionResult CheckCollisions(InterpreterContext *intp, int index) function HandleForces (line 2043) | void HandleForces(InterpreterContext *intp) function HandleSounds (line 2084) | void HandleSounds(InterpreterContext *intp) function HandleGameScreen (line 2112) | bool HandleGameScreen(InterpreterContext *intp, RuntimeGraphContext *gra... FILE: Engine/Interpreter.h type RuntimePin (line 22) | typedef struct RuntimePin type RuntimeNode (line 36) | typedef struct RuntimeNode type RuntimeGraphContext (line 50) | typedef struct RuntimeGraphContext type ValueType (line 59) | typedef enum type HitboxType (line 74) | typedef enum type Hitbox (line 82) | typedef struct type Sprite (line 94) | typedef struct type PropType (line 109) | typedef enum type Prop (line 116) | typedef struct type SceneComponent (line 131) | typedef struct type Value (line 143) | typedef struct type Force (line 160) | typedef struct type ActiveSound (line 169) | typedef struct type InterpreterContext (line 175) | typedef struct type SpecialValuesInList (line 236) | typedef enum type ComponentLayers (line 248) | typedef enum type CollisionResult (line 257) | typedef enum FILE: Engine/Nodes.c function GraphContext (line 6) | GraphContext InitGraphContext() function FreeGraphContext (line 25) | void FreeGraphContext(GraphContext *graph) function FindPinIndexByID (line 56) | int FindPinIndexByID(GraphContext *graph, int id) function SaveGraphToFile (line 68) | int SaveGraphToFile(const char *filename, GraphContext *graph) function LoadGraphFromFile (line 94) | bool LoadGraphFromFile(const char *filename, GraphContext *graph) function Pin (line 145) | Pin CreatePin(GraphContext *graph, int nodeID, bool isInput, PinType typ... function CreateNode (line 201) | bool CreateNode(GraphContext *graph, NodeType type, Vector2 pos) function GetPinIndexByID (line 283) | int GetPinIndexByID(int id, GraphContext *graph) function DuplicateNode (line 293) | bool DuplicateNode(GraphContext *graph, const Node *src, Vector2 pos, in... function CreateLink (line 377) | void CreateLink(GraphContext *graph, Pin Pin1, Pin Pin2) function DeleteNode (line 437) | void DeleteNode(GraphContext *graph, int nodeID) function RemoveConnections (line 605) | void RemoveConnections(GraphContext *graph, int pinID) FILE: Engine/Nodes.h type Node (line 19) | typedef struct Node type Pin (line 33) | typedef struct Pin type Link (line 52) | typedef struct Link type GraphContext (line 60) | typedef struct GraphContext FILE: Engine/ProjectManager.c function DrawMovingDotAlongRectangle (line 11) | void DrawMovingDotAlongRectangle() function DrawX (line 54) | void DrawX(Vector2 center, float size, float thickness, Color color) function DrawTopButtons (line 67) | void DrawTopButtons() type MainWindowButton (line 98) | typedef enum function MainWindow (line 105) | int MainWindow(Font font, Font fontRE) function WindowLoadProject (line 205) | int WindowLoadProject(char *projectFilePath, Font font) function CreateProject (line 342) | bool CreateProject(ProjectOptions PO) function WindowCreateProject (line 423) | int WindowCreateProject(char *projectFilePath, Font font) FILE: Engine/ProjectManager.h type ProjectOptions (line 12) | typedef struct ProjectOptions type ProjectManagerWindowMode (line 18) | typedef enum FILE: Engine/TextEditor.c function TextEditorContext (line 6) | TextEditorContext InitTextEditorContext() function FreeTextEditorContext (line 30) | void FreeTextEditorContext(TextEditorContext *txEd) function ClearTextEditorContext (line 41) | void ClearTextEditorContext(TextEditorContext *txEd) function AddToLogFromTextEditor (line 55) | void AddToLogFromTextEditor(TextEditorContext *txEd, char *message, int ... function LoadFileInTextEditor (line 68) | bool LoadFileInTextEditor(const char *fileName, TextEditorContext *txEd) function MeasureTextUntilEx (line 99) | int MeasureTextUntilEx(Font font, const char *text, int index, float fon... function DeleteSymbol (line 107) | void DeleteSymbol(TextEditorContext *txEd) function AddNewLine (line 146) | void AddNewLine(TextEditorContext *txEd) function ArrowKeysInput (line 179) | void ArrowKeysInput(TextEditorContext *txEd, float frameTime) function AddSymbol (line 350) | void AddSymbol(TextEditorContext *txEd, char newChar) function DrawSelector (line 368) | void DrawSelector(TextEditorContext *txEd, Rectangle viewportBoundary) function TextEditorDeleteSelected (line 404) | void TextEditorDeleteSelected(TextEditorContext *txEd) function TextEditorCopy (line 451) | void TextEditorCopy(TextEditorContext *txEd) function TextEditorPaste (line 511) | void TextEditorPaste(TextEditorContext *txEd) function DrawOptionsMenu (line 558) | void DrawOptionsMenu(TextEditorContext *txEd, Vector2 mousePos) function SaveTextFile (line 612) | void SaveTextFile(TextEditorContext *txEd, char *filePath) function KeyboardShortcuts (line 628) | void KeyboardShortcuts(TextEditorContext *txEd) function HandleTextEditor (line 656) | void HandleTextEditor(TextEditorContext *txEd, Vector2 mousePos, Rectang... FILE: Engine/TextEditor.h type TextEditorContext (line 15) | typedef struct TextEditorContext FILE: Engine/definitions.h function OpenFile (line 29) | static inline void OpenFile(const char *filePath) function OpenFile (line 42) | static inline void OpenFile(const char *filePath) function OpenFile (line 57) | static inline void OpenFile(const char *filePath) type Polygon (line 73) | typedef struct type LogLevel (line 82) | typedef enum
Condensed preview — 34 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (9,022K chars).
[
{
"path": ".gitignore",
"chars": 137,
"preview": ".vscode/\nRE_resources/\nre_early_dev/\nbuild/\nv1.0.0/\nRapidEngine.exe\nengine_log.txt\nerror_codes.txt\nlines.ps1\npush.ps1\nru"
},
{
"path": ".gitmodules",
"chars": 95,
"preview": "[submodule \"Engine/raylib\"]\n\tpath = Engine/raylib\n\turl = https://github.com/raysan5/raylib.git\n"
},
{
"path": "CMakeLists.txt",
"chars": 1061,
"preview": "cmake_minimum_required(VERSION 3.10)\nproject(RapidEngine C)\n\nset(CMAKE_C_STANDARD 99)\nset(CMAKE_C_STANDARD_REQUIRED ON)\n"
},
{
"path": "Engine/CGEditor.c",
"chars": 74639,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"CGEditor.h\"\n#include <stdlib.h"
},
{
"path": "Engine/CGEditor.h",
"chars": 2039,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdio.h>\n#includ"
},
{
"path": "Engine/Engine.c",
"chars": 114904,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include <stdio.h>\n#include <raylib.h>\n#"
},
{
"path": "Engine/Engine.h",
"chars": 4763,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include \"raylib.h\"\n#inclu"
},
{
"path": "Engine/HitboxEditor.c",
"chars": 8131,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"raylib.h\"\n#include <stdio.h>\n#"
},
{
"path": "Engine/HitboxEditor.h",
"chars": 899,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include \"raylib.h\"\n#inclu"
},
{
"path": "Engine/InfoByType.c",
"chars": 1292,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"InfoByType.h\"\n\nconst char *men"
},
{
"path": "Engine/InfoByType.h",
"chars": 30468,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdio.h>\n#includ"
},
{
"path": "Engine/Interpreter.c",
"chars": 79209,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"Interpreter.h\"\n#include \"rayma"
},
{
"path": "Engine/Interpreter.h",
"chars": 4621,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdio.h>\n#includ"
},
{
"path": "Engine/Nodes.c",
"chars": 17519,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"Nodes.h\"\n\nGraphContext InitGra"
},
{
"path": "Engine/Nodes.h",
"chars": 2007,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdio.h>\n#includ"
},
{
"path": "Engine/ProjectManager.c",
"chars": 19985,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"ProjectManager.h\"\n#include <st"
},
{
"path": "Engine/ProjectManager.h",
"chars": 529,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include \"raylib.h\"\n#inclu"
},
{
"path": "Engine/TextEditor.c",
"chars": 25558,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"TextEditor.h\"\n\nTextEditorConte"
},
{
"path": "Engine/TextEditor.h",
"chars": 1195,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdio.h>\n#includ"
},
{
"path": "Engine/definitions.c",
"chars": 2014,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#include \"definitions.h\"\n\nchar *strmac(c"
},
{
"path": "Engine/definitions.h",
"chars": 6628,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#pragma once\n\n#include <stdlib.h>\n#inclu"
},
{
"path": "Engine/resources/fonts.c",
"chars": 7809272,
"preview": "unsigned char arialbd_ttf[] = {\n0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x00, 0x04, 0x00, 0x90, \n0x44, 0x53, 0x4"
},
{
"path": "Engine/resources/resources.h",
"chars": 777,
"preview": "#pragma once\n\n// Textures\nextern unsigned char node_gear_png[];\nextern unsigned int node_gear_png_len;\n\nextern unsigned "
},
{
"path": "Engine/resources/sound.c",
"chars": 91351,
"preview": "unsigned char save_wav[] = {\n 0x52, 0x49, 0x46, 0x46, 0x8A, 0x37, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, \n 0x66, 0x6D"
},
{
"path": "Engine/resources/textures.c",
"chars": 587079,
"preview": "unsigned char node_gear_png[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x"
},
{
"path": "Engine/version.h",
"chars": 113,
"preview": "// Copyright 2025 Emil Dimov\n// Licensed under the Apache License, Version 2.0\n\n#define RAPID_ENGINE_VERSION 412"
},
{
"path": "LICENSE",
"chars": 11340,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Projects/Example/Example.config",
"chars": 200,
"preview": "Engine:\n\nSound=true\nFPSLimit=140\nShowFPS=false\nAutoSave=false\nHideCursorinFullscreen=true\nLowSpecMode=false\nOpenFilesWit"
},
{
"path": "Projects/Example/ex.txt",
"chars": 42,
"preview": "int main(){\n printf(\"Hello World!\");\n}\n"
},
{
"path": "Projects/Example/random.abc",
"chars": 17,
"preview": "Example .abc file"
},
{
"path": "Projects/Example/textdocument.txt",
"chars": 208,
"preview": "Line one of text.\nLine two is here.\nHere comes line three.\nThis is line four.\nLine five is in place.\nSixth line appears "
},
{
"path": "RAYLIB_LICENSE",
"chars": 877,
"preview": "Copyright (c) 2013-2025 Ramon Santamaria (@raysan5)\n\nThis software is provided \"as-is\", without any express or implied w"
},
{
"path": "README.md",
"chars": 3289,
"preview": "# <img width=\"44\" height=\"32\" alt=\"icon\" src=\"https://github.com/user-attachments/assets/6cd5b753-9a23-4d7a-ba2c-b50ac59"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the EmilDimov93/Rapid-Engine GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 34 files (8.5 MB), approximately 2.2M tokens, and a symbol index with 162 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.