Merge pull request 'feat/imgui' (#5) from feat/imgui into main
All checks were successful
CI / build-and-test (push) Successful in 2m14s

Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
2025-11-13 22:32:44 +00:00
19 changed files with 6568 additions and 205 deletions

6
.gitmodules vendored
View File

@@ -7,3 +7,9 @@
[submodule "external/acutest"]
path = external/acutest
url = https://github.com/mity/acutest
[submodule "external/imgui"]
path = external/imgui
url = https://github.com/ocornut/imgui.git
[submodule "external/rlImGui"]
path = external/rlImGui
url = https://github.com/raylib-extras/rlImGui.git

73
.vscode/settings.json vendored
View File

@@ -1,6 +1,77 @@
{
"files.associations": {
"*.h": "c",
"xstring": "cpp"
"xstring": "cpp",
"thread": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"format": "cpp",
"*.m": "cpp"
}
}

View File

@@ -14,7 +14,33 @@ add_subdirectory(external/raylib)
# -------------------------
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
# -------------------------
# Dear ImGui (docking branch)
# -------------------------
# Ensure the submodule exists at external/imgui (added with git submodule)
add_subdirectory(cmake/imgui)
# -------------------------
# rlImGui (optional - Raylib ImGui integration)
# -------------------------
# If you add rlImGui as a submodule under external/rlImGui, CMake will pick it up
option(USE_RLIMGUI "Enable rlImGui integration if found in external/rlImGui" ON)
set(RLIMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/rlImGui")
if(USE_RLIMGUI)
if(EXISTS "${RLIMGUI_ROOT}/CMakeLists.txt")
message(STATUS "rlImGui CMakeLists found at ${RLIMGUI_ROOT} - adding to build")
add_subdirectory(${RLIMGUI_ROOT} EXCLUDE_FROM_ALL)
set(HAVE_RLIMGUI TRUE)
elseif(EXISTS "${RLIMGUI_ROOT}")
message(STATUS "rlImGui sources found at ${RLIMGUI_ROOT} - using project wrapper cmake/rlImGui")
add_subdirectory(cmake/rlImGui)
set(HAVE_RLIMGUI TRUE)
else()
set(HAVE_RLIMGUI FALSE)
endif()
else()
set(HAVE_RLIMGUI FALSE)
endif()
# -------------------------
# Tests
# -------------------------
@@ -51,6 +77,7 @@ add_executable(simian
src/ScriptEngine.cpp
src/ScriptBindings.cpp
src/HotReload.cpp
src/GuiManager.cpp
src/log/log.c
)
@@ -61,20 +88,32 @@ target_include_directories(simian PUBLIC
external/angelscript/sdk/angelscript/include
external/angelscript/sdk/add_on/scriptstdstring
external/angelscript/sdk/add_on/scriptbuilder
external/imgui
)
target_link_libraries(simian
target_link_libraries(simian PRIVATE
raylib
angelscript
scriptstdstring
scriptbuilder
imgui
)
if(HAVE_RLIMGUI)
target_include_directories(simian PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian PRIVATE rlImGui)
# Expose HAVE_RLIMGUI to the C++ compiler so source can guard ImGui calls
target_compile_definitions(simian PRIVATE HAVE_RLIMGUI=1)
message(STATUS "Linking rlImGui into simian")
else()
message(STATUS "rlImGui not found - building without rlImGui integration")
endif()
# -------------------------
# 5⃣ Platform-specific linking
# -------------------------
if(WIN32)
target_link_libraries(simian winmm gdi32)
target_link_libraries(simian PRIVATE winmm gdi32)
elseif(UNIX)
# Linux / macOS: Raylib handles system libs automatically
endif()

76
Justfile Normal file
View File

@@ -0,0 +1,76 @@
# Justfile for Simian project
# Usage: just <recipe>
# Default configuration
set shell := ["powershell", "-Command"]
# Variables
BUILD_DIR := "build"
CMAKE_CONFIG := "Release"
CMAKE_GENERATOR := "Visual Studio 17 2022"
CMAKE_ARCH := "x64"
# Initialize and update submodules
submodules:
@echo "Updating submodules..."
git submodule update --init --recursive
# Configure the project (out-of-source)
configure:
@echo "Configuring CMake (generator: {{CMAKE_GENERATOR}}, arch: {{CMAKE_ARCH}})"
cmake -G "{{CMAKE_GENERATOR}}" -A {{CMAKE_ARCH}} -S . -B {{BUILD_DIR}}
# Configure for Debug
configure-debug:
@echo "Configuring Debug"
cmake -G "{{CMAKE_GENERATOR}}" -A {{CMAKE_ARCH}} -S . -B {{BUILD_DIR}} -DCMAKE_BUILD_TYPE=Debug
# Build (Release by default)
build:
@echo "Building (config: {{CMAKE_CONFIG}})"
cmake --build {{BUILD_DIR}} --config {{CMAKE_CONFIG}}
# Build Debug
build-debug:
@echo "Building Debug"
cmake --build {{BUILD_DIR}} --config Debug
# Run the built executable (defaults to Debug for development)
run:
@echo "Running simian (Release)..."
{{BUILD_DIR}}\Release\simian.exe
# Run Debug build
run-debug:
@echo "Running simian (Debug)..."
{{BUILD_DIR}}\Debug\simian.exe
# Run Release build
run-release:
@echo "Running simian (Release)..."
{{BUILD_DIR}}\Release\simian.exe
# Clean build folder
clean:
@echo "Removing {{BUILD_DIR}} folder"
Remove-Item -Recurse -Force {{BUILD_DIR}} -ErrorAction SilentlyContinue
# Reconfigure, build and run (convenience)
rebuild-run: submodules configure build run
# Lint / formatting (placeholder)
format:
@echo "No formatter configured. Add commands here."
# Tests (placeholder - uses CTest if configured)
test:
@echo "Running tests (if configured)"
cmake --build {{BUILD_DIR}} --config {{CMAKE_CONFIG}} --target RUN_TESTS
# Provide a help recipe
help:
@echo "Available recipes: submodules, configure, configure-debug, build, build-debug, run, clean, rebuild-run, format, test"
# Notes for non-Windows shells
# If you're running on Git Bash or WSL, set shell and generator accordingly, e.g.:
# just -s --shell "bash -lc" configure

256
README.md
View File

@@ -1,200 +1,130 @@
# Simian
## Simian
**Simian** is a Raylib + AngelScript test project on Windows and Linux.
It demonstrates how to integrate **Raylib** for graphics and **AngelScript** for scripting, including the `scriptstdstring` add-on.
Simian is a small Raylib + AngelScript test project, refactored to be easy to extend and to show a safe hot-reload workflow for AngelScript.
## Refactored Architecture
This README was updated to reflect the current layout, build paths, and the small CMake wrapper approach used to optionally build ImGui / rlImGui when those folders are present.
The project has been refactored from a single `main.cpp` file into a more maintainable modular structure:
## What changed
### Core Components
- **Application** (`Application.h/cpp`) - Main application lifecycle and game loop
- **ScriptEngine** (`ScriptEngine.h/cpp`) - AngelScript engine management and script compilation
- **ScriptBindings** (`ScriptBindings.h/cpp`) - C++ to AngelScript function bindings
- **HotReload** (`HotReload.h/cpp`) - File monitoring for automatic script reloading
- **main.cpp** - Minimal entry point
- The original single-file demo was split into modules: Application, ScriptEngine, ScriptBindings and HotReload.
- Hot-reloads are performed safely: scripts compile into a temporary module first and only replace the running module on successful compilation. Compilation errors will no longer crash the running app.
- CMake wrappers live in `cmake/` to build third-party projects that don't ship a CMakeLists.txt (example: ImGui, rlImGui). This keeps submodules untouched.
- A `Justfile` was added with convenient recipes for configure, build, run and cleaning.
### Directory Structure
```
Simian/
├─ include/ # Header files
│ ├─ Application.h
│ ├─ ScriptEngine.h
│ ├─ ScriptBindings.h
│ └─ HotReload.h
├─ src/ # Source files
│ ├─ Application.cpp
│ ├─ ScriptEngine.cpp
│ ├─ ScriptBindings.cpp
│ └─ HotReload.cpp
├─ external/ # Dependencies
├─ scripts/ # AngelScript files
├─ main.cpp # Entry point
└─ CMakeLists.txt # Build configuration
```
## Quick start (Windows)
---
Open a developer prompt for Visual Studio or use PowerShell / cmd configured for MSVC.
## Requirements
Clone with submodules:
### Windows
- Windows 10/11 (tested)
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with **Desktop development with C++** workload
- [CMake 3.25+](https://cmake.org/download/)
- Git
- Optional: VSCode for editing
### Linux
- Ubuntu 20.04+ or equivalent Linux distribution
- GCC 9+ or Clang 10+
- [CMake 3.25+](https://cmake.org/download/)
- Git
- Development libraries: `sudo apt install build-essential cmake git`
---
## Getting Started
### 1. Clone the repository with submodules
```bash
git clone --recurse-submodules <your-repo-url>
```powershell
git clone --recurse-submodules <repo-url>
cd Simian
```
If you forgot `--recurse-submodules`, run:
Using the included `Justfile` (optional):
```bash
git submodule update --init --recursive
```powershell
# from project root
just configure # runs cmake -S . -B build -G "Visual Studio 17 2022" -A x64
just build # builds Debug by default (uses cmake --build)
just run # runs the built executable (Debug)
```
This will pull **Raylib** and **AngelScript** into `external/`.
Or use raw CMake commands:
---
### 2. Build with CMake
#### Windows (Visual Studio)
```bash
# Create build folder
mkdir build
cd build
# Generate Visual Studio solution (64-bit)
```powershell
mkdir build; cd build
cmake -G "Visual Studio 17 2022" -A x64 ..
# Build Release version
cmake --build . --config Release
```
After building, the executable will be at:
```
# run:
build\Release\simian.exe
```
#### Linux (Unix Makefiles)
If you are using cmd.exe, the same commands apply (adjust quotes for generator if needed).
## Linux / Unix
```bash
# Create build folder
mkdir build
cd build
# Generate Unix Makefiles
git clone --recurse-submodules <repo-url>
cd Simian
mkdir build && cd build
cmake -G "Unix Makefiles" ..
# Build
cmake --build .
./simian
```
After building, the executable will be at:
```
build/simian
```
---
### 3. Run the project
#### Windows
```bash
build\Release\simian.exe
```
#### Linux
```bash
./build/simian
```
You should see a **Raylib window** open and the console will output:
```
[Script] Hello from AngelScript!
```
---
## Project Structure
## Project layout (important files)
```
Simian/
├─ external/
│ ├─ raylib/ # Git submodule
│ └─ angelscript/ # Git submodule (includes scriptstdstring add-on)
├─ scripts/ # Optional: .as scripts to be loaded at runtime
├─ main.cpp # Entry point (Raylib + AngelScript)
├─ CMakeLists.txt # Build configuration
└─ README.md
├─ CMakeLists.txt # top level CMake
├─ Justfile # convenience tasks (configure/build/run/clean)
├─ include/ # public headers (Application, ScriptEngine, ScriptBindings, HotReload)
├─ src/ # implementation
├─ scripts/ # AngelScript source files loaded at runtime
├─ external/ # git submodules (raylib, angelscript, optional imgui/rlImGui)
└─ cmake/ # local CMake wrappers (imgui, rlImGui)
```
---
## Hot-reload notes
- The app watches `scripts/` for changes. When a script is updated it is compiled into a temporary AngelScript module.
- If compilation succeeds, the temp module replaces the live `main` module and new functions are used. If compilation fails, the running module is left intact and a single-line error is shown on-screen.
- This avoids crashes that happen when a script with compilation errors would otherwise replace/break function pointers while the engine is executing.
## ImGui / rlImGui (optional)
The repository prefers to keep upstream code as submodules. Some upstream projects (like certain ImGui tags or rlImGui) don't provide a CMakeLists.txt. To support those without editing submodules, the project includes small wrappers in `cmake/`:
- `cmake/imgui/` builds ImGui core (and optionally backends) when `external/imgui` is present.
- `cmake/rlImGui/` builds the rlImGui integration and links it to the `imgui` target if available.
If you prefer to use the upstream CMake (when present), the top-level `CMakeLists.txt` will prefer `external/rlImGui`'s CMake if it detects one; otherwise it falls back to the wrapper.
To add ImGui/rlImGui as submodules:
```powershell
# from repo root
git submodule add https://github.com/ocornut/imgui.git external/imgui
git submodule add https://github.com/NeoSpark314/rlImGui.git external/rlImGui
git submodule update --init --recursive
```
Then re-run CMake from a clean build directory.
Troubleshooting: if Git warns about "embedded git repository" when adding files, convert the folder to a submodule (remove cached tracked files, then add as submodule) — this README intentionally avoids modifying submodule contents.
## Tests / Verification
Quick smoke test after building:
```powershell
# run the Debug build
build\Debug\simian.exe
```
You should see the Raylib window. The running console prints from script when the default script executes (e.g. "[Script] Hello from AngelScript!").
If a script has a compilation error, the app will continue running and a brief error message is presented on the top-left of the window.
## Updating submodules
Keep submodules pinned for reproducible builds. To update them safely:
```powershell
cd external/raylib && git fetch && git checkout <commit-or-branch>
cd ../angelscript && git fetch && git checkout <commit-or-branch>
cd ../.. && git add external/raylib external/angelscript && git commit -m "Update submodules"
```
## Notes
* **AngelScript `string` support** is enabled via `scriptstdstring` add-on.
* **Hot-reloading scripts** can be implemented by loading `.as` files from the `scripts/` folder.
* Update submodules with:
```bash
cd external/raylib
git pull origin master
cd ../angelscript
git pull origin master
cd ../..
git add external/raylib external/angelscript
git commit -m "Update submodules"
```
---
## CMake Targets
* `simian` — main executable
* `raylib` — static library submodule
* `angelscript` — static library submodule
* `scriptstdstring` — static library add-on for AngelScript
---
## Tips
#### Windows
* Always use **VS2022 Developer Command Prompt** or VSCode terminal configured for MSVC.
#### Linux
* Make sure you have the required development packages installed.
* On some distributions you may need additional X11 development libraries.
#### General
* Pin submodules to specific commits for reproducible builds.
* To clean build: delete the `build/` folder and regenerate with CMake.
---
- The project was developed and tested on Windows with Visual Studio; Linux builds are supported but may need X11 / audio dev packages installed depending on your environment.
- The codebase avoids changing tracked submodule contents; use the `cmake/` wrappers when a submodule doesn't include a CMake build.
## License
* Raylib: [Zlib License](https://github.com/raysan5/raylib#license)
* AngelScript: [MIT License](https://github.com/angelcode/angelscript/blob/master/license.txt)
* Simian: Not open source (Yet)
* Raylib: Zlib License (see https://github.com/raysan5/raylib#license)
* AngelScript: MIT License (see https://github.com/angelcode/angelscript/blob/master/license.txt)
* Simian: Not open source (yet)

View File

@@ -0,0 +1,63 @@
cmake_minimum_required(VERSION 3.10)
project(imgui_wrapper NONE)
# Root of the imgui submodule (expected to live at external/imgui)
set(IMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/imgui")
if(NOT EXISTS "${IMGUI_ROOT}/imgui.h")
message(FATAL_ERROR "ImGui not found in ${IMGUI_ROOT}. Run: git submodule update --init --recursive")
endif()
# Options: build demo and/or backends. By default we only build the core library which
# avoids pulling in platform deps (SDL/Android/etc) from the backends and examples.
option(IMGUI_BUILD_DEMO "Include imgui_demo.cpp in the build" OFF)
option(IMGUI_BUILD_BACKENDS "Include selected backend implementations from backends/" OFF)
# Core sources (explicit list - avoids accidentally including examples)
set(IMGUI_CORE_SRC
"${IMGUI_ROOT}/imgui.cpp"
"${IMGUI_ROOT}/imgui_draw.cpp"
"${IMGUI_ROOT}/imgui_tables.cpp"
"${IMGUI_ROOT}/imgui_widgets.cpp"
)
if(IMGUI_BUILD_DEMO)
list(APPEND IMGUI_CORE_SRC "${IMGUI_ROOT}/imgui_demo.cpp")
endif()
add_library(imgui STATIC ${IMGUI_CORE_SRC})
target_include_directories(imgui PUBLIC
${IMGUI_ROOT}
)
# Optionally include a small, safe set of backends (desktop common ones) when requested.
if(IMGUI_BUILD_BACKENDS)
# Gather backend sources and whitelist a few common desktop backends
file(GLOB IMGUI_BACKEND_SRCS "${IMGUI_ROOT}/backends/*.cpp")
set(IMGUI_BACKEND_WHITELIST
"imgui_impl_glfw.cpp"
"imgui_impl_opengl3.cpp"
"imgui_impl_win32.cpp"
"imgui_impl_sdl2.cpp"
)
set(IMGUI_SELECTED_BACKENDS)
foreach(_f IN LISTS IMGUI_BACKEND_SRCS)
get_filename_component(_name ${_f} NAME)
foreach(_allowed IN LISTS IMGUI_BACKEND_WHITELIST)
if(_name STREQUAL _allowed)
list(APPEND IMGUI_SELECTED_BACKENDS ${_f})
endif()
endforeach()
endforeach()
if(IMGUI_SELECTED_BACKENDS)
target_sources(imgui PRIVATE ${IMGUI_SELECTED_BACKENDS})
target_include_directories(imgui PUBLIC "${IMGUI_ROOT}/backends")
endif()
endif()
# Build position independent code to make static lib usable in shared contexts
set_target_properties(imgui PROPERTIES POSITION_INDEPENDENT_CODE ON)

View File

@@ -0,0 +1,51 @@
cmake_minimum_required(VERSION 3.10)
project(rlImGui_wrapper NONE)
# Path to the rlImGui sources (submodule should be under external/rlImGui)
set(RLIMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/rlImGui")
if(NOT EXISTS "${RLIMGUI_ROOT}/README.md")
message(FATAL_ERROR "rlImGui not found in ${RLIMGUI_ROOT}. Add it as a submodule: git submodule add <url> external/rlImGui")
endif()
# Collect sources - rlImGui has sources under src/ and maybe example/backends
# Look for sources either under src/ or at the project root
file(GLOB RLIMGUI_ROOT_SRCS "${RLIMGUI_ROOT}/*.c" "${RLIMGUI_ROOT}/*.cpp")
file(GLOB RLIMGUI_SRC_DIR_SRCS "${RLIMGUI_ROOT}/src/*.c" "${RLIMGUI_ROOT}/src/*.cpp")
set(RLIMGUI_SOURCES ${RLIMGUI_ROOT_SRCS} ${RLIMGUI_SRC_DIR_SRCS})
# Exclude examples/tests if present
list(FILTER RLIMGUI_SOURCES EXCLUDE REGEX ".*example.*|.*test.*")
if(RLIMGUI_SOURCES)
add_library(rlImGui STATIC ${RLIMGUI_SOURCES})
# Add includes: rlImGui root and src if present
target_include_directories(rlImGui PUBLIC
${RLIMGUI_ROOT}
${RLIMGUI_ROOT}/src
)
# Ensure ImGui's headers are available (imgui submodule expected at external/imgui)
set(IMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/imgui")
if(EXISTS "${IMGUI_ROOT}/imgui.h")
target_include_directories(rlImGui PUBLIC ${IMGUI_ROOT})
endif()
# If the imgui target exists (from cmake/imgui wrapper), link it so we reuse the same lib
if(TARGET imgui)
target_link_libraries(rlImGui PRIVATE imgui)
endif()
else()
message(FATAL_ERROR "No rlImGui sources found under ${RLIMGUI_ROOT}. Expected rlImGui.cpp or src/*.cpp")
endif()
# Provide a property so consumers can check availability
set_target_properties(rlImGui PROPERTIES EXPORT_NAME rlImGui)
# Try to link to raylib if available in the parent project
if(TARGET raylib)
target_link_libraries(rlImGui PRIVATE raylib)
endif()

1
external/imgui vendored Submodule

Submodule external/imgui added at e7d2d636af

1
external/rlImGui vendored Submodule

Submodule external/rlImGui added at 4d8a618429

78
imgui.ini Normal file
View File

@@ -0,0 +1,78 @@
[Window][Debug##Default]
Pos=60,60
Size=400,400
Collapsed=0
[Window][Simian ImGui Demo]
Pos=141,224
Size=516,224
Collapsed=0
[Window][DockSpaceHost]
Pos=0,0
Size=1280,720
Collapsed=0
[Window][Left Panel]
Pos=292,567
Size=980,145
Collapsed=0
DockId=0x00000002,0
[Window][Right Panel]
Pos=8,27
Size=282,685
Collapsed=0
DockId=0x00000003,0
[Window][##TOAST2]
Pos=548,314
Size=232,82
Collapsed=0
[Window][##TOAST1]
Pos=548,406
Size=232,82
Collapsed=0
[Window][##TOAST0]
Pos=548,498
Size=232,82
Collapsed=0
[Window][##TOAST4]
Pos=548,130
Size=232,82
Collapsed=0
[Window][##TOAST3]
Pos=548,222
Size=232,82
Collapsed=0
[Window][##TOAST5]
Pos=548,38
Size=232,82
Collapsed=0
[Window][Log Viewer]
Pos=8,580
Size=1264,132
Collapsed=0
DockId=0x00000006,0
[Window][Game Window]
Pos=8,27
Size=1264,551
Collapsed=0
DockId=0x00000001,0
[Docking][Data]
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,27 Size=1264,685 Split=Y Selected=0x6D1308E5
DockNode ID=0x00000005 Parent=0x9076BACA SizeRef=1264,551 Split=X
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=282,685 Selected=0x6D1308E5
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=980,685 Split=Y
DockNode ID=0x00000001 Parent=0x00000004 SizeRef=1264,538 CentralNode=1 Selected=0x27A02DAA
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=1264,145 Selected=0x995FC207
DockNode ID=0x00000006 Parent=0x9076BACA SizeRef=1264,132 Selected=0xBEDDA0C1

View File

@@ -1,13 +1,15 @@
#pragma once
#include "ScriptEngine.h"
#include "HotReload.h"
#include "GuiManager.h"
#include "raylib.h"
class Application {
public:
Application();
~Application();
bool Initialize();
bool Initialize(int argc, char* argv[]);
void Run();
void Shutdown();
@@ -16,9 +18,12 @@ private:
HotReload* hotReload;
bool scriptCompilationError;
FILE* logFile;
GuiManager guiManager;
bool enableEditor;
RenderTexture2D renderTexture; // Declare renderTexture for Raylib rendering
static const int WINDOW_WIDTH = 800;
static const int WINDOW_HEIGHT = 600;
static const int WINDOW_WIDTH = 1280;
static const int WINDOW_HEIGHT = 720;
static const int TARGET_FPS = 60;
static const char* WINDOW_TITLE;
static const char* SCRIPT_FILE;

20
include/GuiManager.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "rlImGui.h"
#include "imgui.h"
#include "extras/IconsFontAwesome6.h"
#include "gui/fa-solid-900.h"
#include "gui/ImGuiNotify.hpp"
class GuiManager {
public:
GuiManager();
~GuiManager();
void Initialize();
void Render(RenderTexture2D& renderTexture);
void Shutdown();
private:
void SetupDockspace(RenderTexture2D& renderTexture);
void RenderNotifications();
};

682
include/gui/ImGuiNotify.hpp Normal file
View File

@@ -0,0 +1,682 @@
/**
* @file ImGuiNotify.hpp
* @brief A header-only library for creating toast notifications with ImGui.
*
* Based on imgui-notify by patrickcjk
* https://github.com/patrickcjk/imgui-notify
*
* @version 0.0.3 by TyomaVader
* @date 07.07.2024
*/
#ifndef IMGUI_NOTIFY
#define IMGUI_NOTIFY
#pragma once
#include <vector> // Vector for storing notifications list
#include <string>
#include <chrono> // For the notifications timed dissmiss
#include <functional> // For storing the code, which executest on the button click in the notification
#include "imgui.h"
#include "imgui_internal.h"
#include "extras/IconsFontAwesome6.h"
/**
* CONFIGURATION SECTION Start
*/
#define NOTIFY_MAX_MSG_LENGTH 4096 // Max message content length
#define NOTIFY_PADDING_X 20.f // Bottom-left X padding
#define NOTIFY_PADDING_Y 20.f // Bottom-left Y padding
#define NOTIFY_PADDING_MESSAGE_Y 10.f // Padding Y between each message
#define NOTIFY_FADE_IN_OUT_TIME 150 // Fade in and out duration
#define NOTIFY_DEFAULT_DISMISS 3000 // Auto dismiss after X ms (default, applied only of no data provided in constructors)
#define NOTIFY_OPACITY 0.8f // 0-1 Toast opacity
#define NOTIFY_USE_SEPARATOR false // If true, a separator will be rendered between the title and the content
#define NOTIFY_USE_DISMISS_BUTTON true // If true, a dismiss button will be rendered in the top right corner of the toast
#define NOTIFY_RENDER_LIMIT 5 // Max number of toasts rendered at the same time. Set to 0 for unlimited
// Warning: Requires ImGui docking with multi-viewport enabled
#ifndef NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW
#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW true // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window
#endif
/**
* CONFIGURATION SECTION End
*/
static const ImGuiWindowFlags NOTIFY_DEFAULT_TOAST_FLAGS = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing;
#define NOTIFY_NULL_OR_EMPTY(str) (!str || !strlen(str))
#define NOTIFY_FORMAT(fn, format, ...) \
if (format) \
{ \
va_list args; \
va_start(args, format); \
fn(format, args, ##__VA_ARGS__); \
va_end(args); \
}
enum class ImGuiToastType : uint8_t
{
None,
Success,
Warning,
Error,
Info,
COUNT
};
enum class ImGuiToastPhase : uint8_t
{
FadeIn,
Wait,
FadeOut,
Expired,
COUNT
};
enum class ImGuiToastPos : uint8_t
{
TopLeft,
TopCenter,
TopRight,
BottomLeft,
BottomCenter,
BottomRight,
Center,
COUNT
};
/**
* @brief A class for creating toast notifications with ImGui.
*/
class ImGuiToast
{
private:
ImGuiWindowFlags flags = NOTIFY_DEFAULT_TOAST_FLAGS;
ImGuiToastType type = ImGuiToastType::None;
char title[NOTIFY_MAX_MSG_LENGTH];
char content[NOTIFY_MAX_MSG_LENGTH];
int dismissTime = NOTIFY_DEFAULT_DISMISS;
std::chrono::system_clock::time_point creationTime = std::chrono::system_clock::now();
std::function<void()> onButtonPress = nullptr; // A lambda variable, which will be executed when button in notification is pressed
char buttonLabel[NOTIFY_MAX_MSG_LENGTH];
private:
// Setters
inline void setTitle(const char *format, va_list args)
{
vsnprintf(this->title, sizeof(this->title), format, args);
}
inline void setContent(const char *format, va_list args)
{
vsnprintf(this->content, sizeof(this->content), format, args);
}
inline void setButtonLabel(const char *format, va_list args)
{
vsnprintf(this->buttonLabel, sizeof(this->buttonLabel), format, args);
}
public:
/**
* @brief Set the title of the toast notification.
*
* @param format The format string for the title.
* @param ... The arguments for the format string.
*/
inline void setTitle(const char *format, ...)
{
NOTIFY_FORMAT(this->setTitle, format);
}
/**
* @brief Set the content of the toast notification.
*
* @param format The format string for the content.
* @param ... The arguments for the format string.
*/
inline void setContent(const char *format, ...)
{
NOTIFY_FORMAT(this->setContent, format);
}
/**
* @brief Set the type of the toast notification.
*
* @param type The type of the toast notification.
*/
inline void setType(const ImGuiToastType &type)
{
IM_ASSERT(type < ImGuiToastType::COUNT);
this->type = type;
};
/**
* @brief Set the ImGui window flags for the notification.
*
* @param flags ImGui window flags to set.
*/
inline void setWindowFlags(const ImGuiWindowFlags &flags)
{
this->flags = flags;
}
/**
* @brief Set the function to run on the button click in the notification.
*
* @param onButtonPress std::fuction or lambda expression, which contains the code for execution.
*/
inline void setOnButtonPress(const std::function<void()> &onButtonPress)
{
this->onButtonPress = onButtonPress;
}
/**
* @brief Set the label for the button in the notification.
*
* @param format The format string for the label.
* @param ... The arguments for the format string.
*/
inline void setButtonLabel(const char *format, ...)
{
NOTIFY_FORMAT(this->setButtonLabel, format);
}
public:
// Getters
/**
* @brief Get the title of the toast notification.
*
* @return const char* The title of the toast notification.
*/
inline const char *getTitle()
{
return this->title;
};
/**
* @brief Get the default title of the toast notification based on its type.
*
* @return const char* The default title of the toast notification.
*/
inline const char *getDefaultTitle()
{
if (!strlen(this->title))
{
switch (this->type)
{
case ImGuiToastType::None:
return nullptr;
case ImGuiToastType::Success:
return "Success";
case ImGuiToastType::Warning:
return "Warning";
case ImGuiToastType::Error:
return "Error";
case ImGuiToastType::Info:
return "Info";
default:
return nullptr;
}
}
return this->title;
};
/**
* @brief Get the type of the toast notification.
*
* @return ImGuiToastType The type of the toast notification.
*/
inline ImGuiToastType getType()
{
return this->type;
};
/**
* @brief Get the color of the toast notification based on its type.
*
* @return ImVec4 The color of the toast notification.
*/
inline ImVec4 getColor()
{
switch (this->type)
{
case ImGuiToastType::None:
return {255, 255, 255, 255}; // White
case ImGuiToastType::Success:
return {0, 255, 0, 255}; // Green
case ImGuiToastType::Warning:
return {255, 255, 0, 255}; // Yellow
case ImGuiToastType::Error:
return {255, 0, 0, 255}; // Error
case ImGuiToastType::Info:
return {0, 157, 255, 255}; // Blue
default:
return {255, 255, 255, 255}; // White
}
}
/**
* @brief Get the icon of the toast notification based on its type.
*
* @return const char* The icon of the toast notification.
*/
inline const char *getIcon()
{
switch (this->type)
{
case ImGuiToastType::None:
return nullptr;
case ImGuiToastType::Success:
return ICON_FA_CIRCLE_CHECK; // Font Awesome 6
case ImGuiToastType::Warning:
return ICON_FA_TRIANGLE_EXCLAMATION; // Font Awesome 6
case ImGuiToastType::Error:
return ICON_FA_CIRCLE_EXCLAMATION; // Font Awesome 6
case ImGuiToastType::Info:
return ICON_FA_CIRCLE_INFO; // Font Awesome 6
default:
return nullptr;
}
}
/**
* @brief Get the content of the toast notification.
*
* @return char* The content of the toast notification.
*/
inline char *getContent()
{
return this->content;
};
/**
* @brief Get the elapsed time in milliseconds since the creation of the object.
*
* @return int64_t The elapsed time in milliseconds.
* @throws An exception with the message "Unsupported platform" if the platform is not supported.
*/
inline std::chrono::nanoseconds getElapsedTime()
{
return std::chrono::system_clock::now() - this->creationTime;
}
/**
* @brief Get the current phase of the toast notification based on the elapsed time since its creation.
*
* @return ImGuiToastPhase The current phase of the toast notification.
* - ImGuiToastPhase::FadeIn: The notification is fading in.
* - ImGuiToastPhase::Wait: The notification is waiting to be dismissed.
* - ImGuiToastPhase::FadeOut: The notification is fading out.
* - ImGuiToastPhase::Expired: The notification has expired and should be removed.
*/
inline ImGuiToastPhase getPhase()
{
const int64_t elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(getElapsedTime()).count();
if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismissTime + NOTIFY_FADE_IN_OUT_TIME)
{
return ImGuiToastPhase::Expired;
}
else if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismissTime)
{
return ImGuiToastPhase::FadeOut;
}
else if (elapsed > NOTIFY_FADE_IN_OUT_TIME)
{
return ImGuiToastPhase::Wait;
}
else
{
return ImGuiToastPhase::FadeIn;
}
}
/**
* Returns the percentage of fade for the notification.
* @return The percentage of fade for the notification.
*/
inline float getFadePercent()
{
const ImGuiToastPhase phase = getPhase();
const int64_t elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(getElapsedTime()).count();
if (phase == ImGuiToastPhase::FadeIn)
{
return ((float)elapsed / (float)NOTIFY_FADE_IN_OUT_TIME) * NOTIFY_OPACITY;
}
else if (phase == ImGuiToastPhase::FadeOut)
{
return (1.f - (((float)elapsed - (float)NOTIFY_FADE_IN_OUT_TIME - (float)this->dismissTime) / (float)NOTIFY_FADE_IN_OUT_TIME)) * NOTIFY_OPACITY;
}
return 1.f * NOTIFY_OPACITY;
}
/**
* @return ImGui window flags for the notification.
*/
inline ImGuiWindowFlags getWindowFlags()
{
return this->flags;
}
/**
* @return The function, which is executed on the button click in the notification.
*/
inline std::function<void()> getOnButtonPress()
{
return this->onButtonPress;
}
/**
* @return The label on the button in notification.
*/
inline const char *getButtonLabel()
{
return this->buttonLabel;
}
public:
// Constructors
/**
* @brief Creates a new ImGuiToast object with the specified type and dismiss time.
*
* @param type The type of the toast.
* @param dismissTime The time in milliseconds after which the toast should be dismissed. Default is NOTIFY_DEFAULT_DISMISS.
*/
ImGuiToast(ImGuiToastType type, int dismissTime = NOTIFY_DEFAULT_DISMISS)
{
IM_ASSERT(type < ImGuiToastType::COUNT);
this->type = type;
this->dismissTime = dismissTime;
this->creationTime = std::chrono::system_clock::now();
memset(this->title, 0, sizeof(this->title));
memset(this->content, 0, sizeof(this->content));
}
/**
* @brief Constructor for creating an ImGuiToast object with a specified type and message format.
*
* @param type The type of the toast message.
* @param format The format string for the message.
* @param ... The variable arguments to be formatted according to the format string.
*/
ImGuiToast(ImGuiToastType type, const char *format, ...) : ImGuiToast(type)
{
NOTIFY_FORMAT(this->setContent, format);
}
/**
* @brief Constructor for creating a new ImGuiToast object with a specified type, dismiss time, and content format.
*
* @param type The type of the toast message.
* @param dismissTime The time in milliseconds before the toast message is dismissed.
* @param format The format string for the content of the toast message.
* @param ... The variable arguments to be formatted according to the format string.
*/
ImGuiToast(ImGuiToastType type, int dismissTime, const char *format, ...) : ImGuiToast(type, dismissTime)
{
NOTIFY_FORMAT(this->setContent, format);
}
/**
* @brief Constructor for creating a new ImGuiToast object with a specified type, dismiss time, title format, content format and a button.
*
* @param type The type of the toast message.
* @param dismissTime The time in milliseconds before the toast message is dismissed.
* @param buttonLabel The label for the button.
* @param onButtonPress The lambda function to be executed when the button is pressed.
* @param format The format string for the content of the toast message.
* @param ... The variable arguments to be formatted according to the format string.
*/
ImGuiToast(ImGuiToastType type, int dismissTime, const char *buttonLabel, const std::function<void()> &onButtonPress, const char *format, ...) : ImGuiToast(type, dismissTime)
{
NOTIFY_FORMAT(this->setContent, format);
this->onButtonPress = onButtonPress;
this->setButtonLabel(buttonLabel);
}
};
namespace ImGui
{
inline std::vector<ImGuiToast> notifications;
/**
* Inserts a new notification into the notification queue.
* @param toast The notification to be inserted.
*/
inline void InsertNotification(const ImGuiToast &toast)
{
notifications.push_back(toast);
}
/**
* @brief Removes a notification from the list of notifications.
*
* @param index The index of the notification to remove.
*/
inline void RemoveNotification(int index)
{
notifications.erase(notifications.begin() + index);
}
/**
* Renders all notifications in the notifications vector.
* Each notification is rendered as a toast window with a title, content and an optional icon.
* If a notification is expired, it is removed from the vector.
*/
inline void RenderNotifications()
{
const ImVec2 mainWindowSize = GetMainViewport()->Size;
float height = 0.f;
for (size_t i = 0; i < notifications.size();)
{
ImGuiToast *currentToast = &notifications[i];
// Remove toast if expired (erase while iterating safely)
if (currentToast->getPhase() == ImGuiToastPhase::Expired)
{
RemoveNotification(static_cast<int>(i));
// Do not increment i: elements shifted left
continue;
}
#if NOTIFY_RENDER_LIMIT > 0
if (i > NOTIFY_RENDER_LIMIT)
{
continue;
}
#endif
// Get icon, title and other data
const char *icon = currentToast->getIcon();
const char *title = currentToast->getTitle();
const char *content = currentToast->getContent();
const char *defaultTitle = currentToast->getDefaultTitle();
const float opacity = currentToast->getFadePercent(); // Get opacity based of the current phase
// Window rendering
ImVec4 textColor = currentToast->getColor();
textColor.w = opacity;
// Generate new unique name for this toast
char windowName[50];
#ifdef _WIN32
sprintf_s(windowName, "##TOAST%d", (int)i);
#elif defined(__linux__) || defined(__EMSCRIPTEN__)
std::sprintf(windowName, "##TOAST%d", (int)i);
#elif defined(__APPLE__)
std::snprintf(windowName, 50, "##TOAST%d", (int)i);
#else
throw "Unsupported platform";
#endif
// PushStyleColor(ImGuiCol_Text, textColor);
SetNextWindowBgAlpha(opacity);
#if NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW
// Try to obtain the monitor for the main viewport safely. Some backends may not populate PlatformIO monitors.
ImGuiViewport *main_vp = GetMainViewport();
int mainMonitorId = -1;
if (main_vp)
{
// ImGuiViewportP::PlatformMonitor is signed short in some versions; read safely
mainMonitorId = static_cast<int>(reinterpret_cast<ImGuiViewportP *>(main_vp)->PlatformMonitor);
}
ImGuiPlatformIO &platformIO = GetPlatformIO();
if (mainMonitorId >= 0 && mainMonitorId < static_cast<int>(platformIO.Monitors.size()))
{
ImGuiPlatformMonitor &monitor = platformIO.Monitors[mainMonitorId];
// Set notification window position to bottom right corner of the monitor
SetNextWindowPos(ImVec2(monitor.WorkPos.x + monitor.WorkSize.x - NOTIFY_PADDING_X, monitor.WorkPos.y + monitor.WorkSize.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f));
}
else
{
// Fallback to main viewport position if monitor info not available
ImVec2 mainWindowPos = main_vp ? main_vp->Pos : ImVec2(0, 0);
SetNextWindowPos(ImVec2(mainWindowPos.x + mainWindowSize.x - NOTIFY_PADDING_X, mainWindowPos.y + mainWindowSize.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f));
}
#else
// Set notification window position to bottom right corner of the main window, considering the main window size and location in relation to the display
ImVec2 mainWindowPos = GetMainViewport()->Pos;
SetNextWindowPos(ImVec2(mainWindowPos.x + mainWindowSize.x - NOTIFY_PADDING_X, mainWindowPos.y + mainWindowSize.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f));
#endif
// Set notification window flags
if (!NOTIFY_USE_DISMISS_BUTTON && currentToast->getOnButtonPress() == nullptr)
{
currentToast->setWindowFlags(NOTIFY_DEFAULT_TOAST_FLAGS | ImGuiWindowFlags_NoInputs);
}
Begin(windowName, nullptr, currentToast->getWindowFlags());
// Render over all other windows
BringWindowToDisplayFront(GetCurrentWindow());
// Here we render the toast content
{
PushTextWrapPos(mainWindowSize.x / 3.f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width
bool wasTitleRendered = false;
// If an icon is set
if (!NOTIFY_NULL_OR_EMPTY(icon))
{
// Text(icon); // Render icon text
TextColored(textColor, "%s", icon);
wasTitleRendered = true;
}
// If a title is set
if (!NOTIFY_NULL_OR_EMPTY(title))
{
// If a title and an icon is set, we want to render on same line
if (!NOTIFY_NULL_OR_EMPTY(icon))
SameLine();
Text("%s", title); // Render title text
wasTitleRendered = true;
}
else if (!NOTIFY_NULL_OR_EMPTY(defaultTitle))
{
if (!NOTIFY_NULL_OR_EMPTY(icon))
SameLine();
Text("%s", defaultTitle); // Render default title text (ImGuiToastType_Success -> "Success", etc...)
wasTitleRendered = true;
}
// If a dismiss button is enabled
if (NOTIFY_USE_DISMISS_BUTTON)
{
// If a title or content is set, we want to render the button on the same line
if (wasTitleRendered || !NOTIFY_NULL_OR_EMPTY(content))
{
SameLine();
}
// Render the dismiss button on the top right corner
// NEEDS TO BE REWORKED
float scale = 0.8f;
if (CalcTextSize(content).x > GetContentRegionAvail().x)
{
scale = 0.8f;
}
SetCursorPosX(GetCursorPosX() + (GetWindowSize().x - GetCursorPosX()) * scale);
// If the button is pressed, we want to remove the notification
if (Button(ICON_FA_XMARK))
{
RemoveNotification(i);
}
}
// In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically
if (wasTitleRendered && !NOTIFY_NULL_OR_EMPTY(content))
{
SetCursorPosY(GetCursorPosY() + 5.f); // Must be a better way to do this!!!!
}
// If a content is set
if (!NOTIFY_NULL_OR_EMPTY(content))
{
if (wasTitleRendered)
{
if constexpr (NOTIFY_USE_SEPARATOR)
{
Separator();
}
}
Text("%s", content); // Render content text
}
// If a button is set
if (currentToast->getOnButtonPress() != nullptr)
{
// If the button is pressed, we want to execute the lambda function
if (Button(currentToast->getButtonLabel()))
{
currentToast->getOnButtonPress()();
}
}
PopTextWrapPos();
}
// Save height for next toasts
height += GetWindowHeight() + NOTIFY_PADDING_MESSAGE_Y;
// End
End();
// advance index only when we didn't erase
++i;
}
}
}
#endif

5100
include/gui/fa-solid-900.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
#include "Application.h"
#include "src/log/log.h"
int main() {
int main(int argc, char* argv[]) {
Application app;
if (!app.Initialize()) {
if (!app.Initialize(argc, argv)) {
log_error("Failed to initialize application");
return -1;
}
@@ -13,4 +13,4 @@ int main() {
app.Shutdown();
return 0;
}
}

11
scripts/as.predefined Normal file
View File

@@ -0,0 +1,11 @@
typedef void string;
void Print(const string &in);
void Log(int level, const string &in);
void DrawText(const string &in, int x, int y, int fontSize, int color);
int LOG_TRACE;
int LOG_DEBUG;
int LOG_INFO;
int LOG_WARN;
int LOG_ERROR;
int LOG_FATAL;

View File

@@ -8,4 +8,4 @@ void Update(float dt) {
Print("X position reset!");
Log(LOG_INFO, "Log INFO: reset happened");
}
}//
}

View File

@@ -2,29 +2,67 @@
#include "raylib.h"
#include <chrono>
#include <thread>
#include <cstring>
#include <filesystem>
#include "log/log.h"
const char* Application::WINDOW_TITLE = "Raylib + AngelScript";
const char* Application::SCRIPT_FILE = "scripts/test.as";
Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr) {
#include "rlImGui.h"
#include "imgui.h"
#include "extras/IconsFontAwesome6.h"
#include "gui/ImGuiNotify.hpp"
#include "GuiManager.h"
#ifdef _WIN32
// On Windows, fopen_s is already available, so no need to define it.
#else
// On non-Windows platforms, define fopen_s as a macro for fopen.
#define fopen_s(pFile, filename, mode) ((*(pFile) = fopen((filename), (mode))) == NULL)
#endif
const char *Application::WINDOW_TITLE = "Simian";
const char *Application::SCRIPT_FILE = "scripts/test.as";
Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr), renderTexture{} // Initialize renderTexture
{
}
Application::~Application() {
Shutdown();
Application::~Application()
{
// Reminder in case the change main.cpp, but we do not want to close the
// window more than once
// Shutdown();
}
bool Application::Initialize() {
logFile = fopen("log.txt", "w");
log_add_fp(logFile, LOG_TRACE);
bool Application::Initialize(int argc, char *argv[])
{
if (fopen_s(&logFile, "log.txt", "w") != 0)
{
logFile = nullptr;
log_error("Failed to open log file");
}
else
{
log_add_fp(logFile, LOG_TRACE);
}
SetTraceLogCallback(raylib_log);
enableEditor = false;
// Parse command-line arguments
for (int i = 1; i < argc; ++i)
{
if (std::strcmp(argv[i], "--editor") == 0)
{
enableEditor = true;
}
}
// Initialize Raylib
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
SetTargetFPS(TARGET_FPS);
// Initialize AngelScript
if (!scriptEngine.Initialize()) {
if (!scriptEngine.Initialize())
{
log_error("Failed to initialize script engine");
return false;
}
@@ -34,34 +72,49 @@ bool Application::Initialize() {
{
std::filesystem::path p(SCRIPT_FILE);
std::string watchPath = p.parent_path().string();
if (watchPath.empty()) watchPath = ".";
if (watchPath.empty())
watchPath = ".";
hotReload = new HotReload(watchPath);
}
// Compile initial script
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
if (enableEditor)
{
guiManager.Initialize();
renderTexture = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT);
}
return true;
}
void Application::Run() {
while (!WindowShouldClose()) {
void Application::Run()
{
while (!WindowShouldClose())
{
float deltaTime = GetFrameTime();
Update(deltaTime);
Draw();
// Small sleep to prevent excessive CPU usage
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void Application::Update(float deltaTime) {
void Application::Update(float deltaTime)
{
// Check for hot reload
if (hotReload && hotReload->CheckForChanges()) {
if (hotReload && hotReload->CheckForChanges())
{
bool success = scriptEngine.CompileScript(SCRIPT_FILE);
scriptCompilationError = !success;
if (!success) {
if (!success)
{
log_warn("Script compilation failed - keeping previous version");
}
}
@@ -70,35 +123,89 @@ void Application::Update(float deltaTime) {
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
}
void Application::Draw() {
BeginDrawing();
ClearBackground(RAYWHITE);
void Application::Draw()
{
if (enableEditor)
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
// Show script error status in top left if there's an error, otherwise show normal message
if (scriptCompilationError) {
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
// Fallback to default values if screen dimensions are invalid
if (screenWidth <= 0 || screenHeight <= 0)
{
log_warn("Invalid screen dimensions: %d x %d. Using default values.", screenWidth, screenHeight);
screenWidth = 800;
screenHeight = 600;
}
ImGuiIO &io = ImGui::GetIO();
io.DisplaySize = ImVec2((float)screenWidth, (float)screenHeight); // Set DisplaySize
BeginTextureMode(renderTexture);
ClearBackground(RAYWHITE);
// Show script error status in top left if there's an error, otherwise show normal message
if (scriptCompilationError)
{
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
}
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
EndTextureMode();
BeginDrawing();
ClearBackground(RAYWHITE);
guiManager.Render(renderTexture); // Render the GUI
EndDrawing();
}
else
{
BeginDrawing();
ClearBackground(RAYWHITE);
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
// Show script error status in top left if there's an error, otherwise show normal message
if (scriptCompilationError)
{
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
}
EndDrawing();
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
EndDrawing();
}
}
void Application::Shutdown() {
if (hotReload) {
void Application::Shutdown()
{
if (hotReload)
{
delete hotReload;
hotReload = nullptr;
}
scriptEngine.Shutdown();
if (enableEditor)
{
guiManager.Shutdown();
}
// Shutdown rlImGui first while the GL context and window are still valid
rlImGuiShutdown();
// Shutdown script engine
scriptEngine.Shutdown();
// Clear the trace log callback before closing window to prevent logging after cleanup
SetTraceLogCallback(nullptr);
CloseWindow();
// Close log file after everything else is cleaned up
if (logFile) {
if (logFile)
{
fclose(logFile);
logFile = nullptr;
}

122
src/GuiManager.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "GuiManager.h"
#include "raylib.h"
#include <fstream> // Add this to fix the incomplete type error
#include <string>
#include <iostream>
#include "log.h"
GuiManager::GuiManager() {}
GuiManager::~GuiManager() {}
void GuiManager::Initialize()
{
rlImGuiSetup(true);
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
io.Fonts->AddFontDefault();
float baseFontSize = 16.0f;
float iconFontSize = baseFontSize * 2.0f / 3.0f;
static constexpr ImWchar iconsRanges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
ImFontConfig iconsConfig;
iconsConfig.MergeMode = true;
iconsConfig.PixelSnapH = true;
iconsConfig.GlyphMinAdvanceX = iconFontSize;
io.Fonts->AddFontFromMemoryCompressedTTF(fa_solid_900_compressed_data, fa_solid_900_compressed_size, iconFontSize, &iconsConfig, iconsRanges);
log_trace("GuiManager::Initialize - Started");
}
void GuiManager::Render(RenderTexture2D &renderTexture)
{
rlImGuiBegin();
SetupDockspace(renderTexture); // Pass the renderTexture parameter
RenderNotifications();
rlImGuiEnd();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
void GuiManager::Shutdown()
{
rlImGuiShutdown();
}
void GuiManager::SetupDockspace(RenderTexture2D &renderTexture)
{
static bool dockspaceOpen = true;
static bool opt_fullscreen = true;
static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
if (opt_fullscreen)
{
ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
}
ImGui::Begin("DockSpaceHost", &dockspaceOpen, window_flags);
if (opt_fullscreen)
ImGui::PopStyleVar(2);
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
// Add a Game Window
ImGui::Begin("Game Window");
// Render Raylib content to the Game Window
rlImGuiImageRenderTexture(&renderTexture);
ImGui::End();
// Log Viewer Window
ImGui::Begin("Log Viewer");
static std::string logContent;
static size_t lastFileSize = 0;
// Read the log file if it has changed
std::ifstream logFile("log.txt", std::ios::ate); // Open at the end to get the file size
if (logFile.is_open())
{
size_t fileSize = logFile.tellg();
if (fileSize != lastFileSize)
{
lastFileSize = fileSize;
logFile.seekg(0, std::ios::beg); // Go back to the beginning
logContent.assign((std::istreambuf_iterator<char>(logFile)),
std::istreambuf_iterator<char>());
}
logFile.close();
}
// Display the log content in a scrollable text area
ImGui::BeginChild("LogText", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar);
ImGui::TextUnformatted(logContent.c_str());
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
ImGui::SetScrollHereY(1.0f); // Auto-scroll to the bottom
ImGui::EndChild();
ImGui::End();
ImGui::End();
}
void GuiManager::RenderNotifications()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.10f, 1.00f));
ImGui::RenderNotifications();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
}