diff --git a/.clang-format b/.clang-format
new file mode 100644
index 000000000..b50f575c7
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,79 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: true
+AlignConsecutiveDeclarations: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowShortBlocksOnASingleLine: Empty
+AllowShortFunctionsOnASingleLine: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializers: BeforeColon
+BreakConstructorInitializersBeforeComma: false
+ColumnLimit: 120
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ContinuationIndentWidth: 4
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^<.*'
+ Priority: 1
+ - Regex: '^".*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentAccessModifiers: True
+IndentCaseBlocks: false
+IndentCaseLabels: true
+IndentGotoLabels: true
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+InsertNewlineAtEOF: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: All
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInConditionalStatement: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: Never
+...
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2f327509b..8e22e7e9f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,16 +1,28 @@
name: Build, test and Package
-on: [push]
+on:
+ push:
+ workflow_dispatch:
+ inputs:
+ debug_enabled:
+ type: boolean
+ description: 'Enable remote SSH connection. Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
+ required: false
+ default: false
jobs:
build:
name: Build, test and Package
# env and permissions are setup to add dependencies from vcpkg to the repo's dependency graph
env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VCPKG_FEATURE_FLAGS: dependencygraph
+ VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg
+ FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json
+ VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite"
+ DOTNET_INSTALL_DIR: "./.dotnet"
permissions:
contents: write
+ packages: write
strategy:
fail-fast: false
matrix:
@@ -37,11 +49,19 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0 # Fetch all history for all tags and branches (for SonarCloud)
+ # DEBUGGING ONLY, to run this trigger with even
+ - name: Setup tmate session
+ uses: mxschmitt/action-tmate@v3
+ if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
+ with:
+ limit-access-to-actor: true # Only the person who triggered the workflow can access
+ detached: true # Run in the background and wait for connection
+
- name: Add Ubuntu toolchain repository
if: ${{ matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc'}}
run: |
@@ -90,10 +110,15 @@ jobs:
libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \
libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \
libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \
- libegl1-mesa-dev
+ libegl1-mesa-dev mono-complete dotnet-sdk-9.0 # Pre-install .NET SDK 9.0 for caching
version: 1.0
execute_install_scripts: true
+ - name: Install .NET SDK 9.0
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
- name: Init submodules
run: |
git submodule update --init --recursive
@@ -110,6 +135,40 @@ jobs:
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
+ - name: Add NuGet sources
+ if: ${{ matrix.os == 'windows-latest' }}
+ shell: pwsh
+ env:
+ USERNAME: NexoEngine
+ run: |
+ .$(${{ env.VCPKG_EXE }} fetch nuget) `
+ sources add `
+ -Source "${{ env.FEED_URL }}" `
+ -StorePasswordInClearText `
+ -Name GitHubPackages `
+ -UserName "${{ env.USERNAME }}" `
+ -Password "${{ secrets.GITHUB_TOKEN }}"
+ .$(${{ env.VCPKG_EXE }} fetch nuget) `
+ setapikey "${{ secrets.GITHUB_TOKEN }}" `
+ -Source "${{ env.FEED_URL }}"
+
+ - name: Add NuGet sources
+ if: ${{ matrix.os != 'windows-latest' }}
+ shell: bash
+ env:
+ USERNAME: NexoEngine
+ run: |
+ mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \
+ sources add \
+ -Source "${{ env.FEED_URL }}" \
+ -StorePasswordInClearText \
+ -Name GitHubPackages \
+ -UserName "${{ env.USERNAME }}" \
+ -Password "${{ secrets.GITHUB_TOKEN }}"
+ mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \
+ setapikey "${{ secrets.GITHUB_TOKEN }}" \
+ -Source "${{ env.FEED_URL }}"
+
- name: CMake Workflow with preset 'build-coverage' for tests
uses: lukka/run-cmake@v10
with:
@@ -120,6 +179,7 @@ jobs:
CXX: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clangxx || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gxx || '' }}
CMAKE_C_COMPILER: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clang || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gcc || '' }}
CMAKE_CXX_COMPILER: ${{ matrix.compiler == 'clang' && steps.set-up-clang.outputs.clangxx || matrix.compiler == 'gcc' && steps.set-up-gcc.outputs.gxx || '' }}
+ USERNAME: NexoEngine
- name: Install Mesa for Windows
shell: cmd
@@ -204,6 +264,8 @@ jobs:
test-nsis-installer:
name: Test NSIS installer
runs-on: windows-latest
+ env:
+ DOTNET_INSTALL_DIR: "./.dotnet"
needs: build
steps:
- name: Download NSIS installer
@@ -212,6 +274,11 @@ jobs:
with:
pattern: 'nexo-engine-installer-msvc14-windows-latest'
+ - name: Install .NET SDK 9.0
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
- name: Run NSIS installer
shell: pwsh
run: |
@@ -269,6 +336,8 @@ jobs:
test-deb-installer:
name: Test DEB installer
runs-on: ubuntu-latest
+ env:
+ DOTNET_INSTALL_DIR: "./.dotnet"
needs: build
steps:
- name: Download DEB installer
@@ -277,6 +346,11 @@ jobs:
with:
pattern: 'nexo-engine-installer-gcc13-ubuntu-latest'
+ - name: Install .NET SDK 9.0
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
- name: Install DEB package
shell: bash
run: |
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 9c838d8b6..ac37b38d1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -5,6 +5,11 @@ on: [push]
jobs:
analyze:
name: Analyze (${{ matrix.language }}/${{ matrix.compiler }}) on ${{ matrix.os }}
+ env:
+ VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg
+ FEED_URL: https://nuget.pkg.github.com/NexoEngine/index.json
+ VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/NexoEngine/index.json,readwrite"
+ DOTNET_INSTALL_DIR: "./.dotnet"
runs-on: ${{ matrix.os }}
permissions:
# required for all workflows
@@ -57,10 +62,15 @@ jobs:
libxext-dev libxi-dev libgl1-mesa-dev libxinerama-dev \
libxcursor-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev \
libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev \
- libegl1-mesa-dev
+ libegl1-mesa-dev mono-complete dotnet-sdk-9.0 # Pre-install .NET SDK 9.0 for caching
version: 1.0
execute_install_scripts: true
+ - name: Install .NET SDK 9.0
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+
- name: Set up GCC
if: ${{ matrix.compiler == 'gcc' }}
id: set-up-gcc
@@ -82,9 +92,27 @@ jobs:
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
+ - name: Add NuGet sources
+ shell: bash
+ env:
+ USERNAME: NexoEngine
+ run: |
+ mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \
+ sources add \
+ -Source "${{ env.FEED_URL }}" \
+ -StorePasswordInClearText \
+ -Name GitHubPackages \
+ -UserName "${{ env.USERNAME }}" \
+ -Password "${{ secrets.GITHUB_TOKEN }}"
+ mono `${{ env.VCPKG_EXE }} fetch nuget | tail -n 1` \
+ setapikey "${{ secrets.GITHUB_TOKEN }}" \
+ -Source "${{ env.FEED_URL }}"
+
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
+ env:
+ USERNAME: NexoEngine
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml
index c420a3352..b14ee3f30 100644
--- a/.github/workflows/doxygen.yml
+++ b/.github/workflows/doxygen.yml
@@ -12,6 +12,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: git submodule add https://github.com/jothepro/doxygen-awesome-css.git && cd doxygen-awesome-css && git checkout v2.2.0
- - uses: langroodi/doxygenize@v1.6.1
+ - uses: langroodi/doxygenize@v1.7.0
with:
htmloutput: './html/'
diff --git a/.gitignore b/.gitignore
index 400e3d902..f8b9d9755 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# Prevent pushing window layouts
./config/default-layout.ini
+# Do not push auto generated COPYRIGHT
+COPYRIGHT_generated
+
# Prerequisites
*.d
diff --git a/.idea/sonarlint.xml b/.idea/sonarlint.xml
deleted file mode 100644
index 8ab94a851..000000000
--- a/.idea/sonarlint.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8e95a8f99..e808cec2a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,12 +5,14 @@ project(client CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(NEXO_COVERAGE OFF CACHE BOOL "Enable coverage for binaries")
set(NEXO_GIT_SUBMODULE OFF CACHE BOOL "Enable git submodules init and update")
set(NEXO_BOOTSTRAP_VCPKG OFF CACHE BOOL "Enable vcpkg bootstrap")
set(NEXO_BUILD_TESTS ON CACHE BOOL "Enable tests")
set(NEXO_BUILD_EXAMPLES OFF CACHE BOOL "Enable examples")
+set(NEXO_BUILD_SCRIPTING ON CACHE BOOL "Enable C# scripting support")
set(NEXO_GRAPHICS_API "OpenGL" CACHE STRING "Graphics API to use")
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
@@ -19,7 +21,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
set(NEXO_COMPILER_FLAGS_RELEASE -O3)
set(NEXO_COVERAGE_FLAGS -O0 --coverage)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
- set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD})
+ set(NEXO_COMPILER_FLAGS_ALL /std:c++${CMAKE_CXX_STANDARD} /Zc:preprocessor)
set(NEXO_COMPILER_FLAGS_DEBUG /Zi /Od /Zc:preprocessor)
set(NEXO_COMPILER_FLAGS_RELEASE /O2 /Zc:preprocessor)
set(NEXO_COVERAGE_FLAGS "") # MSVC doesn't support coverage in the same way
@@ -95,6 +97,11 @@ message(STATUS "VCPKG done.")
include("${CMAKE_CURRENT_SOURCE_DIR}/editor/CMakeLists.txt")
# SETUP ENGINE
include("${CMAKE_CURRENT_SOURCE_DIR}/engine/CMakeLists.txt")
+# SETUP MANAGED CSHARP LIB
+if(NEXO_BUILD_SCRIPTING)
+ include("${CMAKE_CURRENT_SOURCE_DIR}/engine/src/scripting/managed/CMakeLists.txt")
+ add_dependencies(nexoEditor nexoManaged)
+endif()
# SETUP EXAMPLE
include("${CMAKE_CURRENT_SOURCE_DIR}/examples/CMakeLists.txt")
# SETUP TESTS
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 000000000..56b92264a
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,217 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Comment:
+ NEXO Engine Exhaustive list of all licenses used in the project
+ ---------------------------------------------------------------
+ This file is a template that is processed by CMake to generate the
+ final copyright file. See scripts/copyright.cmake for more information.
+ When generated some information might be missing and should be
+ verified and updated manually.
+ .
+ This file lists copyright holders and licenses for the NEXO project, its
+ source code, dependencies and libraries.
+ .
+ The format is based on the Debian copyright format 1.0, which is a
+ human and machine readable format for copyright information.
+ For more information, see:
+ https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Upstream-Name: NEXO Engine
+Upstream-Contact: NEXO Engine Team
+Source: https://github.com/NexoEngine/game-engine
+
+Files: *
+Comment: NEXO Engine
+Copyright: 2025 NEXO Engine contributors
+License: MIT
+
+Files:
+ resources/nexo.ico
+ resources/nexo.png
+ resources/nexo.rc
+ resources/nexo_header.bmp
+Comment: NEXO Engine Logo
+Copyright: 2025 NEXO Engine contributors
+License: CC-BY-SA-4.0
+
+Files: *
+Copyright: 2006-2021, assimp team
+License: BSD-3-clause
+Comment:
+ assimp full license in /external/licenses/assimp
+
+Files:
+ engine/*
+ editor/*
+ common/*
+Copyright: Boost Software License - Version 1.0 - August 17th, 2003
+License: BSL-1.0
+Comment:
+ boost full license in /external/licenses/boost
+
+Files: *
+Copyright: Google Inc. and other contributors
+License: Apache-2.0
+Comment:
+ draco full license in /external/licenses/draco
+
+Files: *
+Copyright: 2008-2018 The Khronos Group Inc.
+License: Apache-2.0
+Comment:
+ egl-registry full license in /external/licenses/egl-registry
+
+Files:
+ engine/*
+Copyright: 2013-2021 David Herberth
+License: MIT
+Comment:
+ glad full license in /external/licenses/glad
+
+Files:
+ engine/*
+Copyright: 2002-2006 Marcus Geelnard
+ 2006-2019 Camilla Löwy
+License: zlib/libpng
+Comment:
+ glfw3 full license in /external/licenses/glfw3
+
+Files:
+ engine/*
+ editor/*
+ common/*
+Copyright: 2005 - G-Truc Creation
+License: MIT
+Comment:
+ glm full license in /external/licenses/glm
+
+Files:
+ tests/*
+Copyright: 2008, Google Inc.
+License: BSD-3-clause
+Comment:
+ gtest full license in /external/licenses/gtest
+
+Files:
+ editor/*
+Copyright: 2014-2025 Omar Cornut
+License: MIT
+Comment:
+ imgui full license in /external/licenses/imgui
+
+Files:
+ editor/*
+Copyright: 2016 Cedric Guillemet
+License: MIT
+Comment:
+ imguizmo full license in /external/licenses/imguizmo
+
+Files: *
+Copyright: 2009-2018, Poly2Tri Contributors
+License: BSD-3-clause
+Comment:
+ jhasse-poly2tri full license in /external/licenses/jhasse-poly2tri
+
+Files:
+ engine/*
+Copyright: 2021 Jorrit Rouwe
+License: MIT
+Comment:
+ joltphysics full license in /external/licenses/joltphysics
+
+Files: *
+Copyright: Kuba Podgórski
+License: public-domain
+Comment:
+ kubazip full license in /external/licenses/kubazip
+
+Files:
+ engine/*
+ editor/*
+ common/*
+Copyright: Emil Ernerfeldt
+License: public-domain
+Comment:
+ loguru full license in /external/licenses/loguru
+
+Files: *
+Copyright: 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson
+License: zlib
+Comment:
+ minizip full license in /external/licenses/minizip
+
+Files:
+ engine/*
+ editor/*
+ common/*
+Copyright: 2013-2022 Niels Lohmann
+License: MIT
+Comment:
+ nlohmann-json full license in /external/licenses/nlohmann-json
+
+Files: *
+Copyright: 2008-2018 The Khronos Group Inc.
+License: MIT
+Comment:
+ opengl-registry full license in /external/licenses/opengl-registry
+
+Files: *
+Copyright: Angus Johnson and other contributors
+License: BSL-1.0
+Comment:
+ polyclipping full license in /external/licenses/polyclipping
+
+Files: *
+Copyright: 2006-2023 Arseny Kapoulkine
+License: MIT
+Comment:
+ pugixml full license in /external/licenses/pugixml
+
+Files: *
+Copyright: 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
+License: MIT
+Comment:
+ rapidjson full license in /external/licenses/rapidjson
+
+Files:
+ engine/*
+Copyright: 2017 Sean Barrett
+License: MIT or public-domain
+Comment:
+ stb full license in /external/licenses/stb
+
+Files:
+ editor/*
+Copyright: Pascal Costanza
+License: zlib/libpng
+Comment:
+ tinyfiledialogs full license in /external/licenses/tinyfiledialogs
+
+Files: *
+Copyright: Nemanja Trifunovic
+License: BSL-1.0
+Comment:
+ utfcpp full license in /external/licenses/utfcpp
+
+Files: vcpkg
+Copyright: Microsoft Corporation
+License: MIT
+Comment:
+ vcpkg-cmake full license in /external/licenses/vcpkg-cmake
+
+Files: vcpkg
+Copyright: Microsoft Corporation
+License: MIT
+Comment:
+ vcpkg-cmake-config full license in /external/licenses/vcpkg-cmake-config
+
+Files: vcpkg
+Copyright: Microsoft Corporation
+License: MIT
+Comment:
+ vcpkg-cmake-get-vars full license in /external/licenses/vcpkg-cmake-get-vars
+
+Files: *
+Copyright: 1995-2022 Jean-loup Gailly and Mark Adler
+License: zlib
+Comment:
+ zlib full license in /external/licenses/zlib
diff --git a/README.md b/README.md
index 7ce283086..1dc07dded 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,10 @@ Welcome to the NEXO Engine repository! This project is a collaborative effort to
- [Create an installer for Linux (DEB)](#create-an-installer-for-linux-deb)
- [Run the tests](#run-the-tests)
- [The Team](#the-team)
+ - [Acknowledgements](#acknowledgements)
+ - [License](#license)
+ - [How to extract the third-party licenses file](#how-to-extract-the-third-party-licenses-file)
+ - [How to generate the COPYRIGHT file](#how-to-generate-the-copyright-file)
> [!NOTE]
> Find the whole documentation on our [website](https://nexoengine.github.io/game-engine/).
@@ -53,6 +57,7 @@ https://github.com/user-attachments/assets/f675cdc0-3a53-4fb8-8544-a22dc7a332f4
To run this project, ensure you have the following:
- **CMake**: Necessary for building the project from source.
- **C++ Compiler**: We recommend using GCC or Clang for Linux and MacOS, and MSVC for Windows.
+- **.NET SDK 9.0**: Required for the C# scripting support.
## Build the project
@@ -175,3 +180,36 @@ NEXO Engine is brought to life by a dedicated team of fourth-year students from
This project is part of our curriculum and end of studies project, showcasing our collective skills in advanced software development with modern C++.
We thank Epitech for the opportunity to work on such an engaging project and for the support throughout our educational journey.
+
+## License
+
+This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
+For more information about the copyright of the project, please refer to the [COPYRIGHT](COPYRIGHT) file.
+You can also find the license of the third-party libraries used in the project in the [external/licenses](external/licenses) directory.
+
+> [!TIP]
+> For any license inquiry, please contact us at [nexo.engine@gmail.com](mailto:nexo.engine@gmail.com?subject=[NEXO%20Engine]%20License)
+
+### How to extract the third-party licenses file
+
+You can use the cmake install command:
+```bash
+cmake --install build --prefix /path/to/install --component generate-licenses
+```
+
+This will extract all licenses per third-party library in the `/path/to/install/external/licenses` directory.
+
+> [!NOTE]
+> These licenses are automatically extracted from vcpkg, there might be missing third-party libraries.
+
+### How to generate the COPYRIGHT file
+
+You can use the cmake install command:
+```bash
+cmake --install build --prefix /path/to/install --component generate-copyright
+```
+
+This will generate the COPYRIGHT file in the `/path/to/install` directory.
+
+> [!WARNING]
+> By default the COPYRIGHT file is generated with some `TODO:`, the generator cannot always determine exact licenses for some files. Please check each entry for errors.
diff --git a/common/Logger.hpp b/common/Logger.hpp
index 48f414d0f..a8afd4825 100644
--- a/common/Logger.hpp
+++ b/common/Logger.hpp
@@ -20,6 +20,8 @@
#include
#include
#include
+#include
+#include
namespace nexo {
@@ -36,13 +38,14 @@ namespace nexo {
}
}
- enum class LogLevel {
+ enum class LogLevel : uint32_t {
FATAL,
- ERROR,
+ ERR,
WARN,
INFO,
DEBUG,
- DEV
+ DEV,
+ USER
};
inline std::string toString(const LogLevel level)
@@ -50,9 +53,10 @@ namespace nexo {
switch (level)
{
case LogLevel::FATAL: return "FATAL";
- case LogLevel::ERROR: return "ERROR";
+ case LogLevel::ERR: return "ERROR";
case LogLevel::WARN: return "WARN";
case LogLevel::INFO: return "INFO";
+ case LogLevel::USER: return "USER";
case LogLevel::DEBUG: return "DEBUG";
case LogLevel::DEV: return "DEV";
}
@@ -67,17 +71,73 @@ namespace nexo {
return path;
}
- inline void defaultCallback(const LogLevel level, const std::string &message)
+ inline void defaultCallback(const LogLevel level, const std::source_location& loc, const std::string &message)
{
- if (level == LogLevel::FATAL || level == LogLevel::ERROR)
- std::cerr << "[" << toString(level) << "] " << message << std::endl;
- else
- std::cout << "[" << toString(level) << "] " << message << std::endl;
+ std::ostream& outputStream = level == LogLevel::FATAL || level == LogLevel::ERR
+ ? std::cerr
+ : std::cout;
+
+ outputStream << "[" << toString(level) << "] " << getFileName(loc.file_name()) << ":" << loc.line() << " - " << message << std::endl;
}
+ /**
+ * @brief Registry to track which log messages have been emitted
+ *
+ * This class is used to ensure certain messages are only logged once
+ * until they are explicitly reset.
+ */
+ class OnceRegistry {
+ public:
+ /**
+ * @brief Get the singleton instance of the registry
+ *
+ * @return OnceRegistry& Reference to the registry
+ */
+ static OnceRegistry& instance() {
+ static OnceRegistry registry;
+ return registry;
+ }
+
+ /**
+ * @brief Check if a message has been logged and mark it as logged
+ *
+ * @param key The unique identifier for the message
+ * @return true If this is the first time seeing this message
+ * @return false If this message has been logged before
+ */
+ bool shouldLog(const std::string& key) {
+ std::scoped_lock lock(m_mutex);
+ return m_loggedKeys.insert(key).second;
+ }
+
+ /**
+ * @brief Reset a specific message so it can be logged again
+ *
+ * @param key The unique identifier for the message to reset
+ */
+ void reset(const std::string& key) {
+ std::scoped_lock lock(m_mutex);
+ m_loggedKeys.erase(key);
+ }
+
+ /**
+ * @brief Reset all messages so they can be logged again
+ */
+ void resetAll() {
+ std::scoped_lock lock(m_mutex);
+ m_loggedKeys.clear();
+ }
+
+ private:
+ OnceRegistry() = default;
+
+ std::unordered_set m_loggedKeys;
+ std::mutex m_mutex;
+ };
+
class Logger {
public:
- static void setCallback(std::function callback)
+ static void setCallback(std::function callback)
{
logCallback = std::move(callback);
}
@@ -93,24 +153,69 @@ namespace nexo {
},
transformed);
- if (level == LogLevel::INFO)
- logString(level, message);
- else
- {
- std::stringstream ss;
- ss << getFileName(loc.file_name()) << ":" << loc.line() << " - " << message;
- logString(level, ss.str());
- }
+ logCallback(level, loc, message);
}
- private:
- static void logString(const LogLevel level, const std::string &message)
+
+ /**
+ * @brief Generate a key incorporating format string and parameter values
+ *
+ * @tparam Args Variadic template for format arguments
+ * @param fmt Format string
+ * @param args Format arguments
+ * @return std::string Key incorporating the parameters
+ */
+ template
+ static std::string generateKey(const std::string_view fmt, const std::string& location, Args&&... args)
+ {
+ std::stringstream ss;
+ ss << fmt << "@" << location << "|";
+
+ // Add parameter values to the key
+ ((ss << toFormatFriendly(args) << "|"), ...);
+
+ return ss.str();
+ }
+
+ /**
+ * @brief Log a message only once until reset
+ *
+ * This method logs a message only the first time it is called with a given key.
+ * The key incorporates both the format string and the parameter values.
+ *
+ * @tparam Args Variadic template for format arguments
+ * @param level Log level
+ * @param loc Source location of the log call
+ * @param fmt Format string
+ * @param key Key including format string and parameter values
+ * @param args Format arguments
+ */
+ template
+ static void logOnce(const LogLevel level, const std::source_location loc,
+ const std::string_view fmt, const std::string& key, Args &&... args)
{
- if (logCallback)
- logCallback(level, message);
- else
- defaultCallback(level, message);
+ if (OnceRegistry::instance().shouldLog(key)) {
+ logWithFormat(level, loc, fmt, std::forward(args)...);
+ }
}
- static inline std::function logCallback = nullptr;
+
+ /**
+ * @brief Reset a specific log message so it can be logged again with logOnce
+ *
+ * @param key The unique identifier for the log message to reset
+ */
+ static void resetOnce(const std::string& key) {
+ OnceRegistry::instance().reset(key);
+ }
+
+ /**
+ * @brief Reset all log messages so they can be logged again with logOnce
+ */
+ static void resetAllOnce() {
+ OnceRegistry::instance().resetAll();
+ }
+
+ private:
+ static inline std::function logCallback = defaultCallback;
};
}
@@ -121,8 +226,28 @@ namespace nexo {
#define LOG_EXCEPTION(exception) \
LOG(NEXO_ERROR, "{}:{} - Exception: {}", exception.getFile(), exception.getLine(), exception.getMessage())
+/**
+ * @brief Generate a unique key for a log message incorporating format and parameters
+ *
+ * This creates a key that includes both the format string and the parameter values,
+ * allowing specific message instances to be reset later.
+ */
+#define NEXO_LOG_ONCE_KEY(fmt, ...) \
+ nexo::Logger::generateKey(fmt, std::string(__FILE__) + ":" + std::to_string(__LINE__), ##__VA_ARGS__)
+
+/**
+ * @brief Log a message only once until it's reset
+ *
+ * This ensures the message is only logged the first time this line is executed
+ * with these specific parameters. Subsequent calls with the same parameters
+ * will be ignored until the message is reset.
+ */
+#define LOG_ONCE(level, fmt, ...) \
+ nexo::Logger::logOnce(level, std::source_location::current(), fmt, \
+ NEXO_LOG_ONCE_KEY(fmt, ##__VA_ARGS__), ##__VA_ARGS__)
+
#define NEXO_FATAL nexo::LogLevel::FATAL
-#define NEXO_ERROR nexo::LogLevel::ERROR
+#define NEXO_ERROR nexo::LogLevel::ERR
#define NEXO_WARN nexo::LogLevel::WARN
#define NEXO_INFO nexo::LogLevel::INFO
#define NEXO_DEBUG nexo::LogLevel::DEBUG
diff --git a/common/String.hpp b/common/String.hpp
new file mode 100644
index 000000000..810010dab
--- /dev/null
+++ b/common/String.hpp
@@ -0,0 +1,38 @@
+//// String.hpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Guillaume HEIN
+// Date: 23/11/2024
+// Description: Utils for strings
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include
+#include
+
+namespace nexo {
+
+ /**
+ * @brief Compare two strings case-insensitively.
+ *
+ * @param a The first string.
+ * @param b The second string.
+ * @return true if the strings are equal (case-insensitive), false otherwise.
+ */
+ [[nodiscard]] constexpr bool iequals(const std::string_view& a, const std::string_view& b) {
+ return a.size() == b.size() &&
+ std::equal(a.begin(), a.end(), b.begin(), [](const char _a, const char _b) {
+ return std::tolower(static_cast(_a)) ==
+ std::tolower(static_cast(_b));
+ });
+ }
+
+
+} // namespace nexo
diff --git a/common/Timestep.hpp b/common/Timestep.hpp
index f1f61eb03..4349d1e46 100644
--- a/common/Timestep.hpp
+++ b/common/Timestep.hpp
@@ -16,14 +16,15 @@
namespace nexo {
class Timestep {
public:
- Timestep(const float time = 0.0f) : m_time(time) {};
+ explicit(false) Timestep(const double time = 0.0f) : m_time(time) {}
- operator float() const {return m_time; };
+ explicit operator float() const { return m_time; }
+ explicit operator double() const { return m_time; }
- [[nodiscard]] float getSeconds() const {return m_time; };
- [[nodiscard]] float getMilliseconds() const { return m_time * 1000.0f; };
+ [[nodiscard]] double getSeconds() const {return m_time; }
+ [[nodiscard]] double getMilliseconds() const { return m_time * 1000.0f; }
private:
- float m_time = 0.0f;
+ double m_time = 0.0f;
};
}
diff --git a/common/math/Projection.cpp b/common/math/Projection.cpp
new file mode 100644
index 000000000..0f4cbf4f0
--- /dev/null
+++ b/common/math/Projection.cpp
@@ -0,0 +1,39 @@
+//// Projection.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 20/04/2025
+// Description: Source file for the math projection utils
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "Projection.hpp"
+
+namespace nexo::math {
+
+ glm::vec3 projectRayToWorld(const float x, const float y,
+ const glm::mat4 &viewProjectionMatrix,
+ const glm::vec3 &cameraPosition,
+ const unsigned int width, const unsigned int height
+ ) {
+ // Convert to NDC
+ const float ndcX = (2.0f * x) / static_cast(width) - 1.0f;
+ const float ndcY = 1.0f - (2.0f * y) / static_cast(height);
+
+ const glm::mat4 inverseViewProj = glm::inverse(viewProjectionMatrix);
+
+ // Points in NDC space at near and far planes
+ glm::vec4 nearPoint = inverseViewProj * glm::vec4(ndcX, ndcY, -1.0f, 1.0f);
+
+ nearPoint /= nearPoint.w;
+
+ const glm::vec3 rayDir = glm::normalize(glm::vec3(nearPoint) - cameraPosition);
+
+ return rayDir;
+ }
+}
diff --git a/common/math/Projection.hpp b/common/math/Projection.hpp
new file mode 100644
index 000000000..ec4b0056d
--- /dev/null
+++ b/common/math/Projection.hpp
@@ -0,0 +1,24 @@
+//// Projection.hpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 20/04/2025
+// Description: Header file for the math projection utils
+//
+///////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include
+
+namespace nexo::math {
+ glm::vec3 projectRayToWorld(float x, float y,
+ const glm::mat4 &viewProjectionMatrix,
+ const glm::vec3 &cameraPosition,
+ unsigned int width, unsigned int height
+ );
+}
diff --git a/common/math/Vector.cpp b/common/math/Vector.cpp
index c191a0d63..999a2e374 100644
--- a/common/math/Vector.cpp
+++ b/common/math/Vector.cpp
@@ -46,4 +46,9 @@ namespace nexo::math {
return glm::degrees(euler);
}
+
+ bool isPosInBounds(const glm::vec2 pos, const glm::vec2 &min, const glm::vec2 &max)
+ {
+ return pos.x >= min.x && pos.x <= max.x && pos.y >= min.y && pos.y <= max.y;
+ }
}
diff --git a/common/math/Vector.hpp b/common/math/Vector.hpp
index d856d377b..64bd05f02 100644
--- a/common/math/Vector.hpp
+++ b/common/math/Vector.hpp
@@ -44,4 +44,6 @@ namespace nexo::math {
* when sinp approaches ±1.
*/
glm::vec3 customQuatToEuler(const glm::quat &q);
+
+ bool isPosInBounds(const glm::vec2 pos, const glm::vec2 &min, const glm::vec2 &max);
}
diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt
index d7ebae4a5..a614bb773 100644
--- a/editor/CMakeLists.txt
+++ b/editor/CMakeLists.txt
@@ -15,21 +15,66 @@ set(SRCS
editor/src/backends/ImGuiBackend.cpp
editor/src/backends/opengl/openglImGuiBackend.cpp
editor/src/context/Selector.cpp
- editor/src/Components/Components.cpp
- editor/src/Components/EntityPropertiesComponents.cpp
- editor/src/Components/Widgets.cpp
+ editor/src/context/ActionManager.cpp
+ editor/src/context/ActionHistory.cpp
+ editor/src/context/ActionGroup.cpp
+ editor/src/context/actions/EntityActions.cpp
+ editor/src/context/actions/ComponentRestoreFactory.cpp
+ editor/src/ImNexo/EntityProperties.cpp
+ editor/src/ImNexo/Components.cpp
+ editor/src/ImNexo/Elements.cpp
+ editor/src/ImNexo/Panels.cpp
+ editor/src/ImNexo/Utils.cpp
+ editor/src/ImNexo/Widgets.cpp
+ editor/src/ImNexo/ImNexo.cpp
editor/src/utils/ScenePreview.cpp
editor/src/utils/Config.cpp
editor/src/utils/String.cpp
+ editor/src/utils/FileSystem.cpp
+ editor/src/utils/EditorProps.cpp
+ editor/src/inputs/Command.cpp
+ editor/src/inputs/InputManager.cpp
+ editor/src/inputs/WindowState.cpp
editor/src/Editor.cpp
editor/src/WindowRegistry.cpp
editor/src/DockingRegistry.cpp
- editor/src/DocumentWindows/ConsoleWindow.cpp
- editor/src/DocumentWindows/EditorScene.cpp
- editor/src/DocumentWindows/SceneTreeWindow.cpp
+ editor/src/ADocumentWindow.cpp
+ editor/src/DocumentWindows/EditorScene/Gizmo.cpp
+ editor/src/DocumentWindows/EditorScene/Init.cpp
+ editor/src/DocumentWindows/EditorScene/Shortcuts.cpp
+ editor/src/DocumentWindows/EditorScene/Show.cpp
+ editor/src/DocumentWindows/EditorScene/Shutdown.cpp
+ editor/src/DocumentWindows/EditorScene/Toolbar.cpp
+ editor/src/DocumentWindows/EditorScene/Update.cpp
+ editor/src/DocumentWindows/AssetManager/Init.cpp
+ editor/src/DocumentWindows/AssetManager/Show.cpp
+ editor/src/DocumentWindows/AssetManager/Shutdown.cpp
+ editor/src/DocumentWindows/AssetManager/Update.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Init.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Log.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Show.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Update.cpp
+ editor/src/DocumentWindows/ConsoleWindow/Utils.cpp
+ editor/src/DocumentWindows/InspectorWindow/Init.cpp
+ editor/src/DocumentWindows/InspectorWindow/Show.cpp
+ editor/src/DocumentWindows/InspectorWindow/Shutdown.cpp
+ editor/src/DocumentWindows/InspectorWindow/Update.cpp
+ editor/src/DocumentWindows/MaterialInspector/Init.cpp
+ editor/src/DocumentWindows/MaterialInspector/Show.cpp
+ editor/src/DocumentWindows/MaterialInspector/Shutdown.cpp
+ editor/src/DocumentWindows/MaterialInspector/Update.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Hovering.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Init.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/NodeHandling.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Rename.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/SceneCreation.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Selection.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Show.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Shutdown.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Update.cpp
+ editor/src/DocumentWindows/SceneTreeWindow/Shortcuts.cpp
editor/src/DocumentWindows/PopupManager.cpp
- editor/src/DocumentWindows/InspectorWindow.cpp
- editor/src/DocumentWindows/MaterialInspector.cpp
editor/src/DocumentWindows/EntityProperties/TransformProperty.cpp
editor/src/DocumentWindows/EntityProperties/RenderProperty.cpp
editor/src/DocumentWindows/EntityProperties/AmbientLightProperty.cpp
@@ -38,7 +83,13 @@ set(SRCS
editor/src/DocumentWindows/EntityProperties/SpotLightProperty.cpp
editor/src/DocumentWindows/EntityProperties/CameraProperty.cpp
editor/src/DocumentWindows/EntityProperties/CameraController.cpp
- editor/src/DocumentWindows/AssetManagerWindow.cpp
+ editor/src/DocumentWindows/EntityProperties/CameraTarget.cpp
+ editor/src/DocumentWindows/EntityProperties/TypeErasedProperty.cpp
+ editor/src/DocumentWindows/TestWindow/Init.cpp
+ editor/src/DocumentWindows/TestWindow/Parser.cpp
+ editor/src/DocumentWindows/TestWindow/Show.cpp
+ editor/src/DocumentWindows/TestWindow/Shutdown.cpp
+ editor/src/DocumentWindows/TestWindow/Update.cpp
)
# Windows App Icon
@@ -105,7 +156,7 @@ target_link_libraries(nexoEditor PRIVATE Boost::uuid)
include_directories(include)
if(NEXO_GRAPHICS_API STREQUAL "OpenGL")
- target_compile_definitions(nexoEditor PRIVATE GRAPHICS_API_OPENGL)
+ target_compile_definitions(nexoEditor PRIVATE NX_GRAPHICS_API_OPENGL)
endif()
# Set the output directory for the executable (prevents generator from creating Debug/Release folders)
diff --git a/editor/main.cpp b/editor/main.cpp
index f4dfe00c0..dd5f6db66 100644
--- a/editor/main.cpp
+++ b/editor/main.cpp
@@ -13,49 +13,64 @@
///////////////////////////////////////////////////////////////////////////////
#include "src/Editor.hpp"
-#include "src/DocumentWindows/ConsoleWindow.hpp"
-#include "src/DocumentWindows/EditorScene.hpp"
-#include "src/DocumentWindows/SceneTreeWindow.hpp"
-#include "src/DocumentWindows/InspectorWindow.hpp"
-#include "src/DocumentWindows/AssetManagerWindow.hpp"
-#include "src/DocumentWindows/MaterialInspector.hpp"
+#include "src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp"
+#include "src/DocumentWindows/EditorScene/EditorScene.hpp"
+#include "src/DocumentWindows/SceneTreeWindow/SceneTreeWindow.hpp"
+#include "src/DocumentWindows/InspectorWindow/InspectorWindow.hpp"
+#include "src/DocumentWindows/AssetManager/AssetManagerWindow.hpp"
+#include "src/DocumentWindows/MaterialInspector/MaterialInspector.hpp"
#include
+#include
#include
+#include "Path.hpp"
+#ifdef NEXO_SCRIPTING_ENABLED
+#include "scripting/native/ManagedTypedef.hpp"
+#include "scripting/native/Scripting.hpp"
+#endif
+
int main(int argc, char **argv)
-{
- try {
- loguru::init(argc, argv);
- loguru::g_stderr_verbosity = loguru::Verbosity_3;
- nexo::editor::Editor &editor = nexo::editor::Editor::getInstance();
-
- editor.registerWindow(NEXO_WND_USTRID_DEFAULT_SCENE);
- editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE);
- editor.registerWindow(NEXO_WND_USTRID_INSPECTOR);
- editor.registerWindow(NEXO_WND_USTRID_CONSOLE);
- editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR);
- editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER);
-
- if (auto defaultScene = editor.getWindow(NEXO_WND_USTRID_DEFAULT_SCENE).lock())
- defaultScene->setDefault();
-
- editor.init();
-
- while (editor.isOpen())
- {
- auto start = std::chrono::high_resolution_clock::now();
- editor.render();
- editor.update();
-
- auto end = std::chrono::high_resolution_clock::now();
- std::chrono::duration elapsed = end - start;
- std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed);
- }
- editor.shutdown();
- return 0;
- } catch (const nexo::Exception &e) {
- LOG_EXCEPTION(e);
- return 1;
+try {
+ loguru::init(argc, argv);
+ loguru::g_stderr_verbosity = loguru::Verbosity_3;
+ nexo::editor::Editor &editor = nexo::editor::Editor::getInstance();
+
+ editor.registerWindow(
+ std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)
+ );
+ editor.registerWindow(NEXO_WND_USTRID_SCENE_TREE);
+ editor.registerWindow(NEXO_WND_USTRID_INSPECTOR);
+ editor.registerWindow(NEXO_WND_USTRID_CONSOLE);
+ editor.registerWindow(NEXO_WND_USTRID_MATERIAL_INSPECTOR);
+ editor.registerWindow(NEXO_WND_USTRID_ASSET_MANAGER);
+
+ if (const auto defaultScene = editor.getWindow(std::format("Default Scene{}{}", NEXO_WND_USTRID_DEFAULT_SCENE, 0)).lock())
+ defaultScene->setDefault();
+
+ editor.init();
+
+ while (editor.isOpen())
+ {
+ auto start = std::chrono::high_resolution_clock::now();
+ editor.render();
+ editor.update();
+
+ auto end = std::chrono::high_resolution_clock::now();
+ std::chrono::duration elapsed = end - start;
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(16) - elapsed);
}
+
+ editor.shutdown();
+ return 0;
+} catch (const nexo::Exception &e) {
+ LOG_EXCEPTION(e);
+ return 1;
+} catch (const std::exception &e) {
+ LOG(NEXO_ERROR, "Unhandled exception: {}", e.what());
+ return 1;
+} catch (...) {
+ LOG(NEXO_ERROR, "Unhandled unknown exception");
+ return 1;
}
diff --git a/editor/src/ADocumentWindow.cpp b/editor/src/ADocumentWindow.cpp
new file mode 100644
index 000000000..009f07c66
--- /dev/null
+++ b/editor/src/ADocumentWindow.cpp
@@ -0,0 +1,80 @@
+//// ADocumentWindow.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 29/04/2025
+// Description: Source file for the abstract document window class
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ADocumentWindow.hpp"
+#include
+
+namespace nexo::editor {
+
+ void ADocumentWindow::beginRender(const std::string &windowName)
+ {
+ dockingUpdate(windowName);
+ visibilityUpdate();
+ sizeUpdate();
+ }
+
+ void ADocumentWindow::dockingUpdate(const std::string &windowName)
+ {
+ if (const ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow)
+ {
+ const bool isDocked = currentWindow->DockIsActive;
+ const ImGuiID currentDockID = currentWindow->DockId;
+ auto dockId = m_windowRegistry.getDockId(windowName);
+
+ // If it's the first time opening the window and we have a dock id saved in the registry, then we force set it
+ if (m_firstOpened && (dockId && currentDockID != *dockId))
+ ImGui::DockBuilderDockWindow(windowName.c_str(), *dockId);
+ // If the docks ids differ, it means the window got rearranged in the global layout
+ // If we are docked but we dont have a dock id saved in the registry, it means the user moved the window
+ // In both cases, we update our docking registry with the new dock id
+ else if ((dockId && currentDockID != *dockId) || (isDocked && !dockId))
+ m_windowRegistry.setDockId(windowName, currentDockID);
+
+
+ // If it is not docked anymore, we have a floating window without docking node,
+ // So we erase it from the docking registry
+ if (!m_firstOpened && !isDocked)
+ m_windowRegistry.resetDockId(windowName);
+ m_firstOpened = false;
+ }
+ }
+
+ void ADocumentWindow::visibilityUpdate()
+ {
+ m_focused = ImGui::IsWindowFocused();
+ const bool isDocked = ImGui::IsWindowDocked();
+ const ImGuiWindow* window = ImGui::GetCurrentWindow();
+
+ if (isDocked) {
+ // If the window is currently being rendered with normal content,
+ // and not hidden or set to skip items, then it is visible
+ m_isVisibleInDock = !window->Hidden && !window->SkipItems && window->Active;
+ }
+ else {
+ // Not docked windows are visible if we've reached this point
+ m_isVisibleInDock = true;
+ }
+ m_hovered = ImGui::IsWindowHovered();
+ }
+
+ void ADocumentWindow::sizeUpdate()
+ {
+ const ImGuiWindow* window = ImGui::GetCurrentWindow();
+ m_windowPos = window->Pos;
+ m_windowSize = window->Size;
+ m_contentSizeMin = ImGui::GetWindowContentRegionMin();
+ m_contentSizeMax = ImGui::GetWindowContentRegionMax();
+ m_contentSize = ImGui::GetContentRegionAvail();
+ }
+}
diff --git a/editor/src/ADocumentWindow.hpp b/editor/src/ADocumentWindow.hpp
index 648f37d12..798c6070b 100644
--- a/editor/src/ADocumentWindow.hpp
+++ b/editor/src/ADocumentWindow.hpp
@@ -14,20 +14,23 @@
#pragma once
+#include
+
#include "IDocumentWindow.hpp"
#include "Nexo.hpp"
#include "WindowRegistry.hpp"
-
-#include
+#include "inputs/WindowState.hpp"
namespace nexo::editor {
- #define NEXO_WND_USTRID_INSPECTOR "Inspector"
- #define NEXO_WND_USTRID_SCENE_TREE "Scene Tree"
- #define NEXO_WND_USTRID_ASSET_MANAGER "Asset Manager"
- #define NEXO_WND_USTRID_CONSOLE "Console"
- #define NEXO_WND_USTRID_MATERIAL_INSPECTOR "Material Inspector"
- #define NEXO_WND_USTRID_DEFAULT_SCENE "Default Scene"
+ #define NEXO_WND_USTRID_INSPECTOR "###Inspector"
+ #define NEXO_WND_USTRID_SCENE_TREE "###Scene Tree"
+ #define NEXO_WND_USTRID_ASSET_MANAGER "###Asset Manager"
+ #define NEXO_WND_USTRID_CONSOLE "###Console"
+ #define NEXO_WND_USTRID_MATERIAL_INSPECTOR "###Material Inspector"
+ #define NEXO_WND_USTRID_DEFAULT_SCENE "###Default Scene"
+ #define NEXO_WND_USTRID_BOTTOM_BAR "###CommandsBar"
+ #define NEXO_WND_USTRID_TEST "###TestWindow"
class ADocumentWindow : public IDocumentWindow {
public:
@@ -37,7 +40,7 @@ namespace nexo::editor {
* Initializes the document window by storing a reference to the provided WindowRegistry and assigning a unique window
* identifier. This setup is essential for integrating the window with the docking management system.
*/
- explicit ADocumentWindow(const std::string &windowName, WindowRegistry &windowRegistry) : m_windowName(windowName), m_windowRegistry(windowRegistry)
+ explicit ADocumentWindow(std::string windowName, WindowRegistry &windowRegistry) : m_windowName(std::move(windowName)), m_windowRegistry(windowRegistry)
{
windowId = nextWindowId++;
};
@@ -45,8 +48,11 @@ namespace nexo::editor {
[[nodiscard]] bool isFocused() const override { return m_focused; }
[[nodiscard]] bool isOpened() const override { return m_opened; }
+ void setOpened(bool opened) override { m_opened = opened; }
[[nodiscard]] bool isHovered() const override { return m_hovered; }
+ [[nodiscard]] const ImVec2 &getContentSize() const override { return m_contentSize; }
+
/**
* @brief Retrieves the open state of the document window.
*
@@ -60,43 +66,46 @@ namespace nexo::editor {
[[nodiscard]] const std::string &getWindowName() const override { return m_windowName; }
- /**
- * @brief Initializes the docking configuration for the document window on its first display.
- *
- * This function retrieves the current ImGui window and checks its docking state to ensure it aligns with the expected
- * configuration from the WindowRegistry. On the first open (when m_firstOpened is true), if the window is not actively
- * docked or its current dock ID does not match the expected ID obtained via the provided window name, the function assigns
- * the expected dock ID to the window. If the window is already docked but the dock IDs still differ, the current dock ID is
- * saved to the WindowRegistry. The m_firstOpened flag is then set to false so that the docking configuration is applied only once.
- *
- * @param windowName The name used to look up the expected dock identifier in the WindowRegistry.
- */
- void firstDockSetup(const std::string &windowName)
- {
- if (ImGuiWindow* currentWindow = ImGui::GetCurrentWindow(); currentWindow)
- {
- const bool isDocked = currentWindow->DockIsActive;
- const ImGuiID currentDockID = currentWindow->DockId;
- auto dockId = m_windowRegistry.getDockId(windowName);
-
- if (m_firstOpened && (!isDocked || (dockId && currentDockID != *dockId)))
- currentWindow->DockId = *dockId;
- else if (dockId && currentDockID != *dockId)
- m_windowRegistry.setDockId(windowName, currentDockID);
- m_firstOpened = false;
- }
- }
+ [[nodiscard]] const WindowState &getWindowState() const override { return m_windowState; };
+
+
WindowId windowId;
protected:
bool m_opened = true;
bool m_focused = false;
bool m_hovered = false; // TODO: make these update without user intervention
+ bool m_wasVisibleLastFrame = false;
+ bool m_isVisibleInDock = true;
+
+ ImVec2 m_windowPos;
+ ImVec2 m_windowSize;
+ ImVec2 m_contentSizeMin;
+ ImVec2 m_contentSizeMax;
+ ImVec2 m_contentSize;
bool m_firstOpened = true;
std::string m_windowName;
+ WindowState m_windowState;
WindowRegistry &m_windowRegistry;
+
+ void beginRender(const std::string &windowName);
+ private:
+ /**
+ * @brief Initializes the docking configuration for the document window on its first display.
+ *
+ * This function retrieves the current ImGui window and checks its docking state to ensure it aligns with the expected
+ * configuration from the WindowRegistry. On the first open (when m_firstOpened is true), if the window is not actively
+ * docked or its current dock ID does not match the expected ID obtained via the provided window name, the function assigns
+ * the expected dock ID to the window. If the window is already docked but the dock IDs still differ, the current dock ID is
+ * saved to the WindowRegistry. The m_firstOpened flag is then set to false so that the docking configuration is applied only once.
+ *
+ * @param windowName The name used to look up the expected dock identifier in the WindowRegistry.
+ */
+ void dockingUpdate(const std::string &windowName);
+ void visibilityUpdate();
+ void sizeUpdate();
};
}
diff --git a/editor/src/Components/Components.cpp b/editor/src/Components/Components.cpp
deleted file mode 100644
index f38fef1f3..000000000
--- a/editor/src/Components/Components.cpp
+++ /dev/null
@@ -1,323 +0,0 @@
-//// Components.cpp ///////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 17/02/2025
-// Description: Source file for the utilitary ImGui functions
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include "Components.hpp"
-
-#include
-#include
-#include
-#include
-
-namespace nexo::editor {
-
- bool Components::drawButton(
- const std::string &label,
- const ImVec2 &size,
- const ImU32 bg,
- const ImU32 bgHovered,
- const ImU32 bgActive, const ImU32 txtColor
- ) {
- if (bg != 0) ImGui::PushStyleColor(ImGuiCol_Button, bg);
- if (bgHovered != 0) ImGui::PushStyleColor(ImGuiCol_ButtonHovered, bgHovered);
- if (bgActive != 0) ImGui::PushStyleColor(ImGuiCol_ButtonActive, bgActive);
- if (txtColor != 0) ImGui::PushStyleColor(ImGuiCol_Text, txtColor);
-
- const bool clicked = ImGui::Button(label.c_str(), size);
-
- const int popCount = (bg != 0) + (bgHovered != 0) + (bgActive != 0) + (txtColor != 0);
- ImGui::PopStyleColor(popCount);
- return clicked;
- }
-
- void Components::drawButtonBorder(
- const ImU32 borderColor,
- const ImU32 borderColorHovered,
- const ImU32 borderColorActive,
- const float rounding,
- const ImDrawFlags flags,
- const float thickness
- ) {
- const ImVec2 p_min = ImGui::GetItemRectMin();
- const ImVec2 p_max = ImGui::GetItemRectMax();
- ImU32 color = borderColor ? borderColor : ImGui::GetColorU32(ImGuiCol_Button);
- if (ImGui::IsItemHovered())
- color = borderColorHovered ? borderColorHovered : ImGui::GetColorU32(ImGuiCol_ButtonHovered);
- if (ImGui::IsItemActive())
- color = borderColorActive ? borderColorActive : ImGui::GetColorU32(ImGuiCol_ButtonActive);
-
- ImGui::GetWindowDrawList()->AddRect(p_min, p_max, color, rounding, flags, thickness);
- }
-
- void Components::drawButtonInnerBorder(
- const ImU32 borderColor,
- const ImU32 borderColorHovered,
- const ImU32 borderColorActive,
- const float rounding,
- const ImDrawFlags flags,
- const float thickness
- ) {
- ImVec2 p_min = ImGui::GetItemRectMin();
- ImVec2 p_max = ImGui::GetItemRectMax();
- ImU32 color = borderColor ? borderColor : ImGui::GetColorU32(ImGuiCol_Button);
- if (ImGui::IsItemHovered())
- color = borderColorHovered ? borderColorHovered : ImGui::GetColorU32(ImGuiCol_ButtonHovered);
- if (ImGui::IsItemActive())
- color = borderColorActive ? borderColorActive : ImGui::GetColorU32(ImGuiCol_ButtonActive);
-
- ImGui::GetWindowDrawList()->AddRect(
- ImVec2(p_min.x + thickness, p_min.y + thickness),
- ImVec2(p_max.x - thickness, p_max.y - thickness),
- color, rounding, flags, thickness);
- }
-
- bool Components::drawDragFloat(
- const std::string &label,
- float *values, const float speed,
- const float min, const float max,
- const std::string &format,
- const ImU32 bg, const ImU32 bgHovered, const ImU32 bgActive, const ImU32 textColor
- ) {
- if (bg) ImGui::PushStyleColor(ImGuiCol_FrameBg, bg);
- if (bgHovered) ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, bgHovered);
- if (bgActive) ImGui::PushStyleColor(ImGuiCol_FrameBgActive, bgActive);
- if (textColor) ImGui::PushStyleColor(ImGuiCol_Text, textColor);
- const bool clicked = ImGui::DragFloat(label.c_str(), values, speed, min, max, format.c_str());
-
- const int popCount = (bg != 0) + (bgHovered != 0) + (bgActive != 0) + (textColor != 0);
- ImGui::PopStyleColor(popCount);
- return clicked;
- }
-
- void Components::drawColorButton(const std::string &label, const ImVec2 size, const ImVec4 color, bool *clicked, ImGuiColorEditFlags flags)
- {
- flags |= ImGuiColorEditFlags_NoTooltip;
- constexpr float borderThickness = 3.0f;
- const float defaultSize = ImGui::GetFrameHeight() + borderThickness;
- const auto calculatedSize = ImVec2(size.x == 0 ? defaultSize : size.x - borderThickness * 2, size.y == 0 ? defaultSize : size.y - borderThickness * 2);
- if (ImGui::ColorButton(label.c_str(),
- color,
- flags,
- calculatedSize) && clicked)
- {
- *clicked = !*clicked;
- }
- Components::drawButtonBorder(ImGui::GetColorU32(ImGuiCol_Button), ImGui::GetColorU32(ImGuiCol_ButtonHovered), ImGui::GetColorU32(ImGuiCol_ButtonActive), borderThickness);
- }
-
- void Components::drawCustomSeparatorText(const std::string &text, const float textPadding, const float leftSpacing, const float thickness, ImU32 lineColor, ImU32 textColor)
- {
- const ImVec2 pos = ImGui::GetCursorScreenPos();
- const float availWidth = ImGui::GetContentRegionAvail().x;
- const float textWidth = ImGui::CalcTextSize(text.c_str()).x;
-
- // Compute the length of each line. Clamp to zero if the region is too small.
- float lineWidth = (availWidth - textWidth - 2 * textPadding) * leftSpacing;
- if (lineWidth < 0.0f)
- lineWidth = 0.0f;
-
- // Compute Y coordinate to draw lines so they align with the text center.
- const float lineY = pos.y + ImGui::GetTextLineHeight() * 0.5f;
-
- ImDrawList* draw_list = ImGui::GetWindowDrawList();
-
- const ImVec2 lineStart(pos.x, lineY);
- const ImVec2 lineEnd(pos.x + lineWidth, lineY);
- draw_list->AddLine(lineStart, lineEnd, lineColor, thickness);
-
- const ImVec2 textPos(pos.x + lineWidth + textPadding, pos.y);
- draw_list->AddText(textPos, textColor, text.c_str());
-
- const ImVec2 rightLineStart(pos.x + lineWidth + textPadding + textWidth + textPadding, lineY);
- const ImVec2 rightLineEnd(pos.x + availWidth, lineY);
- draw_list->AddLine(rightLineStart, rightLineEnd, lineColor, thickness);
-
- ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight()));
- }
-
- /**
- * @brief Linearly interpolates between two colors (ImU32, ImGui 32-bits ARGB format).
- * @param[in] colA The first color (ARGB format).
- * @param[in] colB The second color (ARGB format).
- * @param[in] t The interpolation factor (0.0 to 1.0).
- * @return The interpolated color (ARGB format).
- */
- static ImU32 imLerpColor(const ImU32 colA, const ImU32 colB, const float t)
- {
- const unsigned char a0 = (colA >> 24) & 0xFF, r0 = (colA >> 16) & 0xFF, g0 = (colA >> 8) & 0xFF, b0 = colA & 0xFF;
- const unsigned char a1 = (colB >> 24) & 0xFF, r1 = (colB >> 16) & 0xFF, g1 = (colB >> 8) & 0xFF, b1 = colB & 0xFF;
- const auto a = static_cast(static_cast(a0) + t * static_cast(a1 - a0));
- const auto r = static_cast(static_cast(r0) + t * static_cast(r1 - r0));
- const auto g = static_cast(static_cast(g0) + t * static_cast(g1 - g0));
- const auto b = static_cast(static_cast(b0) + t * static_cast(b1 - b0));
- return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
- }
-
- /**
- * @brief Clip a convex polygon against a half-plane defined by: (dot(normal, v) >= offset)
- *
- * This function uses the Sutherland-Hodgman algorithm to clip a polygon against a line defined by a normal vector and an offset.
- * @param[in] poly Vector of vertices representing the polygon to be clipped.
- * @param[in] normal The normal vector of the line used for clipping.
- * @param[in] offset The offset from the origin of the line.
- * @param[out] outPoly Output vector to store the clipped polygon vertices.
- */
- static void clipPolygonWithLine(const std::vector& poly, const ImVec2& normal, float offset, std::vector& outPoly)
- {
- outPoly.clear();
- const auto count = poly.size();
- outPoly.reserve(count * 2); // Preallocate space for the output polygon (prepare worst case)
- for (size_t i = 0; i < count; i++) {
- const ImVec2& a = poly[i];
- const ImVec2& b = poly[(i + 1) % count];
- const float da = ImDot(a, normal) - offset;
- const float db = ImDot(b, normal) - offset;
- if (da >= 0)
- outPoly.push_back(a);
- // if the edge spans the boundary, compute intersection
- if ((da >= 0 && db < 0) || (da < 0 && db >= 0)) {
- const float t = da / (da - db);
- ImVec2 inter;
- inter.x = a.x + t * (b.x - a.x);
- inter.y = a.y + t * (b.y - a.y);
- outPoly.push_back(inter);
- }
- }
- }
-
- /**
- * @brief Fill a convex polygon with triangles using a triangle fan.
- * @param[in] drawList The ImDrawList to which the triangles will be added.
- * @param[in] poly Vector of vertices representing the polygon to be filled.
- * @param[in] polyColors Vector of colors for each vertex in the polygon.
- */
- static void fillConvexPolygon(ImDrawList* drawList, const std::vector& poly, const std::vector& polyColors)
- {
- if (poly.size() < 3)
- return;
- const auto count = static_cast(poly.size());
- drawList->PrimReserve((count - 2) * 3, count);
- // Use the first vertex as pivot.
- for (int i = 1; i < count - 1; i++) {
- const auto currentIdx = drawList->_VtxCurrentIdx;
- drawList->PrimWriteIdx(static_cast(currentIdx));
- drawList->PrimWriteIdx(static_cast(currentIdx + i));
- drawList->PrimWriteIdx(static_cast(currentIdx + i + 1));
- }
- // Write vertices with their computed colors.
- for (int i = 0; i < count; i++) {
- // For a vertex, we determine its position t between the segment boundaries later.
- // Here we assume the provided poly_colors already correspond vertex-by-vertex.
- drawList->PrimWriteVtx(poly[i], drawList->_Data->TexUvWhitePixel, polyColors[i]);
- }
- }
-
-
- void Components::drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle,
- std::vector stops, ImDrawList* drawList)
- {
- if (!drawList)
- drawList = ImGui::GetWindowDrawList();
-
- // Check if we have at least two stops.
- // If not, we can't create a gradient.
- if (stops.size() < 2)
- return;
-
- angle -= 90.0f; // rotate 90 degrees to match the CSS gradients rotations
-
- // Convert angle from degrees to radians. Also keep it in range of radians
- // [0, 2*PI) for consistency.
- angle = fmodf(angle, 360.0f);
- if (angle < 0.0f)
- angle += 360.0f;
- angle = angle * std::numbers::pi_v / 180.0f;
-
- const auto gradDir = ImVec2(cosf(angle), sinf(angle));
-
- // Define rectangle polygon (clockwise order).
- const std::vector rectPoly = { pMin, ImVec2(pMax.x, pMin.y), pMax, ImVec2(pMin.x, pMax.y) };
-
- // Compute projection range (d_min, d_max) for the rectangle.
- float d_min = std::numeric_limits::max();
- float d_max = -std::numeric_limits::max();
- for (auto const& v : rectPoly) {
- const float d = ImDot(v, gradDir);
- if (d < d_min) d_min = d;
- if (d > d_max) d_max = d;
- }
-
- // sanitize stops
- float stop_max = 0.0f;
- for (auto& [pos, color] : stops) {
- (void)color; // ignore color for now
- // Clamp stop position to [0.0f, 1.0f]
- if (pos < 0.0f) pos = 0.0f;
- if (pos > 1.0f) pos = 1.0f;
-
- // Clamp stop position to [stop_max, 1.0f]
- if (pos < stop_max) {
- pos = stop_max;
- } else {
- stop_max = pos;
- }
- }
-
- // if first stop does not start at 0.0f, we need to add a stop at 0.0f
- if (stops[0].pos > 0.0f) {
- stops.insert(stops.begin(), { 0.0f, stops[0].color });
- }
- // if last stop does not end at 1.0f, we need to add a stop at 1.0f
- if (stops[stops.size() - 1].pos < 1.0f) {
- stops.push_back({ 1.0f, stops[stops.size() - 1].color });
- }
-
- // For each segment defined by consecutive stops:
- for (long i = static_cast(stops.size()) - 1; i > 0; i--) {
- const long posStart = i - 1;
- const long posEnd = i;
- // Compute threshold projections for the current segment.
- const float segStart = d_min + stops[posStart].pos * (d_max - d_min);
- const float segEnd = d_min + stops[posEnd].pos * (d_max - d_min);
-
- // Start with the whole rectangle.
- std::vector segPoly = rectPoly;
- std::vector tempPoly;
- // Clip against lower boundary: d >= seg_start
- clipPolygonWithLine(segPoly, gradDir, segStart, tempPoly);
- segPoly = tempPoly; // copy result
- // Clip against upper boundary: d <= seg_end
- // To clip with an upper-bound, invert the normal.
- clipPolygonWithLine(segPoly, ImVec2(-gradDir.x, -gradDir.y), -segEnd, tempPoly);
- segPoly = tempPoly;
-
- if (segPoly.empty())
- continue;
-
- // Now, compute per-vertex colors for the segment polygon.
- std::vector polyColors;
- polyColors.reserve(segPoly.size());
- for (const ImVec2& v : segPoly) {
- // Compute projection for the vertex.
- const float d = ImDot(v, gradDir);
- // Map projection to [0,1] relative to current segment boundaries.
- const float t = (d - segStart) / (segEnd - segStart);
- // Interpolate the color between the two stops.
- polyColors.push_back(imLerpColor(stops[posStart].color, stops[posEnd].color, t));
- }
-
- // Draw the filled and colored polygon.
- fillConvexPolygon(drawList, segPoly, polyColors);
- }
- }
-}
diff --git a/editor/src/Components/Components.hpp b/editor/src/Components/Components.hpp
deleted file mode 100644
index b0b29d955..000000000
--- a/editor/src/Components/Components.hpp
+++ /dev/null
@@ -1,153 +0,0 @@
-//// Components.hpp ///////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 17/02/2025
-// Description: Header file for the utilitary ImGui functions
-//
-///////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include
-#include
-#include
-
-namespace nexo::editor {
-
- /**
- * @brief A collection of utility functions for custom ImGui components.
- *
- * This class provides helper functions to draw custom buttons, drag floats,
- * color buttons, and separators with text.
- */
- class Components {
- public:
- /**
- * @brief Draws a button with custom style colors.
- *
- * Pushes custom style colors for the button and its states, draws the button,
- * and then pops the style colors.
- *
- * @param label The button label.
- * @param size The size of the button.
- * @param bg The background color.
- * @param bgHovered The background color when hovered.
- * @param bgActive The background color when active.
- * @param txtColor The text color.
- * @return true if the button was clicked; false otherwise.
- */
- static bool drawButton(const std::string &label, const ImVec2& size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0);
-
- /**
- * @brief Draws a border around the last item.
- *
- * Uses the current item's rectangle and draws a border with specified colors
- * for normal, hovered, and active states.
- *
- * @param borderColor The border color for normal state.
- * @param borderColorHovered The border color when hovered.
- * @param borderColorActive The border color when active.
- * @param rounding The rounding of the border corners.
- * @param flags Additional draw flags.
- * @param thickness The thickness of the border.
- */
- static void drawButtonBorder(ImU32 borderColor, ImU32 borderColorHovered, ImU32 borderColorActive, float rounding = 2.0f, ImDrawFlags flags = 0, float thickness = 3.0f);
-
- /**
- * @brief Draws a border inside the last item.
- *
- * Similar to drawButtonBorder, but draws a border inside the item rectangle instead of outside.
- *
- * @param borderColor The border color for normal state.
- * @param borderColorHovered The border color when hovered.
- * @param borderColorActive The border color when active.
- * @param rounding The rounding of the border corners.
- * @param flags Additional draw flags.
- * @param thickness The thickness of the border.
- */
- static void drawButtonInnerBorder(ImU32 borderColor, ImU32 borderColorHovered, ImU32 borderColorActive, float rounding = 2.0f, ImDrawFlags flags = 0, float thickness = 3.0f);
-
-
- /**
- * @brief Draws a draggable float widget with custom styling.
- *
- * Pushes custom style colors for the drag float widget, draws it, and then pops the styles.
- *
- * @param label The label for the drag float.
- * @param values Pointer to the float value.
- * @param speed The speed of value change.
- * @param min The minimum allowable value.
- * @param max The maximum allowable value.
- * @param format The display format.
- * @param bg The background color.
- * @param bgHovered The background color when hovered.
- * @param bgActive The background color when active.
- * @param textColor The text color.
- * @return true if the value was changed; false otherwise.
- */
- static bool drawDragFloat(const std::string &label, float *values, float speed, float min, float max, const std::string &format, ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 textColor = 0);
-
- /**
- * @brief Draws an icon button with custom style colors.
- *
- * Similar to drawButton, but intended for icon-only buttons.
- *
- * @param label The label for the button.
- * @param size The size of the button.
- * @param bg The background color.
- * @param bgHovered The background color when hovered.
- * @param bgActive The background color when active.
- * @param txtColor The text (icon) color.
- * @return true if the button was clicked; false otherwise.
- */
- static bool drawIconButton(const std::string &label, ImVec2 size = ImVec2(0, 0), ImU32 bg = 0, ImU32 bgHovered = 0, ImU32 bgActive = 0, ImU32 txtColor = 0);
-
- /**
- * @brief Draws a color button with a border.
- *
- * Displays a color button with the provided label and size. Optionally toggles a clicked state.
- *
- * @param label The label for the color button.
- * @param size The size of the button.
- * @param color The color to display.
- * @param clicked Optional pointer to a boolean that is toggled when the button is clicked.
- * @param flags Additional color edit flags.
- */
- static void drawColorButton(const std::string &label, ImVec2 size, ImVec4 color, bool *clicked = nullptr, ImGuiColorEditFlags flags = ImGuiColorEditFlags_None);
-
- /**
- * @brief Draws a custom separator with centered text.
- *
- * Renders a separator line with text in the middle, with customizable padding, spacing,
- * thickness, and colors.
- *
- * @param text The text to display at the separator.
- * @param textPadding Padding around the text.
- * @param leftSpacing The spacing multiplier for the left separator line.
- * @param thickness The thickness of the separator lines.
- * @param lineColor The color of the separator lines.
- * @param textColor The color of the text.
- */
- static void drawCustomSeparatorText(const std::string &text, float textPadding, float leftSpacing, float thickness, ImU32 lineColor, ImU32 textColor);
-
- struct GradientStop
- {
- float pos; // percentage position along the gradient [0.0f, 1.0f]
- ImU32 color; // color at this stop
- };
-
- /**
- * @brief Draw filled rectangle with a linear gradient defined by an arbitrary angle and gradient stops.
- * @param pMin Upper left corner position of the rectangle
- * @param pMax Lower right corner position of the rectangle
- * @param angle Angle of the gradient in degrees (0.0f = down, 90.0f = right, 180.0f = up, 270.0f = left)
- * @param stops Vector of gradient stops, each defined by a position (0.0f to 1.0f) and a color
- */
- static void drawRectFilledLinearGradient(const ImVec2& pMin, const ImVec2& pMax, float angle, std::vector stops, ImDrawList* drawList = nullptr);
- };
-}
diff --git a/editor/src/Components/EntityPropertiesComponents.cpp b/editor/src/Components/EntityPropertiesComponents.cpp
deleted file mode 100644
index d822fa488..000000000
--- a/editor/src/Components/EntityPropertiesComponents.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-//// EntityPropertiesComponents.cpp ///////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 22/02/2025
-// Description: Source file for the entity properties components
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include "EntityPropertiesComponents.hpp"
-#include "Components.hpp"
-
-namespace nexo::editor {
-
- bool EntityPropertiesComponents::drawHeader(const std::string &label, std::string_view headerText)
- {
- float increasedPadding = 2.0f;
- ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
- ImVec2(ImGui::GetStyle().FramePadding.x, increasedPadding));
-
- bool open = ImGui::TreeNodeEx(label.c_str(),
- ImGuiTreeNodeFlags_DefaultOpen |
- ImGuiTreeNodeFlags_Framed |
- ImGuiTreeNodeFlags_AllowItemOverlap);
- ImGui::PopStyleVar();
-
- // Horizontal centering:
- const float arrowPosX = ImGui::GetCursorPosX();
- ImGui::SameLine(0.0f, 0.0f);
- const float totalWidth = ImGui::GetContentRegionAvail().x + arrowPosX;
- const ImVec2 textSize = ImGui::CalcTextSize(headerText.data());
- const float textPosX = (totalWidth - textSize.x) * 0.5f;
- ImGui::SetCursorPosX(textPosX);
- ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 2.5f); // This stuff seems strange, should check in the long run if there is a better way
-
- ImGui::TextUnformatted(headerText.data());
-
- return open;
- }
-
- void EntityPropertiesComponents::drawRowLabel(const ChannelLabel &rowLabel)
- {
- ImGui::TableNextColumn();
- if (rowLabel.fixedWidth != -1.0f)
- {
- //TODO: Implement now fixed width row label
- //float fixedCellWidth = rowLabel.fixedWidth;
- //ImVec2 textSize = ImGui::CalcTextSize(rowLabel.label.c_str());
- //float offsetX = (fixedCellWidth - textSize.x) * 0.5f;
- //float rowHeight = ImGui::GetTextLineHeightWithSpacing();
- //float offsetY = (rowHeight - textSize.y) * 0.5f;
- //ImVec2 cellPos = ImGui::GetCursorPos();
- }
- //ImGui::SetWindowFontScale(1.11f);
-
- ImGui::TextUnformatted(rowLabel.label.c_str());
- //ImGui::SetWindowFontScale(1.0f);
- }
-
- bool EntityPropertiesComponents::drawRowDragFloat(const Channels &channels)
- {
- bool clicked = false;
- for (unsigned int i = 0; i < channels.count; ++i)
- {
- ImGui::TableNextColumn();
- if (!channels.badges[i].label.empty())
- {
- const auto &[label, size, bg, bgHovered, bgActive, txtColor] = channels.badges[i];
- Components::drawButton(label, size, bg, bgHovered, bgActive, txtColor);
- }
- ImGui::SameLine(0, 2);
- const auto &[label, value, speed, min, max, bg, bgHovered, bgActive, textColor, format] = channels.sliders[i];
- clicked = Components::drawDragFloat(
- label,
- value,
- speed,
- min,
- max,
- format,
- bg,
- bgHovered,
- bgActive) || clicked;
- }
- return clicked;
- }
-
- bool EntityPropertiesComponents::drawRowDragFloat1(const char *uniqueLabel, const std::string &badgeLabel, float *value, float minValue, float maxValue, float speed)
- {
- const std::string labelStr = uniqueLabel;
- std::string labelX = std::string("##X") + labelStr;
-
- std::string badgeLabelX = (badgeLabel.empty()) ? "" : badgeLabel + std::string("##") + labelStr;
-
- ImGui::TableNextRow();
-
- ChannelLabel chanLabel;
- chanLabel.label = std::string(uniqueLabel);
- chanLabel.fixedWidth = -1.0f;
-
- std::vector badges;
- badges.reserve(1);
- badges.emplace_back(Badge{badgeLabelX, {0, 0}, IM_COL32(80, 0, 0, 255), IM_COL32(80, 0, 0, 255), IM_COL32(80, 0, 0, 255), IM_COL32(255, 180, 180, 255)});
-
- std::vector sliders;
- sliders.reserve(1);
- sliders.emplace_back(labelX, value, speed, minValue, maxValue, 0, 0, 0, 0, "%.2f");
-
- Channels channels;
- channels.count = 1;
- channels.badges = badges;
- channels.sliders = sliders;
-
- EntityPropertiesComponents::drawRowLabel(chanLabel);
- return EntityPropertiesComponents::drawRowDragFloat(channels);
- }
-
- bool EntityPropertiesComponents::drawRowDragFloat2(
- const char *uniqueLabel,
- const std::string &badLabelX,
- const std::string &badLabelY,
- float *values,
- float minValue,
- float maxValue,
- float speed,
- std::vector badgeColor,
- std::vector textBadgeColor,
- const bool disabled
- )
- {
- const std::string labelStr = uniqueLabel;
- std::string labelX = std::string("##X") + labelStr;
- std::string labelY = std::string("##Y") + labelStr;
-
- const std::string badgeLabelX = badLabelX + std::string("##") + labelStr;
- const std::string badgeLabelY = badLabelY + std::string("##") + labelStr;
-
- ImGui::TableNextRow();
-
- ChannelLabel chanLabel;
- chanLabel.label = std::string(uniqueLabel);
- chanLabel.fixedWidth = -1.0f;
-
- if (badgeColor.empty())
- badgeColor = {IM_COL32(102, 28, 28, 255), IM_COL32(0, 80, 0, 255)};
- if (textBadgeColor.empty())
- textBadgeColor = {IM_COL32(255, 180, 180, 255), IM_COL32(180, 255, 180, 255)};
- std::vector badges;
- badges.reserve(2);
- badges.emplace_back(Badge{badgeLabelX, {0, 0}, badgeColor[0], badgeColor[0], badgeColor[0], textBadgeColor[0]});
- badges.emplace_back(Badge{badgeLabelY, {0, 0}, badgeColor[1], badgeColor[1], badgeColor[1], textBadgeColor[1]});
-
- std::vector sliders;
- sliders.reserve(2);
- std::vector sliderColors = {ImGui::GetColorU32(ImGuiCol_FrameBg), ImGui::GetColorU32(ImGuiCol_FrameBgHovered), ImGui::GetColorU32(ImGuiCol_FrameBgActive), ImGui::GetColorU32(ImGuiCol_Text)};
- if (disabled)
- sliderColors = {ImGui::GetColorU32(ImGuiCol_FrameBg), ImGui::GetColorU32(ImGuiCol_FrameBgHovered), ImGui::GetColorU32(ImGuiCol_FrameBgActive), ImGui::GetColorU32(ImGuiCol_TextDisabled)};
- sliders.emplace_back(labelX, &values[0], speed, minValue, maxValue, sliderColors[0], sliderColors[1],
- sliderColors[2], sliderColors[3], "%.2f");
- sliders.emplace_back(labelY, &values[1], speed, minValue, maxValue, sliderColors[0], sliderColors[1],
- sliderColors[2], sliderColors[3], "%.2f");
-
- Channels channels;
- channels.count = 2;
- channels.badges = badges;
- channels.sliders = sliders;
-
- EntityPropertiesComponents::drawRowLabel(chanLabel);
- return EntityPropertiesComponents::drawRowDragFloat(channels);
- }
-
- bool EntityPropertiesComponents::drawRowDragFloat3(
- const char *uniqueLabel,
- const std::string &badLabelX,
- const std::string &badLabelY,
- const std::string &badLabelZ,
- float *values,
- float minValue,
- float maxValue,
- float speed,
- std::vector badgeColors,
- std::vector textBadgeColor
- )
- {
- std::string labelStr = uniqueLabel;
- std::string labelX = std::string("##X") + labelStr;
- std::string labelY = std::string("##Y") + labelStr;
- std::string labelZ = std::string("##Z") + labelStr;
-
- std::string badgeLabelX = badLabelX + std::string("##") + labelStr;
- std::string badgeLabelY = badLabelY + std::string("##") + labelStr;
- std::string badgeLabelZ = badLabelZ + std::string("##") + labelStr;
-
- ImGui::TableNextRow();
-
- ChannelLabel chanLabel;
- chanLabel.label = std::string(uniqueLabel);
- chanLabel.fixedWidth = -1.0f;
-
- float badgeSize = ImGui::GetFrameHeight();
- if (badgeColors.empty())
- badgeColors = {IM_COL32(102, 28, 28, 255), IM_COL32(0, 80, 0, 255), IM_COL32(38, 49, 121, 255)};
- if (textBadgeColor.empty())
- textBadgeColor = {IM_COL32(255, 180, 180, 255), IM_COL32(180, 255, 180, 255), IM_COL32(180, 180, 255, 255)};
- std::vector badges;
- badges.reserve(3);
- badges.emplace_back(Badge{badgeLabelX, {badgeSize, badgeSize}, badgeColors[0], badgeColors[0], badgeColors[0], textBadgeColor[0]});
- badges.emplace_back(Badge{badgeLabelY, {badgeSize, badgeSize}, badgeColors[1], badgeColors[1], badgeColors[1], textBadgeColor[1]});
- badges.emplace_back(Badge{badgeLabelZ, {badgeSize, badgeSize}, badgeColors[2], badgeColors[2], badgeColors[2], textBadgeColor[2]});
-
- std::vector sliders;
- sliders.reserve(3);
- sliders.emplace_back(labelX, &values[0], speed, minValue, maxValue, 0,
- 0, 0, ImGui::GetColorU32(ImGuiCol_Text),
- "%.2f");
- sliders.emplace_back(labelY, &values[1], speed, minValue, maxValue, 0,
- 0, 0, ImGui::GetColorU32(ImGuiCol_Text),
- "%.2f");
- sliders.emplace_back(labelZ, &values[2], speed, minValue, maxValue, 0,
- 0, 0, ImGui::GetColorU32(ImGuiCol_Text),
- "%.2f");
-
- Channels channels;
- channels.count = 3;
- channels.badges = badges;
- channels.sliders = sliders;
-
- if (!chanLabel.label.empty())
- EntityPropertiesComponents::drawRowLabel(chanLabel);
- return EntityPropertiesComponents::drawRowDragFloat(channels);
- }
-
- bool EntityPropertiesComponents::drawToggleButtonWithSeparator(const std::string &label, bool* toggled)
- {
- bool clicked = false;
- ImGui::PushID(label.c_str());
-
- constexpr ImVec2 buttonSize(24, 24);
- if (const std::string arrowLabel = "##arrow" + label; ImGui::InvisibleButton(arrowLabel.c_str(), buttonSize))
- clicked = true;
- if (clicked)
- *toggled = !(*toggled);
-
- const ImVec2 btnPos = ImGui::GetItemRectMin();
- const ImVec2 btnSize = ImGui::GetItemRectSize();
- const ImVec2 center(btnPos.x + btnSize.x * 0.5f, btnPos.y + btnSize.y * 0.5f);
-
- ImDrawList* draw_list = ImGui::GetWindowDrawList();
- constexpr float arrowSize = 5.0f;
- const ImU32 arrowColor = ImGui::GetColorU32(ImGuiCol_TextTab);
- if (*toggled)
- {
- // Draw a downward pointing arrow
- draw_list->AddTriangleFilled(
- ImVec2(center.x - arrowSize, center.y - arrowSize),
- ImVec2(center.x + arrowSize, center.y - arrowSize),
- ImVec2(center.x, center.y + arrowSize),
- arrowColor);
- }
- else
- {
- // Draw a rightward pointing arrow
- draw_list->AddTriangleFilled(
- ImVec2(center.x - arrowSize, center.y - arrowSize),
- ImVec2(center.x - arrowSize, center.y + arrowSize),
- ImVec2(center.x + arrowSize, center.y),
- arrowColor);
- }
-
- ImGui::SameLine();
- const ImVec2 separatorPos = ImGui::GetCursorScreenPos();
- constexpr float separatorHeight = buttonSize.y; // match button height
- draw_list->AddLine(separatorPos, ImVec2(separatorPos.x, separatorPos.y + separatorHeight),
- ImGui::GetColorU32(ImGuiCol_Separator), 1.0f);
- ImGui::Dummy(ImVec2(4, buttonSize.y));
-
- ImGui::SameLine();
- Components::drawCustomSeparatorText(label, 10.0f, 0.1f, 0.5f, IM_COL32(255, 255, 255, 255), IM_COL32(255, 255, 255, 255));
- ImGui::PopID();
- return clicked;
- }
-}
diff --git a/editor/src/Components/EntityPropertiesComponents.hpp b/editor/src/Components/EntityPropertiesComponents.hpp
deleted file mode 100644
index cf42cbe86..000000000
--- a/editor/src/Components/EntityPropertiesComponents.hpp
+++ /dev/null
@@ -1,202 +0,0 @@
-//// EntityPropertiesComponents.hpp ///////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 22/05/2025
-// Description: Header file for the entity properties components
-//
-///////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include
-#include
-#include
-
-namespace nexo::editor {
-
- /**
- * @struct ChannelLabel
- * @brief Represents a label for a channel in the entity properties editor
- *
- * Labels can have optional fixed width for precise layout control.
- */
- struct ChannelLabel {
- std::string label;
- float fixedWidth = -1.0f;
- };
-
- /**
- * @struct Badge
- * @brief A styled badge component with customizable appearance
- *
- * Used as visual indicators or labels in the UI, typically alongside sliders.
- */
- struct Badge {
- std::string label; ///< The displayed text
- ImVec2 size; ///< Size of the badge in pixels
- ImU32 bg; ///< Background color
- ImU32 bgHovered; ///< Background color when hovered
- ImU32 bgActive; ///< Background color when active
- ImU32 txtColor; ///< Text color
- };
-
- /**
- * @struct DragFloat
- * @brief A drag float slider component with customizable appearance
- *
- * Used for editing float values with adjustable range and visual styling.
- */
- struct DragFloat {
- std::string label; ///< Unique label/ID for the component
- float *value; ///< Pointer to the value being edited
- float speed; ///< Speed of value change during dragging
- float min; ///< Minimum value
- float max; ///< Maximum value
- ImU32 bg; ///< Background color
- ImU32 bgHovered; ///< Background color when hovered
- ImU32 bgActive; ///< Background color when active
- ImU32 textColor; ///< Text color
- std::string format; ///< Format string for displaying the value
- };
-
- /**
- * @struct Channels
- * @brief A collection of badges and sliders forming a multi-channel editing row
- *
- * Used to create rows with multiple editable values (like X, Y, Z components).
- */
- struct Channels {
- unsigned int count; ///< Number of channels
- std::vector badges; ///< Badge component for each channel
- std::vector sliders; ///< Slider component for each channel
- };
-
- /**
- * @class EntityPropertiesComponents
- * @brief Static class providing UI components for entity property editing
- *
- * This class offers methods to draw various ImGui-based UI components
- * specifically designed for editing entity properties in a consistent
- * and visually appealing way.
- */
- class EntityPropertiesComponents {
- public:
- /**
- * @brief Draws a collapsible header with centered text
- *
- * @param[in] label Unique label/ID for the header
- * @param[in] headerText Text to display in the header
- * @return true if the header is open/expanded, false otherwise
- */
- static bool drawHeader(const std::string &label, std::string_view headerText);
-
- /**
- * @brief Draws a row label in the current table column
- *
- * @param[in] rowLabel The label configuration to draw
- */
- static void drawRowLabel(const ChannelLabel &rowLabel);
-
- /**
- * @brief Draws a row with a single float value slider
- *
- * @param[in] uniqueLabel Unique label/ID for the component
- * @param[in] badgeLabel Text for the badge (empty for no badge)
- * @param[in,out] value Pointer to the float value to edit
- * @param[in] minValue Minimum allowed value (default: -FLT_MAX)
- * @param[in] maxValue Maximum allowed value (default: FLT_MAX)
- * @param[in] speed Speed of value change during dragging (default: 0.3f)
- * @return true if the value was changed, false otherwise
- */
- static bool drawRowDragFloat1(
- const char *uniqueLabel,
- const std::string &badgeLabel,
- float *value,
- float minValue = -FLT_MAX,
- float maxValue = FLT_MAX,
- float speed = 0.3f);
-
- /**
- * @brief Draws a row with two float value sliders (X and Y components)
- *
- * @param[in] uniqueLabel Unique label/ID for the component
- * @param[in] badLabelX Text for the X component badge
- * @param[in] badLabelY Text for the Y component badge
- * @param[in,out] values Pointer to array of two float values to edit
- * @param[in] minValue Minimum allowed value (default: -FLT_MAX)
- * @param[in] maxValue Maximum allowed value (default: FLT_MAX)
- * @param[in] speed Speed of value change during dragging (default: 0.3f)
- * @param[in] badgeColor Optional custom colors for badges
- * @param[in] textBadgeColor Optional custom text colors for badges
- * @param[in] disabled If true, renders in an inactive/disabled state (default: false)
- * @return true if any value was changed, false otherwise
- */
- static bool drawRowDragFloat2(
- const char *uniqueLabel,
- const std::string &badLabelX,
- const std::string &badLabelY,
- float *values,
- float minValue = -FLT_MAX,
- float maxValue = FLT_MAX,
- float speed = 0.3f,
- std::vector badgeColor = {},
- std::vector textBadgeColor = {},
- bool disabled = false
- );
-
- /**
- * @brief Draws a row with three float value sliders (X, Y, and Z components)
- *
- * @param[in] uniqueLabel Unique label/ID for the component
- * @param[in] badLabelX Text for the X component badge
- * @param[in] badLabelY Text for the Y component badge
- * @param[in] badLabelZ Text for the Z component badge
- * @param[in,out] values Pointer to array of three float values to edit
- * @param[in] minValue Minimum allowed value (default: -FLT_MAX)
- * @param[in] maxValue Maximum allowed value (default: FLT_MAX)
- * @param[in] speed Speed of value change during dragging (default: 0.3f)
- * @param[in] badgeColors Optional custom colors for badges
- * @param[in] textBadgeColors Optional custom text colors for badges
- * @return true if any value was changed, false otherwise
- */
- static bool drawRowDragFloat3(
- const char *uniqueLabel,
- const std::string &badLabelX,
- const std::string &badLabelY,
- const std::string &badLabelZ,
- float *values,
- float minValue = -FLT_MAX,
- float maxValue = FLT_MAX,
- float speed = 0.3f,
- std::vector badgeColors = {},
- std::vector textBadgeColors = {}
- );
-
- /**
- * @brief Draws a row with multiple channels (badge + slider pairs)
- *
- * This is a lower-level function used by the other drawRowDragFloatX functions.
- *
- * @param[in] channels The channel configuration to draw
- * @return true if any value was changed, false otherwise
- */
- static bool drawRowDragFloat(const Channels &channels);
-
- /**
- * @brief Draws a toggle button with a separator and label
- *
- * Creates a collapsible section control with an arrow that toggles
- * between expanded and collapsed states.
- *
- * @param[in] label The label to display
- * @param[in,out] toggled Pointer to bool that tracks the toggle state
- * @return true if the toggle state changed, false otherwise
- */
- static bool drawToggleButtonWithSeparator(const std::string &label, bool* toggled);
- };
-}
diff --git a/editor/src/Components/Widgets.cpp b/editor/src/Components/Widgets.cpp
deleted file mode 100644
index 62fdc5385..000000000
--- a/editor/src/Components/Widgets.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-//// Widgets.cpp //////////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 22/02/2025
-// Description: Source file for the widgets components
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include "Widgets.hpp"
-
-#include
-#include
-
-#include "Components.hpp"
-#include "IconsFontAwesome.h"
-#include "tinyfiledialogs.h"
-
-namespace nexo::editor {
- bool Widgets::drawColorEditor(
- const std::string &label,
- glm::vec4 *selectedEntityColor,
- ImGuiColorEditFlags *colorPickerMode,
- bool *showPicker,
- const ImGuiColorEditFlags colorButtonFlags
- ) {
- const ImGuiStyle &style = ImGui::GetStyle();
- const ImVec2 contentAvailable = ImGui::GetContentRegionAvail();
- bool colorModified = false;
-
- const std::string colorButton = std::string("##ColorButton") + label;
-
- const ImVec2 cogIconSize = ImGui::CalcTextSize(ICON_FA_COG);
- const ImVec2 cogIconPadding = style.FramePadding;
- const ImVec2 itemSpacing = style.ItemSpacing;
-
- // Color button
- Components::drawColorButton(
- colorButton,
- ImVec2(contentAvailable.x - cogIconSize.x - cogIconPadding.x * 2 - itemSpacing.x, 0), // Make room for the cog button
- ImVec4(selectedEntityColor->x, selectedEntityColor->y, selectedEntityColor->z, selectedEntityColor->w),
- showPicker,
- colorButtonFlags
- );
-
- ImGui::SameLine();
-
- const std::string pickerSettings = std::string("##PickerSettings") + label;
- const std::string colorPickerPopup = std::string("##ColorPickerPopup") + label;
-
- // Cog button
- if (Components::drawButton(std::string(ICON_FA_COG) + pickerSettings)) {
- ImGui::OpenPopup(colorPickerPopup.c_str());
- }
-
- if (ImGui::BeginPopup(colorPickerPopup.c_str()))
- {
- ImGui::Text("Picker Mode:");
- if (ImGui::RadioButton("Hue Wheel", *colorPickerMode == ImGuiColorEditFlags_PickerHueWheel))
- *colorPickerMode = ImGuiColorEditFlags_PickerHueWheel;
- if (ImGui::RadioButton("Hue bar", *colorPickerMode == ImGuiColorEditFlags_PickerHueBar))
- *colorPickerMode = ImGuiColorEditFlags_PickerHueBar;
- ImGui::EndPopup();
- }
-
- const std::string colorPickerInline = std::string("##ColorPickerInline") + label;
- if (*showPicker)
- {
- ImGui::Spacing();
- colorModified = ImGui::ColorPicker4(colorPickerInline.c_str(),
- reinterpret_cast(selectedEntityColor), *colorPickerMode);
- }
- return colorModified;
- }
-
- bool Widgets::drawTextureButton(const std::string &label, std::shared_ptr &texture)
- {
- bool textureModified = false;
- constexpr ImVec2 previewSize(32, 32);
- ImGui::PushID(label.c_str());
-
- const ImTextureID textureId = texture ? static_cast(static_cast(texture->getId())) : 0;
- const std::string textureButton = std::string("##TextureButton") + label;
-
- if (ImGui::ImageButton(textureButton.c_str(), textureId, previewSize))
- {
- const char* filePath = tinyfd_openFileDialog(
- "Open Texture",
- "",
- 0,
- nullptr,
- nullptr,
- 0
- );
-
- if (filePath)
- {
- const std::string path(filePath);
- std::shared_ptr newTexture = renderer::Texture2D::create(path);
- if (newTexture)
- {
- texture = newTexture;
- textureModified = true;
- }
- }
- }
- Components::drawButtonBorder(IM_COL32(255,255,255,0), IM_COL32(255,255,255,255), IM_COL32(255,255,255,0), 0.0f, 0, 2.0f);
- ImGui::PopID();
- ImGui::SameLine();
- ImGui::Text("%s", label.c_str());
- return textureModified;
- }
-
- bool Widgets::drawMaterialInspector(components::Material *material)
- {
- bool modified = false;
- // --- Shader Selection ---
- ImGui::BeginGroup();
- {
- ImGui::Text("Shader:");
- ImGui::SameLine();
-
- static int currentShaderIndex = 0;
- const char* shaderOptions[] = { "Standard", "Unlit", "CustomPBR" };
- const float availableWidth = ImGui::GetContentRegionAvail().x;
- ImGui::SetNextItemWidth(availableWidth);
-
- if (ImGui::Combo("##ShaderCombo", ¤tShaderIndex, shaderOptions, IM_ARRAYSIZE(shaderOptions)))
- {
- //TODO: implement shader selection
- }
- }
- ImGui::EndGroup();
- ImGui::Spacing();
-
- // --- Rendering mode selection ---
- ImGui::Text("Rendering mode:");
- ImGui::SameLine();
- static int currentRenderingModeIndex = 0;
- const char* renderingModeOptions[] = { "Opaque", "Transparent", "Refraction" };
- float availableWidth = ImGui::GetContentRegionAvail().x;
-
- ImGui::SetNextItemWidth(availableWidth);
- if (ImGui::Combo("##RenderingModeCombo", ¤tRenderingModeIndex, renderingModeOptions, IM_ARRAYSIZE(renderingModeOptions)))
- {
- //TODO: implement rendering mode
- }
-
- // --- Albedo texture ---
- static ImGuiColorEditFlags colorPickerModeAlbedo = ImGuiColorEditFlags_PickerHueBar;
- static bool showColorPickerAlbedo = false;
- modified = Widgets::drawTextureButton("Albedo texture", material->albedoTexture) || modified;
- ImGui::SameLine();
- modified = Widgets::drawColorEditor("##ColorEditor Albedo texture", &material->albedoColor, &colorPickerModeAlbedo, &showColorPickerAlbedo) || modified;
-
- // --- Specular texture ---
- static ImGuiColorEditFlags colorPickerModeSpecular = ImGuiColorEditFlags_PickerHueBar;
- static bool showColorPickerSpecular = false;
- modified = Widgets::drawTextureButton("Specular texture", material->metallicMap) || modified;
- ImGui::SameLine();
- modified = Widgets::drawColorEditor("##ColorEditor Specular texture", &material->specularColor, &colorPickerModeSpecular, &showColorPickerSpecular) || modified;
- return modified;
- }
-}
diff --git a/editor/src/Components/Widgets.hpp b/editor/src/Components/Widgets.hpp
deleted file mode 100644
index 551b35148..000000000
--- a/editor/src/Components/Widgets.hpp
+++ /dev/null
@@ -1,77 +0,0 @@
-//// Widgets.hpp //////////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 22/02/2025
-// Description: Header file for the widgets components
-//
-///////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include
-#include
-#include
-
-#include "components/Render3D.hpp"
-#include "renderer/Texture.hpp"
-
-namespace nexo::editor {
-
- /**
- * @brief A collection of custom ImGui widget drawing functions.
- *
- * Provides utility functions for drawing color editors, texture buttons, and a material inspector,
- * which can be used to simplify UI code for rendering material properties.
- */
- class Widgets {
- public:
-
- /**
- * @brief Draws a color editor with a button and an optional inline color picker.
- *
- * Displays a custom color button (with a cog icon for picker settings) and, if enabled,
- * an inline color picker. The function returns true if the color was modified.
- *
- * @param label A unique label identifier for the widget.
- * @param selectedEntityColor Pointer to the glm::vec4 representing the current color.
- * @param colorPickerMode Pointer to the ImGuiColorEditFlags for the picker mode.
- * @param showPicker Pointer to a boolean that determines if the inline color picker is visible.
- * @param colorButtonFlags Optional flags for the color button (default is none).
- * @return true if the color was modified; false otherwise.
- */
- static bool drawColorEditor(
- const std::string &label,
- glm::vec4 *selectedEntityColor,
- ImGuiColorEditFlags *colorPickerMode,
- bool *showPicker,
- ImGuiColorEditFlags colorButtonFlags = ImGuiColorEditFlags_None);
-
- /**
- * @brief Draws a texture button that displays a texture preview.
- *
- * When clicked, opens a file dialog to select a new texture. If a new texture is loaded,
- * the passed texture pointer is updated and the function returns true.
- *
- * @param label A unique label identifier for the button.
- * @param texture A shared pointer to the renderer::Texture2D that holds the texture.
- * @return true if the texture was modified; false otherwise.
- */
- static bool drawTextureButton(const std::string &label, std::shared_ptr &texture);
-
- /**
- * @brief Draws a material inspector widget for editing material properties.
- *
- * This function displays controls for shader selection, rendering mode, and textures/colors
- * for material properties such as albedo and specular components.
- *
- * @param material Pointer to the components::Material to be inspected and modified.
- * @return true if any material property was modified; false otherwise.
- */
- static bool drawMaterialInspector(components::Material *material);
- };
-}
diff --git a/editor/src/DockingRegistry.cpp b/editor/src/DockingRegistry.cpp
index eacbc17c2..7cb210dd6 100644
--- a/editor/src/DockingRegistry.cpp
+++ b/editor/src/DockingRegistry.cpp
@@ -14,10 +14,11 @@
#include "DockingRegistry.hpp"
#include
+#include
namespace nexo::editor {
- void DockingRegistry::setDockId(const std::string& name, ImGuiID id)
+ void DockingRegistry::setDockId(const std::string& name, const ImGuiID id)
{
dockIds[name] = id;
}
@@ -30,4 +31,12 @@ namespace nexo::editor {
}
return std::nullopt;
}
+
+ void DockingRegistry::resetDockId(const std::string &name)
+ {
+ auto it = dockIds.find(name);
+ if (it == dockIds.end())
+ return;
+ dockIds.erase(it);
+ }
}
diff --git a/editor/src/DockingRegistry.hpp b/editor/src/DockingRegistry.hpp
index 395e6e289..094081d14 100644
--- a/editor/src/DockingRegistry.hpp
+++ b/editor/src/DockingRegistry.hpp
@@ -22,6 +22,17 @@
namespace nexo::editor {
+ /**
+ * @brief Manages associations between window names and their docking identifiers.
+ *
+ * The DockingRegistry maintains a mapping between window names and ImGui dock IDs,
+ * allowing the editor to track and restore docking configurations across sessions.
+ * This class is central to the editor's window layout management system, enabling
+ * persistent window arrangements and proper docking behavior.
+ *
+ * It uses a TransparentStringHash for efficient string lookups and provides methods
+ * for setting, retrieving, and removing dock ID associations.
+ */
class DockingRegistry {
public:
@@ -47,6 +58,20 @@ namespace nexo::editor {
*/
std::optional getDockId(const std::string& name) const;
+ /**
+ * @brief Removes a dock ID association for the specified name.
+ *
+ * This method removes the association between the given name and its dock ID
+ * from the registry. If the name does not exist in the registry, this method
+ * has no effect.
+ *
+ * Removing a dock ID is useful when a window is closed or when its docking
+ * configuration needs to be reset to default.
+ *
+ * @param name The name identifier of the dock association to remove.
+ */
+ void resetDockId(const std::string &name);
+
private:
std::unordered_map> dockIds;
};
diff --git a/editor/src/DocumentWindows/AssetManagerWindow.hpp b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp
similarity index 94%
rename from editor/src/DocumentWindows/AssetManagerWindow.hpp
rename to editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp
index d665eb5cb..ad6a51d5e 100644
--- a/editor/src/DocumentWindows/AssetManagerWindow.hpp
+++ b/editor/src/DocumentWindows/AssetManager/AssetManagerWindow.hpp
@@ -14,11 +14,8 @@
#pragma once
#include
-#include
#include
-#include
#include
-#include
#include
namespace nexo::editor {
@@ -73,7 +70,6 @@ namespace nexo::editor {
void drawAssetsGrid();
void drawAsset(const assets::GenericAssetRef& asset, int index, const ImVec2& itemPos, const ImVec2& itemSize);
void handleSelection(int index, bool isSelected);
- ImU32 getAssetTypeOverlayColor(assets::AssetType type) const;
};
} // namespace nexo::editor
diff --git a/editor/src/DocumentWindows/AssetManager/Init.cpp b/editor/src/DocumentWindows/AssetManager/Init.cpp
new file mode 100644
index 000000000..349c8a235
--- /dev/null
+++ b/editor/src/DocumentWindows/AssetManager/Init.cpp
@@ -0,0 +1,41 @@
+//// Init.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the setup of the asset manager window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "AssetManagerWindow.hpp"
+#include "assets/AssetCatalog.hpp"
+#include "assets/Assets/Model/ModelImporter.hpp"
+#include "assets/Assets/Texture/TextureImporter.hpp"
+#include "Path.hpp"
+
+namespace nexo::editor {
+ void AssetManagerWindow::setup()
+ {
+ auto& catalog = assets::AssetCatalog::getInstance();
+ auto asset = std::make_unique();
+ catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), std::move(asset));
+
+ /*{
+ assets::AssetImporter importer;
+ std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf");
+ assets::ImporterFileInput fileInput{path};
+ auto assetRef9mn = importer.importAsset(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput);
+ }*/
+ {
+ assets::AssetImporter importer;
+ std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png");
+ assets::ImporterFileInput fileInput{path};
+ auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@foo/bar/"), fileInput);
+ }
+ }
+}
diff --git a/editor/src/DocumentWindows/AssetManagerWindow.cpp b/editor/src/DocumentWindows/AssetManager/Show.cpp
similarity index 63%
rename from editor/src/DocumentWindows/AssetManagerWindow.cpp
rename to editor/src/DocumentWindows/AssetManager/Show.cpp
index 064b6df7c..3aa3b2e8d 100644
--- a/editor/src/DocumentWindows/AssetManagerWindow.cpp
+++ b/editor/src/DocumentWindows/AssetManager/Show.cpp
@@ -1,4 +1,4 @@
-//// AssetManagerWindow.cpp ///////////////////////////////////////////////////
+//// Show.cpp ///////////////////////////////////////////////////////////////
//
// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
// zzzzzzz zzz zzzz zzzz zzzz zzzz
@@ -6,74 +6,35 @@
// zzz zzz zzz z zzzz zzzz zzzz zzzz
// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
//
-// Author: Guillaume HEIN
-// Date: 18/11/2024
-// Description: Source file for the AssetManagerWindow class
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the rendering of the asset manager window
//
///////////////////////////////////////////////////////////////////////////////
#include "AssetManagerWindow.hpp"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
+#include "assets/AssetCatalog.hpp"
+#include "IconsFontAwesome.h"
namespace nexo::editor {
-
- void AssetManagerWindow::setup() {
- auto& catalog = assets::AssetCatalog::getInstance();
-
- const auto asset = new assets::Model();
-
- catalog.registerAsset(assets::AssetLocation("my_package::My_Model@foo/bar/"), asset);
-
-
-
- {
- assets::AssetImporter importer;
- std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/models/9mn/scene.gltf");
- assets::ImporterFileInput fileInput{path};
- auto assetRef9mn = importer.importAssetAuto(assets::AssetLocation("my_package::9mn@foo/bar/"), fileInput);
- }
- {
- assets::AssetImporter importer;
- std::filesystem::path path = Path::resolvePathRelativeToExe("../resources/textures/logo_nexo.png");
- assets::ImporterFileInput fileInput{path};
- auto textureRef = importer.importAsset(assets::AssetLocation("nexo_logo@foo/bar/"), fileInput);
+ void AssetManagerWindow::drawMenuBar()
+ {
+ if (ImGui::BeginMenuBar()) {
+ if (ImGui::BeginMenu("Options")) {
+ ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f");
+ ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32);
+ ImGui::EndMenu();
+ }
+ ImGui::EndMenuBar();
}
}
- void AssetManagerWindow::shutdown() {
- }
-
- void AssetManagerWindow::show() {
- ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
- ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" "###" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar);
- firstDockSetup(NEXO_WND_USTRID_ASSET_MANAGER);
-
- drawMenuBar();
-
- float availWidth = ImGui::GetContentRegionAvail().x;
- calculateLayout(availWidth);
-
- drawAssetsGrid();
-
- ImGui::End();
- }
-
- void AssetManagerWindow::update() {
- // Update logic if necessary
- }
-
- void AssetManagerWindow::calculateLayout(float availWidth) {
+ void AssetManagerWindow::calculateLayout(const float availWidth)
+ {
// Sizes
- m_layout.size.columnCount = std::max(static_cast(availWidth / (m_layout.size.iconSize + m_layout.size.iconSpacing)), 1);
+ m_layout.size.columnCount = std::max(static_cast(availWidth / (m_layout.size.iconSize + static_cast(m_layout.size.iconSpacing))), 1);
m_layout.size.itemSize = ImVec2(m_layout.size.iconSize + ImGui::GetFontSize() * 1.5f, m_layout.size.iconSize + ImGui::GetFontSize() * 1.7f);
- m_layout.size.itemStep = ImVec2(m_layout.size.itemSize.x + m_layout.size.iconSpacing, m_layout.size.itemSize.y + m_layout.size.iconSpacing);
+ m_layout.size.itemStep = ImVec2(m_layout.size.itemSize.x + static_cast(m_layout.size.iconSpacing), m_layout.size.itemSize.y + static_cast(m_layout.size.iconSpacing));
// Colors
m_layout.color.thumbnailBg = ImGui::GetColorU32(ImGuiCol_Button);
@@ -91,51 +52,58 @@ namespace nexo::editor {
m_layout.color.titleText = ImGui::GetColorU32(ImGuiCol_Text);
}
- void AssetManagerWindow::drawMenuBar() {
- if (ImGui::BeginMenuBar()) {
- if (ImGui::BeginMenu("Options")) {
- ImGui::SliderFloat("Icon Size", &m_layout.size.iconSize, 32.0f, 128.0f, "%.0f");
- ImGui::SliderInt("Icon Spacing", &m_layout.size.iconSpacing, 0, 32);
- ImGui::EndMenu();
+ void AssetManagerWindow::handleSelection(int index, const bool isSelected)
+ {
+ LOG(NEXO_INFO, "Asset {} {}", index, isSelected ? "deselected" : "selected");
+ if (ImGui::GetIO().KeyCtrl) {
+ if (isSelected)
+ m_selectedAssets.erase(index);
+ else
+ m_selectedAssets.insert(index);
+ } else if (ImGui::GetIO().KeyShift) {
+ const int latestSelected = m_selectedAssets.empty() ? 0 : *m_selectedAssets.rbegin();
+ if (latestSelected <= index) {
+ for (int i = latestSelected ; i <= index; ++i) {
+ m_selectedAssets.insert(i);
+ }
+ } else {
+ for (int i = index; i <= latestSelected; ++i) {
+ m_selectedAssets.insert(i);
+ }
}
- ImGui::EndMenuBar();
+ } else {
+ m_selectedAssets.clear();
+ m_selectedAssets.insert(index);
}
}
- void AssetManagerWindow::drawAssetsGrid() {
- ImVec2 startPos = ImGui::GetCursorScreenPos();
-
- ImGuiListClipper clipper;
- const auto assets = assets::AssetCatalog::getInstance().getAssets();
- clipper.Begin(assets.size(), m_layout.size.itemStep.y);
- while (clipper.Step()) {
- for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) {
- int startIdx = lineIdx * m_layout.size.columnCount;
- int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(assets.size()));
-
- for (int i = startIdx; i < endIdx; ++i) {
- ImVec2 itemPos = ImVec2(startPos.x + (i % m_layout.size.columnCount) * m_layout.size.itemStep.x,
- startPos.y + (i / m_layout.size.columnCount) * m_layout.size.itemStep.y);
- drawAsset(assets[i], i, itemPos, m_layout.size.itemSize);
- }
- }
+ static ImU32 getAssetTypeOverlayColor(const assets::AssetType type)
+ {
+ switch (type) {
+ case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255);
+ case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255);
+ default: return IM_COL32(0, 0, 0, 0);
}
- clipper.End();
}
- void AssetManagerWindow::drawAsset(const assets::GenericAssetRef& asset, int index, const ImVec2& itemPos, const ImVec2& itemSize) {
+ void AssetManagerWindow::drawAsset(
+ const assets::GenericAssetRef& asset,
+ const int index,
+ const ImVec2& itemPos,
+ const ImVec2& itemSize
+ ) {
auto assetData = asset.lock();
if (!assetData)
return;
ImDrawList* drawList = ImGui::GetWindowDrawList();
- ImVec2 itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y);
+ const auto itemEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y);
ImGui::PushID(index);
// Highlight selection
- bool isSelected = std::find(m_selectedAssets.begin(), m_selectedAssets.end(), index) != m_selectedAssets.end();
- ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg;
+ const bool isSelected = std::ranges::find(m_selectedAssets, index) != m_selectedAssets.end();
+ const ImU32 bgColor = isSelected ? m_layout.color.thumbnailBgSelected : m_layout.color.thumbnailBg;
drawList->AddRectFilled(itemPos, itemEnd, bgColor, m_layout.size.cornerRadius);
// Add selection border
@@ -152,17 +120,17 @@ namespace nexo::editor {
}
// Draw thumbnail
- ImVec2 thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio);
+ const auto thumbnailEnd = ImVec2(itemPos.x + itemSize.x, itemPos.y + itemSize.y * m_layout.size.thumbnailHeightRatio);
drawList->AddRectFilled(itemPos, thumbnailEnd, m_layout.color.thumbnailBg);
// Draw type overlay
- ImVec2 overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.overlayPadding, itemPos.y + m_layout.size.overlayPadding);
- ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType());
+ const auto overlayPos = ImVec2(thumbnailEnd.x - m_layout.size.overlayPadding, itemPos.y + m_layout.size.overlayPadding);
+ const ImU32 overlayColor = getAssetTypeOverlayColor(assetData->getType());
drawList->AddRectFilled(overlayPos, ImVec2(overlayPos.x + m_layout.size.overlaySize, overlayPos.y + m_layout.size.overlaySize), overlayColor);
// Draw title
const char *assetName = assetData->getMetadata().location.getName().c_str();
- ImVec2 textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f,
+ const auto textPos = ImVec2(itemPos.x + (itemSize.x - ImGui::CalcTextSize(assetName).x) * 0.5f,
thumbnailEnd.y + m_layout.size.titlePadding);
// Background rectangle for text
drawList->AddRectFilled(ImVec2(itemPos.x, thumbnailEnd.y), ImVec2(itemEnd.x, itemEnd.y), m_layout.color.titleBg);
@@ -182,41 +150,52 @@ namespace nexo::editor {
}
ImGui::PopID();
-
-
}
- void AssetManagerWindow::handleSelection(int index, bool isSelected)
+ void AssetManagerWindow::drawAssetsGrid()
{
- LOG(NEXO_INFO, "Asset {} {}", index, isSelected ? "deselected" : "selected");
- if (ImGui::GetIO().KeyCtrl) {
- if (isSelected)
- m_selectedAssets.erase(index);
- else
- m_selectedAssets.insert(index);
- } else if (ImGui::GetIO().KeyShift) {
- const int latestSelected = m_selectedAssets.empty() ? 0 : *m_selectedAssets.rbegin();
- if (latestSelected <= index) {
- for (int i = latestSelected ; i <= index; ++i) {
- m_selectedAssets.insert(i);
- }
- } else {
- for (int i = index; i <= latestSelected; ++i) {
- m_selectedAssets.insert(i);
+ ImVec2 startPos = ImGui::GetCursorScreenPos();
+
+ ImGuiListClipper clipper;
+ const auto assets = assets::AssetCatalog::getInstance().getAssets();
+ clipper.Begin(static_cast(assets.size()), m_layout.size.itemStep.y);
+ while (clipper.Step()) {
+ for (int lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx) {
+ int startIdx = lineIdx * m_layout.size.columnCount;
+ int endIdx = std::min(startIdx + m_layout.size.columnCount, static_cast(assets.size()));
+
+ int columns = m_layout.size.columnCount;
+ float stepX = m_layout.size.itemStep.x;
+ float stepY = m_layout.size.itemStep.y;
+
+ for (int i = startIdx; i < endIdx; ++i) {
+ auto idx = static_cast(i);
+ float col = std::fmod(idx, static_cast(columns));
+ float row = std::floor(idx / static_cast(columns));
+ ImVec2 itemPos{
+ startPos.x + col * stepX,
+ startPos.y + row * stepY
+ };
+ drawAsset(assets[i], i, itemPos, m_layout.size.itemSize);
}
}
- } else {
- m_selectedAssets.clear();
- m_selectedAssets.insert(index);
}
+ clipper.End();
}
- ImU32 AssetManagerWindow::getAssetTypeOverlayColor(assets::AssetType type) const {
- switch (type) {
- case assets::AssetType::TEXTURE: return IM_COL32(200, 70, 70, 255);
- case assets::AssetType::MODEL: return IM_COL32(70, 170, 70, 255);
- default: return IM_COL32(0, 0, 0, 0);
- }
- }
+ void AssetManagerWindow::show()
+ {
+ ImGui::SetNextWindowSize(ImVec2(800, 600), ImGuiCond_FirstUseEver);
+ ImGui::Begin(ICON_FA_FOLDER_OPEN " Asset Manager" NEXO_WND_USTRID_ASSET_MANAGER, &m_opened, ImGuiWindowFlags_MenuBar);
+ beginRender(NEXO_WND_USTRID_ASSET_MANAGER);
+
+ drawMenuBar();
-} // namespace nexo::editor
+ float availWidth = ImGui::GetContentRegionAvail().x;
+ calculateLayout(availWidth);
+
+ drawAssetsGrid();
+
+ ImGui::End();
+ }
+}
diff --git a/editor/src/DocumentWindows/AssetManager/Shutdown.cpp b/editor/src/DocumentWindows/AssetManager/Shutdown.cpp
new file mode 100644
index 000000000..0bd9b07e8
--- /dev/null
+++ b/editor/src/DocumentWindows/AssetManager/Shutdown.cpp
@@ -0,0 +1,24 @@
+//// Shutdown.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the shutdown of the asset manager window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "AssetManagerWindow.hpp"
+
+namespace nexo::editor {
+
+ void AssetManagerWindow::shutdown()
+ {
+ //Nothing to do in the shutdown for now
+ }
+
+}
diff --git a/editor/src/DocumentWindows/AssetManager/Update.cpp b/editor/src/DocumentWindows/AssetManager/Update.cpp
new file mode 100644
index 000000000..475078433
--- /dev/null
+++ b/editor/src/DocumentWindows/AssetManager/Update.cpp
@@ -0,0 +1,24 @@
+//// Update.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the update function of the asset manager window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "AssetManagerWindow.hpp"
+
+namespace nexo::editor {
+
+ void AssetManagerWindow::update()
+ {
+ // Nothing to do for now
+ }
+
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow.cpp b/editor/src/DocumentWindows/ConsoleWindow.cpp
deleted file mode 100644
index bba45007f..000000000
--- a/editor/src/DocumentWindows/ConsoleWindow.cpp
+++ /dev/null
@@ -1,296 +0,0 @@
-//// ConsoleWindow.cpp ////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Guillaume HEIN
-// Date: 10/11/2024
-// Description: Source file for the console window class
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include
-#include
-#include "ConsoleWindow.hpp"
-
-#include
-
-namespace nexo::editor {
- /**
- * @brief Converts a loguru verbosity level to its corresponding string label.
- *
- * This function maps a given loguru verbosity level to a predefined string representation,
- * such as "[FATAL]", "[ERROR]", "[WARNING]", "[INFO]", "[INVALID]", "[DEBUG]", or "[DEV]".
- * If the provided level does not match any known values, it returns "[UNKNOWN]".
- *
- * @param level The loguru verbosity level to convert.
- * @return std::string The string label corresponding to the provided verbosity level.
- */
- static inline std::string verbosityToString(const loguru::Verbosity level)
- {
- switch (level)
- {
- case loguru::Verbosity_FATAL: return "[FATAL]";
- case loguru::Verbosity_ERROR: return "[ERROR]";
- case loguru::Verbosity_WARNING: return "[WARNING]";
- case loguru::Verbosity_INFO: return "[INFO]";
- case loguru::Verbosity_INVALID: return "[INVALID]";
- case loguru::Verbosity_1: return "[DEBUG]";
- case loguru::Verbosity_2: return "[DEV]";
- default: return "[UNKNOWN]";
- }
- }
-
- /**
- * @brief Converts a custom LogLevel to its corresponding loguru::Verbosity level.
- *
- * Maps each supported LogLevel to a specific loguru verbosity constant. If the provided
- * level does not match any known value, the function returns loguru::Verbosity_INVALID.
- *
- * @param level The custom logging level to convert.
- * @return The equivalent loguru verbosity level.
- */
- loguru::Verbosity nexoLevelToLoguruLevel(const LogLevel level)
- {
- switch (level)
- {
- case LogLevel::FATAL: return loguru::Verbosity_FATAL;
- case LogLevel::ERROR: return loguru::Verbosity_ERROR;
- case LogLevel::WARN: return loguru::Verbosity_WARNING;
- case LogLevel::INFO: return loguru::Verbosity_INFO;
- case LogLevel::DEBUG: return loguru::Verbosity_1;
- case LogLevel::DEV: return loguru::Verbosity_2;
- default: return loguru::Verbosity_INVALID;
- }
- return loguru::Verbosity_INVALID;
- }
-
- /**
- * @brief Returns the color corresponding to a log verbosity level.
- *
- * Maps the given loguru::Verbosity level to a specific ImVec4 color used for rendering log messages in the console.
- * - Fatal and error messages are shown in red.
- * - Warnings use yellow.
- * - Informational messages appear in blue.
- * - Debug levels display distinct pink and purple hues.
- * The default color is white for any unrecognized verbosity levels.
- *
- * @param level The verbosity level for which the corresponding color is computed.
- * @return ImVec4 The color associated with the specified verbosity level.
- */
- static constexpr ImVec4 getVerbosityColor(loguru::Verbosity level)
- {
- ImVec4 color;
-
- switch (level)
- {
- case loguru::Verbosity_FATAL: // Red
- case loguru::Verbosity_ERROR: color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
- break; // Red
- case loguru::Verbosity_WARNING: color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
- break; // Yellow
- case loguru::Verbosity_INFO: color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f);
- break; // Blue
- case loguru::Verbosity_1: color = ImVec4(0.898f, 0.0f, 1.0f, 1.0f); // Debug
- break; // Pink
- case loguru::Verbosity_2: color = ImVec4(0.388f, 0.055f, 0.851f, 1.0f); // Debug
- break; // Purple
- default: color = ImVec4(1, 1, 1, 1); // White
- }
- return color;
- }
-
-
- void ConsoleWindow::loguruCallback([[maybe_unused]] void *userData,
- const loguru::Message &message)
- {
- const auto console = static_cast(userData);
- console->addLog({
- .verbosity = message.verbosity,
- .message = message.message,
- .prefix = message.prefix
- });
- }
-
-
- ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) : ADocumentWindow(windowName, registry)
- {
- loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback,
- this, loguru::Verbosity_MAX);
-
- auto engineLogCallback = [](const LogLevel level, const std::string &message) {
- const auto loguruLevel = nexoLevelToLoguruLevel(level);
- VLOG_F(loguruLevel, "%s", message.c_str());
- };
- Logger::setCallback(engineLogCallback);
- };
-
- void ConsoleWindow::setup()
- {
- //All the setup is made in the constructor because the rest of the editor needs the log setup before setting up the windows
- }
-
- void ConsoleWindow::shutdown()
- {
- clearLog();
- }
-
- void ConsoleWindow::addLog(const LogMessage &message)
- {
- m_logs.push_back(message);
- }
-
- void ConsoleWindow::clearLog()
- {
- m_logs.clear();
- items.clear();
- }
-
- template
- void ConsoleWindow::addLog(const char *fmt, Args &&... args)
- {
- try
- {
- std::string formattedMessage = std::vformat(fmt, std::make_format_args(std::forward(args)...));
- items.emplace_back(formattedMessage);
- } catch (const std::format_error &e)
- {
- items.emplace_back(std::format("[Error formatting log message]: {}", e.what()));
- }
-
- scrollToBottom = true;
- }
-
- void ConsoleWindow::executeCommand(const char *command_line)
- {
- commands.emplace_back(command_line);
- addLog("# {}\n", command_line);
- }
-
- void ConsoleWindow::calcLogPadding()
- {
- m_logPadding = 0.0f;
- for (const auto &[verbosity, message, prefix]: m_logs)
- {
- if (!selectedVerbosityLevels.contains(verbosity))
- continue;
-
- const std::string tag = verbosityToString(verbosity);
- const ImVec2 textSize = ImGui::CalcTextSize(tag.c_str());
- if (textSize.x > m_logPadding)
- {
- m_logPadding = textSize.x;
- }
- }
- m_logPadding += ImGui::GetStyle().ItemSpacing.x;
- }
-
-
- void ConsoleWindow::displayLog(loguru::Verbosity verbosity, const std::string &msg) const
- {
- ImVec4 color = getVerbosityColor(verbosity);
- ImGui::PushStyleColor(ImGuiCol_Text, color);
-
- const std::string tag = verbosityToString(verbosity);
- ImGui::TextUnformatted(tag.c_str());
- ImGui::PopStyleColor();
-
- ImGui::SameLine();
- ImGui::SetCursorPosX(m_logPadding);
-
- ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
- ImGui::TextWrapped("%s", msg.c_str());
- ImGui::PopTextWrapPos();
- }
-
- void ConsoleWindow::showVerbositySettingsPopup()
- {
- ImGui::Text("Select Verbosity Levels");
- ImGui::Separator();
-
- const struct {
- loguru::Verbosity level;
- const char *name;
- } levels[] = {
- {loguru::Verbosity_FATAL, "FATAL"},
- {loguru::Verbosity_ERROR, "ERROR"},
- {loguru::Verbosity_WARNING, "WARNING"},
- {loguru::Verbosity_INFO, "INFO"},
- {loguru::Verbosity_1, "DEBUG"},
- {loguru::Verbosity_2, "DEV"},
- };
-
- for (const auto &[level, name]: levels)
- {
- bool selected = (selectedVerbosityLevels.contains(level));
- if (ImGui::Checkbox(name, &selected))
- {
- if (selected)
- {
- selectedVerbosityLevels.insert(level);
- calcLogPadding();
- } else
- {
- selectedVerbosityLevels.erase(level);
- calcLogPadding();
- }
- }
- }
-
- ImGui::EndPopup();
- }
-
- void ConsoleWindow::show()
- {
- ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
- ImGui::Begin(ICON_FA_FILE_TEXT " Console" "###" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse);
- firstDockSetup(NEXO_WND_USTRID_CONSOLE);
-
- const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
- ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar);
-
- if (m_logPadding == 0.0f)
- calcLogPadding();
-
- auto id = 0;
- for (const auto &[verbosity, message, prefix]: m_logs)
- {
- if (!selectedVerbosityLevels.contains(verbosity))
- continue;
-
- ImGui::PushID(id++);
- displayLog(verbosity, message);
- ImGui::PopID();
- }
-
- if (scrollToBottom)
- ImGui::SetScrollHereY(1.0f);
- scrollToBottom = false;
-
- ImGui::EndChild();
- ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f);
-
- if (ImGui::InputText("Input", inputBuf, IM_ARRAYSIZE(inputBuf), ImGuiInputTextFlags_EnterReturnsTrue))
- {
- executeCommand(inputBuf);
- std::memset(inputBuf, '\0', sizeof(inputBuf));
- }
-
- ImGui::SameLine();
- if (ImGui::Button("..."))
- ImGui::OpenPopup("VerbositySettings");
-
- if (ImGui::BeginPopup("VerbositySettings"))
- showVerbositySettingsPopup();
-
- ImGui::End();
- }
-
- void ConsoleWindow::update()
- {
- //No need to update anything
- }
-}
diff --git a/editor/src/DocumentWindows/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow.hpp
deleted file mode 100644
index c8c0531d5..000000000
--- a/editor/src/DocumentWindows/ConsoleWindow.hpp
+++ /dev/null
@@ -1,139 +0,0 @@
-//// ConsoleWindow.hpp ////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Guillaume HEIN
-// Date: 10/11/2024
-// Description: Header file for the console window class
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#pragma once
-
-#include "ADocumentWindow.hpp"
-#include "Editor.hpp"
-
-namespace nexo::editor {
-
- constexpr auto LOGURU_CALLBACK_NAME = "GEE";
-
- struct LogMessage {
- loguru::Verbosity verbosity;
- std::string message;
- std::string prefix;
- };
-
- class ConsoleWindow final : public ADocumentWindow {
- public:
- /**
- * @brief Constructs and initializes a ConsoleWindow.
- *
- * This constructor sets up the console's logging functionality by registering a loguru callback via
- * loguru::add_callback to route log messages to the console (using the static loguruCallback) and by
- * establishing an engine log callback that maps custom LogLevel messages to loguru verbosity levels
- * using nexoLevelToLoguruLevel before logging them with VLOG_F.
- *
- * @param registry The window registry used to register this console window.
- */
- explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry);
-
- /**
- * @brief Destructor that cleans up the ConsoleWindow.
- *
- * Removes the registered loguru callback identified by LOGURU_CALLBACK_NAME to prevent further logging after the window is destroyed.
- */
- ~ConsoleWindow() override
- {
- loguru::remove_callback(LOGURU_CALLBACK_NAME);
- };
-
- void setup() override;
-
- /**
- * @brief Clears all stored log entries during shutdown.
- *
- * This method resets the console's log by invoking clearLog(), ensuring that all previous
- * log entries are removed as part of the shutdown process.
- */
- void shutdown() override;
-
- /**
- * @brief Renders the console window interface.
- *
- * This method initializes and displays the console window using ImGui. It sets a predefined window size and
- * creates a scrolling region to display log messages filtered by selected verbosity levels. When the console is
- * opened for the first time, it performs an initial docking setup. The function also adjusts log padding for proper
- * alignment and automatically scrolls to the bottom if new messages have been added.
- *
- * An input field is provided for entering commands, which are executed upon pressing Enter, with the input buffer
- * cleared afterward. Additionally, a popup for adjusting verbosity settings is available, accessible via a button.
- */
- void show() override;
- void update() override;
-
- template
- void addLog(const char* fmt, Args&&... args);
- void executeCommand(const char* command_line);
-
- private:
- float m_logPadding = 0.0f;
- char inputBuf[512] = {};
- std::deque items;
- bool scrollToBottom = true;
- std::vector commands; // History of executed commands.
-
- std::set selectedVerbosityLevels = {
- loguru::Verbosity_FATAL,
- loguru::Verbosity_ERROR,
- loguru::Verbosity_WARNING,
- loguru::Verbosity_INFO,
- };
-
- std::vector m_logs;
-
- /**
- * @brief Clears all log entries and display items.
- *
- * Removes all log messages from the internal storage and resets the list of display items.
- */
- void clearLog();
-
- /**
- * @brief Appends a log message to the console's log collection.
- *
- * This method adds the provided log message to the internal container, ensuring it is available
- * for display in the console window.
- *
- * @param message The log message to append.
- */
- void addLog(const LogMessage& message);
- void displayLog(loguru::Verbosity verbosity, const std::string &msg) const;
- void showVerbositySettingsPopup();
-
- /**
- * @brief Updates the horizontal padding for log entries.
- *
- * Iterates over the stored log messages to compute the maximum width of the verbosity tag text for
- * messages that are currently visible (filtered by selected verbosity levels). The computed maximum
- * width is then increased by the spacing defined in the ImGui style to ensure proper alignment in the UI.
- */
- void calcLogPadding();
-
- /**
- * @brief Processes a loguru message and adds it to the console log.
- *
- * Converts a loguru message to the internal log format and appends it to the ConsoleWindow's log list.
- * The userData pointer is cast to a ConsoleWindow instance, which is then used to record the message details,
- * including verbosity, message content, and prefix.
- *
- * @param userData Pointer to the ConsoleWindow instance.
- * @param message The loguru message carrying the log details.
- */
- static void loguruCallback(void *userData, const loguru::Message& message);
- };
-
-}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp
new file mode 100644
index 000000000..3d25cb82b
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/ConsoleWindow.hpp
@@ -0,0 +1,247 @@
+//// ConsoleWindow.hpp ////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Guillaume HEIN
+// Date: 10/11/2024
+// Description: Header file for the console window class
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include "ADocumentWindow.hpp"
+#include "Editor.hpp"
+#include
+
+namespace nexo::editor {
+
+ std::string verbosityToString(loguru::Verbosity level);
+ loguru::Verbosity nexoLevelToLoguruLevel(LogLevel level);
+ const ImVec4 getVerbosityColor(loguru::Verbosity level);
+ std::string generateLogFilePath();
+
+ constexpr auto LOGURU_CALLBACK_NAME = "GEE";
+
+ /**
+ * @brief Structure representing a formatted log message.
+ *
+ * Contains all necessary information for displaying a log message in the console,
+ * including its verbosity level, the message text, and an optional prefix.
+ */
+ struct LogMessage {
+ loguru::Verbosity verbosity; ///< The verbosity level of the log message
+ std::string message; ///< The content of the log message
+ std::string prefix; ///< Optional prefix for the log message
+ };
+
+
+ /**
+ * @brief Console window for displaying and managing application logs.
+ *
+ * This class provides a visual interface for viewing log messages with different
+ * verbosity levels, executing commands, and managing log settings. It integrates
+ * with the loguru logging system to display real-time log messages.
+ */
+ class ConsoleWindow final : public ADocumentWindow {
+ public:
+ /**
+ * @brief Constructs and initializes a ConsoleWindow.
+ *
+ * This constructor sets up the console's logging functionality by registering a loguru callback via
+ * loguru::add_callback to route log messages to the console (using the static loguruCallback) and by
+ * establishing an engine log callback that maps custom LogLevel messages to loguru verbosity levels
+ * using nexoLevelToLoguruLevel before logging them with VLOG_F.
+ *
+ * @param registry The window registry used to register this console window.
+ */
+ explicit ConsoleWindow(const std::string &windowName, WindowRegistry ®istry);
+
+ /**
+ * @brief Destructor that cleans up the ConsoleWindow.
+ *
+ * Removes the registered loguru callback identified by LOGURU_CALLBACK_NAME to prevent further logging after the window is destroyed.
+ */
+ ~ConsoleWindow() override
+ {
+ loguru::remove_callback(LOGURU_CALLBACK_NAME);
+ };
+
+ // No-op method in this class
+ void setup() override;
+
+ /**
+ * @brief Clears all stored log entries during shutdown.
+ *
+ * This method resets the console's log by invoking clearLog(), ensuring that all previous
+ * log entries are removed as part of the shutdown process.
+ */
+ void shutdown() override;
+
+ /**
+ * @brief Renders the console window interface.
+ *
+ * This method initializes and displays the console window using ImGui. It sets a predefined window size and
+ * creates a scrolling region to display log messages filtered by selected verbosity levels. When the console is
+ * opened for the first time, it performs an initial docking setup. The function also adjusts log padding for proper
+ * alignment and automatically scrolls to the bottom if new messages have been added.
+ *
+ * An input field is provided for entering commands, which are executed upon pressing Enter, with the input buffer
+ * cleared afterward. Additionally, a popup for adjusting verbosity settings is available, accessible via a button.
+ */
+ void show() override;
+
+ // No-op method in this class
+ void update() override;
+
+ /**
+ * @brief Executes a command entered in the console.
+ *
+ * Processes the given command line, adds it to the command history,
+ * and displays it in the log.
+ *
+ * @param commandLine The command text to execute.
+ * @note not implemented yet
+ */
+ void executeCommand(const char* commandLine);
+
+ private:
+ char m_inputBuf[512] = {};
+ std::vector m_commands; // History of executed commands.
+
+ std::string m_logFilePath;
+ bool m_exportLog = true;
+
+ bool m_scrollToBottom = true;
+
+
+ std::set m_selectedVerbosityLevels = {
+ loguru::Verbosity_FATAL,
+ loguru::Verbosity_ERROR,
+ loguru::Verbosity_WARNING,
+ loguru::Verbosity_INFO,
+ loguru::Verbosity_1,
+ };
+
+ float m_logPadding = 0.0f;
+ std::vector m_logs;
+ size_t m_maxLogCapacity = 200;
+ std::vector m_bufferLogsToExport;
+ size_t m_maxBufferLogToExportCapacity = 20;
+
+
+ /**
+ * @brief Clears all log entries and display items.
+ *
+ * Removes all log messages from the internal storage and resets the list of display items.
+ */
+ void clearLog();
+
+ /**
+ * @brief Appends a log message to the console's log collection.
+ *
+ * This method adds the provided log message to the internal container, ensuring it is available
+ * for display in the console window.
+ *
+ * @param message The log message to append.
+ */
+ void addLog(const LogMessage& message);
+
+ /**
+ * @brief Adds a formatted log message to the console.
+ *
+ * Creates a log message using printf-style formatting and adds it to the log collection.
+ *
+ * @tparam Args Variadic template for format arguments
+ * @param fmt Format string similar to printf
+ * @param args Arguments for the format string
+ */
+ template
+ void addLog(std::format_string fmt, Args&&... args)
+ {
+ try {
+ std::string formattedString = std::format(fmt, std::forward(args)...);
+
+ LogMessage newMessage;
+ newMessage.verbosity = loguru::Verbosity_1;
+ newMessage.message = formattedString;
+ newMessage.prefix = "";
+ m_logs.push_back(newMessage);
+ } catch (const std::exception &e) {
+ LogMessage newMessage;
+ newMessage.verbosity = loguru::Verbosity_ERROR;
+
+ char errorBuffer[1024];
+
+ // format up to sizeof(errorBuffer)-1 characters
+ auto result = std::format_to_n(
+ std::begin(errorBuffer),
+ std::size(errorBuffer) - 1,
+ "Error formatting log message: {}",
+ e.what()
+ );
+
+ // null-terminate
+ *result.out = '\0';
+
+ newMessage.message = std::string(errorBuffer);
+ newMessage.prefix = "";
+ m_logs.push_back(newMessage);
+ }
+
+ m_scrollToBottom = true;
+ }
+
+ /**
+ * @brief Displays a single log entry in the console UI.
+ *
+ * Renders a log message with appropriate styling based on its verbosity level.
+ *
+ * @param verbosity The verbosity level that determines the message's color
+ * @param msg The message text to display
+ */
+ void displayLog(loguru::Verbosity verbosity, const std::string &msg) const;
+
+ /**
+ * @brief Exports buffered logs to the log file.
+ *
+ * Writes any logs in the export buffer to the configured log file,
+ * helping to prevent memory buildup from excessive logging.
+ */
+ void exportLogsBuffered() const;
+
+ /**
+ * @brief Displays the popup for configuring verbosity settings.
+ *
+ * Shows a popup menu that allows the user to select which verbosity levels
+ * to display in the console and configure other log-related settings.
+ */
+ void showVerbositySettingsPopup();
+
+ /**
+ * @brief Updates the horizontal padding for log entries.
+ *
+ * Iterates over the stored log messages to compute the maximum width of the verbosity tag text for
+ * messages that are currently visible (filtered by selected verbosity levels). The computed maximum
+ * width is then increased by the spacing defined in the ImGui style to ensure proper alignment in the UI.
+ */
+ void calcLogPadding();
+
+ /**
+ * @brief Processes a loguru message and adds it to the console log.
+ *
+ * Converts a loguru message to the internal log format and appends it to the ConsoleWindow's log list.
+ * The userData pointer is cast to a ConsoleWindow instance, which is then used to record the message details,
+ * including verbosity, message content, and prefix.
+ *
+ * @param userData Pointer to the ConsoleWindow instance.
+ * @param message The loguru message carrying the log details.
+ */
+ static void loguruCallback(void *userData, const loguru::Message& message);
+ };
+
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Init.cpp b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp
new file mode 100644
index 000000000..f1aec186e
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Init.cpp
@@ -0,0 +1,52 @@
+//// Init.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the setup function of the console window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+#include "Path.hpp"
+
+namespace nexo::editor {
+ void ConsoleWindow::loguruCallback([[maybe_unused]] void *userData,
+ const loguru::Message &message)
+ {
+ const auto console = static_cast(userData);
+ LogMessage newMessage;
+ newMessage.verbosity = message.verbosity;
+ newMessage.message = message.message;
+ newMessage.prefix = message.prefix;
+ console->addLog(newMessage);
+ }
+
+
+ ConsoleWindow::ConsoleWindow(const std::string &windowName, WindowRegistry ®istry) : ADocumentWindow(windowName, registry)
+ {
+ loguru::add_callback(LOGURU_CALLBACK_NAME, &ConsoleWindow::loguruCallback,
+ this, loguru::Verbosity_MAX);
+
+ auto engineLogCallback = [](const LogLevel level, const std::source_location& loc, const std::string &message) {
+ const auto loguruLevel = nexoLevelToLoguruLevel(level);
+ if (loguruLevel > loguru::current_verbosity_cutoff())
+ return;
+ loguru::log(loguruLevel, loc.file_name(), loc.line(), "%s", message.c_str());
+ };
+ Logger::setCallback(engineLogCallback);
+ m_logFilePath = Path::resolvePathRelativeToExe(generateLogFilePath()).string();
+ m_logs.reserve(m_maxLogCapacity);
+ m_bufferLogsToExport.reserve(m_maxBufferLogToExportCapacity);
+ };
+
+ void ConsoleWindow::setup()
+ {
+ //All the setup is made in the constructor because the rest of the editor needs the log setup before setting up the windows
+ }
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Log.cpp b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp
new file mode 100644
index 000000000..e2af09fce
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Log.cpp
@@ -0,0 +1,59 @@
+//// Log.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the log function of the console window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+#include
+
+namespace nexo::editor {
+
+ void ConsoleWindow::addLog(const LogMessage &message)
+ {
+ if (m_logs.size() >= m_maxLogCapacity) {
+ m_bufferLogsToExport.push_back(message);
+ m_logs.erase(m_logs.begin());
+ }
+
+ if (m_bufferLogsToExport.size() > m_maxBufferLogToExportCapacity) {
+ exportLogsBuffered();
+ m_bufferLogsToExport.clear();
+ }
+
+ m_logs.push_back(message);
+ }
+
+ void ConsoleWindow::clearLog()
+ {
+ exportLogsBuffered();
+ if (m_exportLog) {
+ std::ofstream logFile(m_logFilePath, std::ios::app);
+ for (const auto& log : m_logs) {
+ logFile << verbosityToString(log.verbosity) << " "
+ << log.message << std::endl;
+ }
+ }
+
+ m_logs.clear();
+ }
+
+ void ConsoleWindow::exportLogsBuffered() const
+ {
+ if (!m_exportLog)
+ return;
+ std::ofstream logFile(m_logFilePath, std::ios::app);
+ for (const auto& log : m_bufferLogsToExport) {
+ logFile << verbosityToString(log.verbosity) << " "
+ << log.message << std::endl;
+ }
+ }
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Show.cpp b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp
new file mode 100644
index 000000000..bec5ba2dc
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Show.cpp
@@ -0,0 +1,147 @@
+//// Show.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the rendering of the console window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+#include "IconsFontAwesome.h"
+#include "ImNexo/Elements.hpp"
+#include "utils/FileSystem.hpp"
+#include "Path.hpp"
+
+namespace nexo::editor {
+
+ void ConsoleWindow::showVerbositySettingsPopup()
+ {
+ ImGui::Text("Select Verbosity Levels");
+ ImGui::Separator();
+
+ const struct {
+ loguru::Verbosity level;
+ const char *name;
+ } levels[] = {
+ {loguru::Verbosity_FATAL, "FATAL"},
+ {loguru::Verbosity_ERROR, "ERROR"},
+ {loguru::Verbosity_WARNING, "WARNING"},
+ {loguru::Verbosity_INFO, "INFO"},
+ {loguru::Verbosity_1, "USER"},
+ {loguru::Verbosity_2, "DEBUG"},
+ {loguru::Verbosity_3, "DEV"}
+ };
+
+ for (const auto &[level, name]: levels)
+ {
+ bool selected = (m_selectedVerbosityLevels.contains(level));
+ if (ImGui::Checkbox(name, &selected))
+ {
+ if (selected)
+ {
+ m_selectedVerbosityLevels.insert(level);
+ calcLogPadding();
+ } else
+ {
+ m_selectedVerbosityLevels.erase(level);
+ calcLogPadding();
+ }
+ }
+ }
+
+ ImGui::Separator();
+ ImGui::Checkbox("File logging", &m_exportLog);
+ if (ImNexo::Button("Open log folder"))
+ utils::openFolder(Path::resolvePathRelativeToExe("../logs").string());
+
+ ImGui::EndPopup();
+ }
+
+ void ConsoleWindow::executeCommand(const char *commandLine)
+ {
+ m_commands.emplace_back(commandLine);
+ addLog("{}", commandLine);
+ }
+
+ void ConsoleWindow::calcLogPadding()
+ {
+ m_logPadding = 0.0f;
+
+ for (const auto &selectedLevel : m_selectedVerbosityLevels) {
+ const std::string tag = verbosityToString(selectedLevel);
+ const ImVec2 textSize = ImGui::CalcTextSize(tag.c_str());
+ if (textSize.x > m_logPadding)
+ m_logPadding = textSize.x;
+ }
+ m_logPadding += ImGui::GetStyle().ItemSpacing.x;
+ }
+
+ void ConsoleWindow::displayLog(loguru::Verbosity verbosity, const std::string &msg) const
+ {
+ ImVec4 color = getVerbosityColor(verbosity);
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+
+ const std::string tag = verbosityToString(verbosity);
+ ImGui::TextUnformatted(tag.c_str());
+ ImGui::PopStyleColor();
+
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(m_logPadding);
+
+ ImGui::PushTextWrapPos(ImGui::GetContentRegionAvail().x);
+ ImGui::TextWrapped("%s", msg.c_str());
+ ImGui::PopTextWrapPos();
+ }
+
+ void ConsoleWindow::show()
+ {
+ ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
+ ImGui::Begin(ICON_FA_FILE_TEXT " Console" NEXO_WND_USTRID_CONSOLE, &m_opened, ImGuiWindowFlags_NoCollapse);
+ beginRender(NEXO_WND_USTRID_CONSOLE);
+
+ const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
+ ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footerHeight), false, ImGuiWindowFlags_HorizontalScrollbar);
+
+ if (m_logPadding == 0.0f)
+ calcLogPadding();
+
+ auto id = 0;
+ for (const auto &[verbosity, message, prefix]: m_logs)
+ {
+ if (!m_selectedVerbosityLevels.contains(verbosity))
+ continue;
+
+ ImGui::PushID(id++);
+ displayLog(verbosity, message);
+ ImGui::PopID();
+ }
+
+ if (m_scrollToBottom)
+ ImGui::SetScrollHereY(1.0f);
+ m_scrollToBottom = false;
+
+ ImGui::EndChild();
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f);
+
+ if (ImGui::InputText("Input", m_inputBuf, IM_ARRAYSIZE(m_inputBuf), ImGuiInputTextFlags_EnterReturnsTrue))
+ {
+ executeCommand(m_inputBuf);
+ std::memset(m_inputBuf, '\0', sizeof(m_inputBuf));
+ }
+
+ ImGui::SameLine();
+ if (ImNexo::Button("..."))
+ ImGui::OpenPopup("VerbositySettings");
+
+ if (ImGui::BeginPopup("VerbositySettings"))
+ showVerbositySettingsPopup();
+
+ ImGui::End();
+ }
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp
new file mode 100644
index 000000000..a2bb7ec7f
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Shutdown.cpp
@@ -0,0 +1,24 @@
+//// Shutdown.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the shutdown of the console window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+
+namespace nexo::editor {
+
+ void ConsoleWindow::shutdown()
+ {
+ clearLog();
+ }
+
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Update.cpp b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp
new file mode 100644
index 000000000..25c0711db
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Update.cpp
@@ -0,0 +1,22 @@
+//// Update.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the update of the console window
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+
+namespace nexo::editor {
+ void ConsoleWindow::update()
+ {
+ //No need to update anything
+ }
+}
diff --git a/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp
new file mode 100644
index 000000000..9a517ce9f
--- /dev/null
+++ b/editor/src/DocumentWindows/ConsoleWindow/Utils.cpp
@@ -0,0 +1,117 @@
+//// Utils.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the utils methods
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "ConsoleWindow.hpp"
+#include
+
+namespace nexo::editor {
+ /**
+ * @brief Converts a loguru verbosity level to its corresponding string label.
+ *
+ * This function maps a given loguru verbosity level to a predefined string representation,
+ * such as "[FATAL]", "[ERROR]", "[WARNING]", "[INFO]", "[INVALID]", "[DEBUG]", or "[DEV]".
+ * If the provided level does not match any known values, it returns "[UNKNOWN]".
+ *
+ * @param level The loguru verbosity level to convert.
+ * @return std::string The string label corresponding to the provided verbosity level.
+ */
+ std::string verbosityToString(const loguru::Verbosity level)
+ {
+ switch (level)
+ {
+ case loguru::Verbosity_FATAL: return "[FATAL]";
+ case loguru::Verbosity_ERROR: return "[ERROR]";
+ case loguru::Verbosity_WARNING: return "[WARNING]";
+ case loguru::Verbosity_INFO: return "[INFO]";
+ case loguru::Verbosity_INVALID: return "[INVALID]";
+ case loguru::Verbosity_1: return "[USER]";
+ case loguru::Verbosity_2: return "[DEBUG]";
+ case loguru::Verbosity_3: return "[DEV]";
+ default: return "[UNKNOWN]";
+ }
+ }
+
+ /**
+ * @brief Converts a custom LogLevel to its corresponding loguru::Verbosity level.
+ *
+ * Maps each supported LogLevel to a specific loguru verbosity constant. If the provided
+ * level does not match any known value, the function returns loguru::Verbosity_INVALID.
+ *
+ * @param level The custom logging level to convert.
+ * @return The equivalent loguru verbosity level.
+ */
+ loguru::Verbosity nexoLevelToLoguruLevel(const LogLevel level)
+ {
+ switch (level)
+ {
+ case LogLevel::FATAL: return loguru::Verbosity_FATAL;
+ case LogLevel::ERR: return loguru::Verbosity_ERROR;
+ case LogLevel::WARN: return loguru::Verbosity_WARNING;
+ case LogLevel::INFO: return loguru::Verbosity_INFO;
+ case LogLevel::USER: return loguru::Verbosity_1;
+ case LogLevel::DEBUG: return loguru::Verbosity_2;
+ case LogLevel::DEV: return loguru::Verbosity_3;
+ default: return loguru::Verbosity_INVALID;
+ }
+ }
+
+ /**
+ * @brief Returns the color corresponding to a log verbosity level.
+ *
+ * Maps the given loguru::Verbosity level to a specific ImVec4 color used for rendering log messages in the console.
+ * - Fatal and error messages are shown in red.
+ * - Warnings use yellow.
+ * - Informational messages appear in blue.
+ * - Debug levels display distinct pink and purple hues.
+ * The default color is white for any unrecognized verbosity levels.
+ *
+ * @param level The verbosity level for which the corresponding color is computed.
+ * @return ImVec4 The color associated with the specified verbosity level.
+ */
+ const ImVec4 getVerbosityColor(const loguru::Verbosity level)
+ {
+ ImVec4 color;
+
+ switch (level)
+ {
+ case loguru::Verbosity_FATAL: // Red
+ case loguru::Verbosity_ERROR: color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
+ break; // Red
+ case loguru::Verbosity_WARNING: color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f);
+ break; // Yellow
+ case loguru::Verbosity_INFO: color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f);
+ break; // Blue
+ case loguru::Verbosity_1: color = ImVec4(0.09f, 0.67f, 0.14f, 1.0f); // User
+ break; // Green
+ case loguru::Verbosity_2: color = ImVec4(0.898f, 0.0f, 1.0f, 1.0f); // Debug
+ break; // Pink
+ case loguru::Verbosity_3: color = ImVec4(0.388f, 0.055f, 0.851f, 1.0f); // Dev
+ break; // Purple
+ default: color = ImVec4(1, 1, 1, 1); // White
+ }
+ return color;
+ }
+
+ std::string generateLogFilePath()
+ {
+ using namespace std::chrono;
+
+ // Truncate to seconds precision
+ auto now = floor(system_clock::now());
+ zoned_time local_zoned{ current_zone(), now };
+
+ std::string ts = std::format("{:%Y%m%d_%H%M%S}", local_zoned);
+ return std::format("../logs/NEXO-{}.log", ts);
+ }
+}
diff --git a/editor/src/DocumentWindows/EditorScene.cpp b/editor/src/DocumentWindows/EditorScene.cpp
deleted file mode 100644
index 143b29ab8..000000000
--- a/editor/src/DocumentWindows/EditorScene.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-//// EditorScene.cpp //////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Guillaume HEIN
-// Date: 10/11/2024
-// Description: Source for the editor scene document window
-//
-///////////////////////////////////////////////////////////////////////////////
-
-#include "EditorScene.hpp"
-
-#include
-
-#include "EntityFactory3D.hpp"
-#include "LightFactory.hpp"
-#include "CameraFactory.hpp"
-#include "Nexo.hpp"
-#include "WindowRegistry.hpp"
-#include "components/Camera.hpp"
-#include "components/Uuid.hpp"
-#include "math/Matrix.hpp"
-#include "context/Selector.hpp"
-#include "utils/String.hpp"
-
-#include
-#include
-#include
-#include
-
-namespace nexo::editor {
-
- void EditorScene::setup()
- {
- setupImguizmo();
- setupWindow();
- setupScene();
- }
-
- void EditorScene::setupScene()
- {
- auto &app = getApp();
-
- // New handling
- m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName));
- renderer::FramebufferSpecs framebufferSpecs;
- framebufferSpecs.attachments = {
- renderer::FrameBufferTextureFormats::RGBA8, renderer::FrameBufferTextureFormats::RED_INTEGER, renderer::FrameBufferTextureFormats::Depth
- };
- framebufferSpecs.width = static_cast(m_viewSize.x);
- framebufferSpecs.height = static_cast(m_viewSize.y);
- const auto renderTarget = renderer::Framebuffer::create(framebufferSpecs);
- m_activeCamera = CameraFactory::createPerspectiveCamera({0.0f, 0.0f, 0.0f}, static_cast(m_viewSize.x), static_cast(m_viewSize.y), renderTarget);
- m_cameras.insert(static_cast(m_activeCamera));
- app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_activeCamera));
- components::PerspectiveCameraController controller;
- Application::m_coordinator->addComponent(static_cast(m_activeCamera), controller);
-
- m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid();
- if (m_defaultScene)
- loadDefaultEntities();
- }
-
- void EditorScene::setupImguizmo() const
- {
- ImGuizmo::SetOrthographic(true);
- }
-
- void EditorScene::loadDefaultEntities() const
- {
- auto &app = getApp();
- scene::Scene &scene = app.getSceneManager().getScene(m_sceneId);
- const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f});
- scene.addEntity(ambientLight);
- const ecs::Entity pointLight = LightFactory::createPointLight({1.2f, 5.0f, 0.1f});
- scene.addEntity(pointLight);
- const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f});
- scene.addEntity(directionalLight);
- const ecs::Entity spotLight = LightFactory::createSpotLight({0.0f, 0.5f, -2.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f});
- scene.addEntity(spotLight);
- const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, -5.0f, -5.0f}, {20.0f, 1.0f, 20.0f},
- {0.0f, 0.0f, 0.0f}, {1.0f, 0.5f, 0.31f, 1.0f});
- app.getSceneManager().getScene(m_sceneId).addEntity(basicCube);
- }
-
- void EditorScene::setupWindow()
- {
- constexpr auto size = ImVec2(1280, 720);
- m_viewSize = size;
- }
-
- void EditorScene::shutdown()
- {
- // Should probably check if it is necessary to delete the scene here ? (const for now)
- }
-
- void EditorScene::handleKeyEvents() const
- {
- // Will be implemeneted later
- }
-
- void EditorScene::deleteCamera(const ecs::Entity cameraId)
- {
- if (cameraId == m_activeCamera)
- m_activeCamera = -1;
- m_cameras.erase(cameraId);
- if (!m_cameras.empty())
- m_activeCamera = *m_cameras.begin();
- }
-
- void EditorScene::renderToolbar() const
- {
- // Empty for now, will add it later
- }
-
- void EditorScene::renderGizmo() const
- {
- const auto &coord = nexo::Application::m_coordinator;
- auto const &selector = Selector::get();
- if (selector.getSelectionType() != SelectionType::ENTITY ||
- selector.getSelectedScene() != m_sceneId)
- return;
- const ecs::Entity entity = selector.getSelectedEntity();
- const auto &transformCameraComponent = coord->getComponent(m_activeCamera);
- const auto &cameraComponent = coord->getComponent(m_activeCamera);
- ImGuizmo::SetOrthographic(cameraComponent.type == components::CameraType::ORTHOGRAPHIC);
- ImGuizmo::SetDrawlist();
- ImGuizmo::SetID(static_cast(entity));
- ImGuizmo::SetRect(m_viewPosition.x, m_viewPosition.y, m_viewSize.x, m_viewSize.y);
- glm::mat4 viewMatrix = cameraComponent.getViewMatrix(transformCameraComponent);
- glm::mat4 projectionMatrix = cameraComponent.getProjectionMatrix();
- const auto transf = coord->tryGetComponent(entity);
- if (!transf)
- return;
- const glm::mat4 rotationMat = glm::toMat4(transf->get().quat);
- glm::mat4 transformMatrix = glm::translate(glm::mat4(1.0f), transf->get().pos) *
- rotationMat *
- glm::scale(glm::mat4(1.0f), {transf->get().size.x, transf->get().size.y, transf->get().size.z});
- ImGuizmo::Enable(true);
- ImGuizmo::Manipulate(glm::value_ptr(viewMatrix), glm::value_ptr(projectionMatrix),
- m_currentGizmoOperation,
- ImGuizmo::MODE::WORLD,
- glm::value_ptr(transformMatrix));
-
- glm::vec3 translation(0);
- glm::vec3 scale(0);
- glm::quat quaternion;
-
- math::decomposeTransformQuat(transformMatrix, translation, quaternion, scale);
-
- if (ImGuizmo::IsUsing())
- {
- transf->get().pos = translation;
- transf->get().quat = quaternion;
- transf->get().size = scale;
- }
- }
-
- void EditorScene::renderView()
- {
- const auto viewPortOffset = ImGui::GetCursorPos();
- auto &cameraComponent = Application::m_coordinator->getComponent(m_activeCamera);
-
- // Resize handling
- if (const ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail();
- m_viewSize.x != viewportPanelSize.x || m_viewSize.y != viewportPanelSize.y)
- {
- cameraComponent.resize(static_cast(viewportPanelSize.x),
- static_cast(viewportPanelSize.y));
-
- m_viewSize.x = viewportPanelSize.x;
- m_viewSize.y = viewportPanelSize.y;
- }
-
- // Render framebuffer
- const unsigned int textureId = cameraComponent.m_renderTarget->getColorAttachmentId(0);
- ImGui::Image(static_cast(static_cast(textureId)), m_viewSize, ImVec2(0, 1), ImVec2(1, 0));
-
- const auto windowSize = ImGui::GetWindowSize();
- auto minBounds = ImGui::GetWindowPos();
-
- minBounds.x += viewPortOffset.x;
- minBounds.y += viewPortOffset.y;
-
- const ImVec2 maxBounds = {minBounds.x + windowSize.x, minBounds.y + windowSize.y};
- m_viewportBounds[0] = minBounds;
- m_viewportBounds[1] = maxBounds;
- }
-
- void EditorScene::show()
- {
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
- ImGui::SetNextWindowSizeConstraints(ImVec2(480, 270), ImVec2(1920, 1080));
- auto &selector = Selector::get();
-
- if (ImGui::Begin(m_windowName.c_str(), &m_opened, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse))
- {
- firstDockSetup(m_windowName);
- auto &app = getApp();
- m_viewPosition = ImGui::GetCursorScreenPos();
-
- m_focused = ImGui::IsWindowFocused();
- m_hovered = ImGui::IsWindowHovered();
- app.getSceneManager().getScene(m_sceneId).setActiveStatus(m_focused);
- if (m_focused && selector.getSelectedScene() != m_sceneId)
- {
- selector.setSelectedScene(m_sceneId);
- selector.unselectEntity();
- }
-
- if (m_activeCamera == -1)
- {
- // No active camera, render the text at the center of the screen
- ImVec2 textSize = ImGui::CalcTextSize("No active camera");
- auto textPos = ImVec2((m_viewSize.x - textSize.x) / 2, (m_viewSize.y - textSize.y) / 2);
-
- ImGui::SetCursorScreenPos(textPos);
- ImGui::Text("No active camera");
- }
- else
- {
- renderView();
- renderGizmo();
- }
- }
- ImGui::End();
- ImGui::PopStyleVar();
- }
-
- void EditorScene::update()
- {
- auto &selector = Selector::get();
- //m_windowName = selector.getUiHandle(m_sceneUuid, m_windowName);
- if (!m_opened || m_activeCamera == -1)
- return;
- if (m_focused && m_hovered)
- handleKeyEvents();
-
- auto const &cameraComponent = Application::m_coordinator->getComponent(static_cast(m_activeCamera));
- runEngine(m_sceneId, RenderingType::FRAMEBUFFER);
- if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsUsing() && m_focused)
- {
- auto [mx, my] = ImGui::GetMousePos();
- mx -= m_viewportBounds[0].x;
- my -= m_viewportBounds[0].y;
-
- // Flip the y-coordinate to match opengl texture format (maybe make it modular in some way)
- my = m_viewSize.y - my;
-
- // Mouse is not inside the viewport
- if (!(mx >= 0 && my >= 0 && mx < m_viewSize.x && my < m_viewSize.y))
- return;
-
- cameraComponent.m_renderTarget->bind();
- int data = cameraComponent.m_renderTarget->getPixel(1, static_cast(mx), static_cast(my));
- cameraComponent.m_renderTarget->unbind();
- if (data == -1)
- {
- selector.unselectEntity();
- return;
- }
- const auto uuid = Application::m_coordinator->tryGetComponent(data);
- if (uuid)
- {
- selector.setSelectedEntity(uuid->get().uuid, data);
- selector.setSelectionType(SelectionType::ENTITY);
- }
- selector.setSelectedScene(m_sceneId);
- }
- }
-
-}
diff --git a/editor/src/DocumentWindows/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene.hpp
deleted file mode 100644
index 5833dd668..000000000
--- a/editor/src/DocumentWindows/EditorScene.hpp
+++ /dev/null
@@ -1,130 +0,0 @@
-//// MainScene.hpp ////////////////////////////////////////////////////////////
-//
-// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
-// zzzzzzz zzz zzzz zzzz zzzz zzzz
-// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
-// zzz zzz zzz z zzzz zzzz zzzz zzzz
-// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
-//
-// Author: Mehdy MORVAN
-// Date: 10/11/2024
-// Description: Header file for the main document window
-//
-///////////////////////////////////////////////////////////////////////////////
-#pragma once
-
-#include "ADocumentWindow.hpp"
-#include "IDocumentWindow.hpp"
-#include "core/scene/SceneManager.hpp"
-#include
-#include
-
-namespace nexo::editor {
- class EditorScene : public ADocumentWindow {
- public:
- using ADocumentWindow::ADocumentWindow;
-
- /**
- * @brief Initializes the main scene.
- *
- * Configures essential components of the main scene by sequentially:
- * - Setting up ImGuizmo parameters,
- * - Initializing the window settings, and
- * - Creating and configuring the scene.
- */
- void setup() override;
- void shutdown() override;
-
- /**
- * @brief Displays the main scene window and updates the active scene selection.
- *
- * This method creates an ImGui window with specific size constraints and zero padding,
- * then determines the window's focus status to update the scene's active state. When focused,
- * it sets the current scene as selected in the scene view manager and clears any entity selection.
- * Finally, it renders both the scene view and transformation gizmos within the window.
- */
- void show() override;
-
- /**
- * @brief Updates the scene by processing input events and rendering the current frame.
- *
- * This function handles key events and updates the scene by executing the rendering engine in framebuffer mode.
- * It processes left mouse clicks when the scene view is focused and ImGuizmo is not active. The mouse position is
- * adjusted relative to the viewport and its y-coordinate is flipped to match OpenGL's texture format. If the click
- * falls within valid bounds and corresponds to a valid entity (pixel value not equal to -1), the entity is selected
- * and the SceneViewManager is notified of the active scene; otherwise, any existing selection is cleared.
- *
- * The update is skipped entirely if the scene window is not open.
- */
- void update() override;
-
- /**
- * @brief Retrieves the unique identifier of the scene.
- *
- * @return scene::SceneId The identifier of this scene.
- */
- [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;};
-
-
- /**
- * @brief Removes a camera from the scene and updates the active camera.
- *
- * Removes the specified camera entity from the collection. If the removed camera was the active one,
- * it resets the active camera to an invalid state (-1) and, if any cameras remain, sets the active camera
- * to the first available camera in the collection.
- *
- * @param cameraId The identifier of the camera entity to delete.
- */
- void deleteCamera(ecs::Entity cameraId);
-
- void setDefault() { m_defaultScene = true; };
-
- private:
- bool m_defaultScene = false;
- ImVec2 m_viewSize = {0, 0};
- ImVec2 m_viewPosition = {0, 0};
- ImVec2 m_viewportBounds[2];
- ImGuizmo::OPERATION m_currentGizmoOperation = ImGuizmo::UNIVERSAL;
- ImGuizmo::MODE m_currentGizmoMode = ImGuizmo::WORLD;
-
- int m_sceneId = -1;
- std::string m_sceneUuid;
- std::set m_cameras;
- int m_activeCamera = -1;
-
- /**
- * @brief Sets the main scene window's view size.
- *
- * Configures the view to a default size of 1280x720 pixels.
- */
- void setupWindow();
- void setupImguizmo() const;
- void setupScene();
- void loadDefaultEntities() const;
-
- void handleKeyEvents() const;
-
- /**
- * @brief Renders the toolbar overlay within the main scene view.
- *
- * This method uses ImGui to display a toolbar that includes buttons for switching between orthographic and perspective camera modes,
- * a popup placeholder for adding primitive entities, and a draggable input for adjusting the target frames per second (FPS).
- * The toolbar is positioned relative to the current view to align with the scene layout.
- */
- void renderToolbar() const;
-
- /**
- * @brief Renders the transformation gizmo for the selected entity.
- *
- * This method displays an interactive ImGuizmo tool to manipulate the translation, rotation, and scale
- * of the currently selected entity. It first verifies that the selection is an entity and that the active
- * scene corresponds to the one managed by this instance. The method then retrieves the view and projection
- * matrices from the active camera, configures ImGuizmo to match the view's dimensions, and constructs the
- * entity's transformation matrix from its current translation, rotation, and scale.
- *
- * If the gizmo is actively manipulated, the entity's transform component is updated with the new values.
- */
- void renderGizmo() const;
- void renderView();
- };
-}
diff --git a/editor/src/DocumentWindows/EditorScene/EditorScene.hpp b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp
new file mode 100644
index 000000000..cf2a4390e
--- /dev/null
+++ b/editor/src/DocumentWindows/EditorScene/EditorScene.hpp
@@ -0,0 +1,310 @@
+//// MainScene.hpp ////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 10/11/2024
+// Description: Header file for the main document window
+//
+///////////////////////////////////////////////////////////////////////////////
+#pragma once
+
+#include "ADocumentWindow.hpp"
+#include "inputs/WindowState.hpp"
+#include "core/scene/SceneManager.hpp"
+#include "../PopupManager.hpp"
+#include
+#include
+#include "ImNexo/Widgets.hpp"
+
+namespace nexo::editor {
+
+ class EditorScene final : public ADocumentWindow {
+ public:
+ using ADocumentWindow::ADocumentWindow;
+
+ /**
+ * @brief Initializes the main scene.
+ *
+ * Configures essential components of the main scene by sequentially:
+ * - Setting up ImGuizmo parameters,
+ * - Initializing the window settings, and
+ * - Creating and configuring the scene.
+ */
+ void setup() override;
+
+ // No-op method in this class
+ void shutdown() override;
+
+ /**
+ * @brief Displays the main scene window and updates the active scene selection.
+ *
+ * This method creates an ImGui window with specific size constraints and zero padding,
+ * then determines the window's focus status to update the scene's active state. When focused,
+ * it sets the current scene as selected in the scene view manager and clears any entity selection.
+ * Finally, it renders both the scene view and transformation gizmos within the window.
+ */
+ void show() override;
+
+ /**
+ * @brief Updates the scene by processing input events and rendering the current frame.
+ *
+ * This function handles key events and updates the scene by executing the rendering engine in framebuffer mode.
+ * It processes left mouse clicks when the scene view is focused and ImGuizmo is not active. The mouse position is
+ * adjusted relative to the viewport and its y-coordinate is flipped to match OpenGL's texture format. If the click
+ * falls within valid bounds and corresponds to a valid entity (pixel value not equal to -1), the entity is selected
+ * and the SceneViewManager is notified of the active scene; otherwise, any existing selection is cleared.
+ *
+ * The update is skipped entirely if the scene window is not open.
+ */
+ void update() override;
+
+ /**
+ * @brief Retrieves the unique identifier of the scene.
+ *
+ * @return scene::SceneId The identifier of this scene.
+ */
+ [[nodiscard]] scene::SceneId getSceneId() const {return m_sceneId;};
+
+ /**
+ * @brief Sets the active camera for this scene.
+ *
+ * Deactivates the current camera and switches to the specified camera entity.
+ * The previously active camera will have its render and active flags set to false.
+ *
+ * @param cameraId Entity ID of the camera to set as active.
+ */
+ void setCamera(ecs::Entity cameraId);
+
+ /**
+ * @brief Marks this scene as the default scene.
+ *
+ * When a scene is set as the default, it will be populated with
+ * default entities (lights, basic geometry) during setup.
+ */
+ void setDefault() { m_defaultScene = true; };
+
+ private:
+ bool m_defaultScene = false;
+ ImVec2 m_viewportBounds[2];
+ ImGuizmo::OPERATION m_currentGizmoOperation = ImGuizmo::UNIVERSAL;
+ ImGuizmo::MODE m_currentGizmoMode = ImGuizmo::WORLD;
+ bool m_snapTranslateOn = false;
+ glm::vec3 m_snapTranslate = {10.0f, 10.0f, 10.0f};
+ bool m_snapRotateOn = false;
+ float m_angleSnap = 90.0f;
+ bool m_snapToGrid = false;
+ bool m_wireframeEnabled = false;
+
+ int m_sceneId = -1;
+ std::string m_sceneUuid;
+ int m_activeCamera = -1;
+ int m_editorCamera = -1;
+
+ PopupManager m_popupManager;
+
+ const std::vector m_buttonGradient = {
+ {0.0f, IM_COL32(50, 50, 70, 230)},
+ {1.0f, IM_COL32(30, 30, 45, 230)}
+ };
+
+ // Selected button gradient - lighter blue gradient
+ const std::vector m_selectedGradient = {
+ {0.0f, IM_COL32(70, 70, 120, 230)},
+ {1.0f, IM_COL32(50, 50, 100, 230)}
+ };
+
+ /**
+ * @brief Sets the main scene window's view size.
+ *
+ * Configures the view to a default size of 1280x720 pixels.
+ */
+ void setupWindow();
+
+ /**
+ * @brief Creates and initializes a scene with basic components.
+ *
+ * Sets up the scene with a framebuffer, editor camera, and loads default
+ * entities if this is the default scene.
+ */
+ void setupScene();
+
+ void hideAllButSelectionCallback() const;
+ void selectAllCallback();
+ void unhideAllCallback() const;
+ void deleteCallback();
+
+ void setupGlobalState();
+ void setupGizmoState();
+ void setupGizmoTranslateState();
+ void setupGizmoRotateState();
+ void setupGizmoScaleState();
+ void setupShortcuts();
+
+ /**
+ * @brief Populates the scene with default entities.
+ *
+ * Creates standard light sources (ambient, directional, point, spot)
+ * and a simple ground plane in the scene.
+ */
+ void loadDefaultEntities() const;
+
+ /**
+ * @brief Renders the toolbar overlay within the main scene view.
+ *
+ * This method uses ImGui to display a toolbar that includes buttons for switching between orthographic and perspective camera modes,
+ * a popup placeholder for adding primitive entities, and a draggable input for adjusting the target frames per second (FPS).
+ * The toolbar is positioned relative to the current view to align with the scene layout.
+ */
+ void renderToolbar();
+
+ /**
+ * @brief Sets up the initial layout and style for the toolbar.
+ *
+ * Creates a child window with specific size and style settings for the toolbar,
+ * positions it at the top of the viewport, and configures spacing.
+ *
+ * @param buttonWidth Standard width for toolbar buttons
+ */
+ void initialToolbarSetup(float buttonWidth) const;
+
+ /**
+ * @brief Renders the editor camera button in the toolbar.
+ *
+ * Shows either a camera settings button (when editor camera is active) or a
+ * "switch back to editor camera" button (when a different camera is active).
+ */
+ void renderEditorCameraToolbarButton();
+
+ /**
+ * @brief Renders the button to toggle between world/local coordinate modes.
+ *
+ * Updates the button props based on the current mode and renders the appropriate
+ * button with correct styling.
+ *
+ * @param showGizmoModeMenu Flag indicating if the mode dropdown menu is visible
+ * @param activeGizmoMode Reference to store the active mode button properties
+ * @param inactiveGizmoMode Reference to store the inactive mode button properties
+ * @return true if the button was clicked
+ */
+ bool renderGizmoModeToolbarButton(
+ bool showGizmoModeMenu,
+ ImNexo::ButtonProps &activeGizmoMode,
+ ImNexo::ButtonProps &inactiveGizmoMode
+ );
+
+ /**
+ * @brief Renders the primitive creation dropdown menu.
+ *
+ * Creates a dropdown with buttons for adding primitive shapes like cubes,
+ * spheres, etc. to the scene.
+ *
+ * @param primitiveButtonPos Position of the parent button that opened this menu
+ * @param buttonSize Size of buttons in the dropdown
+ * @param showPrimitiveMenu Reference to the flag controlling menu visibility
+ */
+ void renderPrimitiveSubMenu(const ImVec2 &primitiveButtonPos, const ImVec2 &buttonSize, bool &showPrimitiveMenu) const;
+
+ /**
+ * @brief Renders the snap settings dropdown menu.
+ *
+ * Creates a dropdown with toggles for different snapping modes (translate, rotate)
+ * and their settings.
+ *
+ * @param snapButtonPos Position of the parent button that opened this menu
+ * @param buttonSize Size of buttons in the dropdown
+ * @param showSnapMenu Reference to the flag controlling menu visibility
+ */
+ void renderSnapSubMenu(const ImVec2 &snapButtonPos, const ImVec2 &buttonSize, bool &showSnapMenu);
+
+ /**
+ * @brief Handles the snap settings popup dialog.
+ *
+ * Creates a modal popup allowing users to configure fine-grained snap settings
+ * like translate values and rotation angles.
+ */
+ void snapSettingsPopup();
+
+ void gridSettingsPopup();
+
+ /**
+ * @brief Renders a standard toolbar button with optional tooltip and styling.
+ *
+ * Creates a gradient button with the specified icon and shows a tooltip on hover.
+ *
+ * @param uniqueId Unique identifier for the ImGui control
+ * @param icon Font icon to display on the button
+ * @param tooltip Text to show when hovering over the button
+ * @param gradientStop Color gradient for the button background
+ * @return true if the button was clicked
+ */
+ static bool renderToolbarButton(
+ const std::string &uniqueId,
+ const std::string &icon,
+ const std::string &tooltip,
+ const std::vector & gradientStop,
+ bool *rightClicked = nullptr
+ );
+
+ ImGuizmo::OPERATION getLastGuizmoOperation();
+
+ /**
+ * @brief Renders the transformation gizmo for the selected entity.
+ *
+ * This method displays an interactive ImGuizmo tool to manipulate the translation, rotation, and scale
+ * of the currently selected entity. It first verifies that the selection is an entity and that the active
+ * scene corresponds to the one managed by this instance. The method then retrieves the view and projection
+ * matrices from the active camera, configures ImGuizmo to match the view's dimensions, and constructs the
+ * entity's transformation matrix from its current translation, rotation, and scale.
+ *
+ * If the gizmo is actively manipulated, the entity's transform component is updated with the new values.
+ */
+ void renderGizmo();
+ void setupGizmoContext(const components::CameraComponent& camera) const;
+ float* getSnapSettingsForOperation(ImGuizmo::OPERATION operation);
+ static void captureInitialTransformStates(const std::vector& entities);
+ void applyTransformToEntities(
+ ecs::Entity sourceEntity,
+ const components::TransformComponent& sourceTransform,
+ const components::TransformComponent& newTransform,
+ const std::vector& targetEntities) const;
+ static void createTransformUndoActions(const std::vector& entities);
+ static bool s_wasUsingGizmo;
+ static ImGuizmo::OPERATION s_lastOperation;
+ static std::unordered_map s_initialTransformStates;
+
+ /**
+ * @brief Renders the main viewport showing the 3D scene.
+ *
+ * Handles resizing of the viewport, draws the framebuffer texture containing the
+ * rendered scene, and updates viewport bounds for input handling.
+ */
+ void renderView();
+ void renderNoActiveCamera() const;
+ void renderNewEntityPopup();
+
+ void handleSelection();
+ int sampleEntityTexture(float mx, float my) const;
+ void updateSelection(int entityId, bool isShiftPressed, bool isCtrlPressed);
+ void updateWindowState();
+
+ enum class EditorState {
+ GLOBAL,
+ GIZMO,
+ GIZMO_TRANSLATE,
+ GIZMO_ROTATE,
+ GIZMO_SCALE,
+ NB_STATE
+ };
+
+ WindowState m_globalState;
+ WindowState m_gizmoState;
+ WindowState m_gizmoTranslateState;
+ WindowState m_gizmoRotateState;
+ WindowState m_gizmoScaleState;
+ };
+}
diff --git a/editor/src/DocumentWindows/EditorScene/Gizmo.cpp b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp
new file mode 100644
index 000000000..19a2ae304
--- /dev/null
+++ b/editor/src/DocumentWindows/EditorScene/Gizmo.cpp
@@ -0,0 +1,292 @@
+//// Gizmo.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the gizmo rendering
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "EditorScene.hpp"
+#include "context/Selector.hpp"
+#include "context/ActionManager.hpp"
+#include "math/Matrix.hpp"
+
+#include
+
+namespace nexo::editor {
+ // Class-level variables for tracking gizmo state between frames
+ bool EditorScene::s_wasUsingGizmo = false;
+ ImGuizmo::OPERATION EditorScene::s_lastOperation = ImGuizmo::OPERATION::UNIVERSAL;
+ std::unordered_map EditorScene::s_initialTransformStates;
+
+ static ImGuizmo::OPERATION getActiveGuizmoOperation()
+ {
+ for (int bitPos = 0; bitPos <= 13; bitPos++)
+ {
+ auto op = static_cast(1u << bitPos);
+ if (ImGuizmo::IsOver(op))
+ return op;
+ }
+ return ImGuizmo::OPERATION::UNIVERSAL;
+ }
+
+ static std::optional findEntityWithTransform(const std::vector& entities)
+ {
+ const auto& coord = nexo::Application::m_coordinator;
+
+ for (const auto& entity : entities) {
+ if (coord->tryGetComponent(entity)) {
+ return entity;
+ }
+ }
+
+ return std::nullopt;
+ }
+
+ void EditorScene::setupGizmoContext(const components::CameraComponent& camera) const
+ {
+ ImGuizmo::SetOrthographic(camera.type == components::CameraType::ORTHOGRAPHIC);
+ ImGuizmo::SetDrawlist();
+ ImGuizmo::SetRect(m_viewportBounds[0].x, m_viewportBounds[0].y, m_contentSize.x, m_contentSize.y);
+ ImGuizmo::Enable(true);
+ }
+
+ static glm::mat4 buildEntityTransformMatrix(const components::TransformComponent& transform)
+ {
+ const glm::mat4 rotationMat = glm::toMat4(transform.quat);
+ return glm::translate(glm::mat4(1.0f), transform.pos) *
+ rotationMat *
+ glm::scale(glm::mat4(1.0f), transform.size);
+ }
+
+ float* EditorScene::getSnapSettingsForOperation(const ImGuizmo::OPERATION operation)
+ {
+ if (m_snapTranslateOn && operation & ImGuizmo::OPERATION::TRANSLATE)
+ return &m_snapTranslate.x;
+ if (m_snapRotateOn && operation & ImGuizmo::OPERATION::ROTATE) {
+ return &m_angleSnap;
+ }
+ return nullptr;
+ }
+
+ void EditorScene::captureInitialTransformStates(const std::vector& entities)
+ {
+ const auto& coord = nexo::Application::m_coordinator;
+ s_initialTransformStates.clear();
+
+ for (const auto& entity : entities) {
+ auto transform = coord->tryGetComponent(entity);
+ if (transform) {
+ s_initialTransformStates[entity] = transform->get().save();
+ }
+ }
+ }
+
+ void EditorScene::applyTransformToEntities(
+ const ecs::Entity sourceEntity,
+ const components::TransformComponent& sourceTransform,
+ const components::TransformComponent& newTransform,
+ const std::vector& targetEntities) const
+ {
+ const auto& coord = nexo::Application::m_coordinator;
+
+ // Calculate transformation deltas
+ const glm::vec3 positionDelta = newTransform.pos - sourceTransform.pos;
+ const glm::vec3 scaleFactor = newTransform.size / sourceTransform.size;
+ const glm::quat rotationDelta = newTransform.quat * glm::inverse(sourceTransform.quat);
+
+ // Apply transforms to all selected entities except the source
+ for (const auto& entity : targetEntities) {
+ if (entity == static_cast(sourceEntity)) continue;
+
+ auto entityTransform = coord->tryGetComponent(entity);
+ if (!entityTransform) continue;
+
+ // Apply relevant transformations based on current operation
+ if (m_currentGizmoOperation & ImGuizmo::OPERATION::TRANSLATE) {
+ entityTransform->get().pos += positionDelta;
+ }
+
+ if (m_currentGizmoOperation & ImGuizmo::OPERATION::ROTATE) {
+ entityTransform->get().quat = rotationDelta * entityTransform->get().quat;
+ }
+
+ if (m_currentGizmoOperation & ImGuizmo::OPERATION::SCALE) {
+ entityTransform->get().size.x *= scaleFactor.x;
+ entityTransform->get().size.y *= scaleFactor.y;
+ entityTransform->get().size.z *= scaleFactor.z;
+ }
+ }
+ }
+
+
+ static bool hasTransformChanged(const components::TransformComponent::Memento& before,
+ const components::TransformComponent::Memento& after)
+ {
+ return before.position != after.position ||
+ before.rotation != after.rotation ||
+ before.scale != after.scale;
+ }
+
+ void EditorScene::createTransformUndoActions(const std::vector& entities)
+ {
+ const auto& coord = nexo::Application::m_coordinator;
+ auto& actionManager = ActionManager::get();
+
+ // If multiple entities selected, create a group action
+ if (entities.size() > 1) {
+ auto groupAction = ActionManager::createActionGroup();
+ bool anyChanges = false;
+
+ for (const auto& entity : entities) {
+ auto transform = coord->tryGetComponent(entity);
+ if (!transform) continue;
+
+ auto it = s_initialTransformStates.find(entity);
+ if (it == s_initialTransformStates.end()) continue;
+
+ auto beforeState = it->second;
+ auto afterState = transform->get().save();
+
+ // Check if anything actually changed
+ if (hasTransformChanged(beforeState, afterState)) {
+ auto action = std::make_unique>(
+ entity, beforeState, afterState);
+ groupAction->addAction(std::move(action));
+ anyChanges = true;
+ }
+ }
+
+ if (anyChanges) {
+ actionManager.recordAction(std::move(groupAction));
+ }
+ }
+ // Single entity selected - simpler action
+ else if (entities.size() == 1) {
+ auto entity = entities[0];
+ auto transform = coord->tryGetComponent(entity);
+
+ if (s_initialTransformStates.contains(entity)) {
+ auto beforeState = s_initialTransformStates[entity];
+ auto afterState = transform->get().save();
+
+ if (hasTransformChanged(beforeState, afterState)) {
+ actionManager.recordComponentChange(
+ entity, beforeState, afterState);
+ }
+ }
+ }
+
+ // Reset stored states
+ s_initialTransformStates.clear();
+ }
+
+ void EditorScene::renderGizmo()
+ {
+ const auto& coord = nexo::Application::m_coordinator;
+ auto const& selector = Selector::get();
+
+ // Skip if no valid selection
+ if (selector.getPrimarySelectionType() == SelectionType::SCENE ||
+ selector.getSelectedScene() != m_sceneId ||
+ !selector.hasSelection()) {
+ return;
+ }
+
+ // Find entity with transform component
+ const auto& selectedEntities = selector.getSelectedEntities();
+ ecs::Entity primaryEntity = selector.getPrimaryEntity();
+
+ auto primaryTransform = coord->tryGetComponent(primaryEntity);
+ if (!primaryTransform) {
+ const auto entityWithTransform = findEntityWithTransform(selectedEntities);
+ if (!entityWithTransform) return; // No entity with transform found
+
+ primaryEntity = *entityWithTransform;
+ primaryTransform = coord->tryGetComponent(primaryEntity);
+ }
+
+ // Camera setup
+ const auto& cameraTransform = coord->getComponent(m_activeCamera);
+ auto& camera = coord->getComponent(m_activeCamera);
+
+ // Configure ImGuizmo
+ setupGizmoContext(camera);
+ ImGuizmo::SetID(static_cast(primaryEntity));
+
+ // Prepare matrices
+ glm::mat4 viewMatrix = camera.getViewMatrix(cameraTransform);
+ glm::mat4 projectionMatrix = camera.getProjectionMatrix();
+ glm::mat4 transformMatrix = buildEntityTransformMatrix(primaryTransform->get());
+
+ // Track which operation is active
+ if (!ImGuizmo::IsUsing()) {
+ s_lastOperation = getActiveGuizmoOperation();
+ }
+
+ // Get snap settings if applicable
+ const float* snap = getSnapSettingsForOperation(s_lastOperation);
+
+ // Capture initial state when starting to use gizmo
+ if (!s_wasUsingGizmo && ImGui::IsMouseDown(ImGuiMouseButton_Left) && ImGuizmo::IsOver()) {
+ captureInitialTransformStates(selectedEntities);
+ }
+
+ // Perform the actual manipulation
+ ImGuizmo::Manipulate(
+ glm::value_ptr(viewMatrix),
+ glm::value_ptr(projectionMatrix),
+ m_currentGizmoOperation,
+ m_currentGizmoMode,
+ glm::value_ptr(transformMatrix),
+ nullptr,
+ snap
+ );
+
+ // Update isUsingGizmo after manipulation
+ bool isUsingGizmo = ImGuizmo::IsUsing();
+
+ if (isUsingGizmo) {
+ // Disable camera movement during manipulation
+ camera.active = false;
+
+ // Extract the original transform values
+ const components::TransformComponent originalTransform = primaryTransform->get();
+
+ // Extract the new transform values from the matrix
+ glm::vec3 newPos;
+ glm::vec3 newScale;
+ glm::quat newRot;
+ math::decomposeTransformQuat(transformMatrix, newPos, newRot, newScale);
+
+ // Update the primary entity's transform
+ primaryTransform->get().pos = newPos;
+ primaryTransform->get().quat = newRot;
+ primaryTransform->get().size = newScale;
+
+ // Apply changes to other selected entities
+ applyTransformToEntities(
+ primaryEntity,
+ originalTransform,
+ primaryTransform->get(),
+ selectedEntities
+ );
+ }
+ else if (s_wasUsingGizmo) {
+ // Re-enable camera when done
+ camera.active = true;
+
+ // Create undo/redo actions
+ createTransformUndoActions(selectedEntities);
+ }
+
+ // Update state for next frame
+ s_wasUsingGizmo = isUsingGizmo;
+ }
+}
diff --git a/editor/src/DocumentWindows/EditorScene/Init.cpp b/editor/src/DocumentWindows/EditorScene/Init.cpp
new file mode 100644
index 000000000..2db883bec
--- /dev/null
+++ b/editor/src/DocumentWindows/EditorScene/Init.cpp
@@ -0,0 +1,90 @@
+//// Init.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the init functions of the editor scene
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "EditorScene.hpp"
+#include "CameraFactory.hpp"
+#include "LightFactory.hpp"
+#include "EntityFactory3D.hpp"
+#include "utils/EditorProps.hpp"
+
+namespace nexo::editor {
+
+ void EditorScene::setup()
+ {
+ setupWindow();
+ setupScene();
+ setupShortcuts();
+ }
+
+ void EditorScene::setupScene()
+ {
+ auto &app = getApp();
+
+ m_sceneId = static_cast(app.getSceneManager().createScene(m_windowName));
+ renderer::NxFramebufferSpecs framebufferSpecs;
+ framebufferSpecs.attachments = {
+ renderer::NxFrameBufferTextureFormats::RGBA8, renderer::NxFrameBufferTextureFormats::RED_INTEGER, renderer::NxFrameBufferTextureFormats::Depth
+ };
+ framebufferSpecs.width = static_cast(m_contentSize.x);
+ framebufferSpecs.height = static_cast(m_contentSize.y);
+ const auto renderTarget = renderer::NxFramebuffer::create(framebufferSpecs);
+ m_editorCamera = static_cast(CameraFactory::createPerspectiveCamera({0.0f, 3.0f, -2.0f}, static_cast(m_contentSize.x), static_cast(m_contentSize.y), renderTarget));
+ auto &cameraComponent = Application::m_coordinator->getComponent(m_editorCamera);
+ cameraComponent.render = true;
+ app.getSceneManager().getScene(m_sceneId).addEntity(static_cast(m_editorCamera));
+ const components::PerspectiveCameraController controller;
+ Application::m_coordinator->addComponent(static_cast(m_editorCamera), controller);
+ constexpr components::EditorCameraTag editorCameraTag;
+ Application::m_coordinator->addComponent(m_editorCamera, editorCameraTag);
+ m_activeCamera = m_editorCamera;
+
+ m_sceneUuid = app.getSceneManager().getScene(m_sceneId).getUuid();
+ if (m_defaultScene)
+ loadDefaultEntities();
+ }
+
+ void EditorScene::loadDefaultEntities() const
+ {
+ auto &app = getApp();
+ scene::Scene &scene = app.getSceneManager().getScene(m_sceneId);
+ const ecs::Entity ambientLight = LightFactory::createAmbientLight({0.5f, 0.5f, 0.5f});
+ scene.addEntity(ambientLight);
+ const ecs::Entity pointLight = LightFactory::createPointLight({2.0f, 5.0f, 0.0f});
+ utils::addPropsTo(pointLight, utils::PropsType::POINT_LIGHT);
+ scene.addEntity(pointLight);
+ const ecs::Entity directionalLight = LightFactory::createDirectionalLight({0.2f, -1.0f, -0.3f});
+ scene.addEntity(directionalLight);
+ const ecs::Entity spotLight = LightFactory::createSpotLight({-2.0f, 5.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f});
+ utils::addPropsTo(spotLight, utils::PropsType::SPOT_LIGHT);
+ scene.addEntity(spotLight);
+ const ecs::Entity basicCube = EntityFactory3D::createCube({0.0f, 0.25f, 0.0f}, {20.0f, 0.5f, 20.0f},
+ {0.0f, 0.0f, 0.0f}, {0.05f * 1.7, 0.09f * 1.35, 0.13f * 1.45, 1.0f});
+ app.getSceneManager().getScene(m_sceneId).addEntity(basicCube);
+ }
+
+ void EditorScene::setupWindow()
+ {
+ m_contentSize = ImVec2(1280, 720);
+ }
+
+ void EditorScene::setCamera(const ecs::Entity cameraId)
+ {
+ auto &oldCameraComponent = Application::m_coordinator->getComponent(m_activeCamera);
+ oldCameraComponent.active = false;
+ oldCameraComponent.render = false;
+ m_activeCamera = static_cast(cameraId);
+ auto &newCameraComponent = Application::m_coordinator->getComponent(cameraId);
+ newCameraComponent.resize(static_cast(m_contentSize.x), static_cast(m_contentSize.y));
+ }
+}
diff --git a/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp
new file mode 100644
index 000000000..836b199d3
--- /dev/null
+++ b/editor/src/DocumentWindows/EditorScene/Shortcuts.cpp
@@ -0,0 +1,753 @@
+//// Shortcuts.cpp ///////////////////////////////////////////////////////////////
+//
+// zzzzz zzz zzzzzzzzzzzzz zzzz zzzz zzzzzz zzzzz
+// zzzzzzz zzz zzzz zzzz zzzz zzzz
+// zzz zzz zzz zzzzzzzzzzzzz zzzz zzzz zzz
+// zzz zzz zzz z zzzz zzzz zzzz zzzz
+// zzz zzz zzzzzzzzzzzzz zzzz zzz zzzzzzz zzzzz
+//
+// Author: Mehdy MORVAN
+// Date: 28/04/2025
+// Description: Source file for the shortcuts init of the editor scene
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "EditorScene.hpp"
+#include "context/Selector.hpp"
+#include "context/ActionManager.hpp"
+#include "components/Uuid.hpp"
+
+namespace nexo::editor {
+
+ static void hideCallback()
+ {
+ auto &selector = Selector::get();
+ const auto &selectedEntities = selector.getSelectedEntities();
+ auto& actionManager = ActionManager::get();
+ auto actionGroup = ActionManager::createActionGroup();
+ for (const auto entity : selectedEntities) {
+ auto &renderComponent = Application::m_coordinator->getComponent(entity);
+ auto beforeState = renderComponent.save();
+ renderComponent.isRendered = !renderComponent.isRendered;
+ auto afterState = renderComponent.save();
+ actionGroup->addAction(std::make_unique>(
+ entity, beforeState, afterState));
+ }
+ actionManager.recordAction(std::move(actionGroup));
+ selector.clearSelection();
+ }
+
+ void EditorScene::selectAllCallback()
+ {
+ auto &selector = Selector::get();
+ auto &app = nexo::getApp();
+ const auto &scene = app.getSceneManager().getScene(m_sceneId);
+
+ selector.clearSelection();
+
+ for (const auto entity : scene.getEntities()) {
+ if (static_cast(entity) == m_editorCamera) continue; // Skip editor camera
+
+ const auto uuidComponent = Application::m_coordinator->tryGetComponent(entity);
+ if (uuidComponent)
+ selector.addToSelection(uuidComponent->get().uuid, static_cast(entity));
+ }
+ m_windowState = m_gizmoState;
+ }
+
+ void EditorScene::hideAllButSelectionCallback() const
+ {
+ auto &app = getApp();
+ const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities();
+ const auto &selector = Selector::get();
+ auto &actionManager = ActionManager::get();
+ auto actionGroup = ActionManager::createActionGroup();
+ for (const auto entity : entities) {
+ if (Application::m_coordinator->entityHasComponent(entity) && !selector.isEntitySelected(static_cast(entity))) {
+ auto &renderComponent = Application::m_coordinator->getComponent(entity);
+ if (renderComponent.isRendered) {
+ auto beforeState = renderComponent.save();
+ renderComponent.isRendered = false;
+ auto afterState = renderComponent.save();
+ actionGroup->addAction(std::make_unique>(
+ entity, beforeState, afterState));
+ }
+ }
+ }
+ actionManager.recordAction(std::move(actionGroup));
+ }
+
+ void EditorScene::deleteCallback()
+ {
+ auto &selector = Selector::get();
+ const auto &selectedEntities = selector.getSelectedEntities();
+ auto &app = nexo::getApp();
+ auto& actionManager = ActionManager::get();
+ if (selectedEntities.size() > 1) {
+ auto actionGroup = ActionManager::createActionGroup();
+ for (const auto entity : selectedEntities) {
+ actionGroup->addAction(ActionManager::prepareEntityDeletion(entity));
+ app.deleteEntity(entity);
+ }
+ actionManager.recordAction(std::move(actionGroup));
+ } else {
+ auto deleteAction = ActionManager::prepareEntityDeletion(selectedEntities[0]);
+ app.deleteEntity(selectedEntities[0]);
+ actionManager.recordAction(std::move(deleteAction));
+ }
+ selector.clearSelection();
+ this->m_windowState = m_globalState;
+ }
+
+ void EditorScene::unhideAllCallback() const
+ {
+ auto &app = getApp();
+ const auto &entities = app.getSceneManager().getScene(m_sceneId).getEntities();
+ auto &actionManager = ActionManager::get();
+ auto actionGroup = ActionManager::createActionGroup();
+ for (const auto entity : entities) {
+ if (Application::m_coordinator->entityHasComponent