diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index cbf58fe..495d531 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -64,6 +64,13 @@ jobs: cmake --build cmake-build-libprojectm --parallel cmake --install "${{ github.workspace }}/cmake-build-libprojectm" + - name: Build/Install projectm-eval + run: | + mkdir cmake-build-projectm-eval + cmake -G Ninja -S projectm/vendor/projectm-eval -B cmake-build-projectm-eval -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-projectm-eval + cmake --build cmake-build-projectm-eval --parallel + cmake --install "${{ github.workspace }}/cmake-build-projectm-eval" + - name: Checkout frontend-sdl2 Sources uses: actions/checkout@v4 with: @@ -75,7 +82,7 @@ jobs: mkdir cmake-build-frontend-sdl2 cmake -G Ninja -S frontend-sdl2 -B cmake-build-frontend-sdl2 \ -DCMAKE_BUILD_TYPE=Release \ - "-DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install-libprojectm;${GITHUB_WORKSPACE}/install-poco" \ + "-DCMAKE_PREFIX_PATH=${GITHUB_WORKSPACE}/install-libprojectm;${{ github.workspace }}/install-projectm-eval;${GITHUB_WORKSPACE}/install-poco" \ "-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install-frontend-sdl2" cmake --build cmake-build-frontend-sdl2 --parallel @@ -126,20 +133,6 @@ jobs: setapikey "${{ secrets.VCPKG_PACKAGES_TOKEN }}" ` -Source "${{ env.FEED_URL }}" - - name: Checkout libprojectM Sources - uses: actions/checkout@v4 - with: - repository: projectM-visualizer/projectm - path: projectm - submodules: recursive - - - name: Build/Install libprojectM - run: | - mkdir cmake-build-libprojectm - cmake -G "Visual Studio 17 2022" -A "X64" -S "${{ github.workspace }}/projectm" -B "${{ github.workspace }}/cmake-build-libprojectm" -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-libprojectm" -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -DCMAKE_VERBOSE_MAKEFILE=YES -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=NO - cmake --build "${{ github.workspace }}/cmake-build-libprojectm" --config Release --parallel - cmake --install "${{ github.workspace }}/cmake-build-libprojectm" --config Release - - name: Checkout projectMSDL Sources uses: actions/checkout@v4 with: @@ -149,7 +142,7 @@ jobs: - name: Build projectMSDL run: | mkdir cmake-build-frontend-sdl2 - cmake -G "Visual Studio 17 2022" -A "X64" -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -DCMAKE_VERBOSE_MAKEFILE=YES -DSDL2_LINKAGE=static -DBUILD_TESTING=YES + cmake -G "Visual Studio 17 2022" -A "X64" -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows-static -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>" -DCMAKE_VERBOSE_MAKEFILE=YES -DSDL2_LINKAGE=static -DBUILD_TESTING=YES cmake --build "${{ github.workspace }}/cmake-build-frontend-sdl2" --parallel --config Release - name: Package projectMSDL @@ -181,10 +174,17 @@ jobs: - name: Build/Install libprojectM run: | mkdir cmake-build-libprojectm - cmake -G Ninja -S "${{ github.workspace }}/projectm" -B "${{ github.workspace }}/cmake-build-libprojectm" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-libprojectm" + cmake -G Ninja -S "${{ github.workspace }}/projectm" -B "${{ github.workspace }}/cmake-build-libprojectm" -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-libprojectm;" cmake --build "${{ github.workspace }}/cmake-build-libprojectm" --parallel cmake --install "${{ github.workspace }}/cmake-build-libprojectm" + - name: Build/Install projectm-eval + run: | + mkdir cmake-build-projectm-eval + cmake -G Ninja -S "${{ github.workspace }}/projectm/vendor/projectm-eval" -B "${{ github.workspace }}/cmake-build-projectm-eval" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-projectm-eval" + cmake --build "${{ github.workspace }}/cmake-build-projectm-eval" --parallel + cmake --install "${{ github.workspace }}/cmake-build-projectm-eval" + - name: Checkout projectMSDL Sources uses: actions/checkout@v4 with: @@ -194,7 +194,7 @@ jobs: - name: Build projectMSDL run: | mkdir cmake-build-frontend-sdl2 - cmake -G Ninja -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" + cmake -G Ninja -S "${{ github.workspace }}/frontend-sdl2" -B "${{ github.workspace }}/cmake-build-frontend-sdl2" -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="${{ github.workspace }}/install-libprojectm;${{ github.workspace }}/install-projectm-eval" -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/install-frontend-sdl2" cmake --build "${{ github.workspace }}/cmake-build-frontend-sdl2" --parallel - name: Package projectMSDL diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d1f4ed..681eb6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ if(NOT SDL2_LINKAGE STREQUAL "shared" AND NOT SDL2_LINKAGE STREQUAL "static") endif() find_package(projectM4 REQUIRED COMPONENTS Playlist) +find_package(projectM-Eval REQUIRED) find_package(SDL2 REQUIRED) find_package(Poco REQUIRED COMPONENTS JSON XML Util Foundation) diff --git a/src/ProjectMWrapper.cpp b/src/ProjectMWrapper.cpp index 75894f1..0f38a7b 100644 --- a/src/ProjectMWrapper.cpp +++ b/src/ProjectMWrapper.cpp @@ -143,6 +143,31 @@ projectm_playlist_handle ProjectMWrapper::Playlist() const return _playlist; } +bool ProjectMWrapper::LoadPresetData(const std::string& presetData, std::string& errorMessage) +{ + projectm_load_preset_data(_projectM, presetData.c_str(), false); + + if (_presetLoadFailed) + { + errorMessage = _presetLoadFailedMessage; + _presetLoadFailed = false; + return false; + } + + return true; +} + +void ProjectMWrapper::UnbindPlaylist() +{ + projectm_playlist_connect(_playlist, nullptr); +} + +void ProjectMWrapper::BindPlaylist() +{ + projectm_playlist_connect(_playlist, _projectM); + projectm_playlist_set_position(_playlist, projectm_playlist_get_position(_playlist), false); +} + int ProjectMWrapper::TargetFPS() { return _projectMConfigView->getInt("fps", 60); @@ -206,6 +231,42 @@ std::string ProjectMWrapper::ProjectMRuntimeVersion() return projectMRuntimeVersion; } +std::string ProjectMWrapper::CurrentPresetFileName() const +{ + if (projectm_playlist_size(_playlist) == 0) + { + return {}; + } + + auto presetName = projectm_playlist_item(_playlist, projectm_playlist_get_position(_playlist)); + std::string presetNameString(presetName); + projectm_playlist_free_string(presetName); + + if (presetNameString.substr(0, 5) == "idle:") + { + return {}; + } + + return presetNameString; +} + +void ProjectMWrapper::EnablePlaybackControl(bool enable) +{ + _playbackControlEnabled = enable; +} + +void ProjectMWrapper::HardLockPreset(bool lock) +{ + if (lock) + { + projectm_set_preset_locked(_projectM, true); + } + else + { + projectm_set_preset_locked(_projectM, _userConfig->getBool("projectM.presetLocked", false)); + } +} + void ProjectMWrapper::PresetFileNameToClipboard() const { auto presetName = projectm_playlist_item(_playlist, projectm_playlist_get_position(_playlist)); @@ -223,8 +284,20 @@ void ProjectMWrapper::PresetSwitchedEvent(bool isHardCut, unsigned int index, vo Poco::NotificationCenter::defaultCenter().postNotification(new UpdateWindowTitleNotification); } +void ProjectMWrapper::PresetSwitchFailedEvent(const char* presetFilename, const char* message, void* context) +{ + auto that = reinterpret_cast(context); + that->_presetLoadFailedMessage = message; + that->_presetLoadFailed = true; +} + void ProjectMWrapper::PlaybackControlNotificationHandler(const Poco::AutoPtr& notification) { + if (!_playbackControlEnabled) + { + return; + } + bool shuffleEnabled = projectm_playlist_get_shuffle(_playlist); switch (notification->ControlAction()) diff --git a/src/ProjectMWrapper.h b/src/ProjectMWrapper.h index 3493dc4..f50c99d 100644 --- a/src/ProjectMWrapper.h +++ b/src/ProjectMWrapper.h @@ -2,8 +2,8 @@ #include "notifications/PlaybackControlNotification.h" -#include #include +#include #include #include @@ -34,6 +34,24 @@ class ProjectMWrapper : public Poco::Util::Subsystem */ projectm_playlist_handle Playlist() const; + /** + * @brief Detaches the current playlist and loads a single preset. + * @param presetData The preset data to load. + * @param errorMessage The error message from projectM if loading failed. + * @return true if the preset was loaded successfully, false if an error occurred. + */ + bool LoadPresetData(const std::string& presetData, std::string& errorMessage); + + /** + * @brief Detaches the internal playlist, so it no longer controls the preset playback. + */ + void UnbindPlaylist(); + + /** + * @brief Binds the internal playlist and resets the preset lock to the user setting. + */ + void BindPlaylist(); + /** * Renders a single projectM frame. */ @@ -70,11 +88,29 @@ class ProjectMWrapper : public Poco::Util::Subsystem std::string ProjectMBuildVersion(); /** - * @brief Returns the libprojectM version this applications currently runs with. + * @brief Returns the libprojectM version this application currently runs with. * @return A string with the libprojectM runtime library version. */ std::string ProjectMRuntimeVersion(); + /** + * @brief Returns the full path of the currently displayed preset. + * @return The full path of the currently displayed preset, or an empty string if the idle preset is loaded. + */ + std::string CurrentPresetFileName() const; + + /** + * @brief Toggles handling of playback control notifications. + * @param enable true to enable handling of playback control notifications, false to disable. + */ + void EnablePlaybackControl(bool enable); + + /** + * @brief Locks or unlocks the current preset without changing the user setting. + * @param lock true to lock the current preset, false to enable auto-switching. + */ + void HardLockPreset(bool lock); + /** * Copies the full path of the current preset into the OS clipboard. */ @@ -89,6 +125,8 @@ class ProjectMWrapper : public Poco::Util::Subsystem */ static void PresetSwitchedEvent(bool isHardCut, unsigned int index, void* context); + static void PresetSwitchFailedEvent(const char* presetFilename, const char* message, void* context); + void PlaybackControlNotificationHandler(const Poco::AutoPtr& notification); std::vector GetPathListWithDefault(const std::string& baseKey, const std::string& defaultPath); @@ -110,6 +148,10 @@ class ProjectMWrapper : public Poco::Util::Subsystem projectm_handle _projectM{nullptr}; //!< Pointer to the projectM instance used by the application. projectm_playlist_handle _playlist{nullptr}; //!< Pointer to the projectM playlist manager instance. + bool _playbackControlEnabled{true}; //!< If false, any playback control notifications are ignored. + + bool _presetLoadFailed{false}; + std::string _presetLoadFailedMessage; Poco::NObserver _playbackControlNotificationObserver{*this, &ProjectMWrapper::PlaybackControlNotificationHandler}; diff --git a/src/RenderLoop.cpp b/src/RenderLoop.cpp index 7d428e0..71c2ad8 100644 --- a/src/RenderLoop.cpp +++ b/src/RenderLoop.cpp @@ -4,6 +4,7 @@ #include "gui/ProjectMGUI.h" +#include #include #include diff --git a/src/SDLRenderingWindow.cpp b/src/SDLRenderingWindow.cpp index efac35e..2186766 100644 --- a/src/SDLRenderingWindow.cpp +++ b/src/SDLRenderingWindow.cpp @@ -384,11 +384,17 @@ SDL_GLContext SDLRenderingWindow::GetGlContext() const void SDLRenderingWindow::UpdateWindowTitleNotificationHandler(POCO_UNUSED const Poco::AutoPtr& notification) { - UpdateWindowTitle(); + UpdateWindowTitle(notification->_customTitle); } -void SDLRenderingWindow::UpdateWindowTitle() +void SDLRenderingWindow::UpdateWindowTitle(const std::string& customTitle) { + if (!customTitle.empty()) + { + SDL_SetWindowTitle(_renderingWindow, customTitle.c_str()); + return; + } + std::string newTitle = "projectM"; if (_config->getBool("displayPresetNameInTitle", true)) diff --git a/src/SDLRenderingWindow.h b/src/SDLRenderingWindow.h index 06c3bbe..b036f8e 100644 --- a/src/SDLRenderingWindow.h +++ b/src/SDLRenderingWindow.h @@ -122,7 +122,7 @@ class SDLRenderingWindow : public Poco::Util::Subsystem /** * @brief Updates the window title. */ - void UpdateWindowTitle(); + void UpdateWindowTitle(const std::string& customTitle = ""); /** * @brief Updates the swap interval from the user settings. diff --git a/src/gui/AboutWindow.cpp b/src/gui/AboutWindow.cpp index 932b3af..bc9138e 100644 --- a/src/gui/AboutWindow.cpp +++ b/src/gui/AboutWindow.cpp @@ -1,5 +1,6 @@ #include "AboutWindow.h" +#include "IconsFontAwesome7.h" #include "ProjectMGUI.h" #include "SystemBrowser.h" @@ -30,7 +31,7 @@ void AboutWindow::Draw() } ImGui::SetNextWindowSize(ImVec2(750, 600), ImGuiCond_FirstUseEver); - if (ImGui::Begin("About the projectM SDL Frontend###About", &_visible, ImGuiWindowFlags_NoCollapse)) + if (ImGui::Begin(ICON_FA_INFO " About the projectM SDL Frontend###About", &_visible, ImGuiWindowFlags_NoCollapse)) { _gui.PushToastFont(); ImGui::TextUnformatted("projectM SDL Frontend"); @@ -46,9 +47,9 @@ void AboutWindow::Draw() ImGui::TextWrapped("The projectM SDL frontend is open-source software licensed under the GNU General Public License, version 3."); ImGui::Dummy({.0f, 10.0f}); ImGui::TextWrapped("Get the source code on GitHub or report an issue with the SDL frontend:"); - if (ImGui::SmallButton("https://github.com/projectM-visualizer/frontend-sdl2")) + if (ImGui::SmallButton(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " https://github.com/projectM-visualizer/frontend-sdl-cpp")) { - SystemBrowser::OpenURL("https://github.com/projectM-visualizer/frontend-sdl2"); + SystemBrowser::OpenURL("https://github.com/projectM-visualizer/frontend-sdl-cpp"); } ImGui::Dummy({.0f, 10.0f}); if (ImGui::CollapsingHeader("Open-Source Software Used in this Application")) @@ -59,6 +60,7 @@ void AboutWindow::Draw() ImGui::BulletText("Dear ImGui by Omar Cornut and contributors (MIT)"); ImGui::BulletText("The POCO C++ Framework by Applied Informatics GmbH (MIT)"); ImGui::BulletText("FreeType 2 (FreeType License / GNU GPL v2)"); + ImGui::BulletText("FontAwesome Free Icons v7 (FontAwesome License / SIL OFL 1.1)"); ImGui::Dummy({.0f, 10.0f}); ImGui::TextUnformatted("Via libprojectM:"); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a2c72ee..b23f2d6 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -18,15 +18,28 @@ add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/LiberationSansFont.h" MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/src/resources/LiberationSans-Regular.ttf" ) +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsRegular7.h" + COMMAND ${BINARY_TO_COMPRESSED_EXECUTABLE} "${CMAKE_SOURCE_DIR}/src/resources/fa-regular-400.ttf" FontAwesomeIconsRegular7 > "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsRegular7.h" + MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/src/resources/fa-regular-400.ttf" + ) + +add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsSolid7.h" + COMMAND ${BINARY_TO_COMPRESSED_EXECUTABLE} "${CMAKE_SOURCE_DIR}/src/resources/fa-solid-900.ttf" FontAwesomeIconsSolid7 > "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsSolid7.h" + MAIN_DEPENDENCY "${CMAKE_SOURCE_DIR}/src/resources/fa-solid-900.ttf" + ) + add_library(ProjectMSDL-GUI STATIC "${CMAKE_CURRENT_BINARY_DIR}/AnonymousProFont.h" "${CMAKE_CURRENT_BINARY_DIR}/LiberationSansFont.h" + "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsRegular7.h" + "${CMAKE_CURRENT_BINARY_DIR}/FontAwesomeIconsSolid7.h" AboutWindow.cpp AboutWindow.h FileChooser.cpp FileChooser.h HelpWindow.cpp HelpWindow.h + IconsFontAwesome7.h MainMenu.cpp MainMenu.h PresetSelection.cpp @@ -54,9 +67,11 @@ target_include_directories(ProjectMSDL-GUI target_link_libraries(ProjectMSDL-GUI PUBLIC + PresetEditor Poco::Util ImGui libprojectM::projectM "$<$:-framework ApplicationServices>" ) +add_subdirectory(preset_editor) diff --git a/src/gui/FileChooser.cpp b/src/gui/FileChooser.cpp index dd1956e..497f5fa 100644 --- a/src/gui/FileChooser.cpp +++ b/src/gui/FileChooser.cpp @@ -1,5 +1,7 @@ #include "FileChooser.h" +#include "IconsFontAwesome7.h" + #include "imgui.h" #include @@ -143,7 +145,7 @@ bool FileChooser::Draw() } ImGui::PushStyleColor(ImGuiCol_Button, 0xFF000080); - if (ImGui::Button("Cancel")) + if (ImGui::Button(ICON_FA_BAN " Cancel")) { _selectedFiles.clear(); fileSelected = true; @@ -151,7 +153,7 @@ bool FileChooser::Draw() } ImGui::PopStyleColor(); ImGui::SameLine(); - if (ImGui::Button("Select")) + if (ImGui::Button(ICON_FA_CHECK " Select")) { for (auto index : _selectedFileIndices) { @@ -194,7 +196,7 @@ void FileChooser::DrawNavButtons() std::vector roots; Poco::Path::listRoots(roots); - if (ImGui::Button("Up")) + if (ImGui::Button(ICON_FA_CARET_UP " Up")) { ChangeDirectory(_currentDir.parent()); poco_debug_f1(_logger, "Going one dir up: %s", _currentDir.toString()); @@ -202,7 +204,7 @@ void FileChooser::DrawNavButtons() ImGui::SameLine(); - if (ImGui::Button("Home")) + if (ImGui::Button(ICON_FA_HOUSE " Home")) { ChangeDirectory(Poco::Path::home()); poco_debug_f1(_logger, "Going to user's home dir: %s", _currentDir.toString()); diff --git a/src/gui/HelpWindow.cpp b/src/gui/HelpWindow.cpp index 16b8db8..4311d37 100644 --- a/src/gui/HelpWindow.cpp +++ b/src/gui/HelpWindow.cpp @@ -1,5 +1,7 @@ #include "HelpWindow.h" +#include "IconsFontAwesome7.h" + #include "imgui.h" #include @@ -32,7 +34,7 @@ void HelpWindow::Draw() constexpr ImGuiTableFlags tableFlags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; ImGui::SetNextWindowSize(ImVec2(1000, 600), ImGuiCond_FirstUseEver); - if (ImGui::Begin("Quick Help###Help", &_visible, windowFlags)) + if (ImGui::Begin(ICON_FA_CIRCLE_QUESTION " Quick Help###Help", &_visible, windowFlags)) { if (ImGui::BeginTabBar("Help Topics", tabBarFlags)) { diff --git a/src/gui/IconsFontAwesome7.h b/src/gui/IconsFontAwesome7.h new file mode 100644 index 0000000..7e840f8 --- /dev/null +++ b/src/gui/IconsFontAwesome7.h @@ -0,0 +1,1421 @@ +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py +// for C and C++ +// from codepoints https://github.com/FortAwesome/Font-Awesome/raw/7.x/metadata/icons.yml +// for use with font https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-regular-400.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.), https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-solid-900.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.) + +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.woff2" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.woff2" + +#define ICON_MIN_FA 0xe005 +#define ICON_MAX_16_FA 0xf8ff +#define ICON_MAX_FA 0xf8ff + +#define ICON_FA_0 "0" // U+0030 +#define ICON_FA_1 "1" // U+0031 +#define ICON_FA_2 "2" // U+0032 +#define ICON_FA_3 "3" // U+0033 +#define ICON_FA_4 "4" // U+0034 +#define ICON_FA_5 "5" // U+0035 +#define ICON_FA_6 "6" // U+0036 +#define ICON_FA_7 "7" // U+0037 +#define ICON_FA_8 "8" // U+0038 +#define ICON_FA_9 "9" // U+0039 +#define ICON_FA_A "A" // U+0041 +#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9 +#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb +#define ICON_FA_ALARM_CLOCK "\xef\x8d\x8e" // U+f34e +#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037 +#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039 +#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036 +#define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038 +#define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d +#define ICON_FA_ANCHOR_CIRCLE_CHECK "\xee\x92\xaa" // U+e4aa +#define ICON_FA_ANCHOR_CIRCLE_EXCLAMATION "\xee\x92\xab" // U+e4ab +#define ICON_FA_ANCHOR_CIRCLE_XMARK "\xee\x92\xac" // U+e4ac +#define ICON_FA_ANCHOR_LOCK "\xee\x92\xad" // U+e4ad +#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107 +#define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104 +#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105 +#define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106 +#define ICON_FA_ANGLES_DOWN "\xef\x84\x83" // U+f103 +#define ICON_FA_ANGLES_LEFT "\xef\x84\x80" // U+f100 +#define ICON_FA_ANGLES_RIGHT "\xef\x84\x81" // U+f101 +#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102 +#define ICON_FA_ANKH "\xef\x99\x84" // U+f644 +#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1 +#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557 +#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063 +#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162 +#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886 +#define ICON_FA_ARROW_DOWN_A_Z "\xef\x85\x9d" // U+f15d +#define ICON_FA_ARROW_DOWN_LONG "\xef\x85\xb5" // U+f175 +#define ICON_FA_ARROW_DOWN_SHORT_WIDE "\xef\xa2\x84" // U+f884 +#define ICON_FA_ARROW_DOWN_UP_ACROSS_LINE "\xee\x92\xaf" // U+e4af +#define ICON_FA_ARROW_DOWN_UP_LOCK "\xee\x92\xb0" // U+e4b0 +#define ICON_FA_ARROW_DOWN_WIDE_SHORT "\xef\x85\xa0" // U+f160 +#define ICON_FA_ARROW_DOWN_Z_A "\xef\xa2\x81" // U+f881 +#define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060 +#define ICON_FA_ARROW_LEFT_LONG "\xef\x85\xb7" // U+f177 +#define ICON_FA_ARROW_POINTER "\xef\x89\x85" // U+f245 +#define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061 +#define ICON_FA_ARROW_RIGHT_ARROW_LEFT "\xef\x83\xac" // U+f0ec +#define ICON_FA_ARROW_RIGHT_FROM_BRACKET "\xef\x82\x8b" // U+f08b +#define ICON_FA_ARROW_RIGHT_LONG "\xef\x85\xb8" // U+f178 +#define ICON_FA_ARROW_RIGHT_TO_BRACKET "\xef\x82\x90" // U+f090 +#define ICON_FA_ARROW_RIGHT_TO_CITY "\xee\x92\xb3" // U+e4b3 +#define ICON_FA_ARROW_ROTATE_LEFT "\xef\x83\xa2" // U+f0e2 +#define ICON_FA_ARROW_ROTATE_RIGHT "\xef\x80\x9e" // U+f01e +#define ICON_FA_ARROW_TREND_DOWN "\xee\x82\x97" // U+e097 +#define ICON_FA_ARROW_TREND_UP "\xee\x82\x98" // U+e098 +#define ICON_FA_ARROW_TURN_DOWN "\xef\x85\x89" // U+f149 +#define ICON_FA_ARROW_TURN_UP "\xef\x85\x88" // U+f148 +#define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062 +#define ICON_FA_ARROW_UP_1_9 "\xef\x85\xa3" // U+f163 +#define ICON_FA_ARROW_UP_9_1 "\xef\xa2\x87" // U+f887 +#define ICON_FA_ARROW_UP_A_Z "\xef\x85\x9e" // U+f15e +#define ICON_FA_ARROW_UP_FROM_BRACKET "\xee\x82\x9a" // U+e09a +#define ICON_FA_ARROW_UP_FROM_GROUND_WATER "\xee\x92\xb5" // U+e4b5 +#define ICON_FA_ARROW_UP_FROM_WATER_PUMP "\xee\x92\xb6" // U+e4b6 +#define ICON_FA_ARROW_UP_LONG "\xef\x85\xb6" // U+f176 +#define ICON_FA_ARROW_UP_RIGHT_DOTS "\xee\x92\xb7" // U+e4b7 +#define ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE "\xef\x82\x8e" // U+f08e +#define ICON_FA_ARROW_UP_SHORT_WIDE "\xef\xa2\x85" // U+f885 +#define ICON_FA_ARROW_UP_WIDE_SHORT "\xef\x85\xa1" // U+f161 +#define ICON_FA_ARROW_UP_Z_A "\xef\xa2\x82" // U+f882 +#define ICON_FA_ARROWS_DOWN_TO_LINE "\xee\x92\xb8" // U+e4b8 +#define ICON_FA_ARROWS_DOWN_TO_PEOPLE "\xee\x92\xb9" // U+e4b9 +#define ICON_FA_ARROWS_LEFT_RIGHT "\xef\x81\xbe" // U+f07e +#define ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE "\xee\x92\xba" // U+e4ba +#define ICON_FA_ARROWS_ROTATE "\xef\x80\xa1" // U+f021 +#define ICON_FA_ARROWS_SPIN "\xee\x92\xbb" // U+e4bb +#define ICON_FA_ARROWS_SPLIT_UP_AND_LEFT "\xee\x92\xbc" // U+e4bc +#define ICON_FA_ARROWS_TO_CIRCLE "\xee\x92\xbd" // U+e4bd +#define ICON_FA_ARROWS_TO_DOT "\xee\x92\xbe" // U+e4be +#define ICON_FA_ARROWS_TO_EYE "\xee\x92\xbf" // U+e4bf +#define ICON_FA_ARROWS_TURN_RIGHT "\xee\x93\x80" // U+e4c0 +#define ICON_FA_ARROWS_TURN_TO_DOTS "\xee\x93\x81" // U+e4c1 +#define ICON_FA_ARROWS_UP_DOWN "\xef\x81\xbd" // U+f07d +#define ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT "\xef\x81\x87" // U+f047 +#define ICON_FA_ARROWS_UP_TO_LINE "\xee\x93\x82" // U+e4c2 +#define ICON_FA_ASTERISK "*" // U+002a +#define ICON_FA_AT "@" // U+0040 +#define ICON_FA_ATOM "\xef\x97\x92" // U+f5d2 +#define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e +#define ICON_FA_AUSTRAL_SIGN "\xee\x82\xa9" // U+e0a9 +#define ICON_FA_AWARD "\xef\x95\x99" // U+f559 +#define ICON_FA_B "B" // U+0042 +#define ICON_FA_BABY "\xef\x9d\xbc" // U+f77c +#define ICON_FA_BABY_CARRIAGE "\xef\x9d\xbd" // U+f77d +#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a +#define ICON_FA_BACKWARD_FAST "\xef\x81\x89" // U+f049 +#define ICON_FA_BACKWARD_STEP "\xef\x81\x88" // U+f048 +#define ICON_FA_BACON "\xef\x9f\xa5" // U+f7e5 +#define ICON_FA_BACTERIA "\xee\x81\x99" // U+e059 +#define ICON_FA_BACTERIUM "\xee\x81\x9a" // U+e05a +#define ICON_FA_BAG_SHOPPING "\xef\x8a\x90" // U+f290 +#define ICON_FA_BAHAI "\xef\x99\xa6" // U+f666 +#define ICON_FA_BAHT_SIGN "\xee\x82\xac" // U+e0ac +#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e +#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d +#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462 +#define ICON_FA_BANGLADESHI_TAKA_SIGN "\xee\x8b\xa6" // U+e2e6 +#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a +#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9 +#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828 +#define ICON_FA_BARS_STAGGERED "\xef\x95\x90" // U+f550 +#define ICON_FA_BASEBALL "\xef\x90\xb3" // U+f433 +#define ICON_FA_BASEBALL_BAT_BALL "\xef\x90\xb2" // U+f432 +#define ICON_FA_BASKET_SHOPPING "\xef\x8a\x91" // U+f291 +#define ICON_FA_BASKETBALL "\xef\x90\xb4" // U+f434 +#define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd +#define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244 +#define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240 +#define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242 +#define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243 +#define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241 +#define ICON_FA_BED "\xef\x88\xb6" // U+f236 +#define ICON_FA_BED_PULSE "\xef\x92\x87" // U+f487 +#define ICON_FA_BEER_MUG_EMPTY "\xef\x83\xbc" // U+f0fc +#define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3 +#define ICON_FA_BELL_CONCIERGE "\xef\x95\xa2" // U+f562 +#define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6 +#define ICON_FA_BEZIER_CURVE "\xef\x95\x9b" // U+f55b +#define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206 +#define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5 +#define ICON_FA_BIOHAZARD "\xef\x9e\x80" // U+f780 +#define ICON_FA_BITCOIN_SIGN "\xee\x82\xb4" // U+e0b4 +#define ICON_FA_BLENDER "\xef\x94\x97" // U+f517 +#define ICON_FA_BLENDER_PHONE "\xef\x9a\xb6" // U+f6b6 +#define ICON_FA_BLOG "\xef\x9e\x81" // U+f781 +#define ICON_FA_BOLD "\xef\x80\xb2" // U+f032 +#define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7 +#define ICON_FA_BOLT_LIGHTNING "\xee\x82\xb7" // U+e0b7 +#define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2 +#define ICON_FA_BONE "\xef\x97\x97" // U+f5d7 +#define ICON_FA_BONG "\xef\x95\x9c" // U+f55c +#define ICON_FA_BOOK "\xef\x80\xad" // U+f02d +#define ICON_FA_BOOK_ATLAS "\xef\x95\x98" // U+f558 +#define ICON_FA_BOOK_BIBLE "\xef\x99\x87" // U+f647 +#define ICON_FA_BOOK_BOOKMARK "\xee\x82\xbb" // U+e0bb +#define ICON_FA_BOOK_JOURNAL_WHILLS "\xef\x99\xaa" // U+f66a +#define ICON_FA_BOOK_MEDICAL "\xef\x9f\xa6" // U+f7e6 +#define ICON_FA_BOOK_OPEN "\xef\x94\x98" // U+f518 +#define ICON_FA_BOOK_OPEN_READER "\xef\x97\x9a" // U+f5da +#define ICON_FA_BOOK_QURAN "\xef\x9a\x87" // U+f687 +#define ICON_FA_BOOK_SKULL "\xef\x9a\xb7" // U+f6b7 +#define ICON_FA_BOOK_TANAKH "\xef\xa0\xa7" // U+f827 +#define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e +#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+f84c +#define ICON_FA_BORDER_NONE "\xef\xa1\x90" // U+f850 +#define ICON_FA_BORDER_TOP_LEFT "\xef\xa1\x93" // U+f853 +#define ICON_FA_BORE_HOLE "\xee\x93\x83" // U+e4c3 +#define ICON_FA_BOTTLE_DROPLET "\xee\x93\x84" // U+e4c4 +#define ICON_FA_BOTTLE_WATER "\xee\x93\x85" // U+e4c5 +#define ICON_FA_BOWL_FOOD "\xee\x93\x86" // U+e4c6 +#define ICON_FA_BOWL_RICE "\xee\x8b\xab" // U+e2eb +#define ICON_FA_BOWLING_BALL "\xef\x90\xb6" // U+f436 +#define ICON_FA_BOX "\xef\x91\xa6" // U+f466 +#define ICON_FA_BOX_ARCHIVE "\xef\x86\x87" // U+f187 +#define ICON_FA_BOX_OPEN "\xef\x92\x9e" // U+f49e +#define ICON_FA_BOX_TISSUE "\xee\x81\x9b" // U+e05b +#define ICON_FA_BOXES_PACKING "\xee\x93\x87" // U+e4c7 +#define ICON_FA_BOXES_STACKED "\xef\x91\xa8" // U+f468 +#define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1 +#define ICON_FA_BRAIN "\xef\x97\x9c" // U+f5dc +#define ICON_FA_BRAZILIAN_REAL_SIGN "\xee\x91\xac" // U+e46c +#define ICON_FA_BREAD_SLICE "\xef\x9f\xac" // U+f7ec +#define ICON_FA_BRIDGE "\xee\x93\x88" // U+e4c8 +#define ICON_FA_BRIDGE_CIRCLE_CHECK "\xee\x93\x89" // U+e4c9 +#define ICON_FA_BRIDGE_CIRCLE_EXCLAMATION "\xee\x93\x8a" // U+e4ca +#define ICON_FA_BRIDGE_CIRCLE_XMARK "\xee\x93\x8b" // U+e4cb +#define ICON_FA_BRIDGE_LOCK "\xee\x93\x8c" // U+e4cc +#define ICON_FA_BRIDGE_WATER "\xee\x93\x8e" // U+e4ce +#define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1 +#define ICON_FA_BRIEFCASE_MEDICAL "\xef\x91\xa9" // U+f469 +#define ICON_FA_BROOM "\xef\x94\x9a" // U+f51a +#define ICON_FA_BROOM_BALL "\xef\x91\x98" // U+f458 +#define ICON_FA_BRUSH "\xef\x95\x9d" // U+f55d +#define ICON_FA_BUCKET "\xee\x93\x8f" // U+e4cf +#define ICON_FA_BUG "\xef\x86\x88" // U+f188 +#define ICON_FA_BUG_SLASH "\xee\x92\x90" // U+e490 +#define ICON_FA_BUGS "\xee\x93\x90" // U+e4d0 +#define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad +#define ICON_FA_BUILDING_CIRCLE_ARROW_RIGHT "\xee\x93\x91" // U+e4d1 +#define ICON_FA_BUILDING_CIRCLE_CHECK "\xee\x93\x92" // U+e4d2 +#define ICON_FA_BUILDING_CIRCLE_EXCLAMATION "\xee\x93\x93" // U+e4d3 +#define ICON_FA_BUILDING_CIRCLE_XMARK "\xee\x93\x94" // U+e4d4 +#define ICON_FA_BUILDING_COLUMNS "\xef\x86\x9c" // U+f19c +#define ICON_FA_BUILDING_FLAG "\xee\x93\x95" // U+e4d5 +#define ICON_FA_BUILDING_LOCK "\xee\x93\x96" // U+e4d6 +#define ICON_FA_BUILDING_NGO "\xee\x93\x97" // U+e4d7 +#define ICON_FA_BUILDING_SHIELD "\xee\x93\x98" // U+e4d8 +#define ICON_FA_BUILDING_UN "\xee\x93\x99" // U+e4d9 +#define ICON_FA_BUILDING_USER "\xee\x93\x9a" // U+e4da +#define ICON_FA_BUILDING_WHEAT "\xee\x93\x9b" // U+e4db +#define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1 +#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140 +#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805 +#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc +#define ICON_FA_BUS "\xef\x88\x87" // U+f207 +#define ICON_FA_BUS_SIDE "\xee\xa0\x9d" // U+e81d +#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e +#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a +#define ICON_FA_C "C" // U+0043 +#define ICON_FA_CABLE_CAR "\xef\x9f\x9a" // U+f7da +#define ICON_FA_CAKE_CANDLES "\xef\x87\xbd" // U+f1fd +#define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec +#define ICON_FA_CALENDAR "\xef\x84\xb3" // U+f133 +#define ICON_FA_CALENDAR_CHECK "\xef\x89\xb4" // U+f274 +#define ICON_FA_CALENDAR_DAY "\xef\x9e\x83" // U+f783 +#define ICON_FA_CALENDAR_DAYS "\xef\x81\xb3" // U+f073 +#define ICON_FA_CALENDAR_MINUS "\xef\x89\xb2" // U+f272 +#define ICON_FA_CALENDAR_PLUS "\xef\x89\xb1" // U+f271 +#define ICON_FA_CALENDAR_WEEK "\xef\x9e\x84" // U+f784 +#define ICON_FA_CALENDAR_XMARK "\xef\x89\xb3" // U+f273 +#define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030 +#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083 +#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8 +#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb +#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786 +#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f +#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b +#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9 +#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df +#define ICON_FA_CAR_BURST "\xef\x97\xa1" // U+f5e1 +#define ICON_FA_CAR_ON "\xee\x93\x9d" // U+e4dd +#define ICON_FA_CAR_REAR "\xef\x97\x9e" // U+f5de +#define ICON_FA_CAR_SIDE "\xef\x97\xa4" // U+f5e4 +#define ICON_FA_CAR_TUNNEL "\xee\x93\x9e" // U+e4de +#define ICON_FA_CARAVAN "\xef\xa3\xbf" // U+f8ff +#define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7 +#define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9 +#define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da +#define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8 +#define ICON_FA_CARROT "\xef\x9e\x87" // U+f787 +#define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218 +#define ICON_FA_CART_FLATBED "\xef\x91\xb4" // U+f474 +#define ICON_FA_CART_FLATBED_SUITCASE "\xef\x96\x9d" // U+f59d +#define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217 +#define ICON_FA_CART_SHOPPING "\xef\x81\xba" // U+f07a +#define ICON_FA_CASH_REGISTER "\xef\x9e\x88" // U+f788 +#define ICON_FA_CAT "\xef\x9a\xbe" // U+f6be +#define ICON_FA_CEDI_SIGN "\xee\x83\x9f" // U+e0df +#define ICON_FA_CENT_SIGN "\xee\x8f\xb5" // U+e3f5 +#define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3 +#define ICON_FA_CHAIR "\xef\x9b\x80" // U+f6c0 +#define ICON_FA_CHALKBOARD "\xef\x94\x9b" // U+f51b +#define ICON_FA_CHALKBOARD_USER "\xef\x94\x9c" // U+f51c +#define ICON_FA_CHAMPAGNE_GLASSES "\xef\x9e\x9f" // U+f79f +#define ICON_FA_CHARGING_STATION "\xef\x97\xa7" // U+f5e7 +#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe +#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080 +#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3 +#define ICON_FA_CHART_DIAGRAM "\xee\x9a\x95" // U+e695 +#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4 +#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201 +#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200 +#define ICON_FA_CHART_SIMPLE "\xee\x91\xb3" // U+e473 +#define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c +#define ICON_FA_CHECK_DOUBLE "\xef\x95\xa0" // U+f560 +#define ICON_FA_CHECK_TO_SLOT "\xef\x9d\xb2" // U+f772 +#define ICON_FA_CHEESE "\xef\x9f\xaf" // U+f7ef +#define ICON_FA_CHESS "\xef\x90\xb9" // U+f439 +#define ICON_FA_CHESS_BISHOP "\xef\x90\xba" // U+f43a +#define ICON_FA_CHESS_BOARD "\xef\x90\xbc" // U+f43c +#define ICON_FA_CHESS_KING "\xef\x90\xbf" // U+f43f +#define ICON_FA_CHESS_KNIGHT "\xef\x91\x81" // U+f441 +#define ICON_FA_CHESS_PAWN "\xef\x91\x83" // U+f443 +#define ICON_FA_CHESS_QUEEN "\xef\x91\x85" // U+f445 +#define ICON_FA_CHESS_ROOK "\xef\x91\x87" // U+f447 +#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078 +#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053 +#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054 +#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077 +#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae +#define ICON_FA_CHILD_COMBATANT "\xee\x93\xa0" // U+e4e0 +#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c +#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d +#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1 +#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d +#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111 +#define ICON_FA_CIRCLE_ARROW_DOWN "\xef\x82\xab" // U+f0ab +#define ICON_FA_CIRCLE_ARROW_LEFT "\xef\x82\xa8" // U+f0a8 +#define ICON_FA_CIRCLE_ARROW_RIGHT "\xef\x82\xa9" // U+f0a9 +#define ICON_FA_CIRCLE_ARROW_UP "\xef\x82\xaa" // U+f0aa +#define ICON_FA_CIRCLE_CHECK "\xef\x81\x98" // U+f058 +#define ICON_FA_CIRCLE_CHEVRON_DOWN "\xef\x84\xba" // U+f13a +#define ICON_FA_CIRCLE_CHEVRON_LEFT "\xef\x84\xb7" // U+f137 +#define ICON_FA_CIRCLE_CHEVRON_RIGHT "\xef\x84\xb8" // U+f138 +#define ICON_FA_CIRCLE_CHEVRON_UP "\xef\x84\xb9" // U+f139 +#define ICON_FA_CIRCLE_DOLLAR_TO_SLOT "\xef\x92\xb9" // U+f4b9 +#define ICON_FA_CIRCLE_DOT "\xef\x86\x92" // U+f192 +#define ICON_FA_CIRCLE_DOWN "\xef\x8d\x98" // U+f358 +#define ICON_FA_CIRCLE_EXCLAMATION "\xef\x81\xaa" // U+f06a +#define ICON_FA_CIRCLE_H "\xef\x91\xbe" // U+f47e +#define ICON_FA_CIRCLE_HALF_STROKE "\xef\x81\x82" // U+f042 +#define ICON_FA_CIRCLE_INFO "\xef\x81\x9a" // U+f05a +#define ICON_FA_CIRCLE_LEFT "\xef\x8d\x99" // U+f359 +#define ICON_FA_CIRCLE_MINUS "\xef\x81\x96" // U+f056 +#define ICON_FA_CIRCLE_NODES "\xee\x93\xa2" // U+e4e2 +#define ICON_FA_CIRCLE_NOTCH "\xef\x87\x8e" // U+f1ce +#define ICON_FA_CIRCLE_PAUSE "\xef\x8a\x8b" // U+f28b +#define ICON_FA_CIRCLE_PLAY "\xef\x85\x84" // U+f144 +#define ICON_FA_CIRCLE_PLUS "\xef\x81\x95" // U+f055 +#define ICON_FA_CIRCLE_QUESTION "\xef\x81\x99" // U+f059 +#define ICON_FA_CIRCLE_RADIATION "\xef\x9e\xba" // U+f7ba +#define ICON_FA_CIRCLE_RIGHT "\xef\x8d\x9a" // U+f35a +#define ICON_FA_CIRCLE_STOP "\xef\x8a\x8d" // U+f28d +#define ICON_FA_CIRCLE_UP "\xef\x8d\x9b" // U+f35b +#define ICON_FA_CIRCLE_USER "\xef\x8a\xbd" // U+f2bd +#define ICON_FA_CIRCLE_XMARK "\xef\x81\x97" // U+f057 +#define ICON_FA_CITY "\xef\x99\x8f" // U+f64f +#define ICON_FA_CLAPPERBOARD "\xee\x84\xb1" // U+e131 +#define ICON_FA_CLIPBOARD "\xef\x8c\xa8" // U+f328 +#define ICON_FA_CLIPBOARD_CHECK "\xef\x91\xac" // U+f46c +#define ICON_FA_CLIPBOARD_LIST "\xef\x91\xad" // U+f46d +#define ICON_FA_CLIPBOARD_QUESTION "\xee\x93\xa3" // U+e4e3 +#define ICON_FA_CLIPBOARD_USER "\xef\x9f\xb3" // U+f7f3 +#define ICON_FA_CLOCK "\xef\x80\x97" // U+f017 +#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da +#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d +#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a +#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2 +#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed +#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee +#define ICON_FA_CLOUD_BOLT "\xef\x9d\xac" // U+f76c +#define ICON_FA_CLOUD_MEATBALL "\xef\x9c\xbb" // U+f73b +#define ICON_FA_CLOUD_MOON "\xef\x9b\x83" // U+f6c3 +#define ICON_FA_CLOUD_MOON_RAIN "\xef\x9c\xbc" // U+f73c +#define ICON_FA_CLOUD_RAIN "\xef\x9c\xbd" // U+f73d +#define ICON_FA_CLOUD_SHOWERS_HEAVY "\xef\x9d\x80" // U+f740 +#define ICON_FA_CLOUD_SHOWERS_WATER "\xee\x93\xa4" // U+e4e4 +#define ICON_FA_CLOUD_SUN "\xef\x9b\x84" // U+f6c4 +#define ICON_FA_CLOUD_SUN_RAIN "\xef\x9d\x83" // U+f743 +#define ICON_FA_CLOVER "\xee\x84\xb9" // U+e139 +#define ICON_FA_CODE "\xef\x84\xa1" // U+f121 +#define ICON_FA_CODE_BRANCH "\xef\x84\xa6" // U+f126 +#define ICON_FA_CODE_COMMIT "\xef\x8e\x86" // U+f386 +#define ICON_FA_CODE_COMPARE "\xee\x84\xba" // U+e13a +#define ICON_FA_CODE_FORK "\xee\x84\xbb" // U+e13b +#define ICON_FA_CODE_MERGE "\xef\x8e\x87" // U+f387 +#define ICON_FA_CODE_PULL_REQUEST "\xee\x84\xbc" // U+e13c +#define ICON_FA_COINS "\xef\x94\x9e" // U+f51e +#define ICON_FA_COLON_SIGN "\xee\x85\x80" // U+e140 +#define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075 +#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651 +#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad +#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5 +#define ICON_FA_COMMENT_NODES "\xee\x9a\x96" // U+e696 +#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3 +#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd +#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086 +#define ICON_FA_COMMENTS_DOLLAR "\xef\x99\x93" // U+f653 +#define ICON_FA_COMPACT_DISC "\xef\x94\x9f" // U+f51f +#define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e +#define ICON_FA_COMPASS_DRAFTING "\xef\x95\xa8" // U+f568 +#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066 +#define ICON_FA_COMPUTER "\xee\x93\xa5" // U+e4e5 +#define ICON_FA_COMPUTER_MOUSE "\xef\xa3\x8c" // U+f8cc +#define ICON_FA_COOKIE "\xef\x95\xa3" // U+f563 +#define ICON_FA_COOKIE_BITE "\xef\x95\xa4" // U+f564 +#define ICON_FA_COPY "\xef\x83\x85" // U+f0c5 +#define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9 +#define ICON_FA_COUCH "\xef\x92\xb8" // U+f4b8 +#define ICON_FA_COW "\xef\x9b\x88" // U+f6c8 +#define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d +#define ICON_FA_CROP "\xef\x84\xa5" // U+f125 +#define ICON_FA_CROP_SIMPLE "\xef\x95\xa5" // U+f565 +#define ICON_FA_CROSS "\xef\x99\x94" // U+f654 +#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b +#define ICON_FA_CROW "\xef\x94\xa0" // U+f520 +#define ICON_FA_CROWN "\xef\x94\xa1" // U+f521 +#define ICON_FA_CRUTCH "\xef\x9f\xb7" // U+f7f7 +#define ICON_FA_CRUZEIRO_SIGN "\xee\x85\x92" // U+e152 +#define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2 +#define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3 +#define ICON_FA_CUBES_STACKED "\xee\x93\xa6" // U+e4e6 +#define ICON_FA_D "D" // U+0044 +#define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0 +#define ICON_FA_DELETE_LEFT "\xef\x95\x9a" // U+f55a +#define ICON_FA_DEMOCRAT "\xef\x9d\x87" // U+f747 +#define ICON_FA_DESKTOP "\xef\x8e\x90" // U+f390 +#define ICON_FA_DHARMACHAKRA "\xef\x99\x95" // U+f655 +#define ICON_FA_DIAGRAM_NEXT "\xee\x91\xb6" // U+e476 +#define ICON_FA_DIAGRAM_PREDECESSOR "\xee\x91\xb7" // U+e477 +#define ICON_FA_DIAGRAM_PROJECT "\xef\x95\x82" // U+f542 +#define ICON_FA_DIAGRAM_SUCCESSOR "\xee\x91\xba" // U+e47a +#define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219 +#define ICON_FA_DIAMOND_TURN_RIGHT "\xef\x97\xab" // U+f5eb +#define ICON_FA_DICE "\xef\x94\xa2" // U+f522 +#define ICON_FA_DICE_D20 "\xef\x9b\x8f" // U+f6cf +#define ICON_FA_DICE_D6 "\xef\x9b\x91" // U+f6d1 +#define ICON_FA_DICE_FIVE "\xef\x94\xa3" // U+f523 +#define ICON_FA_DICE_FOUR "\xef\x94\xa4" // U+f524 +#define ICON_FA_DICE_ONE "\xef\x94\xa5" // U+f525 +#define ICON_FA_DICE_SIX "\xef\x94\xa6" // U+f526 +#define ICON_FA_DICE_THREE "\xef\x94\xa7" // U+f527 +#define ICON_FA_DICE_TWO "\xef\x94\xa8" // U+f528 +#define ICON_FA_DISEASE "\xef\x9f\xba" // U+f7fa +#define ICON_FA_DISPLAY "\xee\x85\xa3" // U+e163 +#define ICON_FA_DIVIDE "\xef\x94\xa9" // U+f529 +#define ICON_FA_DNA "\xef\x91\xb1" // U+f471 +#define ICON_FA_DOG "\xef\x9b\x93" // U+f6d3 +#define ICON_FA_DOLLAR_SIGN "$" // U+0024 +#define ICON_FA_DOLLY "\xef\x91\xb2" // U+f472 +#define ICON_FA_DONG_SIGN "\xee\x85\xa9" // U+e169 +#define ICON_FA_DOOR_CLOSED "\xef\x94\xaa" // U+f52a +#define ICON_FA_DOOR_OPEN "\xef\x94\xab" // U+f52b +#define ICON_FA_DOVE "\xef\x92\xba" // U+f4ba +#define ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xef\x90\xa2" // U+f422 +#define ICON_FA_DOWN_LONG "\xef\x8c\x89" // U+f309 +#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019 +#define ICON_FA_DRAGON "\xef\x9b\x95" // U+f6d5 +#define ICON_FA_DRAW_POLYGON "\xef\x97\xae" // U+f5ee +#define ICON_FA_DROPLET "\xef\x81\x83" // U+f043 +#define ICON_FA_DROPLET_SLASH "\xef\x97\x87" // U+f5c7 +#define ICON_FA_DRUM "\xef\x95\xa9" // U+f569 +#define ICON_FA_DRUM_STEELPAN "\xef\x95\xaa" // U+f56a +#define ICON_FA_DRUMSTICK_BITE "\xef\x9b\x97" // U+f6d7 +#define ICON_FA_DUMBBELL "\xef\x91\x8b" // U+f44b +#define ICON_FA_DUMPSTER "\xef\x9e\x93" // U+f793 +#define ICON_FA_DUMPSTER_FIRE "\xef\x9e\x94" // U+f794 +#define ICON_FA_DUNGEON "\xef\x9b\x99" // U+f6d9 +#define ICON_FA_E "E" // U+0045 +#define ICON_FA_EAR_DEAF "\xef\x8a\xa4" // U+f2a4 +#define ICON_FA_EAR_LISTEN "\xef\x8a\xa2" // U+f2a2 +#define ICON_FA_EARTH_AFRICA "\xef\x95\xbc" // U+f57c +#define ICON_FA_EARTH_AMERICAS "\xef\x95\xbd" // U+f57d +#define ICON_FA_EARTH_ASIA "\xef\x95\xbe" // U+f57e +#define ICON_FA_EARTH_EUROPE "\xef\x9e\xa2" // U+f7a2 +#define ICON_FA_EARTH_OCEANIA "\xee\x91\xbb" // U+e47b +#define ICON_FA_EGG "\xef\x9f\xbb" // U+f7fb +#define ICON_FA_EJECT "\xef\x81\x92" // U+f052 +#define ICON_FA_ELEVATOR "\xee\x85\xad" // U+e16d +#define ICON_FA_ELLIPSIS "\xef\x85\x81" // U+f141 +#define ICON_FA_ELLIPSIS_VERTICAL "\xef\x85\x82" // U+f142 +#define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0 +#define ICON_FA_ENVELOPE_CIRCLE_CHECK "\xee\x93\xa8" // U+e4e8 +#define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6 +#define ICON_FA_ENVELOPE_OPEN_TEXT "\xef\x99\x98" // U+f658 +#define ICON_FA_ENVELOPES_BULK "\xef\x99\xb4" // U+f674 +#define ICON_FA_EQUALS "=" // U+003d +#define ICON_FA_ERASER "\xef\x84\xad" // U+f12d +#define ICON_FA_ETHERNET "\xef\x9e\x96" // U+f796 +#define ICON_FA_EURO_SIGN "\xef\x85\x93" // U+f153 +#define ICON_FA_EXCLAMATION "!" // U+0021 +#define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065 +#define ICON_FA_EXPLOSION "\xee\x93\xa9" // U+e4e9 +#define ICON_FA_EYE "\xef\x81\xae" // U+f06e +#define ICON_FA_EYE_DROPPER "\xef\x87\xbb" // U+f1fb +#define ICON_FA_EYE_LOW_VISION "\xef\x8a\xa8" // U+f2a8 +#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070 +#define ICON_FA_F "F" // U+0046 +#define ICON_FA_FACE_ANGRY "\xef\x95\x96" // U+f556 +#define ICON_FA_FACE_DIZZY "\xef\x95\xa7" // U+f567 +#define ICON_FA_FACE_FLUSHED "\xef\x95\xb9" // U+f579 +#define ICON_FA_FACE_FROWN "\xef\x84\x99" // U+f119 +#define ICON_FA_FACE_FROWN_OPEN "\xef\x95\xba" // U+f57a +#define ICON_FA_FACE_GRIMACE "\xef\x95\xbf" // U+f57f +#define ICON_FA_FACE_GRIN "\xef\x96\x80" // U+f580 +#define ICON_FA_FACE_GRIN_BEAM "\xef\x96\x82" // U+f582 +#define ICON_FA_FACE_GRIN_BEAM_SWEAT "\xef\x96\x83" // U+f583 +#define ICON_FA_FACE_GRIN_HEARTS "\xef\x96\x84" // U+f584 +#define ICON_FA_FACE_GRIN_SQUINT "\xef\x96\x85" // U+f585 +#define ICON_FA_FACE_GRIN_SQUINT_TEARS "\xef\x96\x86" // U+f586 +#define ICON_FA_FACE_GRIN_STARS "\xef\x96\x87" // U+f587 +#define ICON_FA_FACE_GRIN_TEARS "\xef\x96\x88" // U+f588 +#define ICON_FA_FACE_GRIN_TONGUE "\xef\x96\x89" // U+f589 +#define ICON_FA_FACE_GRIN_TONGUE_SQUINT "\xef\x96\x8a" // U+f58a +#define ICON_FA_FACE_GRIN_TONGUE_WINK "\xef\x96\x8b" // U+f58b +#define ICON_FA_FACE_GRIN_WIDE "\xef\x96\x81" // U+f581 +#define ICON_FA_FACE_GRIN_WINK "\xef\x96\x8c" // U+f58c +#define ICON_FA_FACE_KISS "\xef\x96\x96" // U+f596 +#define ICON_FA_FACE_KISS_BEAM "\xef\x96\x97" // U+f597 +#define ICON_FA_FACE_KISS_WINK_HEART "\xef\x96\x98" // U+f598 +#define ICON_FA_FACE_LAUGH "\xef\x96\x99" // U+f599 +#define ICON_FA_FACE_LAUGH_BEAM "\xef\x96\x9a" // U+f59a +#define ICON_FA_FACE_LAUGH_SQUINT "\xef\x96\x9b" // U+f59b +#define ICON_FA_FACE_LAUGH_WINK "\xef\x96\x9c" // U+f59c +#define ICON_FA_FACE_MEH "\xef\x84\x9a" // U+f11a +#define ICON_FA_FACE_MEH_BLANK "\xef\x96\xa4" // U+f5a4 +#define ICON_FA_FACE_ROLLING_EYES "\xef\x96\xa5" // U+f5a5 +#define ICON_FA_FACE_SAD_CRY "\xef\x96\xb3" // U+f5b3 +#define ICON_FA_FACE_SAD_TEAR "\xef\x96\xb4" // U+f5b4 +#define ICON_FA_FACE_SMILE "\xef\x84\x98" // U+f118 +#define ICON_FA_FACE_SMILE_BEAM "\xef\x96\xb8" // U+f5b8 +#define ICON_FA_FACE_SMILE_WINK "\xef\x93\x9a" // U+f4da +#define ICON_FA_FACE_SURPRISE "\xef\x97\x82" // U+f5c2 +#define ICON_FA_FACE_TIRED "\xef\x97\x88" // U+f5c8 +#define ICON_FA_FAN "\xef\xa1\xa3" // U+f863 +#define ICON_FA_FAUCET "\xee\x80\x85" // U+e005 +#define ICON_FA_FAUCET_DRIP "\xee\x80\x86" // U+e006 +#define ICON_FA_FAX "\xef\x86\xac" // U+f1ac +#define ICON_FA_FEATHER "\xef\x94\xad" // U+f52d +#define ICON_FA_FEATHER_POINTED "\xef\x95\xab" // U+f56b +#define ICON_FA_FERRY "\xee\x93\xaa" // U+e4ea +#define ICON_FA_FILE "\xef\x85\x9b" // U+f15b +#define ICON_FA_FILE_ARROW_DOWN "\xef\x95\xad" // U+f56d +#define ICON_FA_FILE_ARROW_UP "\xef\x95\xb4" // U+f574 +#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+f1c7 +#define ICON_FA_FILE_CIRCLE_CHECK "\xee\x96\xa0" // U+e5a0 +#define ICON_FA_FILE_CIRCLE_EXCLAMATION "\xee\x93\xab" // U+e4eb +#define ICON_FA_FILE_CIRCLE_MINUS "\xee\x93\xad" // U+e4ed +#define ICON_FA_FILE_CIRCLE_PLUS "\xee\x92\x94" // U+e494 +#define ICON_FA_FILE_CIRCLE_QUESTION "\xee\x93\xaf" // U+e4ef +#define ICON_FA_FILE_CIRCLE_XMARK "\xee\x96\xa1" // U+e5a1 +#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+f1c9 +#define ICON_FA_FILE_CONTRACT "\xef\x95\xac" // U+f56c +#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd +#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3 +#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e +#define ICON_FA_FILE_FRAGMENT "\xee\x9a\x97" // U+e697 +#define ICON_FA_FILE_HALF_DASHED "\xee\x9a\x98" // U+e698 +#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5 +#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f +#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570 +#define ICON_FA_FILE_INVOICE_DOLLAR "\xef\x95\xb1" // U+f571 +#define ICON_FA_FILE_LINES "\xef\x85\x9c" // U+f15c +#define ICON_FA_FILE_MEDICAL "\xef\x91\xb7" // U+f477 +#define ICON_FA_FILE_PDF "\xef\x87\x81" // U+f1c1 +#define ICON_FA_FILE_PEN "\xef\x8c\x9c" // U+f31c +#define ICON_FA_FILE_POWERPOINT "\xef\x87\x84" // U+f1c4 +#define ICON_FA_FILE_PRESCRIPTION "\xef\x95\xb2" // U+f572 +#define ICON_FA_FILE_SHIELD "\xee\x93\xb0" // U+e4f0 +#define ICON_FA_FILE_SIGNATURE "\xef\x95\xb3" // U+f573 +#define ICON_FA_FILE_VIDEO "\xef\x87\x88" // U+f1c8 +#define ICON_FA_FILE_WAVEFORM "\xef\x91\xb8" // U+f478 +#define ICON_FA_FILE_WORD "\xef\x87\x82" // U+f1c2 +#define ICON_FA_FILE_ZIPPER "\xef\x87\x86" // U+f1c6 +#define ICON_FA_FILL "\xef\x95\xb5" // U+f575 +#define ICON_FA_FILL_DRIP "\xef\x95\xb6" // U+f576 +#define ICON_FA_FILM "\xef\x80\x88" // U+f008 +#define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0 +#define ICON_FA_FILTER_CIRCLE_DOLLAR "\xef\x99\xa2" // U+f662 +#define ICON_FA_FILTER_CIRCLE_XMARK "\xee\x85\xbb" // U+e17b +#define ICON_FA_FINGERPRINT "\xef\x95\xb7" // U+f577 +#define ICON_FA_FIRE "\xef\x81\xad" // U+f06d +#define ICON_FA_FIRE_BURNER "\xee\x93\xb1" // U+e4f1 +#define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134 +#define ICON_FA_FIRE_FLAME_CURVED "\xef\x9f\xa4" // U+f7e4 +#define ICON_FA_FIRE_FLAME_SIMPLE "\xef\x91\xaa" // U+f46a +#define ICON_FA_FISH "\xef\x95\xb8" // U+f578 +#define ICON_FA_FISH_FINS "\xee\x93\xb2" // U+e4f2 +#define ICON_FA_FLAG "\xef\x80\xa4" // U+f024 +#define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e +#define ICON_FA_FLAG_USA "\xef\x9d\x8d" // U+f74d +#define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3 +#define ICON_FA_FLASK_VIAL "\xee\x93\xb3" // U+e4f3 +#define ICON_FA_FLOPPY_DISK "\xef\x83\x87" // U+f0c7 +#define ICON_FA_FLORIN_SIGN "\xee\x86\x84" // U+e184 +#define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b +#define ICON_FA_FOLDER_CLOSED "\xee\x86\x85" // U+e185 +#define ICON_FA_FOLDER_MINUS "\xef\x99\x9d" // U+f65d +#define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c +#define ICON_FA_FOLDER_PLUS "\xef\x99\x9e" // U+f65e +#define ICON_FA_FOLDER_TREE "\xef\xa0\x82" // U+f802 +#define ICON_FA_FONT "\xef\x80\xb1" // U+f031 +#define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4 +#define ICON_FA_FOOTBALL "\xef\x91\x8e" // U+f44e +#define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e +#define ICON_FA_FORWARD_FAST "\xef\x81\x90" // U+f050 +#define ICON_FA_FORWARD_STEP "\xef\x81\x91" // U+f051 +#define ICON_FA_FRANC_SIGN "\xee\x86\x8f" // U+e18f +#define ICON_FA_FROG "\xef\x94\xae" // U+f52e +#define ICON_FA_FUTBOL "\xef\x87\xa3" // U+f1e3 +#define ICON_FA_G "G" // U+0047 +#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b +#define ICON_FA_GAS_PUMP "\xef\x94\xaf" // U+f52f +#define ICON_FA_GAUGE "\xef\x98\xa4" // U+f624 +#define ICON_FA_GAUGE_HIGH "\xef\x98\xa5" // U+f625 +#define ICON_FA_GAUGE_SIMPLE "\xef\x98\xa9" // U+f629 +#define ICON_FA_GAUGE_SIMPLE_HIGH "\xef\x98\xaa" // U+f62a +#define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3 +#define ICON_FA_GEAR "\xef\x80\x93" // U+f013 +#define ICON_FA_GEARS "\xef\x82\x85" // U+f085 +#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5 +#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d +#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2 +#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b +#define ICON_FA_GIFTS "\xef\x9e\x9c" // U+f79c +#define ICON_FA_GLASS_WATER "\xee\x93\xb4" // U+e4f4 +#define ICON_FA_GLASS_WATER_DROPLET "\xee\x93\xb5" // U+e4f5 +#define ICON_FA_GLASSES "\xef\x94\xb0" // U+f530 +#define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac +#define ICON_FA_GOLF_BALL_TEE "\xef\x91\x90" // U+f450 +#define ICON_FA_GOPURAM "\xef\x99\xa4" // U+f664 +#define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d +#define ICON_FA_GREATER_THAN ">" // U+003e +#define ICON_FA_GREATER_THAN_EQUAL "\xef\x94\xb2" // U+f532 +#define ICON_FA_GRIP "\xef\x96\x8d" // U+f58d +#define ICON_FA_GRIP_LINES "\xef\x9e\xa4" // U+f7a4 +#define ICON_FA_GRIP_LINES_VERTICAL "\xef\x9e\xa5" // U+f7a5 +#define ICON_FA_GRIP_VERTICAL "\xef\x96\x8e" // U+f58e +#define ICON_FA_GROUP_ARROWS_ROTATE "\xee\x93\xb6" // U+e4f6 +#define ICON_FA_GUARANI_SIGN "\xee\x86\x9a" // U+e19a +#define ICON_FA_GUITAR "\xef\x9e\xa6" // U+f7a6 +#define ICON_FA_GUN "\xee\x86\x9b" // U+e19b +#define ICON_FA_H "H" // U+0048 +#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+f6e3 +#define ICON_FA_HAMSA "\xef\x99\xa5" // U+f665 +#define ICON_FA_HAND "\xef\x89\x96" // U+f256 +#define ICON_FA_HAND_BACK_FIST "\xef\x89\x95" // U+f255 +#define ICON_FA_HAND_DOTS "\xef\x91\xa1" // U+f461 +#define ICON_FA_HAND_FIST "\xef\x9b\x9e" // U+f6de +#define ICON_FA_HAND_HOLDING "\xef\x92\xbd" // U+f4bd +#define ICON_FA_HAND_HOLDING_DOLLAR "\xef\x93\x80" // U+f4c0 +#define ICON_FA_HAND_HOLDING_DROPLET "\xef\x93\x81" // U+f4c1 +#define ICON_FA_HAND_HOLDING_HAND "\xee\x93\xb7" // U+e4f7 +#define ICON_FA_HAND_HOLDING_HEART "\xef\x92\xbe" // U+f4be +#define ICON_FA_HAND_HOLDING_MEDICAL "\xee\x81\x9c" // U+e05c +#define ICON_FA_HAND_LIZARD "\xef\x89\x98" // U+f258 +#define ICON_FA_HAND_MIDDLE_FINGER "\xef\xa0\x86" // U+f806 +#define ICON_FA_HAND_PEACE "\xef\x89\x9b" // U+f25b +#define ICON_FA_HAND_POINT_DOWN "\xef\x82\xa7" // U+f0a7 +#define ICON_FA_HAND_POINT_LEFT "\xef\x82\xa5" // U+f0a5 +#define ICON_FA_HAND_POINT_RIGHT "\xef\x82\xa4" // U+f0a4 +#define ICON_FA_HAND_POINT_UP "\xef\x82\xa6" // U+f0a6 +#define ICON_FA_HAND_POINTER "\xef\x89\x9a" // U+f25a +#define ICON_FA_HAND_SCISSORS "\xef\x89\x97" // U+f257 +#define ICON_FA_HAND_SPARKLES "\xee\x81\x9d" // U+e05d +#define ICON_FA_HAND_SPOCK "\xef\x89\x99" // U+f259 +#define ICON_FA_HANDCUFFS "\xee\x93\xb8" // U+e4f8 +#define ICON_FA_HANDS "\xef\x8a\xa7" // U+f2a7 +#define ICON_FA_HANDS_ASL_INTERPRETING "\xef\x8a\xa3" // U+f2a3 +#define ICON_FA_HANDS_BOUND "\xee\x93\xb9" // U+e4f9 +#define ICON_FA_HANDS_BUBBLES "\xee\x81\x9e" // U+e05e +#define ICON_FA_HANDS_CLAPPING "\xee\x86\xa8" // U+e1a8 +#define ICON_FA_HANDS_HOLDING "\xef\x93\x82" // U+f4c2 +#define ICON_FA_HANDS_HOLDING_CHILD "\xee\x93\xba" // U+e4fa +#define ICON_FA_HANDS_HOLDING_CIRCLE "\xee\x93\xbb" // U+e4fb +#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684 +#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5 +#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4 +#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060 +#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6 +#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0 +#define ICON_FA_HASHTAG "#" // U+0023 +#define ICON_FA_HAT_COWBOY "\xef\xa3\x80" // U+f8c0 +#define ICON_FA_HAT_COWBOY_SIDE "\xef\xa3\x81" // U+f8c1 +#define ICON_FA_HAT_WIZARD "\xef\x9b\xa8" // U+f6e8 +#define ICON_FA_HEAD_SIDE_COUGH "\xee\x81\xa1" // U+e061 +#define ICON_FA_HEAD_SIDE_COUGH_SLASH "\xee\x81\xa2" // U+e062 +#define ICON_FA_HEAD_SIDE_MASK "\xee\x81\xa3" // U+e063 +#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064 +#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc +#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025 +#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590 +#define ICON_FA_HEART "\xef\x80\x84" // U+f004 +#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc +#define ICON_FA_HEART_CIRCLE_CHECK "\xee\x93\xbd" // U+e4fd +#define ICON_FA_HEART_CIRCLE_EXCLAMATION "\xee\x93\xbe" // U+e4fe +#define ICON_FA_HEART_CIRCLE_MINUS "\xee\x93\xbf" // U+e4ff +#define ICON_FA_HEART_CIRCLE_PLUS "\xee\x94\x80" // U+e500 +#define ICON_FA_HEART_CIRCLE_XMARK "\xee\x94\x81" // U+e501 +#define ICON_FA_HEART_CRACK "\xef\x9e\xa9" // U+f7a9 +#define ICON_FA_HEART_PULSE "\xef\x88\x9e" // U+f21e +#define ICON_FA_HELICOPTER "\xef\x94\xb3" // U+f533 +#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502 +#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807 +#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503 +#define ICON_FA_HEXAGON "\xef\x8c\x92" // U+f312 +#define ICON_FA_HEXAGON_NODES "\xee\x9a\x99" // U+e699 +#define ICON_FA_HEXAGON_NODES_BOLT "\xee\x9a\x9a" // U+e69a +#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591 +#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507 +#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508 +#define ICON_FA_HIPPO "\xef\x9b\xad" // U+f6ed +#define ICON_FA_HOCKEY_PUCK "\xef\x91\x93" // U+f453 +#define ICON_FA_HOLLY_BERRY "\xef\x9e\xaa" // U+f7aa +#define ICON_FA_HORSE "\xef\x9b\xb0" // U+f6f0 +#define ICON_FA_HORSE_HEAD "\xef\x9e\xab" // U+f7ab +#define ICON_FA_HOSPITAL "\xef\x83\xb8" // U+f0f8 +#define ICON_FA_HOSPITAL_USER "\xef\xa0\x8d" // U+f80d +#define ICON_FA_HOT_TUB_PERSON "\xef\x96\x93" // U+f593 +#define ICON_FA_HOTDOG "\xef\xa0\x8f" // U+f80f +#define ICON_FA_HOTEL "\xef\x96\x94" // U+f594 +#define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254 +#define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253 +#define ICON_FA_HOURGLASS_HALF "\xef\x89\x92" // U+f252 +#define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251 +#define ICON_FA_HOUSE "\xef\x80\x95" // U+f015 +#define ICON_FA_HOUSE_CHIMNEY "\xee\x8e\xaf" // U+e3af +#define ICON_FA_HOUSE_CHIMNEY_CRACK "\xef\x9b\xb1" // U+f6f1 +#define ICON_FA_HOUSE_CHIMNEY_MEDICAL "\xef\x9f\xb2" // U+f7f2 +#define ICON_FA_HOUSE_CHIMNEY_USER "\xee\x81\xa5" // U+e065 +#define ICON_FA_HOUSE_CHIMNEY_WINDOW "\xee\x80\x8d" // U+e00d +#define ICON_FA_HOUSE_CIRCLE_CHECK "\xee\x94\x89" // U+e509 +#define ICON_FA_HOUSE_CIRCLE_EXCLAMATION "\xee\x94\x8a" // U+e50a +#define ICON_FA_HOUSE_CIRCLE_XMARK "\xee\x94\x8b" // U+e50b +#define ICON_FA_HOUSE_CRACK "\xee\x8e\xb1" // U+e3b1 +#define ICON_FA_HOUSE_FIRE "\xee\x94\x8c" // U+e50c +#define ICON_FA_HOUSE_FLAG "\xee\x94\x8d" // U+e50d +#define ICON_FA_HOUSE_FLOOD_WATER "\xee\x94\x8e" // U+e50e +#define ICON_FA_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xee\x94\x8f" // U+e50f +#define ICON_FA_HOUSE_LAPTOP "\xee\x81\xa6" // U+e066 +#define ICON_FA_HOUSE_LOCK "\xee\x94\x90" // U+e510 +#define ICON_FA_HOUSE_MEDICAL "\xee\x8e\xb2" // U+e3b2 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_CHECK "\xee\x94\x91" // U+e511 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xee\x94\x92" // U+e512 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_XMARK "\xee\x94\x93" // U+e513 +#define ICON_FA_HOUSE_MEDICAL_FLAG "\xee\x94\x94" // U+e514 +#define ICON_FA_HOUSE_SIGNAL "\xee\x80\x92" // U+e012 +#define ICON_FA_HOUSE_TSUNAMI "\xee\x94\x95" // U+e515 +#define ICON_FA_HOUSE_USER "\xee\x86\xb0" // U+e1b0 +#define ICON_FA_HRYVNIA_SIGN "\xef\x9b\xb2" // U+f6f2 +#define ICON_FA_HURRICANE "\xef\x9d\x91" // U+f751 +#define ICON_FA_I "I" // U+0049 +#define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246 +#define ICON_FA_ICE_CREAM "\xef\xa0\x90" // U+f810 +#define ICON_FA_ICICLES "\xef\x9e\xad" // U+f7ad +#define ICON_FA_ICONS "\xef\xa1\xad" // U+f86d +#define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1 +#define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2 +#define ICON_FA_ID_CARD_CLIP "\xef\x91\xbf" // U+f47f +#define ICON_FA_IGLOO "\xef\x9e\xae" // U+f7ae +#define ICON_FA_IMAGE "\xef\x80\xbe" // U+f03e +#define ICON_FA_IMAGE_PORTRAIT "\xef\x8f\xa0" // U+f3e0 +#define ICON_FA_IMAGES "\xef\x8c\x82" // U+f302 +#define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c +#define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c +#define ICON_FA_INDIAN_RUPEE_SIGN "\xee\x86\xbc" // U+e1bc +#define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275 +#define ICON_FA_INFINITY "\xef\x94\xb4" // U+f534 +#define ICON_FA_INFO "\xef\x84\xa9" // U+f129 +#define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033 +#define ICON_FA_J "J" // U+004a +#define ICON_FA_JAR "\xee\x94\x96" // U+e516 +#define ICON_FA_JAR_WHEAT "\xee\x94\x97" // U+e517 +#define ICON_FA_JEDI "\xef\x99\xa9" // U+f669 +#define ICON_FA_JET_FIGHTER "\xef\x83\xbb" // U+f0fb +#define ICON_FA_JET_FIGHTER_UP "\xee\x94\x98" // U+e518 +#define ICON_FA_JOINT "\xef\x96\x95" // U+f595 +#define ICON_FA_JUG_DETERGENT "\xee\x94\x99" // U+e519 +#define ICON_FA_K "K" // U+004b +#define ICON_FA_KAABA "\xef\x99\xab" // U+f66b +#define ICON_FA_KEY "\xef\x82\x84" // U+f084 +#define ICON_FA_KEYBOARD "\xef\x84\x9c" // U+f11c +#define ICON_FA_KHANDA "\xef\x99\xad" // U+f66d +#define ICON_FA_KIP_SIGN "\xee\x87\x84" // U+e1c4 +#define ICON_FA_KIT_MEDICAL "\xef\x91\xb9" // U+f479 +#define ICON_FA_KITCHEN_SET "\xee\x94\x9a" // U+e51a +#define ICON_FA_KIWI_BIRD "\xef\x94\xb5" // U+f535 +#define ICON_FA_L "L" // U+004c +#define ICON_FA_LAND_MINE_ON "\xee\x94\x9b" // U+e51b +#define ICON_FA_LANDMARK "\xef\x99\xaf" // U+f66f +#define ICON_FA_LANDMARK_DOME "\xef\x9d\x92" // U+f752 +#define ICON_FA_LANDMARK_FLAG "\xee\x94\x9c" // U+e51c +#define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab +#define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109 +#define ICON_FA_LAPTOP_CODE "\xef\x97\xbc" // U+f5fc +#define ICON_FA_LAPTOP_FILE "\xee\x94\x9d" // U+e51d +#define ICON_FA_LAPTOP_MEDICAL "\xef\xa0\x92" // U+f812 +#define ICON_FA_LARI_SIGN "\xee\x87\x88" // U+e1c8 +#define ICON_FA_LAYER_GROUP "\xef\x97\xbd" // U+f5fd +#define ICON_FA_LEAF "\xef\x81\xac" // U+f06c +#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a +#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337 +#define ICON_FA_LEMON "\xef\x82\x94" // U+f094 +#define ICON_FA_LESS_THAN "<" // U+003c +#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537 +#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd +#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb +#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e +#define ICON_FA_LINK "\xef\x83\x81" // U+f0c1 +#define ICON_FA_LINK_SLASH "\xef\x84\xa7" // U+f127 +#define ICON_FA_LIRA_SIGN "\xef\x86\x95" // U+f195 +#define ICON_FA_LIST "\xef\x80\xba" // U+f03a +#define ICON_FA_LIST_CHECK "\xef\x82\xae" // U+f0ae +#define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb +#define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca +#define ICON_FA_LITECOIN_SIGN "\xee\x87\x93" // U+e1d3 +#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124 +#define ICON_FA_LOCATION_CROSSHAIRS "\xef\x98\x81" // U+f601 +#define ICON_FA_LOCATION_DOT "\xef\x8f\x85" // U+f3c5 +#define ICON_FA_LOCATION_PIN "\xef\x81\x81" // U+f041 +#define ICON_FA_LOCATION_PIN_LOCK "\xee\x94\x9f" // U+e51f +#define ICON_FA_LOCK "\xef\x80\xa3" // U+f023 +#define ICON_FA_LOCK_OPEN "\xef\x8f\x81" // U+f3c1 +#define ICON_FA_LOCUST "\xee\x94\xa0" // U+e520 +#define ICON_FA_LUNGS "\xef\x98\x84" // U+f604 +#define ICON_FA_LUNGS_VIRUS "\xee\x81\xa7" // U+e067 +#define ICON_FA_M "M" // U+004d +#define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076 +#define ICON_FA_MAGNIFYING_GLASS "\xef\x80\x82" // U+f002 +#define ICON_FA_MAGNIFYING_GLASS_ARROW_RIGHT "\xee\x94\xa1" // U+e521 +#define ICON_FA_MAGNIFYING_GLASS_CHART "\xee\x94\xa2" // U+e522 +#define ICON_FA_MAGNIFYING_GLASS_DOLLAR "\xef\x9a\x88" // U+f688 +#define ICON_FA_MAGNIFYING_GLASS_LOCATION "\xef\x9a\x89" // U+f689 +#define ICON_FA_MAGNIFYING_GLASS_MINUS "\xef\x80\x90" // U+f010 +#define ICON_FA_MAGNIFYING_GLASS_PLUS "\xef\x80\x8e" // U+f00e +#define ICON_FA_MANAT_SIGN "\xee\x87\x95" // U+e1d5 +#define ICON_FA_MAP "\xef\x89\xb9" // U+f279 +#define ICON_FA_MAP_LOCATION "\xef\x96\x9f" // U+f59f +#define ICON_FA_MAP_LOCATION_DOT "\xef\x96\xa0" // U+f5a0 +#define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276 +#define ICON_FA_MARKER "\xef\x96\xa1" // U+f5a1 +#define ICON_FA_MARS "\xef\x88\xa2" // U+f222 +#define ICON_FA_MARS_AND_VENUS "\xef\x88\xa4" // U+f224 +#define ICON_FA_MARS_AND_VENUS_BURST "\xee\x94\xa3" // U+e523 +#define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227 +#define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229 +#define ICON_FA_MARS_STROKE_RIGHT "\xef\x88\xab" // U+f22b +#define ICON_FA_MARS_STROKE_UP "\xef\x88\xaa" // U+f22a +#define ICON_FA_MARTINI_GLASS "\xef\x95\xbb" // U+f57b +#define ICON_FA_MARTINI_GLASS_CITRUS "\xef\x95\xa1" // U+f561 +#define ICON_FA_MARTINI_GLASS_EMPTY "\xef\x80\x80" // U+f000 +#define ICON_FA_MASK "\xef\x9b\xba" // U+f6fa +#define ICON_FA_MASK_FACE "\xee\x87\x97" // U+e1d7 +#define ICON_FA_MASK_VENTILATOR "\xee\x94\xa4" // U+e524 +#define ICON_FA_MASKS_THEATER "\xef\x98\xb0" // U+f630 +#define ICON_FA_MATTRESS_PILLOW "\xee\x94\xa5" // U+e525 +#define ICON_FA_MAXIMIZE "\xef\x8c\x9e" // U+f31e +#define ICON_FA_MEDAL "\xef\x96\xa2" // U+f5a2 +#define ICON_FA_MEMORY "\xef\x94\xb8" // U+f538 +#define ICON_FA_MENORAH "\xef\x99\xb6" // U+f676 +#define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223 +#define ICON_FA_MESSAGE "\xef\x89\xba" // U+f27a +#define ICON_FA_METEOR "\xef\x9d\x93" // U+f753 +#define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db +#define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130 +#define ICON_FA_MICROPHONE_LINES "\xef\x8f\x89" // U+f3c9 +#define ICON_FA_MICROPHONE_LINES_SLASH "\xef\x94\xb9" // U+f539 +#define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131 +#define ICON_FA_MICROSCOPE "\xef\x98\x90" // U+f610 +#define ICON_FA_MILL_SIGN "\xee\x87\xad" // U+e1ed +#define ICON_FA_MINIMIZE "\xef\x9e\x8c" // U+f78c +#define ICON_FA_MINUS "\xef\x81\xa8" // U+f068 +#define ICON_FA_MITTEN "\xef\x9e\xb5" // U+f7b5 +#define ICON_FA_MOBILE "\xef\x8f\x8e" // U+f3ce +#define ICON_FA_MOBILE_BUTTON "\xef\x84\x8b" // U+f10b +#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527 +#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf +#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd +#define ICON_FA_MOBILE_VIBRATE "\xee\xa0\x96" // U+e816 +#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6 +#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1 +#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b +#define ICON_FA_MONEY_BILL_TRANSFER "\xee\x94\xa8" // U+e528 +#define ICON_FA_MONEY_BILL_TREND_UP "\xee\x94\xa9" // U+e529 +#define ICON_FA_MONEY_BILL_WAVE "\xef\x94\xba" // U+f53a +#define ICON_FA_MONEY_BILL_WHEAT "\xee\x94\xaa" // U+e52a +#define ICON_FA_MONEY_BILLS "\xee\x87\xb3" // U+e1f3 +#define ICON_FA_MONEY_CHECK "\xef\x94\xbc" // U+f53c +#define ICON_FA_MONEY_CHECK_DOLLAR "\xef\x94\xbd" // U+f53d +#define ICON_FA_MONUMENT "\xef\x96\xa6" // U+f5a6 +#define ICON_FA_MOON "\xef\x86\x86" // U+f186 +#define ICON_FA_MORTAR_PESTLE "\xef\x96\xa7" // U+f5a7 +#define ICON_FA_MOSQUE "\xef\x99\xb8" // U+f678 +#define ICON_FA_MOSQUITO "\xee\x94\xab" // U+e52b +#define ICON_FA_MOSQUITO_NET "\xee\x94\xac" // U+e52c +#define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c +#define ICON_FA_MOUND "\xee\x94\xad" // U+e52d +#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+f6fc +#define ICON_FA_MOUNTAIN_CITY "\xee\x94\xae" // U+e52e +#define ICON_FA_MOUNTAIN_SUN "\xee\x94\xaf" // U+e52f +#define ICON_FA_MUG_HOT "\xef\x9e\xb6" // U+f7b6 +#define ICON_FA_MUG_SAUCER "\xef\x83\xb4" // U+f0f4 +#define ICON_FA_MUSIC "\xef\x80\x81" // U+f001 +#define ICON_FA_N "N" // U+004e +#define ICON_FA_NAIRA_SIGN "\xee\x87\xb6" // U+e1f6 +#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff +#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c +#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea +#define ICON_FA_NON_BINARY "\xee\xa0\x87" // U+e807 +#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e +#define ICON_FA_NOTDEF "\xee\x87\xbe" // U+e1fe +#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249 +#define ICON_FA_NOTES_MEDICAL "\xef\x92\x81" // U+f481 +#define ICON_FA_O "O" // U+004f +#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247 +#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248 +#define ICON_FA_OCTAGON "\xef\x8c\x86" // U+f306 +#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613 +#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532 +#define ICON_FA_OM "\xef\x99\xb9" // U+f679 +#define ICON_FA_OTTER "\xef\x9c\x80" // U+f700 +#define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b +#define ICON_FA_P "P" // U+0050 +#define ICON_FA_PAGER "\xef\xa0\x95" // U+f815 +#define ICON_FA_PAINT_ROLLER "\xef\x96\xaa" // U+f5aa +#define ICON_FA_PAINTBRUSH "\xef\x87\xbc" // U+f1fc +#define ICON_FA_PALETTE "\xef\x94\xbf" // U+f53f +#define ICON_FA_PALLET "\xef\x92\x82" // U+f482 +#define ICON_FA_PANORAMA "\xee\x88\x89" // U+e209 +#define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8 +#define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6 +#define ICON_FA_PARACHUTE_BOX "\xef\x93\x8d" // U+f4cd +#define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd +#define ICON_FA_PASSPORT "\xef\x96\xab" // U+f5ab +#define ICON_FA_PASTE "\xef\x83\xaa" // U+f0ea +#define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c +#define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0 +#define ICON_FA_PEACE "\xef\x99\xbc" // U+f67c +#define ICON_FA_PEN "\xef\x8c\x84" // U+f304 +#define ICON_FA_PEN_CLIP "\xef\x8c\x85" // U+f305 +#define ICON_FA_PEN_FANCY "\xef\x96\xac" // U+f5ac +#define ICON_FA_PEN_NIB "\xef\x96\xad" // U+f5ad +#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae +#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044 +#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303 +#define ICON_FA_PENTAGON "\xee\x9e\x90" // U+e790 +#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068 +#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce +#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533 +#define ICON_FA_PEOPLE_LINE "\xee\x94\xb4" // U+e534 +#define ICON_FA_PEOPLE_PULLING "\xee\x94\xb5" // U+e535 +#define ICON_FA_PEOPLE_ROBBERY "\xee\x94\xb6" // U+e536 +#define ICON_FA_PEOPLE_ROOF "\xee\x94\xb7" // U+e537 +#define ICON_FA_PEPPER_HOT "\xef\xa0\x96" // U+f816 +#define ICON_FA_PERCENT "%" // U+0025 +#define ICON_FA_PERSON "\xef\x86\x83" // U+f183 +#define ICON_FA_PERSON_ARROW_DOWN_TO_LINE "\xee\x94\xb8" // U+e538 +#define ICON_FA_PERSON_ARROW_UP_FROM_LINE "\xee\x94\xb9" // U+e539 +#define ICON_FA_PERSON_BIKING "\xef\xa1\x8a" // U+f84a +#define ICON_FA_PERSON_BOOTH "\xef\x9d\x96" // U+f756 +#define ICON_FA_PERSON_BREASTFEEDING "\xee\x94\xba" // U+e53a +#define ICON_FA_PERSON_BURST "\xee\x94\xbb" // U+e53b +#define ICON_FA_PERSON_CANE "\xee\x94\xbc" // U+e53c +#define ICON_FA_PERSON_CHALKBOARD "\xee\x94\xbd" // U+e53d +#define ICON_FA_PERSON_CIRCLE_CHECK "\xee\x94\xbe" // U+e53e +#define ICON_FA_PERSON_CIRCLE_EXCLAMATION "\xee\x94\xbf" // U+e53f +#define ICON_FA_PERSON_CIRCLE_MINUS "\xee\x95\x80" // U+e540 +#define ICON_FA_PERSON_CIRCLE_PLUS "\xee\x95\x81" // U+e541 +#define ICON_FA_PERSON_CIRCLE_QUESTION "\xee\x95\x82" // U+e542 +#define ICON_FA_PERSON_CIRCLE_XMARK "\xee\x95\x83" // U+e543 +#define ICON_FA_PERSON_DIGGING "\xef\xa1\x9e" // U+f85e +#define ICON_FA_PERSON_DOTS_FROM_LINE "\xef\x91\xb0" // U+f470 +#define ICON_FA_PERSON_DRESS "\xef\x86\x82" // U+f182 +#define ICON_FA_PERSON_DRESS_BURST "\xee\x95\x84" // U+e544 +#define ICON_FA_PERSON_DROWNING "\xee\x95\x85" // U+e545 +#define ICON_FA_PERSON_FALLING "\xee\x95\x86" // U+e546 +#define ICON_FA_PERSON_FALLING_BURST "\xee\x95\x87" // U+e547 +#define ICON_FA_PERSON_HALF_DRESS "\xee\x95\x88" // U+e548 +#define ICON_FA_PERSON_HARASSING "\xee\x95\x89" // U+e549 +#define ICON_FA_PERSON_HIKING "\xef\x9b\xac" // U+f6ec +#define ICON_FA_PERSON_MILITARY_POINTING "\xee\x95\x8a" // U+e54a +#define ICON_FA_PERSON_MILITARY_RIFLE "\xee\x95\x8b" // U+e54b +#define ICON_FA_PERSON_MILITARY_TO_PERSON "\xee\x95\x8c" // U+e54c +#define ICON_FA_PERSON_PRAYING "\xef\x9a\x83" // U+f683 +#define ICON_FA_PERSON_PREGNANT "\xee\x8c\x9e" // U+e31e +#define ICON_FA_PERSON_RAYS "\xee\x95\x8d" // U+e54d +#define ICON_FA_PERSON_RIFLE "\xee\x95\x8e" // U+e54e +#define ICON_FA_PERSON_RUNNING "\xef\x9c\x8c" // U+f70c +#define ICON_FA_PERSON_SHELTER "\xee\x95\x8f" // U+e54f +#define ICON_FA_PERSON_SKATING "\xef\x9f\x85" // U+f7c5 +#define ICON_FA_PERSON_SKIING "\xef\x9f\x89" // U+f7c9 +#define ICON_FA_PERSON_SKIING_NORDIC "\xef\x9f\x8a" // U+f7ca +#define ICON_FA_PERSON_SNOWBOARDING "\xef\x9f\x8e" // U+f7ce +#define ICON_FA_PERSON_SWIMMING "\xef\x97\x84" // U+f5c4 +#define ICON_FA_PERSON_THROUGH_WINDOW "\xee\x96\xa9" // U+e5a9 +#define ICON_FA_PERSON_WALKING "\xef\x95\x94" // U+f554 +#define ICON_FA_PERSON_WALKING_ARROW_LOOP_LEFT "\xee\x95\x91" // U+e551 +#define ICON_FA_PERSON_WALKING_ARROW_RIGHT "\xee\x95\x92" // U+e552 +#define ICON_FA_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xee\x95\x93" // U+e553 +#define ICON_FA_PERSON_WALKING_LUGGAGE "\xee\x95\x94" // U+e554 +#define ICON_FA_PERSON_WALKING_WITH_CANE "\xef\x8a\x9d" // U+f29d +#define ICON_FA_PESETA_SIGN "\xee\x88\xa1" // U+e221 +#define ICON_FA_PESO_SIGN "\xee\x88\xa2" // U+e222 +#define ICON_FA_PHONE "\xef\x82\x95" // U+f095 +#define ICON_FA_PHONE_FLIP "\xef\xa1\xb9" // U+f879 +#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd +#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0 +#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c +#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3 +#define ICON_FA_PILLS "\xef\x92\x84" // U+f484 +#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818 +#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f +#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072 +#define ICON_FA_PLANE_ARRIVAL "\xef\x96\xaf" // U+f5af +#define ICON_FA_PLANE_CIRCLE_CHECK "\xee\x95\x95" // U+e555 +#define ICON_FA_PLANE_CIRCLE_EXCLAMATION "\xee\x95\x96" // U+e556 +#define ICON_FA_PLANE_CIRCLE_XMARK "\xee\x95\x97" // U+e557 +#define ICON_FA_PLANE_DEPARTURE "\xef\x96\xb0" // U+f5b0 +#define ICON_FA_PLANE_LOCK "\xee\x95\x98" // U+e558 +#define ICON_FA_PLANE_SLASH "\xee\x81\xa9" // U+e069 +#define ICON_FA_PLANE_UP "\xee\x88\xad" // U+e22d +#define ICON_FA_PLANT_WILT "\xee\x96\xaa" // U+e5aa +#define ICON_FA_PLATE_WHEAT "\xee\x95\x9a" // U+e55a +#define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b +#define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6 +#define ICON_FA_PLUG_CIRCLE_BOLT "\xee\x95\x9b" // U+e55b +#define ICON_FA_PLUG_CIRCLE_CHECK "\xee\x95\x9c" // U+e55c +#define ICON_FA_PLUG_CIRCLE_EXCLAMATION "\xee\x95\x9d" // U+e55d +#define ICON_FA_PLUG_CIRCLE_MINUS "\xee\x95\x9e" // U+e55e +#define ICON_FA_PLUG_CIRCLE_PLUS "\xee\x95\x9f" // U+e55f +#define ICON_FA_PLUG_CIRCLE_XMARK "\xee\x95\xa0" // U+e560 +#define ICON_FA_PLUS "+" // U+002b +#define ICON_FA_PLUS_MINUS "\xee\x90\xbc" // U+e43c +#define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce +#define ICON_FA_POO "\xef\x8b\xbe" // U+f2fe +#define ICON_FA_POO_STORM "\xef\x9d\x9a" // U+f75a +#define ICON_FA_POOP "\xef\x98\x99" // U+f619 +#define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011 +#define ICON_FA_PRESCRIPTION "\xef\x96\xb1" // U+f5b1 +#define ICON_FA_PRESCRIPTION_BOTTLE "\xef\x92\x85" // U+f485 +#define ICON_FA_PRESCRIPTION_BOTTLE_MEDICAL "\xef\x92\x86" // U+f486 +#define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f +#define ICON_FA_PUMP_MEDICAL "\xee\x81\xaa" // U+e06a +#define ICON_FA_PUMP_SOAP "\xee\x81\xab" // U+e06b +#define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e +#define ICON_FA_Q "Q" // U+0051 +#define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029 +#define ICON_FA_QUESTION "?" // U+003f +#define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d +#define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e +#define ICON_FA_R "R" // U+0052 +#define ICON_FA_RADIATION "\xef\x9e\xb9" // U+f7b9 +#define ICON_FA_RADIO "\xef\xa3\x97" // U+f8d7 +#define ICON_FA_RAINBOW "\xef\x9d\x9b" // U+f75b +#define ICON_FA_RANKING_STAR "\xee\x95\xa1" // U+e561 +#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543 +#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9 +#define ICON_FA_RECTANGLE_AD "\xef\x99\x81" // U+f641 +#define ICON_FA_RECTANGLE_LIST "\xef\x80\xa2" // U+f022 +#define ICON_FA_RECTANGLE_XMARK "\xef\x90\x90" // U+f410 +#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8 +#define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d +#define ICON_FA_REPEAT "\xef\x8d\xa3" // U+f363 +#define ICON_FA_REPLY "\xef\x8f\xa5" // U+f3e5 +#define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122 +#define ICON_FA_REPUBLICAN "\xef\x9d\x9e" // U+f75e +#define ICON_FA_RESTROOM "\xef\x9e\xbd" // U+f7bd +#define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079 +#define ICON_FA_RIBBON "\xef\x93\x96" // U+f4d6 +#define ICON_FA_RIGHT_FROM_BRACKET "\xef\x8b\xb5" // U+f2f5 +#define ICON_FA_RIGHT_LEFT "\xef\x8d\xa2" // U+f362 +#define ICON_FA_RIGHT_LONG "\xef\x8c\x8b" // U+f30b +#define ICON_FA_RIGHT_TO_BRACKET "\xef\x8b\xb6" // U+f2f6 +#define ICON_FA_RING "\xef\x9c\x8b" // U+f70b +#define ICON_FA_ROAD "\xef\x80\x98" // U+f018 +#define ICON_FA_ROAD_BARRIER "\xee\x95\xa2" // U+e562 +#define ICON_FA_ROAD_BRIDGE "\xee\x95\xa3" // U+e563 +#define ICON_FA_ROAD_CIRCLE_CHECK "\xee\x95\xa4" // U+e564 +#define ICON_FA_ROAD_CIRCLE_EXCLAMATION "\xee\x95\xa5" // U+e565 +#define ICON_FA_ROAD_CIRCLE_XMARK "\xee\x95\xa6" // U+e566 +#define ICON_FA_ROAD_LOCK "\xee\x95\xa7" // U+e567 +#define ICON_FA_ROAD_SPIKES "\xee\x95\xa8" // U+e568 +#define ICON_FA_ROBOT "\xef\x95\x84" // U+f544 +#define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135 +#define ICON_FA_ROTATE "\xef\x8b\xb1" // U+f2f1 +#define ICON_FA_ROTATE_LEFT "\xef\x8b\xaa" // U+f2ea +#define ICON_FA_ROTATE_RIGHT "\xef\x8b\xb9" // U+f2f9 +#define ICON_FA_ROUTE "\xef\x93\x97" // U+f4d7 +#define ICON_FA_RSS "\xef\x82\x9e" // U+f09e +#define ICON_FA_RUBLE_SIGN "\xef\x85\x98" // U+f158 +#define ICON_FA_RUG "\xee\x95\xa9" // U+e569 +#define ICON_FA_RULER "\xef\x95\x85" // U+f545 +#define ICON_FA_RULER_COMBINED "\xef\x95\x86" // U+f546 +#define ICON_FA_RULER_HORIZONTAL "\xef\x95\x87" // U+f547 +#define ICON_FA_RULER_VERTICAL "\xef\x95\x88" // U+f548 +#define ICON_FA_RUPEE_SIGN "\xef\x85\x96" // U+f156 +#define ICON_FA_RUPIAH_SIGN "\xee\x88\xbd" // U+e23d +#define ICON_FA_S "S" // U+0053 +#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d +#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a +#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445 +#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf +#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0 +#define ICON_FA_SCALE_BALANCED "\xef\x89\x8e" // U+f24e +#define ICON_FA_SCALE_UNBALANCED "\xef\x94\x95" // U+f515 +#define ICON_FA_SCALE_UNBALANCED_FLIP "\xef\x94\x96" // U+f516 +#define ICON_FA_SCHOOL "\xef\x95\x89" // U+f549 +#define ICON_FA_SCHOOL_CIRCLE_CHECK "\xee\x95\xab" // U+e56b +#define ICON_FA_SCHOOL_CIRCLE_EXCLAMATION "\xee\x95\xac" // U+e56c +#define ICON_FA_SCHOOL_CIRCLE_XMARK "\xee\x95\xad" // U+e56d +#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e +#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f +#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4 +#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a +#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9 +#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e +#define ICON_FA_SCROLL_TORAH "\xef\x9a\xa0" // U+f6a0 +#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2 +#define ICON_FA_SECTION "\xee\x91\x87" // U+e447 +#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8 +#define ICON_FA_SEPTAGON "\xee\xa0\xa0" // U+e820 +#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233 +#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f +#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064 +#define ICON_FA_SHARE_FROM_SQUARE "\xef\x85\x8d" // U+f14d +#define ICON_FA_SHARE_NODES "\xef\x87\xa0" // U+f1e0 +#define ICON_FA_SHEET_PLASTIC "\xee\x95\xb1" // U+e571 +#define ICON_FA_SHEKEL_SIGN "\xef\x88\x8b" // U+f20b +#define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132 +#define ICON_FA_SHIELD_CAT "\xee\x95\xb2" // U+e572 +#define ICON_FA_SHIELD_DOG "\xee\x95\xb3" // U+e573 +#define ICON_FA_SHIELD_HALVED "\xef\x8f\xad" // U+f3ed +#define ICON_FA_SHIELD_HEART "\xee\x95\xb4" // U+e574 +#define ICON_FA_SHIELD_VIRUS "\xee\x81\xac" // U+e06c +#define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a +#define ICON_FA_SHIRT "\xef\x95\x93" // U+f553 +#define ICON_FA_SHOE_PRINTS "\xef\x95\x8b" // U+f54b +#define ICON_FA_SHOP "\xef\x95\x8f" // U+f54f +#define ICON_FA_SHOP_LOCK "\xee\x92\xa5" // U+e4a5 +#define ICON_FA_SHOP_SLASH "\xee\x81\xb0" // U+e070 +#define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc +#define ICON_FA_SHRIMP "\xee\x91\x88" // U+e448 +#define ICON_FA_SHUFFLE "\xef\x81\xb4" // U+f074 +#define ICON_FA_SHUTTLE_SPACE "\xef\x86\x97" // U+f197 +#define ICON_FA_SIGN_HANGING "\xef\x93\x99" // U+f4d9 +#define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012 +#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7 +#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277 +#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4 +#define ICON_FA_SINGLE_QUOTE_LEFT "\xee\xa0\x9b" // U+e81b +#define ICON_FA_SINGLE_QUOTE_RIGHT "\xee\xa0\x9c" // U+e81c +#define ICON_FA_SINK "\xee\x81\xad" // U+e06d +#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8 +#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c +#define ICON_FA_SKULL_CROSSBONES "\xef\x9c\x94" // U+f714 +#define ICON_FA_SLASH "\xef\x9c\x95" // U+f715 +#define ICON_FA_SLEIGH "\xef\x9f\x8c" // U+f7cc +#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de +#define ICON_FA_SMOG "\xef\x9d\x9f" // U+f75f +#define ICON_FA_SMOKING "\xef\x92\x8d" // U+f48d +#define ICON_FA_SNOWFLAKE "\xef\x8b\x9c" // U+f2dc +#define ICON_FA_SNOWMAN "\xef\x9f\x90" // U+f7d0 +#define ICON_FA_SNOWPLOW "\xef\x9f\x92" // U+f7d2 +#define ICON_FA_SOAP "\xee\x81\xae" // U+e06e +#define ICON_FA_SOCKS "\xef\x9a\x96" // U+f696 +#define ICON_FA_SOLAR_PANEL "\xef\x96\xba" // U+f5ba +#define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc +#define ICON_FA_SORT_DOWN "\xef\x83\x9d" // U+f0dd +#define ICON_FA_SORT_UP "\xef\x83\x9e" // U+f0de +#define ICON_FA_SPA "\xef\x96\xbb" // U+f5bb +#define ICON_FA_SPAGHETTI_MONSTER_FLYING "\xef\x99\xbb" // U+f67b +#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891 +#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717 +#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110 +#define ICON_FA_SPIRAL "\xee\xa0\x8a" // U+e80a +#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc +#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5 +#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd +#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0 +#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8 +#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c +#define ICON_FA_SQUARE_BINARY "\xee\x9a\x9b" // U+e69b +#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150 +#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191 +#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152 +#define ICON_FA_SQUARE_CARET_UP "\xef\x85\x91" // U+f151 +#define ICON_FA_SQUARE_CHECK "\xef\x85\x8a" // U+f14a +#define ICON_FA_SQUARE_ENVELOPE "\xef\x86\x99" // U+f199 +#define ICON_FA_SQUARE_FULL "\xef\x91\x9c" // U+f45c +#define ICON_FA_SQUARE_H "\xef\x83\xbd" // U+f0fd +#define ICON_FA_SQUARE_MINUS "\xef\x85\x86" // U+f146 +#define ICON_FA_SQUARE_NFI "\xee\x95\xb6" // U+e576 +#define ICON_FA_SQUARE_PARKING "\xef\x95\x80" // U+f540 +#define ICON_FA_SQUARE_PEN "\xef\x85\x8b" // U+f14b +#define ICON_FA_SQUARE_PERSON_CONFINED "\xee\x95\xb7" // U+e577 +#define ICON_FA_SQUARE_PHONE "\xef\x82\x98" // U+f098 +#define ICON_FA_SQUARE_PHONE_FLIP "\xef\xa1\xbb" // U+f87b +#define ICON_FA_SQUARE_PLUS "\xef\x83\xbe" // U+f0fe +#define ICON_FA_SQUARE_POLL_HORIZONTAL "\xef\x9a\x82" // U+f682 +#define ICON_FA_SQUARE_POLL_VERTICAL "\xef\x9a\x81" // U+f681 +#define ICON_FA_SQUARE_ROOT_VARIABLE "\xef\x9a\x98" // U+f698 +#define ICON_FA_SQUARE_RSS "\xef\x85\x83" // U+f143 +#define ICON_FA_SQUARE_SHARE_NODES "\xef\x87\xa1" // U+f1e1 +#define ICON_FA_SQUARE_UP_RIGHT "\xef\x8d\xa0" // U+f360 +#define ICON_FA_SQUARE_VIRUS "\xee\x95\xb8" // U+e578 +#define ICON_FA_SQUARE_XMARK "\xef\x8b\x93" // U+f2d3 +#define ICON_FA_STAFF_SNAKE "\xee\x95\xb9" // U+e579 +#define ICON_FA_STAIRS "\xee\x8a\x89" // U+e289 +#define ICON_FA_STAMP "\xef\x96\xbf" // U+f5bf +#define ICON_FA_STAPLER "\xee\x96\xaf" // U+e5af +#define ICON_FA_STAR "\xef\x80\x85" // U+f005 +#define ICON_FA_STAR_AND_CRESCENT "\xef\x9a\x99" // U+f699 +#define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089 +#define ICON_FA_STAR_HALF_STROKE "\xef\x97\x80" // U+f5c0 +#define ICON_FA_STAR_OF_DAVID "\xef\x9a\x9a" // U+f69a +#define ICON_FA_STAR_OF_LIFE "\xef\x98\xa1" // U+f621 +#define ICON_FA_STERLING_SIGN "\xef\x85\x94" // U+f154 +#define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1 +#define ICON_FA_STOP "\xef\x81\x8d" // U+f04d +#define ICON_FA_STOPWATCH "\xef\x8b\xb2" // U+f2f2 +#define ICON_FA_STOPWATCH_20 "\xee\x81\xaf" // U+e06f +#define ICON_FA_STORE "\xef\x95\x8e" // U+f54e +#define ICON_FA_STORE_SLASH "\xee\x81\xb1" // U+e071 +#define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d +#define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc +#define ICON_FA_STROOPWAFEL "\xef\x95\x91" // U+f551 +#define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c +#define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2 +#define ICON_FA_SUITCASE_MEDICAL "\xef\x83\xba" // U+f0fa +#define ICON_FA_SUITCASE_ROLLING "\xef\x97\x81" // U+f5c1 +#define ICON_FA_SUN "\xef\x86\x85" // U+f185 +#define ICON_FA_SUN_PLANT_WILT "\xee\x95\xba" // U+e57a +#define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b +#define ICON_FA_SWATCHBOOK "\xef\x97\x83" // U+f5c3 +#define ICON_FA_SYNAGOGUE "\xef\x9a\x9b" // U+f69b +#define ICON_FA_SYRINGE "\xef\x92\x8e" // U+f48e +#define ICON_FA_T "T" // U+0054 +#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce +#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a +#define ICON_FA_TABLE_CELLS_COLUMN_LOCK "\xee\x99\xb8" // U+e678 +#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009 +#define ICON_FA_TABLE_CELLS_ROW_LOCK "\xee\x99\xba" // U+e67a +#define ICON_FA_TABLE_CELLS_ROW_UNLOCK "\xee\x9a\x91" // U+e691 +#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db +#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b +#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d +#define ICON_FA_TABLET "\xef\x8f\xbb" // U+f3fb +#define ICON_FA_TABLET_BUTTON "\xef\x84\x8a" // U+f10a +#define ICON_FA_TABLET_SCREEN_BUTTON "\xef\x8f\xba" // U+f3fa +#define ICON_FA_TABLETS "\xef\x92\x90" // U+f490 +#define ICON_FA_TACHOGRAPH_DIGITAL "\xef\x95\xa6" // U+f566 +#define ICON_FA_TAG "\xef\x80\xab" // U+f02b +#define ICON_FA_TAGS "\xef\x80\xac" // U+f02c +#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db +#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b +#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c +#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba +#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e +#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f +#define ICON_FA_TEMPERATURE_ARROW_DOWN "\xee\x80\xbf" // U+e03f +#define ICON_FA_TEMPERATURE_ARROW_UP "\xee\x81\x80" // U+e040 +#define ICON_FA_TEMPERATURE_EMPTY "\xef\x8b\x8b" // U+f2cb +#define ICON_FA_TEMPERATURE_FULL "\xef\x8b\x87" // U+f2c7 +#define ICON_FA_TEMPERATURE_HALF "\xef\x8b\x89" // U+f2c9 +#define ICON_FA_TEMPERATURE_HIGH "\xef\x9d\xa9" // U+f769 +#define ICON_FA_TEMPERATURE_LOW "\xef\x9d\xab" // U+f76b +#define ICON_FA_TEMPERATURE_QUARTER "\xef\x8b\x8a" // U+f2ca +#define ICON_FA_TEMPERATURE_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8 +#define ICON_FA_TENGE_SIGN "\xef\x9f\x97" // U+f7d7 +#define ICON_FA_TENT "\xee\x95\xbd" // U+e57d +#define ICON_FA_TENT_ARROW_DOWN_TO_LINE "\xee\x95\xbe" // U+e57e +#define ICON_FA_TENT_ARROW_LEFT_RIGHT "\xee\x95\xbf" // U+e57f +#define ICON_FA_TENT_ARROW_TURN_LEFT "\xee\x96\x80" // U+e580 +#define ICON_FA_TENT_ARROWS_DOWN "\xee\x96\x81" // U+e581 +#define ICON_FA_TENTS "\xee\x96\x82" // U+e582 +#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120 +#define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034 +#define ICON_FA_TEXT_SLASH "\xef\xa1\xbd" // U+f87d +#define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035 +#define ICON_FA_THERMOMETER "\xef\x92\x91" // U+f491 +#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165 +#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164 +#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d +#define ICON_FA_THUMBTACK_SLASH "\xee\x9a\x8f" // U+e68f +#define ICON_FA_TICKET "\xef\x85\x85" // U+f145 +#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff +#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c +#define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204 +#define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205 +#define ICON_FA_TOILET "\xef\x9f\x98" // U+f7d8 +#define ICON_FA_TOILET_PAPER "\xef\x9c\x9e" // U+f71e +#define ICON_FA_TOILET_PAPER_SLASH "\xee\x81\xb2" // U+e072 +#define ICON_FA_TOILET_PORTABLE "\xee\x96\x83" // U+e583 +#define ICON_FA_TOILETS_PORTABLE "\xee\x96\x84" // U+e584 +#define ICON_FA_TOOLBOX "\xef\x95\x92" // U+f552 +#define ICON_FA_TOOTH "\xef\x97\x89" // U+f5c9 +#define ICON_FA_TORII_GATE "\xef\x9a\xa1" // U+f6a1 +#define ICON_FA_TORNADO "\xef\x9d\xaf" // U+f76f +#define ICON_FA_TOWER_BROADCAST "\xef\x94\x99" // U+f519 +#define ICON_FA_TOWER_CELL "\xee\x96\x85" // U+e585 +#define ICON_FA_TOWER_OBSERVATION "\xee\x96\x86" // U+e586 +#define ICON_FA_TRACTOR "\xef\x9c\xa2" // U+f722 +#define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c +#define ICON_FA_TRAFFIC_LIGHT "\xef\x98\xb7" // U+f637 +#define ICON_FA_TRAILER "\xee\x81\x81" // U+e041 +#define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238 +#define ICON_FA_TRAIN_SUBWAY "\xef\x88\xb9" // U+f239 +#define ICON_FA_TRAIN_TRAM "\xee\x96\xb4" // U+e5b4 +#define ICON_FA_TRANSGENDER "\xef\x88\xa5" // U+f225 +#define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8 +#define ICON_FA_TRASH_ARROW_UP "\xef\xa0\xa9" // U+f829 +#define ICON_FA_TRASH_CAN "\xef\x8b\xad" // U+f2ed +#define ICON_FA_TRASH_CAN_ARROW_UP "\xef\xa0\xaa" // U+f82a +#define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb +#define ICON_FA_TREE_CITY "\xee\x96\x87" // U+e587 +#define ICON_FA_TRIANGLE_EXCLAMATION "\xef\x81\xb1" // U+f071 +#define ICON_FA_TROPHY "\xef\x82\x91" // U+f091 +#define ICON_FA_TROWEL "\xee\x96\x89" // U+e589 +#define ICON_FA_TROWEL_BRICKS "\xee\x96\x8a" // U+e58a +#define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1 +#define ICON_FA_TRUCK_ARROW_RIGHT "\xee\x96\x8b" // U+e58b +#define ICON_FA_TRUCK_DROPLET "\xee\x96\x8c" // U+e58c +#define ICON_FA_TRUCK_FAST "\xef\x92\x8b" // U+f48b +#define ICON_FA_TRUCK_FIELD "\xee\x96\x8d" // U+e58d +#define ICON_FA_TRUCK_FIELD_UN "\xee\x96\x8e" // U+e58e +#define ICON_FA_TRUCK_FRONT "\xee\x8a\xb7" // U+e2b7 +#define ICON_FA_TRUCK_MEDICAL "\xef\x83\xb9" // U+f0f9 +#define ICON_FA_TRUCK_MONSTER "\xef\x98\xbb" // U+f63b +#define ICON_FA_TRUCK_MOVING "\xef\x93\x9f" // U+f4df +#define ICON_FA_TRUCK_PICKUP "\xef\x98\xbc" // U+f63c +#define ICON_FA_TRUCK_PLANE "\xee\x96\x8f" // U+e58f +#define ICON_FA_TRUCK_RAMP_BOX "\xef\x93\x9e" // U+f4de +#define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4 +#define ICON_FA_TURKISH_LIRA_SIGN "\xee\x8a\xbb" // U+e2bb +#define ICON_FA_TURN_DOWN "\xef\x8e\xbe" // U+f3be +#define ICON_FA_TURN_UP "\xef\x8e\xbf" // U+f3bf +#define ICON_FA_TV "\xef\x89\xac" // U+f26c +#define ICON_FA_U "U" // U+0055 +#define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9 +#define ICON_FA_UMBRELLA_BEACH "\xef\x97\x8a" // U+f5ca +#define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd +#define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a +#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c +#define ICON_FA_UNLOCK_KEYHOLE "\xef\x84\xbe" // U+f13e +#define ICON_FA_UP_DOWN "\xef\x8c\xb8" // U+f338 +#define ICON_FA_UP_DOWN_LEFT_RIGHT "\xef\x82\xb2" // U+f0b2 +#define ICON_FA_UP_LONG "\xef\x8c\x8c" // U+f30c +#define ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xef\x90\xa4" // U+f424 +#define ICON_FA_UP_RIGHT_FROM_SQUARE "\xef\x8d\x9d" // U+f35d +#define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093 +#define ICON_FA_USER "\xef\x80\x87" // U+f007 +#define ICON_FA_USER_ASTRONAUT "\xef\x93\xbb" // U+f4fb +#define ICON_FA_USER_CHECK "\xef\x93\xbc" // U+f4fc +#define ICON_FA_USER_CLOCK "\xef\x93\xbd" // U+f4fd +#define ICON_FA_USER_DOCTOR "\xef\x83\xb0" // U+f0f0 +#define ICON_FA_USER_GEAR "\xef\x93\xbe" // U+f4fe +#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501 +#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500 +#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728 +#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502 +#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503 +#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504 +#define ICON_FA_USER_NURSE "\xef\xa0\xaf" // U+f82f +#define ICON_FA_USER_PEN "\xef\x93\xbf" // U+f4ff +#define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234 +#define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b +#define ICON_FA_USER_SHIELD "\xef\x94\x85" // U+f505 +#define ICON_FA_USER_SLASH "\xef\x94\x86" // U+f506 +#define ICON_FA_USER_TAG "\xef\x94\x87" // U+f507 +#define ICON_FA_USER_TIE "\xef\x94\x88" // U+f508 +#define ICON_FA_USER_XMARK "\xef\x88\xb5" // U+f235 +#define ICON_FA_USERS "\xef\x83\x80" // U+f0c0 +#define ICON_FA_USERS_BETWEEN_LINES "\xee\x96\x91" // U+e591 +#define ICON_FA_USERS_GEAR "\xef\x94\x89" // U+f509 +#define ICON_FA_USERS_LINE "\xee\x96\x92" // U+e592 +#define ICON_FA_USERS_RAYS "\xee\x96\x93" // U+e593 +#define ICON_FA_USERS_RECTANGLE "\xee\x96\x94" // U+e594 +#define ICON_FA_USERS_SLASH "\xee\x81\xb3" // U+e073 +#define ICON_FA_USERS_VIEWFINDER "\xee\x96\x95" // U+e595 +#define ICON_FA_UTENSILS "\xef\x8b\xa7" // U+f2e7 +#define ICON_FA_V "V" // U+0056 +#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6 +#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5 +#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221 +#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226 +#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228 +#define ICON_FA_VEST "\xee\x82\x85" // U+e085 +#define ICON_FA_VEST_PATCHES "\xee\x82\x86" // U+e086 +#define ICON_FA_VIAL "\xef\x92\x92" // U+f492 +#define ICON_FA_VIAL_CIRCLE_CHECK "\xee\x96\x96" // U+e596 +#define ICON_FA_VIAL_VIRUS "\xee\x96\x97" // U+e597 +#define ICON_FA_VIALS "\xef\x92\x93" // U+f493 +#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d +#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2 +#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7 +#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074 +#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8 +#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9 +#define ICON_FA_VIRUS_SLASH "\xee\x81\xb5" // U+e075 +#define ICON_FA_VIRUSES "\xee\x81\xb6" // U+e076 +#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897 +#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770 +#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f +#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028 +#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027 +#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026 +#define ICON_FA_VOLUME_XMARK "\xef\x9a\xa9" // U+f6a9 +#define ICON_FA_VR_CARDBOARD "\xef\x9c\xa9" // U+f729 +#define ICON_FA_W "W" // U+0057 +#define ICON_FA_WALKIE_TALKIE "\xef\xa3\xaf" // U+f8ef +#define ICON_FA_WALLET "\xef\x95\x95" // U+f555 +#define ICON_FA_WAND_MAGIC "\xef\x83\x90" // U+f0d0 +#define ICON_FA_WAND_MAGIC_SPARKLES "\xee\x8b\x8a" // U+e2ca +#define ICON_FA_WAND_SPARKLES "\xef\x9c\xab" // U+f72b +#define ICON_FA_WAREHOUSE "\xef\x92\x94" // U+f494 +#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773 +#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5 +#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e +#define ICON_FA_WEB_AWESOME "\xee\x9a\x82" // U+e682 +#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd +#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496 +#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd +#define ICON_FA_WHEAT_AWN_CIRCLE_EXCLAMATION "\xee\x96\x98" // U+e598 +#define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193 +#define ICON_FA_WHEELCHAIR_MOVE "\xee\x8b\x8e" // U+e2ce +#define ICON_FA_WHISKEY_GLASS "\xef\x9e\xa0" // U+f7a0 +#define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb +#define ICON_FA_WIND "\xef\x9c\xae" // U+f72e +#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0 +#define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1 +#define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2 +#define ICON_FA_WINE_BOTTLE "\xef\x9c\xaf" // U+f72f +#define ICON_FA_WINE_GLASS "\xef\x93\xa3" // U+f4e3 +#define ICON_FA_WINE_GLASS_EMPTY "\xef\x97\x8e" // U+f5ce +#define ICON_FA_WON_SIGN "\xef\x85\x99" // U+f159 +#define ICON_FA_WORM "\xee\x96\x99" // U+e599 +#define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad +#define ICON_FA_X "X" // U+0058 +#define ICON_FA_X_RAY "\xef\x92\x97" // U+f497 +#define ICON_FA_XMARK "\xef\x80\x8d" // U+f00d +#define ICON_FA_XMARKS_LINES "\xee\x96\x9a" // U+e59a +#define ICON_FA_Y "Y" // U+0059 +#define ICON_FA_YEN_SIGN "\xef\x85\x97" // U+f157 +#define ICON_FA_YIN_YANG "\xef\x9a\xad" // U+f6ad +#define ICON_FA_Z "Z" // U+005a diff --git a/src/gui/MainMenu.cpp b/src/gui/MainMenu.cpp index 02afba6..e116526 100644 --- a/src/gui/MainMenu.cpp +++ b/src/gui/MainMenu.cpp @@ -1,6 +1,7 @@ #include "gui/MainMenu.h" #include "AudioCapture.h" +#include "IconsFontAwesome7.h" #include "ProjectMSDLApplication.h" #include "ProjectMWrapper.h" @@ -22,145 +23,204 @@ MainMenu::MainMenu(ProjectMGUI& gui) , _projectMWrapper(Poco::Util::Application::instance().getSubsystem()) , _audioCapture(Poco::Util::Application::instance().getSubsystem()) { + _presetChooser.AllowedExtensions({".milk"}); + _presetChooser.MultiSelect(false); } void MainMenu::Draw() { if (ImGui::BeginMainMenuBar()) { - if (ImGui::BeginMenu("File")) - { - if (ImGui::MenuItem("Settings...", "Ctrl+s")) - { - _gui.ShowSettingsWindow(); - } - - ImGui::Separator(); + DrawFileMenu(); + DrawPlaybackMenu(); + DrawOptionsMenu(); + DrawHelpMenu(); - if (ImGui::MenuItem("Quit projectM", "Ctrl+q")) - { - _notificationCenter.postNotification(new QuitNotification); - } + ImGui::EndMainMenuBar(); + } - ImGui::EndMenu(); + if (_presetChooser.Draw()) + { + auto selectedFile = _presetChooser.SelectedFiles(); + if (!selectedFile.empty()) + { + // Open preset editor + _gui.ShowPresetEditor(selectedFile.at(0).path()); } + } +} - if (ImGui::BeginMenu("Playback")) +void MainMenu::DrawFileMenu() +{ + if (ImGui::BeginMenu("File")) + { + if (ImGui::BeginMenu(ICON_FA_PENCIL " Preset Editor")) { - auto& app = ProjectMSDLApplication::instance(); - - if (ImGui::MenuItem("Play Next Preset", "n")) - { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::LastPreset)); - } - if (ImGui::MenuItem("Play Previous Preset", "p")) + if (ImGui::MenuItem(ICON_FA_PENCIL " Edit Current Preset", "Ctrl+e")) { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::PreviousPreset)); + auto currentPreset = _projectMWrapper.CurrentPresetFileName(); + if (currentPreset.empty()) + { + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("No preset is currently loaded which can be edited.")); + } + else + { + _gui.ShowPresetEditor(currentPreset); + } } - if (ImGui::MenuItem("Go Back One Preset", "Backspace")) + if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Select Preset From Disk...", "Ctrl+l")) { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::LastPreset)); + _presetChooser.Title(ICON_FA_FILE_IMPORT " Select a Preset for Editing"); + _presetChooser.Show(); } - if (ImGui::MenuItem("Random Preset", "r")) + if (ImGui::MenuItem(ICON_FA_SQUARE_PLUS " Create New Preset", "Ctrl+Shift+n")) { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::RandomPreset)); + _gui.ShowPresetEditor({}); } - ImGui::Separator(); + ImGui::EndMenu(); + } - if (ImGui::MenuItem("Lock Preset", "Spacebar", app.config().getBool("projectM.presetLocked", false))) - { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::TogglePresetLocked)); - } - if (ImGui::MenuItem("Enable Shuffle", "y", app.config().getBool("projectM.shuffleEnabled", true))) - { - _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::ToggleShuffle)); - } + ImGui::Separator(); - ImGui::Separator(); + if (ImGui::MenuItem(ICON_FA_GEAR " Settings...", "Ctrl+s")) + { + _gui.ShowSettingsWindow(); + } - if (ImGui::MenuItem("Copy Current Preset Filename", "Ctrl+c")) - { - _projectMWrapper.PresetFileNameToClipboard(); - } + ImGui::Separator(); - ImGui::EndMenu(); + if (ImGui::MenuItem(ICON_FA_DOOR_OPEN " Quit projectM", "Ctrl+q")) + { + _notificationCenter.postNotification(new QuitNotification); } - if (ImGui::BeginMenu("Options")) + ImGui::EndMenu(); + } +} + +void MainMenu::DrawPlaybackMenu() +{ + if (ImGui::BeginMenu("Playback")) + { + auto& app = ProjectMSDLApplication::instance(); + + if (ImGui::MenuItem(ICON_FA_FORWARD_STEP " Play Next Preset", "n")) { - auto& app = ProjectMSDLApplication::instance(); + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::LastPreset)); + } + if (ImGui::MenuItem(ICON_FA_BACKWARD_STEP " Play Previous Preset", "p")) + { + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::PreviousPreset)); + } + if (ImGui::MenuItem(ICON_FA_ROTATE_LEFT " Go Back One Preset", "Backspace")) + { + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::LastPreset)); + } + if (ImGui::MenuItem(ICON_FA_WAND_MAGIC_SPARKLES " Random Preset", "r")) + { + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::RandomPreset)); + } - if (ImGui::BeginMenu("Audio Capture Device")) - { - auto devices = _audioCapture.AudioDeviceList(); - auto currentIndex = _audioCapture.AudioDeviceIndex(); + ImGui::Separator(); - for (const auto& device : devices) - { - if (ImGui::MenuItem(device.second.c_str(), "", device.first == currentIndex)) - { - _audioCapture.AudioDeviceIndex(device.first); - } - } - ImGui::EndMenu(); - } + if (ImGui::MenuItem(ICON_FA_LOCK " Lock Preset", "Spacebar", app.config().getBool("projectM.presetLocked", false))) + { + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::TogglePresetLocked)); + } + if (ImGui::MenuItem(ICON_FA_SHUFFLE " Enable Shuffle", "y", app.config().getBool("projectM.shuffleEnabled", true))) + { + _notificationCenter.postNotification(new PlaybackControlNotification(PlaybackControlNotification::Action::ToggleShuffle)); + } - ImGui::Separator(); + ImGui::Separator(); - if (ImGui::MenuItem("Display Toast Messages", "", app.config().getBool("projectM.displayToasts", true))) - { - app.UserConfiguration()->setBool("projectM.displayToasts", !app.config().getBool("projectM.displayToasts", true)); - } - if (ImGui::MenuItem("Display Preset Name in Window Title", "", app.config().getBool("window.displayPresetNameInTitle", true))) - { - app.UserConfiguration()->setBool("window.displayPresetNameInTitle", !app.config().getBool("window.displayPresetNameInTitle", true)); - _notificationCenter.postNotification(new UpdateWindowTitleNotification); - } + if (ImGui::MenuItem("Copy Current Preset Filename", "Ctrl+c")) + { + _projectMWrapper.PresetFileNameToClipboard(); + } - ImGui::Separator(); + ImGui::EndMenu(); + } +} - float beatSensitivity = projectm_get_beat_sensitivity(_projectMWrapper.ProjectM()); - if (ImGui::SliderFloat("Beat Sensitivity", &beatSensitivity, 0.0f, 2.0f)) +void MainMenu::DrawOptionsMenu() +{ + if (ImGui::BeginMenu("Options")) + { + auto& app = ProjectMSDLApplication::instance(); + + if (ImGui::BeginMenu("Audio Capture Device")) + { + auto devices = _audioCapture.AudioDeviceList(); + auto currentIndex = _audioCapture.AudioDeviceIndex(); + + for (const auto& device : devices) { - projectm_set_beat_sensitivity(_projectMWrapper.ProjectM(), beatSensitivity); - app.UserConfiguration()->setDouble("projectM.beatSensitivity", beatSensitivity); + if (ImGui::MenuItem(device.second.c_str(), "", device.first == currentIndex)) + { + _audioCapture.AudioDeviceIndex(device.first); + } } - ImGui::EndMenu(); } - if (ImGui::BeginMenu("Help")) + ImGui::Separator(); + + if (ImGui::MenuItem("Display Toast Messages", "", app.config().getBool("projectM.displayToasts", true))) { - if (ImGui::MenuItem("Quick Help...")) - { - _gui.ShowHelpWindow(); - } + app.UserConfiguration()->setBool("projectM.displayToasts", !app.config().getBool("projectM.displayToasts", true)); + } + if (ImGui::MenuItem("Display Preset Name in Window Title", "", app.config().getBool("window.displayPresetNameInTitle", true))) + { + app.UserConfiguration()->setBool("window.displayPresetNameInTitle", !app.config().getBool("window.displayPresetNameInTitle", true)); + _notificationCenter.postNotification(new UpdateWindowTitleNotification); + } - ImGui::Separator(); + ImGui::Separator(); - if (ImGui::MenuItem("About projectM...")) - { - _gui.ShowAboutWindow(); - } + float beatSensitivity = projectm_get_beat_sensitivity(_projectMWrapper.ProjectM()); + if (ImGui::SliderFloat("Beat Sensitivity", &beatSensitivity, 0.0f, 2.0f)) + { + projectm_set_beat_sensitivity(_projectMWrapper.ProjectM(), beatSensitivity); + app.UserConfiguration()->setDouble("projectM.beatSensitivity", beatSensitivity); + } - ImGui::Separator(); + ImGui::EndMenu(); + } +} - if (ImGui::MenuItem("Visit the projectM Wiki on GitHub")) - { - SystemBrowser::OpenURL("https://github.com/projectM-visualizer/projectm/wiki"); - } - if (ImGui::MenuItem("Report a Bug or Request a Feature")) - { - SystemBrowser::OpenURL("https://github.com/projectM-visualizer/projectm/issues/new/choose"); - } - if (ImGui::MenuItem("Sponsor projectM on OpenCollective")) - { - SystemBrowser::OpenURL("https://opencollective.com/projectm"); - } - ImGui::EndMenu(); +void MainMenu::DrawHelpMenu() +{ + if (ImGui::BeginMenu("Help")) + { + if (ImGui::MenuItem(ICON_FA_CIRCLE_QUESTION " Quick Help...")) + { + _gui.ShowHelpWindow(); } - ImGui::EndMainMenuBar(); + ImGui::Separator(); + + if (ImGui::MenuItem(ICON_FA_INFO " About projectM...")) + { + _gui.ShowAboutWindow(); + } + + ImGui::Separator(); + + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " Visit the projectM Wiki on GitHub")) + { + SystemBrowser::OpenURL("https://github.com/projectM-visualizer/projectm/wiki"); + } + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " Report a Bug or Request a Feature")) + { + SystemBrowser::OpenURL("https://github.com/projectM-visualizer/projectm/issues/new/choose"); + } + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " Sponsor projectM on OpenCollective")) + { + SystemBrowser::OpenURL("https://opencollective.com/projectm"); + } + + ImGui::EndMenu(); } } diff --git a/src/gui/MainMenu.h b/src/gui/MainMenu.h index b3e4fc3..133e779 100644 --- a/src/gui/MainMenu.h +++ b/src/gui/MainMenu.h @@ -1,6 +1,6 @@ #pragma once -#include +#include "FileChooser.h" class ProjectMGUI; class ProjectMWrapper; @@ -23,8 +23,15 @@ class MainMenu void Draw(); private: + void DrawFileMenu(); + void DrawPlaybackMenu(); + void DrawOptionsMenu(); + void DrawHelpMenu(); + Poco::NotificationCenter& _notificationCenter; //!< Notification center instance. ProjectMGUI& _gui; //!< Reference to the GUI subsystem. ProjectMWrapper& _projectMWrapper; //!< Reference to the projectM wrapper subsystem. AudioCapture& _audioCapture; //!< Reference to the audio capture subsystem. + + FileChooser _presetChooser{FileChooser::Mode::File}; //!< The file chooser dialog to select presets for editing. }; diff --git a/src/gui/ProjectMGUI.cpp b/src/gui/ProjectMGUI.cpp index 925c843..0085714 100644 --- a/src/gui/ProjectMGUI.cpp +++ b/src/gui/ProjectMGUI.cpp @@ -1,10 +1,19 @@ #include "ProjectMGUI.h" -#include "AnonymousProFont.h" -#include "LiberationSansFont.h" #include "ProjectMWrapper.h" #include "SDLRenderingWindow.h" +#include "AboutWindow.h" +#include "AnonymousProFont.h" +#include "FontAwesomeIconsRegular7.h" +#include "FontAwesomeIconsSolid7.h" +#include "HelpWindow.h" +#include "LiberationSansFont.h" +#include "MainMenu.h" +#include "PresetEditorGUI.h" +#include "SettingsWindow.h" +#include "ToastMessage.h" + #include "imgui.h" #include "imgui_impl_opengl3.h" #include "imgui_impl_sdl2.h" @@ -15,6 +24,17 @@ #include +ProjectMGUI::ProjectMGUI() + : _mainMenu(std::make_unique(*this)) + , _presetEditorGUI(std::make_unique(*this)) + , _settingsWindow(std::make_unique(*this)) + , _aboutWindow(std::make_unique(*this)) + , _helpWindow(std::make_unique()) +{ +} + +ProjectMGUI::~ProjectMGUI() = default; + const char* ProjectMGUI::name() const { return "Preset Selection GUI"; @@ -90,12 +110,20 @@ void ProjectMGUI::UpdateFontSize() _textScalingFactor = newScalingFactor; - ImFontConfig config; - config.MergeMode = true; - io.Fonts->Clear(); - _uiFont = io.Fonts->AddFontFromMemoryCompressedTTF(&AnonymousPro_compressed_data, AnonymousPro_compressed_size, floor(24.0f * _textScalingFactor)); - _toastFont = io.Fonts->AddFontFromMemoryCompressedTTF(&LiberationSans_compressed_data, LiberationSans_compressed_size, floor(40.0f * _textScalingFactor)); + + ImFontConfig configMainFont; + configMainFont.MergeMode = false; + + ImFontConfig configIconFont; + configIconFont.MergeMode = true; + configIconFont.GlyphMinAdvanceX = floor(24.0f * _textScalingFactor); + + _uiFont = io.Fonts->AddFontFromMemoryCompressedTTF(&AnonymousPro_compressed_data, AnonymousPro_compressed_size, floor(24.0f * _textScalingFactor), &configMainFont); + io.Fonts->AddFontFromMemoryCompressedTTF(&FontAwesomeIconsSolid7_compressed_data, FontAwesomeIconsSolid7_compressed_size, floor(24.0f * _textScalingFactor), &configIconFont); + io.Fonts->AddFontFromMemoryCompressedTTF(&FontAwesomeIconsRegular7_compressed_data, FontAwesomeIconsRegular7_compressed_size, floor(24.0f * _textScalingFactor), &configIconFont); + + _toastFont = io.Fonts->AddFontFromMemoryCompressedTTF(&LiberationSans_compressed_data, LiberationSans_compressed_size, floor(40.0f * _textScalingFactor), &configMainFont); io.Fonts->Build(); ImGui::GetStyle().ScaleAllSizes(1.0); @@ -160,10 +188,13 @@ void ProjectMGUI::Draw() if (_visible) { - _mainMenu.Draw(); - _settingsWindow.Draw(); - _aboutWindow.Draw(); - _helpWindow.Draw(); + if (!_presetEditorGUI->Draw()) + { + _mainMenu->Draw(); + _settingsWindow->Draw(); + _aboutWindow->Draw(); + _helpWindow->Draw(); + } } ImGui::Render(); @@ -192,24 +223,39 @@ void ProjectMGUI::PushUIFont() ImGui::PushFont(_uiFont); } +void ProjectMGUI::PushSolidIconsFont() +{ + ImGui::PushFont(_fontAwesomeIconsSolid); +} + +void ProjectMGUI::PushRegularIconsFont() +{ + ImGui::PushFont(_fontAwesomeIconsRegular); +} + void ProjectMGUI::PopFont() { ImGui::PopFont(); } +void ProjectMGUI::ShowPresetEditor(const std::string& presetFileName) +{ + _presetEditorGUI->Show(presetFileName); +} + void ProjectMGUI::ShowSettingsWindow() { - _settingsWindow.Show(); + _settingsWindow->Show(); } void ProjectMGUI::ShowAboutWindow() { - _aboutWindow.Show(); + _aboutWindow->Show(); } void ProjectMGUI::ShowHelpWindow() { - _helpWindow.Show(); + _helpWindow->Show(); } float ProjectMGUI::GetScalingFactor() diff --git a/src/gui/ProjectMGUI.h b/src/gui/ProjectMGUI.h index aca9d88..f209957 100644 --- a/src/gui/ProjectMGUI.h +++ b/src/gui/ProjectMGUI.h @@ -1,11 +1,5 @@ #pragma once -#include "AboutWindow.h" -#include "HelpWindow.h" -#include "MainMenu.h" -#include "SettingsWindow.h" -#include "ToastMessage.h" - #include "notifications/DisplayToastNotification.h" #include @@ -15,13 +9,28 @@ #include +#include + +namespace Editor { +class PresetEditorGUI; +} + struct ImFont; class ProjectMWrapper; class SDLRenderingWindow; +class MainMenu; +class SettingsWindow; +class AboutWindow; +class HelpWindow; +class ToastMessage; class ProjectMGUI : public Poco::Util::Subsystem { public: + ProjectMGUI(); + + ~ProjectMGUI() override; + const char* name() const override; void initialize(Poco::Util::Application& app) override; @@ -84,11 +93,27 @@ class ProjectMGUI : public Poco::Util::Subsystem */ void PushUIFont(); + /** + * @brief Pushes the "Solid" icon font to the render stack + */ + void PushSolidIconsFont(); + + /** + * @brief Pushes the "Regular" icon font to the render stack + */ + void PushRegularIconsFont(); + /** * @brief Pops the last font from the stack */ void PopFont(); + /** + * @brief Opens the preset editor UI. + * @param presetFileName The file name of the preset to edit, or empty to use the currently loaded preset. + */ + void ShowPresetEditor(const std::string& presetFileName); + /** * @brief Displays the settings window. */ @@ -121,16 +146,19 @@ class ProjectMGUI : public Poco::Util::Subsystem SDL_GLContext _glContext{nullptr}; //!< Pointer to the OpenGL context associated with the window. ImFont* _uiFont{nullptr}; //!< Main UI font (monospaced). ImFont* _toastFont{nullptr}; //!< Toast message font (sans-serif, larger). + ImFont* _fontAwesomeIconsSolid{nullptr}; //!< "Solid" icons set by FontAwesome. + ImFont* _fontAwesomeIconsRegular{nullptr}; //!< "Regular" icons set by FontAwesome. uint64_t _lastFrameTicks{0}; //!< Tick count of the last frame (see SDL_GetTicks64) float _userScalingFactor{1.0f}; //!< The user-defined UI scaling factor. float _textScalingFactor{0.0f}; //!< The text scaling factor. - MainMenu _mainMenu{*this}; - SettingsWindow _settingsWindow{*this}; //!< The settings window. - AboutWindow _aboutWindow{*this}; //!< The about window. - HelpWindow _helpWindow; //!< Help window with shortcuts and tips. + std::unique_ptr _mainMenu; + std::unique_ptr _presetEditorGUI; //!< The preset editor GUI. + std::unique_ptr _settingsWindow; //!< The settings window. + std::unique_ptr _aboutWindow; //!< The about window. + std::unique_ptr _helpWindow; //!< Help window with shortcuts and tips. std::unique_ptr _toast; //!< Current toast to be displayed. diff --git a/src/gui/SettingsWindow.cpp b/src/gui/SettingsWindow.cpp index fa3844d..09b8f5e 100644 --- a/src/gui/SettingsWindow.cpp +++ b/src/gui/SettingsWindow.cpp @@ -4,6 +4,7 @@ #include "ProjectMSDLApplication.h" #include "SDLRenderingWindow.h" +#include "IconsFontAwesome7.h" #include "ProjectMGUI.h" #include "notifications/DisplayToastNotification.h" @@ -38,7 +39,7 @@ void SettingsWindow::Draw() constexpr ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoCollapse; constexpr ImGuiTabBarFlags tabBarFlags = ImGuiTabBarFlags_None; - std::string windowId = "Settings"; + std::string windowId = ICON_FA_GEAR " Settings"; if (_changed) { windowId.append(" [CHANGED - NOT SAVED]"); @@ -84,7 +85,7 @@ void SettingsWindow::DrawProjectMSettingsTab() ImGui::TableSetupColumn("##desc", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableSetupColumn("##setting", ImGuiTableColumnFlags_WidthStretch, .0f); ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 50.0f); + ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupColumn("##override", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableNextRow(); @@ -168,7 +169,7 @@ void SettingsWindow::DrawWindowSettingsTab() ImGui::TableSetupColumn("##desc", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableSetupColumn("##setting", ImGuiTableColumnFlags_WidthStretch, .0f); ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 50.0f); + ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupColumn("##override", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableNextRow(); @@ -270,7 +271,7 @@ void SettingsWindow::DrawAudioSettingsTab() { ImGui::TableSetupColumn("##desc", ImGuiTableColumnFlags_WidthFixed, .0f); ImGui::TableSetupColumn("##setting", ImGuiTableColumnFlags_WidthStretch, .0f); - ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 50.0f); + ImGui::TableSetupColumn("##choose", ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupColumn("##reset", ImGuiTableColumnFlags_WidthFixed, 100.0f); ImGui::TableSetupColumn("##override", ImGuiTableColumnFlags_WidthFixed, .0f); @@ -322,7 +323,7 @@ void SettingsWindow::DrawHelpTab() const void SettingsWindow::SaveButton() { - if (ImGui::Button("Save Settings")) + if (ImGui::Button(ICON_FA_FLOPPY_DISK " Save Settings")) { try { @@ -384,7 +385,7 @@ void SettingsWindow::PathSetting(const std::string& property) if (ImGui::Button("...")) { _pathChooser.CurrentDirectory(path); - _pathChooser.Title("Select directory"); + _pathChooser.Title(ICON_FA_FOLDER_OPEN " Select directory"); _pathChooser.Context(property); _pathChooser.Show(); } @@ -605,7 +606,7 @@ bool SettingsWindow::ResetButton(const std::string& property1, const std::string bool pressed{false}; ImGui::PushID(std::string(property1 + property2 + "_ResetButton").c_str()); - if (ImGui::Button("Reset")) + if (ImGui::Button(ICON_FA_ERASER " Reset")) { _userConfiguration->remove(property1); if (!property2.empty()) diff --git a/src/gui/preset_editor/CMakeLists.txt b/src/gui/preset_editor/CMakeLists.txt new file mode 100644 index 0000000..a191f3c --- /dev/null +++ b/src/gui/preset_editor/CMakeLists.txt @@ -0,0 +1,36 @@ +add_subdirectory(imgui_color_text_editor) + +add_library(PresetEditor STATIC + CodeContextInformation.cpp + CodeContextInformation.h + CodeEditorTab.cpp + CodeEditorTab.h + CodeEditorWindow.cpp + CodeEditorWindow.h + EditorMenu.cpp + EditorMenu.h + EditorPreset.cpp + EditorPreset.h + ExpressionCodeTypes.h + PresetEditorGUI.cpp + PresetEditorGUI.h + PresetFile.cpp + PresetFile.h + ) + +target_link_libraries(PresetEditor + PUBLIC + projectM::Eval + ImGUIColorTextEditor + ImGui + Poco::Util + libprojectM::projectM + ) + +target_include_directories(PresetEditor + PRIVATE + "${CMAKE_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/.." + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) diff --git a/src/gui/preset_editor/CodeContextInformation.cpp b/src/gui/preset_editor/CodeContextInformation.cpp new file mode 100644 index 0000000..d0c0ea5 --- /dev/null +++ b/src/gui/preset_editor/CodeContextInformation.cpp @@ -0,0 +1,968 @@ +#include "CodeContextInformation.h" + +#include "IconsFontAwesome7.h" + +#include + +namespace Editor { + +std::string CodeContextInformation::GetContextName(ExpressionCodeTypes type, int index) +{ + switch (type) + { + case ExpressionCodeTypes::PerFrameInit: + return "Per-Frame Init"; + case ExpressionCodeTypes::PerFrame: + return "Per-Frame"; + case ExpressionCodeTypes::PerVertex: + return "Per-Vertex"; + case ExpressionCodeTypes::CustomWaveInit: + return "Wave " + std::to_string(index) + " Init"; + case ExpressionCodeTypes::CustomWavePerFrame: + return "Wave " + std::to_string(index) + " Per-Frame"; + case ExpressionCodeTypes::CustomWavePerPoint: + return "Wave " + std::to_string(index) + " Per-Point"; + case ExpressionCodeTypes::CustomShapeInit: + return "Shape " + std::to_string(index) + " Init"; + case ExpressionCodeTypes::CustomShapePerFrame: + return "Shape " + std::to_string(index) + " Per-Frame"; + case ExpressionCodeTypes::WarpShader: + return "Warp Shader"; + case ExpressionCodeTypes::CompositeShader: + return "Composite Shader"; + } + + throw std::runtime_error(std::string("Unknown value for argument ExpressionCodeTypes type in CodeContextInformation::GetContextName()")); +} + +std::vector> CodeContextInformation::GetIdentifierList(ExpressionCodeTypes type) +{ + static std::map>> identifierList{ + {ExpressionCodeTypes::PerFrameInit, {{"zoom", "Controls inward/outward motion.\nWritable."}, // + {"zoomexp", "Controls the curvature of the zoom.\nWritable."}, // + {"rot", "Controls the amount of rotation.\nWritable."}, // + {"warp", "Controls the magnitude of the warping.\nWritable."}, // + {"cx", "Controls where the center of rotation and stretching is, horizontally.\nWritable."}, // + {"cy", "Controls where the center of rotation and stretching is, vertically.\nWritable."}, // + {"dx", "Controls amount of constant horizontal motion.\nWritable."}, // + {"dy", "Controls amount of constant vertical motion.\nWritable."}, // + {"sx", "Controls amount of constant horizontal stretching.\nWritable."}, // + {"sy", "Controls amount of constant vertical stretching.\nWritable."}, // + {"wave_mode", "Controls which of the 8/16 types of waveform is drawn.\nWritable."}, // + {"wave_x", "Horizontal position of the waveform (0..1).\nWritable."}, // + {"wave_y", "Vertical position of the waveform (0..1).\nWritable."}, // + {"wave_r", "Amount of red color in the wave (0..1).\nWritable."}, // + {"wave_g", "Amount of green color in the wave (0..1).\nWritable."}, // + {"wave_b", "Amount of blue color in the wave (0..1).\nWritable."}, // + {"wave_a", "Opacity of the wave (0..1).\nWritable."}, // + {"wave_mystery", "This value does different things for each waveform (-1..1).\nWritable."}, // + {"wave_usedots", "If 1, the waveform is drawn as dots instead of lines (0/1).\nWritable."}, // + {"wave_thick", "If 1, the waveform's lines (or dots) are drawn with double thickness (0/1).\nWritable."}, // + {"wave_additive", "If 1, the wave is drawn additively, saturating the image at white (0/1).\nWritable."}, // + {"wave_brighten", "If 1, all 3 r/g/b colors will be scaled up until at least one reaches 1.0 (0/1)\nWritable."}, // + {"ob_size", "Thickness of the outer border drawn at the edges of the screen every frame (0..0.5).\nWritable."}, // + {"ob_r", "Amount of red color in the outer border (0..1).\nWritable."}, // + {"ob_g", "Amount of green color in the outer border (0..1).\nWritable."}, // + {"ob_b", "Amount of blue color in the outer border (0..1).\nWritable."}, // + {"ob_a", "Opacity of the outer border (0=transparent, 1=opaque).\nWritable."}, // + {"ib_size", "Thickness of the inner border drawn at the edges of the screen every frame (0..0.5).\nWritable."}, // + {"ib_r", "Amount of red color in the inner border (0..1).\nWritable."}, // + {"ib_g", "Amount of green color in the inner border (0..1).\nWritable."}, // + {"ib_b", "Amount of blue color in the inner border (0..1).\nWritable."}, // + {"ib_a", "Opacity of the inner border (0=transparent, 1=opaque).\nWritable."}, // + {"mv_r", "Amount of red color in the motion vectors (0..1).\nWritable."}, // + {"mv_g", "Amount of green color in the motion vectors (0..1).\nWritable."}, // + {"mv_b", "Amount of blue color in the motion vectors (0..1).\nWritable."}, // + {"mv_a", "Opacity of the motion vectors (0=transparent, 1=opaque).\nWritable."}, // + {"mv_x", "The number of motion vectors in the X direction (0..64).\nWritable."}, // + {"mv_y", "The number of motion vectors in the Y direction (0..48).\nWritable."}, // + {"mv_l", "The length of the motion vectors (0=no trail, 1=normal, 2=double...).\nWritable."}, // + {"mv_dx", "Horizontal placement offset of the motion vectors (-1..1).\nWritable."}, // + {"mv_dy", "Vertical placement offset of the motion vectors (-1..1).\nWritable."}, // + {"decay", "Controls the eventual fade to black (1=no fade, 0.9=strong fade, 0.98=recommended).\nWritable."}, // + {"gamma", "Controls display brightness (1=normal, 2=double, 3=triple, etc.).\nWritable."}, // + {"echo_zoom", "Controls the size of the second graphics layer (>0).\nWritable."}, // + {"echo_alpha", "Controls the opacity of the second graphics layer (0=transparent/off, 0.5=half-mix, 1=opaque).\nWritable."}, // + {"echo_orient", "Selects an orientation for the second graphics layer (0=normal, 1=flip on x, 2=flip on y, 3=flip on both).\nWritable."}, // + {"darken_center", "If 1, help keeps the image from getting too bright by continually dimming the center point (0/1).\nWritable."}, // + {"wrap", "Sets whether or not screen elements can drift off of one side and onto the other (0/1).\nWritable."}, // + {"invert", "Inverts the colors in the image (0/1).\nWritable."}, // + {"brighten", "Brightens the darker parts of the image (0/1).\nWritable."}, // + {"darken", "Darkens the brighter parts of the image (0/1).\nWritable."}, // + {"solarize", "Emphasizes mid-range colors (0/1).\nWritable."}, // + {"monitor", "Value for debugging preset code. Unsupported in projectM.\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"meshx", "Tells you the user's per-vertex mesh size in the X direction. Always an integer value.\nREAD-ONLY."}, // + {"meshy", "Tells you the user's per-vertex mesh size in the Y direction. Always an integer value.\nREAD-ONLY."}, // + {"pixelsx", "Width of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"pixelsy", "Height of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"aspectx", "Multiply an X coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"aspecty", "Multiply a Y coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"blur1_min", "Blur level 1 range minimum (0..1).\nWritable."}, // + {"blur1_max", "Blur level 1 range maximum (0..1).\nWritable."}, // + {"blur2_min", "Blur level 2 range minimum (0..1).\nWritable."}, // + {"blur2_max", "Blur level 2 range maximum (0..1).\nWritable."}, // + {"blur3_min", "Blur level 3 range minimum (0..1).\nWritable."}, // + {"blur3_max", "Blur level 3 range maximum (0..1).\nWritable."}, // + {"blur1_edge_darken", "Amount of edge darkening at blur level 1 (0..1).\nWritable."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}}}, + + {ExpressionCodeTypes::PerFrame, {{"zoom", "Controls inward/outward motion.\nWritable."}, // + {"zoomexp", "Controls the curvature of the zoom.\nWritable."}, // + {"rot", "Controls the amount of rotation.\nWritable."}, // + {"warp", "Controls the magnitude of the warping.\nWritable."}, // + {"cx", "Controls where the center of rotation and stretching is, horizontally.\nWritable."}, // + {"cy", "Controls where the center of rotation and stretching is, vertically.\nWritable."}, // + {"dx", "Controls amount of constant horizontal motion.\nWritable."}, // + {"dy", "Controls amount of constant vertical motion.\nWritable."}, // + {"sx", "Controls amount of constant horizontal stretching.\nWritable."}, // + {"sy", "Controls amount of constant vertical stretching.\nWritable."}, // + {"wave_mode", "Controls which of the 8/16 types of waveform is drawn.\nWritable."}, // + {"wave_x", "Horizontal position of the waveform (0..1).\nWritable."}, // + {"wave_y", "Vertical position of the waveform (0..1).\nWritable."}, // + {"wave_r", "Amount of red color in the wave (0..1).\nWritable."}, // + {"wave_g", "Amount of green color in the wave (0..1).\nWritable."}, // + {"wave_b", "Amount of blue color in the wave (0..1).\nWritable."}, // + {"wave_a", "Opacity of the wave (0..1).\nWritable."}, // + {"wave_mystery", "This value does different things for each waveform (-1..1).\nWritable."}, // + {"wave_usedots", "If 1, the waveform is drawn as dots instead of lines (0/1).\nWritable."}, // + {"wave_thick", "If 1, the waveform's lines (or dots) are drawn with double thickness (0/1).\nWritable."}, // + {"wave_additive", "If 1, the wave is drawn additively, saturating the image at white (0/1).\nWritable."}, // + {"wave_brighten", "If 1, all 3 r/g/b colors will be scaled up until at least one reaches 1.0 (0/1)\nWritable."}, // + {"ob_size", "Thickness of the outer border drawn at the edges of the screen every frame (0..0.5).\nWritable."}, // + {"ob_r", "Amount of red color in the outer border (0..1).\nWritable."}, // + {"ob_g", "Amount of green color in the outer border (0..1).\nWritable."}, // + {"ob_b", "Amount of blue color in the outer border (0..1).\nWritable."}, // + {"ob_a", "Opacity of the outer border (0=transparent, 1=opaque).\nWritable."}, // + {"ib_size", "Thickness of the inner border drawn at the edges of the screen every frame (0..0.5).\nWritable."}, // + {"ib_r", "Amount of red color in the inner border (0..1).\nWritable."}, // + {"ib_g", "Amount of green color in the inner border (0..1).\nWritable."}, // + {"ib_b", "Amount of blue color in the inner border (0..1).\nWritable."}, // + {"ib_a", "Opacity of the inner border (0=transparent, 1=opaque).\nWritable."}, // + {"mv_r", "Amount of red color in the motion vectors (0..1).\nWritable."}, // + {"mv_g", "Amount of green color in the motion vectors (0..1).\nWritable."}, // + {"mv_b", "Amount of blue color in the motion vectors (0..1).\nWritable."}, // + {"mv_a", "Opacity of the motion vectors (0=transparent, 1=opaque).\nWritable."}, // + {"mv_x", "The number of motion vectors in the X direction (0..64).\nWritable."}, // + {"mv_y", "The number of motion vectors in the Y direction (0..48).\nWritable."}, // + {"mv_l", "The length of the motion vectors (0=no trail, 1=normal, 2=double...).\nWritable."}, // + {"mv_dx", "Horizontal placement offset of the motion vectors (-1..1).\nWritable."}, // + {"mv_dy", "Vertical placement offset of the motion vectors (-1..1).\nWritable."}, // + {"decay", "Controls the eventual fade to black (1=no fade, 0.9=strong fade, 0.98=recommended).\nWritable."}, // + {"gamma", "Controls display brightness (1=normal, 2=double, 3=triple, etc.).\nWritable."}, // + {"echo_zoom", "Controls the size of the second graphics layer (>0).\nWritable."}, // + {"echo_alpha", "Controls the opacity of the second graphics layer (0=transparent/off, 0.5=half-mix, 1=opaque).\nWritable."}, // + {"echo_orient", "Selects an orientation for the second graphics layer (0=normal, 1=flip on x, 2=flip on y, 3=flip on both).\nWritable."}, // + {"darken_center", "If 1, help keeps the image from getting too bright by continually dimming the center point (0/1).\nWritable."}, // + {"wrap", "Sets whether or not screen elements can drift off of one side and onto the other (0/1).\nWritable."}, // + {"invert", "Inverts the colors in the image (0/1).\nWritable."}, // + {"brighten", "Brightens the darker parts of the image (0/1).\nWritable."}, // + {"darken", "Darkens the brighter parts of the image (0/1).\nWritable."}, // + {"solarize", "Emphasizes mid-range colors (0/1).\nWritable."}, // + {"monitor", "Value for debugging preset code. Unsupported in projectM.\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"meshx", "Tells you the user's per-vertex mesh size in the X direction. Always an integer value.\nREAD-ONLY."}, // + {"meshy", "Tells you the user's per-vertex mesh size in the Y direction. Always an integer value.\nREAD-ONLY."}, // + {"pixelsx", "Width of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"pixelsy", "Height of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"aspectx", "Multiply an X coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"aspecty", "Multiply a Y coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"blur1_min", "Blur level 1 range minimum (0..1).\nWritable."}, // + {"blur1_max", "Blur level 1 range maximum (0..1).\nWritable."}, // + {"blur2_min", "Blur level 2 range minimum (0..1).\nWritable."}, // + {"blur2_max", "Blur level 2 range maximum (0..1).\nWritable."}, // + {"blur3_min", "Blur level 3 range minimum (0..1).\nWritable."}, // + {"blur3_max", "Blur level 3 range maximum (0..1).\nWritable."}, // + {"blur1_edge_darken", "Amount of edge darkening at blur level 1 (0..1).\nWritable."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}}}, + + {ExpressionCodeTypes::PerVertex, {{"x", "Retrieves the X position of the current pixel (0..1).\nREAD-ONLY."}, // + {"y", "Retrieves the Y position of the current pixel (0..1).\nREAD-ONLY."}, // + {"rad", "Retrieves the distance of the pixel from the center of the screen.\nAt the center of the screen this will be zero, and at the corners, 1.\nREAD-ONLY."}, // + {"ang", "Retrieves the angle of the current pixel, with respect to the center of the screen.\nThis is equal to the arctangent of y over x.\nREAD-ONLY."}, // + {"zoom", "Controls inward/outward motion.\nWritable."}, // + {"zoomexp", "Controls the curvature of the zoom.\nWritable."}, // + {"rot", "Controls the amount of rotation.\nWritable."}, // + {"warp", "Controls the magnitude of the warping.\nWritable."}, // + {"cx", "Controls where the center of rotation and stretching is, horizontally.\nWritable."}, // + {"cy", "Controls where the center of rotation and stretching is, vertically.\nWritable."}, // + {"dx", "Controls amount of constant horizontal motion.\nWritable."}, // + {"dy", "Controls amount of constant vertical motion.\nWritable."}, // + {"sx", "Controls amount of constant horizontal stretching.\nWritable."}, // + {"sy", "Controls amount of constant vertical stretching.\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"meshx", "Tells you the user's per-vertex mesh size in the X direction. Always an integer value.\nREAD-ONLY."}, // + {"meshy", "Tells you the user's per-vertex mesh size in the Y direction. Always an integer value.\nREAD-ONLY."}, // + {"pixelsx", "Width of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"pixelsy", "Height of the rendering canvas, in pixels.\nREAD-ONLY."}, // + {"aspectx", "Multiply an X coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"aspecty", "Multiply a Y coordinate by this to make the preset look the same at any aspect (>0).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}}}, + + {ExpressionCodeTypes::CustomWaveInit, {{"r", "Base amount of red color in the wave (0..1).\nWritable."}, // + {"g", "Base amount of green color in the wave (0..1).\nWritable."}, // + {"b", "Base amount of blue color in the wave (0..1).\nWritable."}, // + {"a", "Base opacity of the waveform (0=transparent, 1=opaque).\nWritable."}, // + {"samples", "Read: retrieves the # of samples specified for this custom wave (from the menu).\nWrite: lets you dynamically change that #, frame to frame (0..512).\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"t1", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t2", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t3", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t4", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t5", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t6", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t7", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t8", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}}}, + + {ExpressionCodeTypes::CustomWavePerFrame, {{"r", "Base amount of red color in the wave (0..1).\nWritable."}, // + {"g", "Base amount of green color in the wave (0..1).\nWritable."}, // + {"b", "Base amount of blue color in the wave (0..1).\nWritable."}, // + {"a", "Base opacity of the waveform (0=transparent, 1=opaque).\nWritable."}, // + {"samples", "Read: retrieves the # of samples specified for this custom wave (from the menu).\nWrite: lets you dynamically change that #, frame to frame (0..512).\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"t1", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t2", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t3", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t4", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t5", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t6", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t7", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t8", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}}}, + + {ExpressionCodeTypes::CustomWavePerPoint, {{"r", "Base amount of red color in the wave (0..1).\nWritable."}, // + {"g", "Base amount of green color in the wave (0..1).\nWritable."}, // + {"b", "Base amount of blue color in the wave (0..1).\nWritable."}, // + {"a", "Base opacity of the waveform (0=transparent, 1=opaque).\nWritable."}, // + {"x", "The X position of this point that makes up the wave (0=left, 1=right).\nWritable."}, // + {"y", "The Y position of this point that makes up the wave (0=left, 1=right).\nWritable."}, // + {"sample", "How far along we are, through the samples that make up the waveform (0=first sample, 0.5 = half-way through, 1=last sample).\nREAD-ONLY."}, // + {"value1", "The value of the left audio channel sample or spectrum at this point in the waveform.\nREAD-ONLY."}, // + {"value2", "The value of the right audio channel sample or spectrum at this point in the waveform.\nREAD-ONLY."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"t1", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t2", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t3", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t4", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t5", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t6", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t7", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}, // + {"t8", "t1 to t8 values are carried from each wave init to per-frame to per-point.\nWritable."}}}, + + {ExpressionCodeTypes::CustomShapeInit, {{"num_inst", "The total # of shape instances (1..1024).\nREAD-ONLY."}, // + {"sides", "The default number of sides that make up the polygonal shape (3..100).\nWritable."}, // + {"thick", "If non-zero, the border will be overdrawn 4 times to make it thicker, bolder, and more visible (0/1).\nWritable."}, // + {"additive", "If non-zero, the shape will add color to saturate the image towards white; otherwise, it will replace what's there (0/1).\nWritable."}, // + {"x", "Default X position of the shape (0..1; 0=left side, 1=right side).\nWritable."}, // + {"y", "Default Y position of the shape (0..1; 0=left side, 1=right side).\nWritable."}, // + {"rad", "Default radius of the shape (>=0).\nWritable."}, // + {"ang", "Default rotation angle of the shape (0...2*pi).\nWritable."}, // + {"textured", "If non-zero, the shape will be textured with the image from the previous frame (0/1).\nWritable."}, // + {"tex_zoom", "The portion of the previous frame's image to use with the shape (>0).\nWritable."}, // + {"tex_ang", "The angle at which to rotate the previous frame's image before applying it to the shape (0..2*PI).\nWritable."}, // + {"r", "Default amount of red color toward the center of the shape (0..1).\nWritable."}, // + {"g", "Default amount of green color toward the center of the shape (0..1).\nWritable."}, // + {"b", "Default amount of blue color toward the center of the shape (0..1).\nWritable."}, // + {"a", "Default opacity of the center of the shape (0=transparent, 1=opaque).\nWritable."}, // + {"r2", "Default amount of red color toward the outer edge of the shape (0..1).\nWritable."}, // + {"g2", "Default amount of green color toward the outer edge of the shape (0..1).\nWritable."}, // + {"b2", "Default amount of blue color toward the outer edge of the shape (0..1).\nWritable."}, // + {"a2", "Default opacity of the outer edge of the shape (0=transparent, 1=opaque).\nWritable."}, // + {"border_r", "Default amount of red color in the shape's border (0..1).\nWritable."}, // + {"border_g", "Default amount of green color in the shape's border (0..1).\nWritable."}, // + {"border_b", "Default amount of blue color in the shape's border (0..1).\nWritable."}, // + {"border_a", "Default opacity of the shape's border (0=transparent, 1=opaque).\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"t1", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t2", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t3", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t4", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t5", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t6", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t7", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t8", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}}}, + + {ExpressionCodeTypes::CustomShapePerFrame, {{"num_inst", "The total # of shape instances (1..1024).\nREAD-ONLY."}, // + {"instance", "The current instance number that this code is being executed for (0..num_inst-1).\nREAD-ONLY."}, // + {"sides", "The default number of sides that make up the polygonal shape (3..100).\nWritable."}, // + {"thick", "If non-zero, the border will be overdrawn 4 times to make it thicker, bolder, and more visible (0/1).\nWritable."}, // + {"additive", "If non-zero, the shape will add color to saturate the image towards white; otherwise, it will replace what's there (0/1).\nWritable."}, // + {"x", "Default X position of the shape (0..1; 0=left side, 1=right side).\nWritable."}, // + {"y", "Default Y position of the shape (0..1; 0=left side, 1=right side).\nWritable."}, // + {"rad", "Default radius of the shape (>=0).\nWritable."}, // + {"ang", "Default rotation angle of the shape (0...2*pi).\nWritable."}, // + {"textured", "If non-zero, the shape will be textured with the image from the previous frame (0/1).\nWritable."}, // + {"tex_zoom", "The portion of the previous frame's image to use with the shape (>0).\nWritable."}, // + {"tex_ang", "The angle at which to rotate the previous frame's image before applying it to the shape (0..2*PI).\nWritable."}, // + {"r", "Default amount of red color toward the center of the shape (0..1).\nWritable."}, // + {"g", "Default amount of green color toward the center of the shape (0..1).\nWritable."}, // + {"b", "Default amount of blue color toward the center of the shape (0..1).\nWritable."}, // + {"a", "Default opacity of the center of the shape (0=transparent, 1=opaque).\nWritable."}, // + {"r2", "Default amount of red color toward the outer edge of the shape (0..1).\nWritable."}, // + {"g2", "Default amount of green color toward the outer edge of the shape (0..1).\nWritable."}, // + {"b2", "Default amount of blue color toward the outer edge of the shape (0..1).\nWritable."}, // + {"a2", "Default opacity of the outer edge of the shape (0=transparent, 1=opaque).\nWritable."}, // + {"border_r", "Default amount of red color in the shape's border (0..1).\nWritable."}, // + {"border_g", "Default amount of green color in the shape's border (0..1).\nWritable."}, // + {"border_b", "Default amount of blue color in the shape's border (0..1).\nWritable."}, // + {"border_a", "Default opacity of the shape's border (0=transparent, 1=opaque).\nWritable."}, // + {"time", "Retrieves the current time, in seconds, since program start.\nREAD-ONLY."}, // + {"fps", "Retrieves the current framerate, in frames per second.\nREAD-ONLY."}, // + {"frame", "Retrieves the number of frames of animation elapsed since the program started.\nREAD-ONLY."}, // + {"progress", "Progress through the current preset.\nREAD-ONLY."}, // + {"bass", "Retrieves the current amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid", "Retrieves the current amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb", "Retrieves the current amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"bass_att", "Retrieves the attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"mid_att", "Retrieves the attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"treb_att", "Retrieves the attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud).\nREAD-ONLY."}, // + {"q1", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q2", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q3", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q4", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q5", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q6", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q7", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q8", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q9", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q10", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q11", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q12", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q13", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q14", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q15", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q16", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q17", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q18", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q19", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q20", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q21", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q22", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q23", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q24", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q25", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q26", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q27", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q28", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q29", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q30", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q31", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"q32", "q1 to q32 values are carried along a chain to each code block.\nWritable."}, // + {"t1", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t2", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t3", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t4", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t5", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t6", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t7", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}, // + {"t8", "t1 to t8 values are carried from each shape init to per-frame.\nWritable."}}}, + + {ExpressionCodeTypes::WarpShader, {{"shader_body", ICON_FA_PAPER_PLANE " Shader entry point.\nWill be replaced with the appropriate function declaration at runtime."}, // + {"ret", ICON_FA_CUBE " float3\n" ICON_FA_RIGHT_FROM_BRACKET " Shader output RGB color."}, // + {"M_PI", ICON_FA_CUBE " float\nPi constant (3.14159265359)"}, // + {"M_PI_2", ICON_FA_CUBE " float\n2*Pi constant (6.28318530718)"}, // + {"M_INV_PI_2", ICON_FA_CUBE " float\n1/Pi constant (0.159154943091895)"}, // + {"GetMain", ICON_FA_CUBES " float3 GetMain(float2 uv)\nSample main texture at uv, equivalent to:\ntex2D(sampler_main, uv).xyz"}, // + {"GetPixel", ICON_FA_CUBES " float3 GetMain(float2 uv)\nSample main texture at uv, equivalent to:\ntex2D(sampler_main, uv).xyz"}, // + {"GetBlur1", ICON_FA_CUBES " float3 GetBlur1(float2 uv)\nSample blur1 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur1, uv).xyz * blur1_min + blur1_max"}, // + {"GetBlur2", ICON_FA_CUBES " float3 GetBlur2(float2 uv)\nSample blur2 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur2, uv).xyz * blur2_min + blur2_max"}, // + {"GetBlur3", ICON_FA_CUBES " float3 GetBlur3(float2 uv)\nSample blur3 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur3, uv).xyz * blur3_min + blur3_max"}, // + {"lum", ICON_FA_CUBES " float lum(float3 color)\nCalculates the luminosity of the given color equivalent to:\ndot(color, float3(0.32, 0.49, 0.29)"}, // + {"tex2d", ICON_FA_CUBE " float4\nLower-case alias for tex2D"}, // + {"tex3d", ICON_FA_CUBE " float4\nLower-case alias for tex3D"}, // + {"uv", ICON_FA_CUBE " float2\nWarped UV coordinates (approx. 0..1)."}, // + {"uv_orig", ICON_FA_CUBE " float2\nOriginal, unwarped UV coordinates (0..1)."}, // + {"rad", ICON_FA_CUBE " float\nRadius of the current pixel from center of screen (0..1)."}, // + {"ang", ICON_FA_CUBE " float\nAngle of the current pixel from center of screen (0..2*PI)."}, // + {"rand_preset", ICON_FA_CUBE " float4\n4 random floats [0..1], updated once per preset."}, // + {"rand_frame", ICON_FA_CUBE " float4\n4 random floats [0..1], updated each frame."}, // + {"time", ICON_FA_CUBE " float\nThe time, in seconds, starting at zero when the *preset* starts."}, // + {"fps", ICON_FA_CUBE " float\nThe current framerate (frames per second)."}, // + {"frame", ICON_FA_CUBE " float\nThe current frame #."}, // + {"progress", ICON_FA_CUBE " float\nThe progress through the current preset (0..1)."}, // + {"bass", ICON_FA_CUBE " float\nThe current amount of bass (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"mid", ICON_FA_CUBE " float\nThe current amount of middles (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"treb", ICON_FA_CUBE " float\nThe current amount of treble (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"vol", ICON_FA_CUBE " float\nThe current volume of all frequencies (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"bass_att", ICON_FA_CUBE " float\nThe attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"mid_att", ICON_FA_CUBE " float\nThe attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"treb_att", ICON_FA_CUBE " float\nThe attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"vol_att", ICON_FA_CUBE " float\nThe attenuated volume of all frequencies (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"aspect", ICON_FA_CUBE " float4\n.xy: Multiplier to use on UV's to paste an image fullscreen, *aspect-aware*\n.zw: Inverse (1/x) values as in .xy."}, // + {"texsize", ICON_FA_CUBE " float4\nInfo about the size of the drawing canvas, in pixels.\n.xy: (width,height)\n.zw: (1/(float)w, 1/(float)h)"}, // + {"slow_roam_cos", ICON_FA_CUBE " float4\nFour values that slowly roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*cos(time * float4(~0.005, ~0.008, ~0.013, ~0.022))"}, // + {"roam_cos", ICON_FA_CUBE " float4\nFour values that roam around in the [0..1] range.\n.xyzw: 0.5 + 0.5*cos(time * float4(~0.3, ~1.3, ~5, ~20))"}, // + {"slow_roam_sin", ICON_FA_CUBE " float4\nFour values that slowly roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*sin(time * float4(~0.005, ~0.008, ~0.013, ~0.022))"}, // + {"roam_sin", ICON_FA_CUBE " float4\nFour values that roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*sin(time * float4(~0.3, ~1.3, ~5, ~20))"}, // + {"q1", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q2", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q3", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q4", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q5", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q6", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q7", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q8", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q9", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q10", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q11", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q12", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q13", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q14", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q15", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q16", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q17", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q18", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q19", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q20", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q21", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q22", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q23", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q24", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q25", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q26", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q27", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q28", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q29", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q30", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q31", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q32", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"_qa", ICON_FA_CUBE " float4\nA 4-component vector of the q1-q4 variables. Can be swizzled."}, // + {"_qb", ICON_FA_CUBE " float4\nA 4-component vector of the q5-q8 variables. Can be swizzled."}, // + {"_qc", ICON_FA_CUBE " float4\nA 4-component vector of the q9-q12 variables. Can be swizzled."}, // + {"_qd", ICON_FA_CUBE " float4\nA 4-component vector of the q13-q46 variables. Can be swizzled."}, // + {"_qe", ICON_FA_CUBE " float4\nA 4-component vector of the q17-q20 variables. Can be swizzled."}, // + {"_qf", ICON_FA_CUBE " float4\nA 4-component vector of the q21-q24 variables. Can be swizzled."}, // + {"_qg", ICON_FA_CUBE " float4\nA 4-component vector of the q25-q28 variables. Can be swizzled."}, // + {"_qh", ICON_FA_CUBE " float4\nA 4-component vector of the q29-q32 variables. Can be swizzled."}, // + {"blur1_min", ICON_FA_CUBE " float\nBlur level 1 range minimum (0..1)."}, // + {"blur1_max", ICON_FA_CUBE " float\nBlur level 1 range maximum (0..1)."}, // + {"blur2_min", ICON_FA_CUBE " float\nBlur level 2 range minimum (0..1)."}, // + {"blur2_max", ICON_FA_CUBE " float\nBlur level 2 range maximum (0..1)."}, // + {"blur3_min", ICON_FA_CUBE " float\nBlur level 3 range minimum (0..1)."}, // + {"blur3_max", ICON_FA_CUBE " float\nBlur level 3 range maximum (0..1)."}, // + {"rot_s1", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s2", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s3", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s4", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_d1", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d2", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d3", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d4", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_f1", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f2", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f3", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f4", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_vf1", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf2", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf3", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf4", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_uf1", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf2", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf3", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf4", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_rand1", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand2", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand3", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand4", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"sampler_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, wraps around."}, // + {"sampler_fw_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, wraps around."}, // + {"sampler_fc_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nPoint sampling, wraps around."}, // + {"sampler_pc_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nPoint sampling, clamps to edge."}}}, + + {ExpressionCodeTypes::CompositeShader, {{"shader_body", ICON_FA_PAPER_PLANE " Shader entry point.\nWill be replaced with the appropriate function declaration at runtime."}, // + {"ret", ICON_FA_CUBE " float3\n" ICON_FA_RIGHT_FROM_BRACKET " Shader output RGB color."}, // + {"M_PI", ICON_FA_CUBE " float\nPi constant (3.14159265359)"}, // + {"M_PI_2", ICON_FA_CUBE " float\n2*Pi constant (6.28318530718)"}, // + {"M_INV_PI_2", ICON_FA_CUBE " float\n1/Pi constant (0.159154943091895)"}, // + {"GetMain", ICON_FA_CUBES " float3 GetMain(float2 uv)\nSample main texture at uv, equivalent to:\ntex2D(sampler_main, uv).xyz"}, // + {"GetPixel", ICON_FA_CUBES " float3 GetMain(float2 uv)\nSample main texture at uv, equivalent to:\ntex2D(sampler_main, uv).xyz"}, // + {"GetBlur1", ICON_FA_CUBES " float3 GetBlur1(float2 uv)\nSample blur1 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur1, uv).xyz * blur1_min + blur1_max"}, // + {"GetBlur2", ICON_FA_CUBES " float3 GetBlur2(float2 uv)\nSample blur2 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur2, uv).xyz * blur2_min + blur2_max"}, // + {"GetBlur3", ICON_FA_CUBES " float3 GetBlur3(float2 uv)\nSample blur3 texture at uv with range bias applied, equivalent to:\ntex2D(sampler_blur3, uv).xyz * blur3_min + blur3_max"}, // + {"lum", ICON_FA_CUBES " float lum(float3 color)\nCalculates the luminosity of the given color equivalent to:\ndot(color, float3(0.32, 0.49, 0.29)"}, // + {"tex2d", ICON_FA_CUBE " float4\nLower-case alias for tex2D"}, // + {"tex3d", ICON_FA_CUBE " float4\nLower-case alias for tex3D"}, // + {"uv", ICON_FA_CUBE " float2\nUnwarped UV coordinates (0..1)."}, // + {"hue_shader", ICON_FA_CUBE " float3\nA color that varies across the screen (the old 'hue shader' effect from MilkDrop 1)."}, // + {"rad", ICON_FA_CUBE " float\nRadius of the current pixel from center of screen (0..1)."}, // + {"ang", ICON_FA_CUBE " float\nAngle of the current pixel from center of screen (0..2*PI)."}, // + {"rand_preset", ICON_FA_CUBE " float4\n4 random floats [0..1], updated once per preset."}, // + {"rand_frame", ICON_FA_CUBE " float4\n4 random floats [0..1], updated each frame."}, // + {"time", ICON_FA_CUBE " float\nThe time, in seconds, starting at zero when the *preset* starts."}, // + {"fps", ICON_FA_CUBE " float\nThe current framerate (frames per second)."}, // + {"frame", ICON_FA_CUBE " float\nThe current frame #."}, // + {"progress", ICON_FA_CUBE " float\nThe progress through the current preset (0..1)."}, // + {"bass", ICON_FA_CUBE " float\nThe current amount of bass (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"mid", ICON_FA_CUBE " float\nThe current amount of middles (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"treb", ICON_FA_CUBE " float\nThe current amount of treble (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"vol", ICON_FA_CUBE " float\nThe current volume of all frequencies (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"bass_att", ICON_FA_CUBE " float\nThe attenuated amount of bass (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"mid_att", ICON_FA_CUBE " float\nThe attenuated amount of middles (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"treb_att", ICON_FA_CUBE " float\nThe attenuated amount of treble (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"vol_att", ICON_FA_CUBE " float\nThe attenuated volume of all frequencies (1=normal, <~0.7=quiet, >1.3=loud)."}, // + {"aspect", ICON_FA_CUBE " float4\n.xy: Multiplier to use on UV's to paste an image fullscreen, *aspect-aware*\n.zw: Inverse (1/x) values as in .xy."}, // + {"texsize", ICON_FA_CUBE " float4\nInfo about the size of the drawing canvas, in pixels.\n.xy: (width,height)\n.zw: (1/(float)w, 1/(float)h)"}, // + {"slow_roam_cos", ICON_FA_CUBE " float4\nFour values that slowly roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*cos(time * float4(~0.005, ~0.008, ~0.013, ~0.022))"}, // + {"roam_cos", ICON_FA_CUBE " float4\nFour values that roam around in the [0..1] range.\n.xyzw: 0.5 + 0.5*cos(time * float4(~0.3, ~1.3, ~5, ~20))"}, // + {"slow_roam_sin", ICON_FA_CUBE " float4\nFour values that slowly roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*sin(time * float4(~0.005, ~0.008, ~0.013, ~0.022))"}, // + {"roam_sin", ICON_FA_CUBE " float4\nFour values that roam around in the [0..1] range at varying speeds.\n.xyzw: 0.5 + 0.5*sin(time * float4(~0.3, ~1.3, ~5, ~20))"}, // + {"q1", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q2", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q3", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q4", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q5", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q6", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q7", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q8", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q9", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q10", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q11", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q12", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q13", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q14", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q15", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q16", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q17", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q18", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q19", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q20", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q21", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q22", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q23", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q24", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q25", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q26", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q27", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q28", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q29", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q30", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q31", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"q32", ICON_FA_CUBE " float\nq1 to q32 values are copied from the per-frame code into the shader."}, // + {"_qa", ICON_FA_CUBE " float4\nA 4-component vector of the q1-q4 variables. Can be swizzled."}, // + {"_qb", ICON_FA_CUBE " float4\nA 4-component vector of the q5-q8 variables. Can be swizzled."}, // + {"_qc", ICON_FA_CUBE " float4\nA 4-component vector of the q9-q12 variables. Can be swizzled."}, // + {"_qd", ICON_FA_CUBE " float4\nA 4-component vector of the q13-q46 variables. Can be swizzled."}, // + {"_qe", ICON_FA_CUBE " float4\nA 4-component vector of the q17-q20 variables. Can be swizzled."}, // + {"_qf", ICON_FA_CUBE " float4\nA 4-component vector of the q21-q24 variables. Can be swizzled."}, // + {"_qg", ICON_FA_CUBE " float4\nA 4-component vector of the q25-q28 variables. Can be swizzled."}, // + {"_qh", ICON_FA_CUBE " float4\nA 4-component vector of the q29-q32 variables. Can be swizzled."}, // + {"blur1_min", ICON_FA_CUBE " float\nBlur level 1 range minimum (0..1)."}, // + {"blur1_max", ICON_FA_CUBE " float\nBlur level 1 range maximum (0..1)."}, // + {"blur2_min", ICON_FA_CUBE " float\nBlur level 2 range minimum (0..1)."}, // + {"blur2_max", ICON_FA_CUBE " float\nBlur level 2 range maximum (0..1)."}, // + {"blur3_min", ICON_FA_CUBE " float\nBlur level 3 range minimum (0..1)."}, // + {"blur3_max", ICON_FA_CUBE " float\nBlur level 3 range maximum (0..1)."}, // + {"rot_s1", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s2", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s3", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_s4", ICON_FA_CUBE " float4x3\nFour random, static rotations, randomized at preset load time.\nMinor translation component (<1)."}, // + {"rot_d1", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d2", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d3", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_d4", ICON_FA_CUBE " float4x3\nFour random, slowly changing rotations."}, // + {"rot_f1", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f2", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f3", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_f4", ICON_FA_CUBE " float4x3\nFour random, faster-changing rotations."}, // + {"rot_vf1", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf2", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf3", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_vf4", ICON_FA_CUBE " float4x3\nFour random, very-fast-changing rotations."}, // + {"rot_uf1", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf2", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf3", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_uf4", ICON_FA_CUBE " float4x3\nFour random, ultra-fast-changing rotations."}, // + {"rot_rand1", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand2", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand3", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"rot_rand4", ICON_FA_CUBE " float4x3\nRandom rotation on every frame."}, // + {"sampler_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, wraps around."}, // + {"sampler_fw_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, wraps around."}, // + {"sampler_fc_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nPoint sampling, wraps around."}, // + {"sampler_pc_main", ICON_FA_CUBE " sampler\nMain preset image texture sampler.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_lq", ICON_FA_CUBE " sampler\n256x256 2D 4-channel low-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_lq_lite", ICON_FA_CUBE " sampler\n32x32 2D 4-channel low-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_mq", ICON_FA_CUBE " sampler\n64x64 2D 4-channel medium-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noise_hq", ICON_FA_CUBE " sampler\n32x32 2D 4-channel high-quality noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noisevol_lq", ICON_FA_CUBE " sampler\n32x32x32 3D 4-channel low-quality volumetric noise texture.\nPoint sampling, clamps to edge."}, // + {"sampler_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fw_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, wraps around."}, // + {"sampler_fc_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nBilinear filtering, clamps to edge."}, // + {"sampler_pw_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nPoint sampling, wraps around."}, // + {"sampler_pc_noisevol_hq", ICON_FA_CUBE " sampler\n8x8x8 3D 4-channel high-quality volumetric noise texture.\nPoint sampling, clamps to edge."}}}}; + + return identifierList.at(type); +} + +const TextEditor::LanguageDefinition& CodeContextInformation::GetLanguageDefinition(ExpressionCodeTypes type) +{ + static std::map languageDefinitions{ + {ExpressionCodeTypes::PerFrameInit, PopulateLanguageDefinitionForType(ExpressionCodeTypes::PerFrameInit)}, + {ExpressionCodeTypes::PerFrame, PopulateLanguageDefinitionForType(ExpressionCodeTypes::PerFrame)}, + {ExpressionCodeTypes::PerVertex, PopulateLanguageDefinitionForType(ExpressionCodeTypes::PerVertex)}, + {ExpressionCodeTypes::CustomWaveInit, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CustomWaveInit)}, + {ExpressionCodeTypes::CustomWavePerFrame, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CustomWavePerFrame)}, + {ExpressionCodeTypes::CustomWavePerPoint, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CustomWavePerPoint)}, + {ExpressionCodeTypes::CustomShapeInit, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CustomShapeInit)}, + {ExpressionCodeTypes::CustomShapePerFrame, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CustomShapePerFrame)}, + {ExpressionCodeTypes::WarpShader, PopulateLanguageDefinitionForType(ExpressionCodeTypes::WarpShader)}, + {ExpressionCodeTypes::CompositeShader, PopulateLanguageDefinitionForType(ExpressionCodeTypes::CompositeShader)}}; + + return languageDefinitions.at(type); +} + +TextEditor::LanguageDefinition CodeContextInformation::PopulateLanguageDefinitionForType(ExpressionCodeTypes type) +{ + auto definition = TextEditor::LanguageDefinition::MilkdropExpression(); + + if (type == ExpressionCodeTypes::WarpShader || type == ExpressionCodeTypes::CompositeShader) + { + definition = TextEditor::LanguageDefinition::HLSL(); + } + + for (const auto& sourceIdentifier : GetIdentifierList(type)) + { + TextEditor::Identifier id; + id.mDeclaration = sourceIdentifier.second; + definition.mIdentifiers.insert(std::make_pair(sourceIdentifier.first, id)); + } + + return definition; +} + +} // namespace Editor diff --git a/src/gui/preset_editor/CodeContextInformation.h b/src/gui/preset_editor/CodeContextInformation.h new file mode 100644 index 0000000..e670cc0 --- /dev/null +++ b/src/gui/preset_editor/CodeContextInformation.h @@ -0,0 +1,52 @@ +#pragma once + +#include "ExpressionCodeTypes.h" + +#include "TextEditor.h" + +#include +#include + +namespace Editor { + +/** + * @class CodeContextInformation + * @brief Retains a static list of identifiers specific to each code context. + * + * This is used in the code editor to highlight the pre-defined variables and shader inputs, + * giving the user additional hints about which variable is user-defined in the context and + * which is not. + */ +class CodeContextInformation final +{ +public: + CodeContextInformation() = delete; + + /** + * @brief Returns the name of the context, to be displayed in the editor tab. + * @param type The type to return the context name for. + * @param index The index of a custom wave or shape. + * @return The name of the code context for displaying in the editor tab. + */ + static auto GetContextName(ExpressionCodeTypes type, int index = 0) -> std::string; + + /** + * @brief Return a list of pre-defined variables/inputs for a given code context. + * @param type The type to return the identifier list for. + * @return A list of identifiers with description representing the pre-defined in/out variables for this expression context. + */ + static auto GetIdentifierList(ExpressionCodeTypes type) -> std::vector>; + + /** + * @brief Returns the correct editor language definition for the given type. + * The context-specific identifiers are already set up properly. + * @param type The type to return the language definition for. + * @return + */ + static const TextEditor::LanguageDefinition& GetLanguageDefinition(ExpressionCodeTypes type); + +private: + static auto PopulateLanguageDefinitionForType(ExpressionCodeTypes type) -> TextEditor::LanguageDefinition; +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/CodeEditorTab.cpp b/src/gui/preset_editor/CodeEditorTab.cpp new file mode 100644 index 0000000..e6dac75 --- /dev/null +++ b/src/gui/preset_editor/CodeEditorTab.cpp @@ -0,0 +1,211 @@ +#include "CodeEditorTab.h" + +#include "CodeContextInformation.h" +#include "IconsFontAwesome7.h" + +#include + +void projectm_eval_memory_host_lock_mutex() +{ +} +void projectm_eval_memory_host_unlock_mutex() +{ +} + +namespace Editor { + +using CodeDestructor = void (*)(projectm_eval_code*); +using CodeHandle = std::unique_ptr; + +CodeEditorTab::CodeEditorTab(ExpressionCodeTypes type, std::string& code, int index) + : _code(code) + , _textEditor(std::make_unique()) +{ + _tabTitle = CodeContextInformation::GetContextName(type, index); + _textEditor->SetTabsAsSpaces(true); + _textEditor->SetTabSize(4); + _textEditor->SetLanguageDefinition(CodeContextInformation::GetLanguageDefinition(type)); + _textEditor->SetText(code); + + if (type != ExpressionCodeTypes::WarpShader && type != ExpressionCodeTypes::CompositeShader) + { + _compileTextContext.reset(projectm_eval_context_create(nullptr, nullptr)); + CheckCodeSyntax(code); + } + + switch (type) + { + case ExpressionCodeTypes::PerFrameInit: + case ExpressionCodeTypes::PerFrame: + case ExpressionCodeTypes::PerVertex: + _tabHoverColor = {0xe7 / 256.f, 0x4c / 256.f, 0x3c / 256.f, 1.0f}; + break; + + case ExpressionCodeTypes::CustomWaveInit: + case ExpressionCodeTypes::CustomWavePerFrame: + case ExpressionCodeTypes::CustomWavePerPoint: + _tabHoverColor = {0x29 / 256.f, 0x80 / 256.f, 0xb9 / 256.f, 1.0f}; + break; + + case ExpressionCodeTypes::CustomShapeInit: + case ExpressionCodeTypes::CustomShapePerFrame: + _tabHoverColor = {0x8e / 256.f, 0x44 / 256.f, 0xad / 256.f, 1.0f}; + break; + + case ExpressionCodeTypes::WarpShader: + _tabHoverColor = {0x27 / 256.f, 0xae / 256.f, 0x60 / 256.f, 1.0f}; + break; + + case ExpressionCodeTypes::CompositeShader: + _tabHoverColor = {0xf3 / 256.f, 0x9c / 256.f, 0x12 / 256.f, 1.0f}; + break; + } + + _tabActiveColor = {_tabHoverColor.x * 0.8f, _tabHoverColor.y * 0.8f, _tabHoverColor.z * 0.8f, _tabHoverColor.w}; + _tabColor = {_tabHoverColor.x * 0.5f, _tabHoverColor.y * 0.5f, _tabHoverColor.z * 0.5f, _tabHoverColor.w * 0.75f}; +} + +CodeEditorTab& CodeEditorTab::operator=(CodeEditorTab&& other) noexcept +{ + _nextRenderingFlags = other._nextRenderingFlags; + _tabTitle = std::move(other._tabTitle); + _tabColor = other._tabColor; + _tabActiveColor = other._tabActiveColor; + _tabHoverColor = other._tabHoverColor; + _code = other._code; + _textEditor = std::move(other._textEditor); + _documentOpen = other._documentOpen; + _compileTextContext = std::move(other._compileTextContext); + + return *this; +} + +bool CodeEditorTab::Draw() +{ + ImGui::PushID(_tabTitle.c_str()); + + ImGui::PushStyleColor(ImGuiCol_Tab, _tabColor); + ImGui::PushStyleColor(ImGuiCol_TabActive, _tabActiveColor); + ImGui::PushStyleColor(ImGuiCol_TabHovered, _tabHoverColor); + bool tabVisible = ImGui::BeginTabItem(_tabTitle.c_str(), &_documentOpen, _nextRenderingFlags); + + if (tabVisible) + { + bool textChangedByToolBar = DrawToolBar(); + + _textEditor->Render((_tabTitle + "##EditorControl").c_str()); + + if (_textEditor->IsTextChanged() || textChangedByToolBar) + { + std::string changedText = _textEditor->GetText(); + + // The text editor always leaves a newline if empty, check & clear if that's the case. + if (changedText.size() == 1 && changedText.at(0) == '\n') + { + changedText = ""; + } + + CheckCodeSyntax(changedText); + + _code = changedText; + } + + ImGui::EndTabItem(); + } + + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + ImGui::PopStyleColor(); + + ImGui::PopID(); + + _nextRenderingFlags = ImGuiTabItemFlags_None; + + return _documentOpen; +} + +void CodeEditorTab::SetSelected() +{ + _nextRenderingFlags |= ImGuiTabItemFlags_SetSelected; +} + +std::string CodeEditorTab::Title() const +{ + return _tabTitle; +} + +bool CodeEditorTab::DrawToolBar() +{ + bool textChanged = false; + + ImGui::BeginDisabled(!_textEditor->HasSelection()); + if (ImGui::Button(ICON_FA_COPY " Copy##ToolBar")) + { + _textEditor->Copy(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_SCISSORS " Cut##ToolBar")) + { + _textEditor->Cut(); + textChanged= true; + } + ImGui::EndDisabled(); + + ImGui::BeginDisabled(strlen(ImGui::GetClipboardText()) == 0); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PASTE " Paste##ToolBar")) + { + _textEditor->Paste(); + textChanged= true; + } + ImGui::EndDisabled(); + + ImGui::SameLine(); + ImGui::Separator(); + + ImGui::BeginDisabled(!_textEditor->CanUndo()); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_ROTATE_LEFT " Undo##ToolBar")) + { + _textEditor->Undo(); + textChanged= true; + } + ImGui::EndDisabled(); + + ImGui::BeginDisabled(!_textEditor->CanRedo()); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_ROTATE_RIGHT " Redo##ToolBar")) + { + _textEditor->Redo(); + textChanged= true; + } + ImGui::EndDisabled(); + + ImGui::SameLine(); + if (ImGui::Checkbox("Whitespace##ToolBar", &_showWhitespace)) + { + _textEditor->SetShowWhitespaces(_showWhitespace); + } + + return textChanged; +} + +void CodeEditorTab::CheckCodeSyntax(const std::string& codeToCheck) +{ + if (_compileTextContext) + { + auto code = CodeHandle(projectm_eval_code_compile(_compileTextContext.get(), codeToCheck.c_str()), projectm_eval_code_destroy); + if (code) + { + _textEditor->SetErrorMarkers({}); + } + else + { + int line{}; + std::string errorMessage = projectm_eval_get_error(_compileTextContext.get(), &line, nullptr); + _textEditor->SetErrorMarkers({{line, errorMessage}}); + } + } +} + +} // namespace Editor diff --git a/src/gui/preset_editor/CodeEditorTab.h b/src/gui/preset_editor/CodeEditorTab.h new file mode 100644 index 0000000..c996404 --- /dev/null +++ b/src/gui/preset_editor/CodeEditorTab.h @@ -0,0 +1,55 @@ +#pragma once + +#include "ExpressionCodeTypes.h" +#include "TextEditor.h" + +#include + +namespace Editor { + +class CodeEditorTab +{ +public: + CodeEditorTab() = delete; + + explicit CodeEditorTab(ExpressionCodeTypes type, std::string& code, int index); + + virtual ~CodeEditorTab() = default; + + CodeEditorTab(const CodeEditorTab& other) = delete; + CodeEditorTab(CodeEditorTab&& other) noexcept = default; + CodeEditorTab& operator=(const CodeEditorTab& other) = delete; + CodeEditorTab& operator=(CodeEditorTab&& other) noexcept; + + bool Draw(); + + void SetSelected(); + + std::string Title() const; + +private: + using ContextDestructor = void(*)(projectm_eval_context*); + using ContextHandle = std::unique_ptr; + + /** + * Draws the editor toolbar for this tab. + * @return true if an action was executed which changed the editor contents, false if not. + */ + bool DrawToolBar(); + + void CheckCodeSyntax(const std::string& codeToCheck); + + ImGuiTabItemFlags _nextRenderingFlags{}; //!< Additional flags to set when rendering the tab next time, e.g. activate it. + std::string _tabTitle; //!< The title of the tab. + ImVec4 _tabColor{0.0f, 0.0f, 1.0f, 1.0f}; //!< The background color of the inactive tab. + ImVec4 _tabActiveColor{0.0f, 0.0f, 1.0f, 1.0f}; //!< The background color of the active tab. + ImVec4 _tabHoverColor{0.0f, 0.0f, 1.0f, 1.0f}; //!< The background color of the hovered tab. + std::string& _code; //!< A reference to the preset code. + bool _showWhitespace{true}; //!< If true, the editor will display whitespace. + std::unique_ptr _textEditor; //!< The expression/shader code editor control. + bool _documentOpen{true}; //!< This flag holds the opened/closed state of the document, e.g. becomes false if the user closes the tab. + + ContextHandle _compileTextContext{nullptr, projectm_eval_context_destroy}; //!< A projectm-eval context for test-compiling the editor code. +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/CodeEditorWindow.cpp b/src/gui/preset_editor/CodeEditorWindow.cpp new file mode 100644 index 0000000..dd0278f --- /dev/null +++ b/src/gui/preset_editor/CodeEditorWindow.cpp @@ -0,0 +1,87 @@ +#include "CodeEditorWindow.h" + +#include "CodeContextInformation.h" +#include "IconsFontAwesome7.h" + +#include "imgui.h" + +namespace Editor { + +void CodeEditorWindow::Draw() +{ + if (!_visible) + { + return; + } + + ImGui::PushID("CodeEditorWindow"); + + if (ImGui::Button(_collapsed ? ICON_FA_CARET_RIGHT "##CollapseButton" : ICON_FA_CARET_DOWN "##CollapseButton")) + { + _collapsed = !_collapsed; + } + + ImGui::SameLine(); + + if (!_collapsed) + { + ImGui::SetNextWindowSize(ImVec2(0, 500), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowBgAlpha(_collapsed ? 0.0f : 0.5f); + if (ImGui::BeginChild("CodeEditorChild", ImVec2(0, 500), ImGuiChildFlags_ResizeY, ImGuiWindowFlags_None)) + { + if (ImGui::BeginTabBar("OpenCodeTabs", ImGuiTabBarFlags_AutoSelectNewTabs)) + { + for (auto it = begin(_codeEditorTabs); it != end(_codeEditorTabs);) + { + if (it->Draw()) + { + ++it; + continue; + } + + it = _codeEditorTabs.erase(it); + } + } + + ImGui::EndTabBar(); + } + ImGui::EndChild(); + } + else + { + ImGui::SameLine(); + ImGui::TextDisabled("%s", ("(" + std::to_string(_codeEditorTabs.size()) + " Tabs Open)").c_str()); + } + + if (_codeEditorTabs.empty()) + { + _visible = false; + } + + ImGui::PopID(); +} + +void CodeEditorWindow::OpenCodeInTab(ExpressionCodeTypes type, std::string& code, int index) +{ + // Fold out the editor if the user opens a new tab or focuses an existing one. + _collapsed = false; + + std::string newTabTitle = CodeContextInformation::GetContextName(type, index); + for (auto& tab : _codeEditorTabs) + { + if (tab.Title() == newTabTitle) + { + tab.SetSelected(); + return; + } + } + + CodeEditorTab newCodeEditorTab(type, code, index); + newCodeEditorTab.SetSelected(); + _codeEditorTabs.push_back(std::move(newCodeEditorTab)); + + _visible = true; +} + + +} // namespace Editor diff --git a/src/gui/preset_editor/CodeEditorWindow.h b/src/gui/preset_editor/CodeEditorWindow.h new file mode 100644 index 0000000..497a6ad --- /dev/null +++ b/src/gui/preset_editor/CodeEditorWindow.h @@ -0,0 +1,29 @@ +#pragma once + +#include "CodeEditorTab.h" +#include "ExpressionCodeTypes.h" + +#include +#include + +namespace Editor { + +class CodeEditorWindow +{ +public: + CodeEditorWindow() = default; + + virtual ~CodeEditorWindow() = default; + + void Draw(); + + void OpenCodeInTab(ExpressionCodeTypes type, std::string& code, int index); + +private: + bool _visible{false}; //!< Determines if the code editor window is visible. + bool _collapsed{false}; //!< If true, the window is displayed collapsed, e.g. the tab contents aren't rendered. + + std::list _codeEditorTabs; //!< Currently opened editor tabs. +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/EditorMenu.cpp b/src/gui/preset_editor/EditorMenu.cpp new file mode 100644 index 0000000..9f8969f --- /dev/null +++ b/src/gui/preset_editor/EditorMenu.cpp @@ -0,0 +1,115 @@ +#include "EditorMenu.h" + +#include "IconsFontAwesome7.h" +#include "PresetEditorGUI.h" + +#include "gui/SystemBrowser.h" + +#include "notifications/QuitNotification.h" + +#include "imgui.h" + +#include + +namespace Editor { + +EditorMenu::EditorMenu(PresetEditorGUI& gui) + : _notificationCenter(Poco::NotificationCenter::defaultCenter()) + , _presetEditorGUI(gui) +{ +} + +void EditorMenu::Draw() +{ + if (ImGui::BeginMainMenuBar()) + { + DrawFileMenu(); + + ImGui::Separator(); + if (ImGui::Button(ICON_FA_PLAY " Update Preset Preview")) + { + _presetEditorGUI.UpdatePresetPreview(); + } + ImGui::Separator(); + + DrawHelpMenu(); + + ImGui::EndMainMenuBar(); + } +} + +void EditorMenu::DrawFileMenu() +{ + if (ImGui::BeginMenu("File")) + { + if (ImGui::MenuItem(ICON_FA_SQUARE_PLUS " New Preset")) + { + _presetEditorGUI.Show(""); + } + + if (ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Open Preset...")) + { + } + + if (ImGui::MenuItem(ICON_FA_FLOPPY_DISK " Save Preset")) + { + } + + if (ImGui::MenuItem(ICON_FA_FLOPPY_DISK " Save Preset As...")) + { + } + + if (ImGui::MenuItem(ICON_FA_COPY " Copy Preset to Clipboard")) + { + _presetEditorGUI.CopyToClipboard(); + } + + ImGui::Separator(); + + if (ImGui::MenuItem(ICON_FA_CIRCLE_XMARK " Exit Preset Editor")) + { + _presetEditorGUI.Close(); + } + + ImGui::Separator(); + + if (ImGui::MenuItem(ICON_FA_DOOR_OPEN " Quit projectM", "Ctrl+q")) + { + _notificationCenter.postNotification(new QuitNotification); + } + + ImGui::EndMenu(); + } +} + +void EditorMenu::DrawHelpMenu() +{ + + if (ImGui::BeginMenu("Help")) + { + + if (ImGui::BeginMenu(ICON_FA_INFO " Online Documentation")) + { + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " Milkdrop Preset Authoring Guide")) + { + SystemBrowser::OpenURL("https://www.geisswerks.com/milkdrop/milkdrop_preset_authoring.html"); + } + + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " Milkdrop Expression Syntax")) + { + SystemBrowser::OpenURL("https://github.com/projectM-visualizer/projectm-eval/blob/master/docs/Expression-Syntax.md"); + } + + if (ImGui::MenuItem(ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE " DirectX HLSL Reference")) + { + SystemBrowser::OpenURL("https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-reference"); + } + + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } +} + +} // namespace Editor diff --git a/src/gui/preset_editor/EditorMenu.h b/src/gui/preset_editor/EditorMenu.h new file mode 100644 index 0000000..6c8613f --- /dev/null +++ b/src/gui/preset_editor/EditorMenu.h @@ -0,0 +1,33 @@ +#pragma once + +namespace Poco { +class NotificationCenter; +} + +namespace Editor { + +class PresetEditorGUI; + +class EditorMenu +{ +public: + EditorMenu() = delete; + + explicit EditorMenu(PresetEditorGUI& gui); + + /** + * @brief Draws the editor menu bar. + */ + void Draw(); + +private: + void DrawFileMenu(); + void DrawHelpMenu(); + + Poco::NotificationCenter& _notificationCenter; //!< Notification center instance. + + PresetEditorGUI& _presetEditorGUI; +}; + + +} // namespace Editor diff --git a/src/gui/preset_editor/EditorPreset.cpp b/src/gui/preset_editor/EditorPreset.cpp new file mode 100644 index 0000000..3d85faf --- /dev/null +++ b/src/gui/preset_editor/EditorPreset.cpp @@ -0,0 +1,352 @@ +#include "EditorPreset.h" + +namespace Editor { + +void EditorPreset::FromParsedFile(const PresetFile& parsedFile) +{ + // General: + decay = parsedFile.GetFloat("fDecay", decay); + gammaAdj = parsedFile.GetFloat("fGammaAdj", gammaAdj); + videoEchoZoom = parsedFile.GetFloat("fVideoEchoZoom", videoEchoZoom); + videoEchoAlpha = parsedFile.GetFloat("fVideoEchoAlpha", videoEchoAlpha); + videoEchoOrientation = parsedFile.GetInt("nVideoEchoOrientation", videoEchoOrientation); + redBlueStereo = parsedFile.GetBool("bRedBlueStereo", redBlueStereo); + brighten = parsedFile.GetBool("bBrighten", brighten); + darken = parsedFile.GetBool("bDarken", darken); + solarize = parsedFile.GetBool("bSolarize", solarize); + invert = parsedFile.GetBool("bInvert", invert); + shader = parsedFile.GetFloat("fShader", shader); + blur1Min = parsedFile.GetFloat("b1n", blur1Min); + blur2Min = parsedFile.GetFloat("b2n", blur2Min); + blur3Min = parsedFile.GetFloat("b3n", blur3Min); + blur1Max = parsedFile.GetFloat("b1x", blur1Max); + blur2Max = parsedFile.GetFloat("b2x", blur2Max); + blur3Max = parsedFile.GetFloat("b3x", blur3Max); + blur1EdgeDarken = parsedFile.GetFloat("b1ed", blur1EdgeDarken); + + // Wave: + waveMode = parsedFile.GetInt("nWaveMode", waveMode); + additiveWaves = parsedFile.GetBool("bAdditiveWaves", additiveWaves); + waveDots = parsedFile.GetBool("bWaveDots", waveDots); + waveThick = parsedFile.GetBool("bWaveThick", waveThick); + modWaveAlphaByVolume = parsedFile.GetBool("bModWaveAlphaByVolume", modWaveAlphaByVolume); + maximizeWaveColor = parsedFile.GetBool("bMaximizeWaveColor", maximizeWaveColor); + waveScale = parsedFile.GetFloat("fWaveScale", waveScale); + waveSmoothing = parsedFile.GetFloat("fWaveSmoothing", waveSmoothing); + waveParam = parsedFile.GetFloat("fWaveParam", waveParam); + modWaveAlphaStart = parsedFile.GetFloat("fModWaveAlphaStart", modWaveAlphaStart); + modWaveAlphaEnd = parsedFile.GetFloat("fModWaveAlphaEnd", modWaveAlphaEnd); + waveColor.red = parsedFile.GetFloat("wave_r", waveColor.red); + waveColor.green = parsedFile.GetFloat("wave_g", waveColor.green); + waveColor.blue = parsedFile.GetFloat("wave_b", waveColor.blue); + waveColor.alpha = parsedFile.GetFloat("fWaveAlpha", waveColor.alpha); + waveX = parsedFile.GetFloat("wave_x", waveX); + waveY = parsedFile.GetFloat("wave_y", waveY); + mvX = parsedFile.GetFloat("nMotionVectorsX", mvX); + mvY = parsedFile.GetFloat("nMotionVectorsY", mvY); + mvDX = parsedFile.GetFloat("mv_dx", mvDX); + mvDY = parsedFile.GetFloat("mv_dy", mvDY); + mvL = parsedFile.GetFloat("mv_l", mvL); + mvColor.red = parsedFile.GetFloat("mv_r", mvColor.red); + mvColor.green = parsedFile.GetFloat("mv_g", mvColor.green); + mvColor.blue = parsedFile.GetFloat("mv_b", mvColor.blue); + mvColor.alpha = parsedFile.GetBool("bMotionVectorsOn", false) ? 1.0f : 0.0f; // for backwards compatibility + mvColor.alpha = parsedFile.GetFloat("mv_a", mvColor.alpha); + + // Motion: + zoom = parsedFile.GetFloat("zoom", zoom); + rot = parsedFile.GetFloat("rot", rot); + rotCX = parsedFile.GetFloat("cx", rotCX); + rotCY = parsedFile.GetFloat("cy", rotCY); + xPush = parsedFile.GetFloat("dx", xPush); + yPush = parsedFile.GetFloat("dy", yPush); + warpAmount = parsedFile.GetFloat("warp", warpAmount); + stretchX = parsedFile.GetFloat("sx", stretchX); + stretchY = parsedFile.GetFloat("sy", stretchY); + texWrap = parsedFile.GetBool("bTexWrap", texWrap); + darkenCenter = parsedFile.GetBool("bDarkenCenter", darkenCenter); + warpAnimSpeed = parsedFile.GetFloat("fWarpAnimSpeed", warpAnimSpeed); + warpScale = parsedFile.GetFloat("fWarpScale", warpScale); + zoomExponent = parsedFile.GetFloat("fZoomExponent", zoomExponent); + + // Borders: + outerBorderSize = parsedFile.GetFloat("ob_size", outerBorderSize); + outerBorderColor.red = parsedFile.GetFloat("ob_r", outerBorderColor.red); + outerBorderColor.green = parsedFile.GetFloat("ob_g", outerBorderColor.green); + outerBorderColor.blue = parsedFile.GetFloat("ob_b", outerBorderColor.blue); + outerBorderColor.alpha = parsedFile.GetFloat("ob_a", outerBorderColor.alpha); + innerBorderSize = parsedFile.GetFloat("ib_size", innerBorderSize); + innerBorderColor.red = parsedFile.GetFloat("ib_r", innerBorderColor.red); + innerBorderColor.green = parsedFile.GetFloat("ib_g", innerBorderColor.green); + innerBorderColor.blue = parsedFile.GetFloat("ib_b", innerBorderColor.blue); + innerBorderColor.alpha = parsedFile.GetFloat("ib_a", innerBorderColor.alpha); + + // Versions: + presetVersion = parsedFile.GetInt("MILKDROP_PRESET_VERSION", presetVersion); + if (presetVersion < 200) + { + // Milkdrop 1.x did not use shaders. + warpShaderVersion = 0; + compositeShaderVersion = 0; + } + else if (presetVersion == 200) + { + // Milkdrop 2.0 only supported a single shader language level variable. + warpShaderVersion = parsedFile.GetInt("PSVERSION", warpShaderVersion); + compositeShaderVersion = parsedFile.GetInt("PSVERSION", compositeShaderVersion); + } + else + { + warpShaderVersion = parsedFile.GetInt("PSVERSION_WARP", warpShaderVersion); + compositeShaderVersion = parsedFile.GetInt("PSVERSION_COMP", compositeShaderVersion); + } + + // Code: + perFrameInitCode = parsedFile.GetCode("per_frame_init_"); + perFrameCode = parsedFile.GetCode("per_frame_"); + perPixelCode = parsedFile.GetCode("per_pixel_"); + + // Custom waveform code: + for (int index = 0; index < 4; index++) + { + Wave& wave = waves[index]; + + std::string const wavecodePrefix = "wavecode_" + std::to_string(index) + "_"; + + wave.index = index; + wave.enabled = parsedFile.GetBool(wavecodePrefix + "enabled", wave.enabled); + wave.samples = parsedFile.GetInt(wavecodePrefix + "samples", wave.samples); + wave.sep = parsedFile.GetInt(wavecodePrefix + "sep", wave.sep); + wave.spectrum = parsedFile.GetBool(wavecodePrefix + "bSpectrum", wave.spectrum); + wave.useDots = parsedFile.GetBool(wavecodePrefix + "bUseDots", wave.useDots); + wave.drawThick = parsedFile.GetBool(wavecodePrefix + "bDrawThick", wave.drawThick); + wave.additive = parsedFile.GetBool(wavecodePrefix + "bAdditive", wave.additive); + wave.scaling = parsedFile.GetFloat(wavecodePrefix + "scaling", wave.scaling); + wave.smoothing = parsedFile.GetFloat(wavecodePrefix + "smoothing", wave.smoothing); + wave.color.red = parsedFile.GetFloat(wavecodePrefix + "r", wave.color.red); + wave.color.green = parsedFile.GetFloat(wavecodePrefix + "g", wave.color.green); + wave.color.blue = parsedFile.GetFloat(wavecodePrefix + "b", wave.color.blue); + wave.color.alpha = parsedFile.GetFloat(wavecodePrefix + "a", wave.color.alpha); + + std::string const wavePrefix = "wave_" + std::to_string(index) + "_"; + + wave.initCode = parsedFile.GetCode(wavePrefix + "init"); + wave.perFrameCode = parsedFile.GetCode(wavePrefix + "per_frame"); + wave.perPointCode = parsedFile.GetCode(wavePrefix + "per_point"); + } + + // Custom shapes + for (int index = 0; index < 4; index++) + { + Shape& shape = shapes[index]; + + std::string const shapecodePrefix = "shapecode_" + std::to_string(index) + "_"; + + shape.index = index; + shape.enabled = parsedFile.GetBool(shapecodePrefix + "enabled", shape.enabled); + shape.sides = parsedFile.GetInt(shapecodePrefix + "sides", shape.sides); + shape.additive = parsedFile.GetBool(shapecodePrefix + "additive", shape.additive); + shape.thickOutline = parsedFile.GetBool(shapecodePrefix + "thickOutline", shape.thickOutline); + shape.textured = parsedFile.GetBool(shapecodePrefix + "textured", shape.textured); + shape.instances = parsedFile.GetInt(shapecodePrefix + "num_inst", shape.instances); + shape.x = parsedFile.GetFloat(shapecodePrefix + "x", shape.x); + shape.y = parsedFile.GetFloat(shapecodePrefix + "y", shape.y); + shape.radius = parsedFile.GetFloat(shapecodePrefix + "rad", shape.radius); + shape.angle = parsedFile.GetFloat(shapecodePrefix + "ang", shape.angle); + shape.tex_ang = parsedFile.GetFloat(shapecodePrefix + "tex_ang", shape.tex_ang); + shape.tex_zoom = parsedFile.GetFloat(shapecodePrefix + "tex_zoom", shape.tex_zoom); + shape.color.red = parsedFile.GetFloat(shapecodePrefix + "r", shape.color.red); + shape.color.green = parsedFile.GetFloat(shapecodePrefix + "g", shape.color.green); + shape.color.blue = parsedFile.GetFloat(shapecodePrefix + "b", shape.color.blue); + shape.color.alpha = parsedFile.GetFloat(shapecodePrefix + "a", shape.color.alpha); + shape.color2.red = parsedFile.GetFloat(shapecodePrefix + "r2", shape.color2.red); + shape.color2.green = parsedFile.GetFloat(shapecodePrefix + "g2", shape.color2.green); + shape.color2.blue = parsedFile.GetFloat(shapecodePrefix + "b2", shape.color2.blue); + shape.color2.alpha = parsedFile.GetFloat(shapecodePrefix + "a2", shape.color2.alpha); + shape.borderColor.red = parsedFile.GetFloat(shapecodePrefix + "border_r", shape.borderColor.red); + shape.borderColor.green = parsedFile.GetFloat(shapecodePrefix + "border_g", shape.borderColor.green); + shape.borderColor.blue = parsedFile.GetFloat(shapecodePrefix + "border_b", shape.borderColor.blue); + shape.borderColor.alpha = parsedFile.GetFloat(shapecodePrefix + "border_a", shape.borderColor.alpha); + + std::string const shapePrefix = "shape_" + std::to_string(index) + "_"; + + shape.initCode = parsedFile.GetCode(shapePrefix + "init"); + shape.perFrameCode = parsedFile.GetCode(shapePrefix + "per_frame"); + } + + // Shader code: + warpShader = parsedFile.GetCode("warp_"); + compositeShader = parsedFile.GetCode("comp_"); +} + +void EditorPreset::ToParsedFile(PresetFile& parsedFile) +{ + // General: + parsedFile.SetFloat("fDecay", decay); + parsedFile.SetFloat("fGammaAdj", gammaAdj); + parsedFile.SetFloat("fVideoEchoZoom", videoEchoZoom); + parsedFile.SetFloat("fVideoEchoAlpha", videoEchoAlpha); + parsedFile.SetInt("nVideoEchoOrientation", videoEchoOrientation); + parsedFile.SetBool("bRedBlueStereo", redBlueStereo); + parsedFile.SetBool("bBrighten", brighten); + parsedFile.SetBool("bDarken", darken); + parsedFile.SetBool("bSolarize", solarize); + parsedFile.SetBool("bInvert", invert); + parsedFile.SetFloat("fShader", shader); + parsedFile.SetFloat("b1n", blur1Min); + parsedFile.SetFloat("b2n", blur2Min); + parsedFile.SetFloat("b3n", blur3Min); + parsedFile.SetFloat("b1x", blur1Max); + parsedFile.SetFloat("b2x", blur2Max); + parsedFile.SetFloat("b3x", blur3Max); + parsedFile.SetFloat("b1ed", blur1EdgeDarken); + + // Wave: + parsedFile.SetInt("nWaveMode", waveMode); + parsedFile.SetBool("bAdditiveWaves", additiveWaves); + parsedFile.SetBool("bWaveDots", waveDots); + parsedFile.SetBool("bWaveThick", waveThick); + parsedFile.SetBool("bModWaveAlphaByVolume", modWaveAlphaByVolume); + parsedFile.SetBool("bMaximizeWaveColor", maximizeWaveColor); + parsedFile.SetFloat("fWaveAlpha", waveColor.alpha); + parsedFile.SetFloat("fWaveScale", waveScale); + parsedFile.SetFloat("fWaveSmoothing", waveSmoothing); + parsedFile.SetFloat("fWaveParam", waveParam); + parsedFile.SetFloat("fModWaveAlphaStart", modWaveAlphaStart); + parsedFile.SetFloat("fModWaveAlphaEnd", modWaveAlphaEnd); + parsedFile.SetFloat("wave_r", waveColor.red); + parsedFile.SetFloat("wave_g", waveColor.green); + parsedFile.SetFloat("wave_b", waveColor.blue); + parsedFile.SetFloat("wave_x", waveX); + parsedFile.SetFloat("wave_y", waveY); + parsedFile.SetFloat("nMotionVectorsX", mvX); + parsedFile.SetFloat("nMotionVectorsY", mvY); + parsedFile.SetFloat("mv_dx", mvDX); + parsedFile.SetFloat("mv_dy", mvDY); + parsedFile.SetFloat("mv_l", mvL); + parsedFile.SetFloat("mv_r", mvColor.red); + parsedFile.SetFloat("mv_g", mvColor.green); + parsedFile.SetFloat("mv_b", mvColor.blue); + parsedFile.SetFloat("mv_a", mvColor.alpha); + + // Motion: + parsedFile.SetFloat("zoom", zoom); + parsedFile.SetFloat("rot", rot); + parsedFile.SetFloat("cx", rotCX); + parsedFile.SetFloat("cy", rotCY); + parsedFile.SetFloat("dx", xPush); + parsedFile.SetFloat("dy", yPush); + parsedFile.SetFloat("warp", warpAmount); + parsedFile.SetFloat("sx", stretchX); + parsedFile.SetFloat("sy", stretchY); + parsedFile.SetBool("bTexWrap", texWrap); + parsedFile.SetBool("bDarkenCenter", darkenCenter); + parsedFile.SetFloat("fWarpAnimSpeed", warpAnimSpeed); + parsedFile.SetFloat("fWarpScale", warpScale); + parsedFile.SetFloat("fZoomExponent", zoomExponent); + + // Borders: + parsedFile.SetFloat("ob_size", outerBorderSize); + parsedFile.SetFloat("ob_r", outerBorderColor.red); + parsedFile.SetFloat("ob_g", outerBorderColor.green); + parsedFile.SetFloat("ob_b", outerBorderColor.blue); + parsedFile.SetFloat("ob_a", outerBorderColor.alpha); + parsedFile.SetFloat("ib_size", innerBorderSize); + parsedFile.SetFloat("ib_r", innerBorderColor.red); + parsedFile.SetFloat("ib_g", innerBorderColor.green); + parsedFile.SetFloat("ib_b", innerBorderColor.blue); + parsedFile.SetFloat("ib_a", innerBorderColor.alpha); + + // Versions: + parsedFile.SetInt("MILKDROP_PRESET_VERSION", presetVersion); + if (presetVersion < 200) + { + // Milkdrop 1.x did not use shaders. + parsedFile.SetInt("PSVERSION", 0); + parsedFile.SetInt("PSVERSION_WARP", 0); + parsedFile.SetInt("PSVERSION_COMP", 0); + } + else + { + parsedFile.SetInt("PSVERSION", warpShaderVersion); + parsedFile.SetInt("PSVERSION_WARP", warpShaderVersion); + parsedFile.SetInt("PSVERSION_COMP", compositeShaderVersion); + } + + // Code: + parsedFile.SetCode("per_frame_init_", perFrameInitCode); + parsedFile.SetCode("per_frame_", perFrameCode); + parsedFile.SetCode("per_pixel_", perPixelCode); + + // Custom waveform code: + for (int index = 0; index < 4; index++) + { + Wave& wave = waves[index]; + + std::string const wavecodePrefix = "wavecode_" + std::to_string(index) + "_"; + + parsedFile.SetBool(wavecodePrefix + "enabled", wave.enabled); + parsedFile.SetInt(wavecodePrefix + "samples", wave.samples); + parsedFile.SetInt(wavecodePrefix + "sep", wave.sep); + parsedFile.SetBool(wavecodePrefix + "bSpectrum", wave.spectrum); + parsedFile.SetBool(wavecodePrefix + "bUseDots", wave.useDots); + parsedFile.SetBool(wavecodePrefix + "bDrawThick", wave.drawThick); + parsedFile.SetBool(wavecodePrefix + "bAdditive", wave.additive); + parsedFile.SetFloat(wavecodePrefix + "scaling", wave.scaling); + parsedFile.SetFloat(wavecodePrefix + "smoothing", wave.smoothing); + parsedFile.SetFloat(wavecodePrefix + "r", wave.color.red); + parsedFile.SetFloat(wavecodePrefix + "g", wave.color.green); + parsedFile.SetFloat(wavecodePrefix + "b", wave.color.blue); + parsedFile.SetFloat(wavecodePrefix + "a", wave.color.alpha); + + std::string const wavePrefix = "wave_" + std::to_string(index) + "_"; + + parsedFile.SetCode(wavePrefix + "init", wave.initCode); + parsedFile.SetCode(wavePrefix + "per_frame", wave.perFrameCode); + parsedFile.SetCode(wavePrefix + "per_point", wave.perPointCode); + } + + // Custom shapes + for (int index = 0; index < 4; index++) + { + Shape& shape = shapes[index]; + + std::string const shapecodePrefix = "shapecode_" + std::to_string(index) + "_"; + + parsedFile.SetBool(shapecodePrefix + "enabled", shape.enabled); + parsedFile.SetInt(shapecodePrefix + "sides", shape.sides); + parsedFile.SetBool(shapecodePrefix + "additive", shape.additive); + parsedFile.SetBool(shapecodePrefix + "thickOutline", shape.thickOutline); + parsedFile.SetBool(shapecodePrefix + "textured", shape.textured); + parsedFile.SetInt(shapecodePrefix + "nushape.inst", shape.instances); + parsedFile.SetFloat(shapecodePrefix + "x", shape.x); + parsedFile.SetFloat(shapecodePrefix + "y", shape.y); + parsedFile.SetFloat(shapecodePrefix + "rad", shape.radius); + parsedFile.SetFloat(shapecodePrefix + "ang", shape.angle); + parsedFile.SetFloat(shapecodePrefix + "tex_ang", shape.tex_ang); + parsedFile.SetFloat(shapecodePrefix + "tex_zoom", shape.tex_zoom); + parsedFile.SetFloat(shapecodePrefix + "r", shape.color.red); + parsedFile.SetFloat(shapecodePrefix + "g", shape.color.green); + parsedFile.SetFloat(shapecodePrefix + "b", shape.color.blue); + parsedFile.SetFloat(shapecodePrefix + "a", shape.color.alpha); + parsedFile.SetFloat(shapecodePrefix + "r2", shape.color2.red); + parsedFile.SetFloat(shapecodePrefix + "g2", shape.color2.green); + parsedFile.SetFloat(shapecodePrefix + "b2", shape.color2.blue); + parsedFile.SetFloat(shapecodePrefix + "a2", shape.color2.alpha); + parsedFile.SetFloat(shapecodePrefix + "border_r", shape.borderColor.red); + parsedFile.SetFloat(shapecodePrefix + "border_g", shape.borderColor.green); + parsedFile.SetFloat(shapecodePrefix + "border_b", shape.borderColor.blue); + parsedFile.SetFloat(shapecodePrefix + "border_a", shape.borderColor.alpha); + + std::string const shapePrefix = "shape_" + std::to_string(index) + "_"; + + parsedFile.SetCode(shapePrefix + "init", shape.initCode); + parsedFile.SetCode(shapePrefix + "per_frame", shape.perFrameCode); + } + + // Shader code: + parsedFile.SetCode("warp_", warpShader); + parsedFile.SetCode("comp_", compositeShader); +} + +} // namespace Editor diff --git a/src/gui/preset_editor/EditorPreset.h b/src/gui/preset_editor/EditorPreset.h new file mode 100644 index 0000000..ddb87dd --- /dev/null +++ b/src/gui/preset_editor/EditorPreset.h @@ -0,0 +1,166 @@ +#pragma once + +#include "PresetFile.h" + +#include +#include + +namespace Editor { + +/** + * @class EditorPreset + * @brief Holds all parsed preset values and code and syncs them with the file. + * + * Since ImGUI needs to retrieve data on every rendered frame, using the PresetFile getter/setter methods + * is too slow. For this reason, we keep the values easily accessible in this class as public members. + * + * The class provides methods to sync the data in both directions with a PresetFile. + */ +class EditorPreset +{ +public: + /** + * Used like an array of floats in ImGui::ColorEditor4 + */ + struct ColorRGBA { + float red{}; + float green{}; + float blue{}; + float alpha{}; + }; + + class Wave + { + public: + int index{0}; //!< Custom waveform index in the preset. + bool enabled{false}; //!< Render waveform if true. + int samples{512}; //!< Number of samples/vertices in the waveform. + int sep{0}; //!< Separation distance of dual waveforms. + + float scaling{1.0f}; //!< Scale factor of waveform. + float smoothing{0.5f}; //!< Smooth factor of waveform. + float x{0.5f}; + float y{0.5f}; + + ColorRGBA color{1.0f, 1.0f, 1.0f, 1.0f}; //!< Wave color + + bool spectrum{false}; //!< Spectrum data or PCM data. + bool useDots{false}; //!< If non-zero, draw wave as dots instead of lines. + bool drawThick{false}; //!< Draw thicker lines. + bool additive{false}; //!< Add color values together. + + std::string initCode; //!< Custom wave init code, run once on load. + std::string perFrameCode; //!< Custom wave per-frame code, run once after the per-frame code. + std::string perPointCode; //!< Custom wave per-point code, run once per waveform vertex. + }; + + class Shape + { + public: + int index{0}; //!< The custom shape index in the preset. + bool enabled{false}; //!< If false, the shape isn't drawn. + int sides{4}; //!< Number of sides (vertices) + bool additive{false}; //!< Flag that specifies whether the shape should be drawn additive. + bool thickOutline{false}; //!< If true, the shape is rendered with a thick line, otherwise a single-pixel line. + bool textured{false}; //!< If true, the shape will be rendered with the given texture. + int instances{1}; //!< Number of shape instances to render + + float x{0.5f}; //!< The shape x position. + float y{0.5f}; //!< The shape y position. + float radius{0.1f}; //!< The shape radius (1.0 fills the whole screen). + float angle{0.0f}; //!< The shape rotation. + + ColorRGBA color{1.0f, 0.0f, 0.0f, 1.0f}; //!< First color + ColorRGBA color2{0.0f, 1.0f, 0.0f, 0.0f}; //!< Second color + ColorRGBA borderColor{0.0f, 1.0f, 0.0f, 0.0f}; //!< Border color + + float tex_ang{0.0f}; //!< Texture rotation angle. + float tex_zoom{1.0f}; //!< Texture zoom value. + + std::string initCode; //!< Custom shape init code, run once on load. + std::string perFrameCode; //!< Custom shape per-frame code, run once per shape instance. + }; + + void FromParsedFile(const PresetFile& parsedFile); + + void ToParsedFile(PresetFile& parsedFile); + + float gammaAdj{2.0f}; + float videoEchoZoom{2.0f}; + float videoEchoAlpha{0.0f}; + float videoEchoAlphaOld{0.0f}; + int videoEchoOrientation{0}; + int videoEchoOrientationOld{0}; + + float decay{0.98f}; + + int waveMode{0}; + int oldWaveMode{-1}; + bool additiveWaves{false}; + float waveScale{1.0f}; + float waveSmoothing{0.75f}; + bool waveDots{false}; + bool waveThick{false}; + float waveParam{0.0f}; + bool modWaveAlphaByVolume{false}; + float modWaveAlphaStart{0.75f}; + float modWaveAlphaEnd{0.95f}; + float warpAnimSpeed{1.0f}; + float warpScale{1.0f}; + float zoomExponent{1.0f}; + float shader{0.0f}; + bool maximizeWaveColor{true}; + bool texWrap{true}; + bool darkenCenter{false}; + bool redBlueStereo{false}; + bool brighten{false}; + bool darken{false}; + bool solarize{false}; + bool invert{false}; + + float zoom{1.0f}; + float rot{0.0f}; + float rotCX{0.5f}; + float rotCY{0.5f}; + float xPush{0.0f}; + float yPush{0.0f}; + float warpAmount{1.0f}; + float stretchX{1.0f}; + float stretchY{1.0f}; + ColorRGBA waveColor{1.0f, 1.0f, 1.0f, 0.8f}; + float waveX{0.5f}; + float waveY{0.5f}; + float outerBorderSize{0.01f}; + ColorRGBA outerBorderColor{0.0f, 0.0f, 0.0f, 0.0f}; + float innerBorderSize{0.01f}; + ColorRGBA innerBorderColor{0.25f, 0.25f, 0.25f, 0.0f}; + float mvX{12.0f}; + float mvY{9.0f}; + float mvDX{0.0f}; + float mvDY{0.0f}; + float mvL{0.9f}; + ColorRGBA mvColor{1.0f, 1.0f, 1.0f, 1.0f}; + float blur1Min{0.0f}; + float blur2Min{0.0f}; + float blur3Min{0.0f}; + float blur1Max{1.0f}; + float blur2Max{1.0f}; + float blur3Max{1.0f}; + float blur1EdgeDarken{0.25f}; + + int presetVersion{100}; //!< Value of MILKDROP_PRESET_VERSION in preset files. + int warpShaderVersion{2}; //!< PSVERSION or PSVERSION_WARP. + int compositeShaderVersion{2}; //!< PSVERSION or PSVERSION_COMP. + + std::string perFrameInitCode; //!< Preset init code, run once on load. + std::string perFrameCode; //!< Preset per-frame code, run once at the start of each frame. + std::string perPixelCode; //!< Preset per-pixel/per-vertex code, run once per warp mesh vertex. + + std::array waves; + std::array shapes; + + std::string warpShader; //!< Warp shader code. + std::string compositeShader; //!< Composite shader code. +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/ExpressionCodeTypes.h b/src/gui/preset_editor/ExpressionCodeTypes.h new file mode 100644 index 0000000..4e9b821 --- /dev/null +++ b/src/gui/preset_editor/ExpressionCodeTypes.h @@ -0,0 +1,22 @@ +#pragma once + +namespace Editor { + +/** + * @brief An enum with the different expression code contexts used in Milkdrop. + */ +enum class ExpressionCodeTypes : int +{ + PerFrameInit, + PerFrame, + PerVertex, + CustomWaveInit, + CustomWavePerFrame, + CustomWavePerPoint, + CustomShapeInit, + CustomShapePerFrame, + WarpShader, + CompositeShader +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/PresetEditorGUI.cpp b/src/gui/preset_editor/PresetEditorGUI.cpp new file mode 100644 index 0000000..7b96f3c --- /dev/null +++ b/src/gui/preset_editor/PresetEditorGUI.cpp @@ -0,0 +1,1133 @@ +#include "PresetEditorGUI.h" + +#include "CodeContextInformation.h" +#include "CodeEditorWindow.h" +#include "IconsFontAwesome7.h" + +#include "ProjectMSDLApplication.h" +#include "ProjectMWrapper.h" + +#include "notifications/DisplayToastNotification.h" +#include "notifications/UpdateWindowTitleNotification.h" + +#include +#include + +#include +#include + +#include + +namespace Editor { + +PresetEditorGUI::PresetEditorGUI(ProjectMGUI& gui) + : _gui(gui) + , _application(ProjectMSDLApplication::instance()) + , _projectMWrapper(Poco::Util::Application::instance().getSubsystem()) + , _menu(*this) +{ +} + +PresetEditorGUI::~PresetEditorGUI() +{ +} + +void PresetEditorGUI::Show(const std::string& presetFile) +{ + if (presetFile.empty()) + { + _presetFile = PresetFile::EmptyPreset(); + } + else + { + _presetFile = PresetFile(); + if (!_presetFile.ReadFile(presetFile)) + { + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("The selected preset could not be loaded.")); + return; + } + } + + TakeProjectMControl(); + + // Load the parsed preset as data + std::string errorMessage; + if (!_projectMWrapper.LoadPresetData(_presetFile.AsString(), errorMessage)) + { + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification(errorMessage)); + ReleaseProjectMControl(); + return; + } + + _editorPreset.FromParsedFile(_presetFile); + _codeEditorWindow = std::make_unique(); + + _visible = true; +} + +void PresetEditorGUI::Close() +{ + _wantClose = true; +} + +bool PresetEditorGUI::Draw() +{ + if (!_visible) + { + return false; + } + + HandleGlobalEditorKeys(); + + _menu.Draw(); + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->WorkPos); + ImGui::SetNextWindowSize(viewport->WorkSize); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, IM_COL32(0xd9, 0x1e, 0x18, 0xff)); + + if (ImGui::Begin("Preset Editor", &_visible, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground)) + { + DrawLeftSideBar(); + + ImGui::SameLine(); + + _codeEditorWindow->Draw(); + } + ImGui::End(); + + ImGui::PopStyleColor(); + + if (!_visible || _wantClose) + { + // Check for unsaved data + + ReleaseProjectMControl(); + + _visible = false; + _wantClose = false; + + _codeEditorWindow.reset(); + } + + return _visible; +} + +void PresetEditorGUI::UpdatePresetPreview() +{ + std::string errorMessage; + + _editorPreset.ToParsedFile(_presetFile); + if (!_projectMWrapper.LoadPresetData(_presetFile.AsString(), errorMessage)) + { + Poco::NotificationCenter::defaultCenter().postNotification(new DisplayToastNotification("Preset reload failed:\n" + errorMessage)); + } +} + +void PresetEditorGUI::CopyToClipboard() +{ + _editorPreset.ToParsedFile(_presetFile); + SDL_SetClipboardText(_presetFile.AsString().c_str()); +} + +void PresetEditorGUI::HandleGlobalEditorKeys() +{ + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + // Reload edited preset - Alt+Enter or F5 + if (ImGui::IsKeyPressed(ImGuiKey_F5, false) || alt && ImGui::IsKeyPressed(ImGuiKey_Enter, false)) + { + UpdatePresetPreview(); + } + + // Save preset - Ctrl+S + if (ctrl && ImGui::IsKeyPressed(ImGuiKey_S, false)) + { + + } + + // New preset - Ctrl+N + if (ctrl && ImGui::IsKeyPressed(ImGuiKey_N, false)) + { + Show(""); + } +} + +void PresetEditorGUI::TakeProjectMControl() +{ + // Detach playlist, seize control over projectM + _projectMWrapper.UnbindPlaylist(); + _projectMWrapper.EnablePlaybackControl(false); + _projectMWrapper.HardLockPreset(true); + + Poco::NotificationCenter::defaultCenter().postNotification(new UpdateWindowTitleNotification("projectM Preset Editor")); +} + +void PresetEditorGUI::ReleaseProjectMControl() +{ + // Reattach playlist and restore original settings + _projectMWrapper.BindPlaylist(); + _projectMWrapper.EnablePlaybackControl(true); + _projectMWrapper.HardLockPreset(false); + + Poco::NotificationCenter::defaultCenter().postNotification(new UpdateWindowTitleNotification()); +} + +void PresetEditorGUI::EditCode(ExpressionCodeTypes type, std::string& code, int index) +{ + _codeEditorWindow->OpenCodeInTab(type, code, index); +} + +unsigned long PresetEditorGUI::GetLoC(const std::string& code) +{ + std::istringstream iss(code); + unsigned long loc = 0; + std::string line; + while (std::getline(iss, line)) + { + ++loc; + } + + return loc; +} + +void PresetEditorGUI::DrawLeftSideBar() +{ + ImGuiWindowFlags window_flags = ImGuiWindowFlags_None; + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 5.0f); + ImGui::SetNextWindowBgAlpha(0.5f); + ImGui::BeginChild("LeftSideBar", ImVec2(400, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeX, window_flags); + + DrawPresetCompatibilitySettings(); + DrawGeneralParameters(); + DrawDefaultWaveformSettings(); + DrawMotionVectorSettings(); + DrawWarpMotionSettings(); + DrawMotionCodeSettings(); + DrawBorderSettings(); + + if (ImGui::CollapsingHeader("Custom Waveforms")) + { + ImGui::Indent(16.0f); + for (auto& wave : _editorPreset.waves) + { + DrawCustomWaveSettings(wave); + } + ImGui::Unindent(16.0f); + } + + if (ImGui::CollapsingHeader("Custom Shapes")) + { + ImGui::Indent(16.0f); + for (auto& shape : _editorPreset.shapes) + { + DrawCustomShapeSettings(shape); + } + ImGui::Unindent(16.0f); + } + + DrawShaderCodeSettings(); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); +} + +void PresetEditorGUI::DrawPresetCompatibilitySettings() +{ + if (ImGui::CollapsingHeader("Preset Compatibility")) + { + { + static const char* milkdropVersions[] = { + "140 - Milkdrop 1.4, No Shaders", + "200 - Milkdrop 2.0, Same Warp/Comp PS Versions", + "201 - Milkdrop 2.1+ Separate Warp/Comp PS Versions"}; + int selectionIndex = _editorPreset.presetVersion < 200 ? 0 + : _editorPreset.presetVersion == 200 ? 1 + : 2; + const char* comboboxPreviewValue = milkdropVersions[selectionIndex]; + if (ImGui::BeginCombo("Preset Version", comboboxPreviewValue)) + { + for (int index = 0; index < 3; index++) + { + const bool isSelected = (selectionIndex == index); + if (ImGui::Selectable(milkdropVersions[index], isSelected)) + { + switch (index) + { + case 0: + default: + _editorPreset.presetVersion = 140; + _editorPreset.warpShaderVersion = 0; + _editorPreset.compositeShaderVersion = 0; + break; + case 1: + _editorPreset.presetVersion = 200; + _editorPreset.compositeShaderVersion = _editorPreset.warpShaderVersion; + break; + case 2: + _editorPreset.presetVersion = 201; + break; + } + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + DrawHelpTooltip("Determines the feature set of a preset regarding use of shaders."); + } + + if (_editorPreset.presetVersion == 200) + { + ImGui::TextUnformatted("Pixel Shader Version"); + ImGui::Indent(16.0f); + + if (ImGui::SliderInt("PS Version", &_editorPreset.warpShaderVersion, 0, 4)) + { + _editorPreset.compositeShaderVersion = _editorPreset.warpShaderVersion; + } + DrawHelpTooltip("Minimum required DirectX Pixel Shader version.\n0/1=No PS, 2=PS 2.0, 3=PS 2.x, 4=PS 3.0 (Ctrl-click to set higher value)"); + + ImGui::Unindent(16.0f); + } + else if (_editorPreset.presetVersion > 200) + { + ImGui::TextUnformatted("Pixel Shader Versions"); + ImGui::Indent(16.0f); + + ImGui::SliderInt("Warp PS Version", &_editorPreset.warpShaderVersion, 0, 4); + DrawHelpTooltip("Minimum required DirectX Pixel Shader version for the warp shader.\n0/1=No PS, 2=PS 2.0, 3=PS 2.x, 4=PS 3.0 (Ctrl-click to set higher value)"); + ImGui::SliderInt("Composite PS Version", &_editorPreset.compositeShaderVersion, 0, 4); + DrawHelpTooltip("Minimum required DirectX Pixel Shader version for the composite shader.\n0/1=No PS, 2=PS 2.0, 3=PS 2.x, 4=PS 3.0 (Ctrl-click to set higher value)"); + + ImGui::Unindent(16.0f); + } + } +} + +void PresetEditorGUI::DrawGeneralParameters() +{ + if (ImGui::CollapsingHeader("General Parameters")) + { + bool usesCompositeShader = _editorPreset.presetVersion >= 200 && _editorPreset.compositeShaderVersion >= 2; + + ImGui::TextUnformatted("Post-Processing Filters"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Decay##PerFrameDecay", &_editorPreset.decay, 0.00f, 1.0f); + DrawHelpTooltip("Controls the eventual fade to black.\n1=no fade, 0.9=strong fade, 0.98=recommended"); + + ImGui::BeginDisabled(usesCompositeShader); + + ImGui::SliderFloat("Gamma Adjustment##GammaAdjustment", &_editorPreset.gammaAdj, 0.00f, 10.0f); + DrawHelpTooltip("Controls display brightness.\n1=normal, 2=double, 3=triple, etc.\nOnly applied if no composite shader is used!"); + + ImGui::Checkbox("Brighten", &_editorPreset.brighten); + DrawHelpTooltip("Brightens the darker parts of the image (nonlinear; square root filter)\nOnly applied if no composite shader is used!"); + + ImGui::Checkbox("Darken", &_editorPreset.darken); + DrawHelpTooltip("Darkens the brighter parts of the image (nonlinear; squaring filter)\nOnly applied if no composite shader is used!"); + + ImGui::Checkbox("Solarize", &_editorPreset.solarize); + DrawHelpTooltip("Emphasizes mid-range colors\nOnly applied if no composite shader is used!"); + + ImGui::Checkbox("Invert", &_editorPreset.invert); + DrawHelpTooltip("Inverts the colors in the image\nOnly applied if no composite shader is used!"); + + ImGui::Checkbox("Darken Center", &_editorPreset.darkenCenter); + DrawHelpTooltip("Darkens a diamond-shaped area in the center of the image.\nApplied after drawing shapes/waveforms, but before drawing borders."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + static const char* videoEchoOrientations[] = { + "Normal", + "Flip X", + "Flip Y", + "Flip X + Y"}; + + ImGui::TextUnformatted("Video Echo"); + ImGui::Indent(16.0f); + ImGui::SliderFloat("Zoom##VideoEchoZoom", &_editorPreset.videoEchoZoom, 0.01f, 10.0f); + DrawHelpTooltip("Controls the size of the second graphics layer\nOnly applied if no composite shader is used!"); + + ImGui::SliderFloat("Alpha##VideoEchoAlpha", &_editorPreset.videoEchoAlpha, 0.00f, 1.0f); + DrawHelpTooltip("Controls the opacity of the second graphics layer.\n0=transparent (off), 0.5=half-mix, 1=opaque\nOnly applied if no composite shader is used!"); + + { + int selectionIndex = _editorPreset.videoEchoOrientation % 4; + const char* comboboxPreviewValue = videoEchoOrientations[_editorPreset.videoEchoOrientation % 4]; + if (ImGui::BeginCombo("Orientation##VideoEchoOrientation", comboboxPreviewValue)) + { + for (int index = 0; index < 4; index++) + { + const bool isSelected = (selectionIndex == index); + if (ImGui::Selectable(videoEchoOrientations[index], isSelected)) + { + _editorPreset.videoEchoOrientation = index; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + DrawHelpTooltip("Selects an orientation for the second graphics layer.\nOnly applied if no composite shader is used!"); + } + + ImGui::EndDisabled(); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Blur Texture Value Range"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("Normally these are set to 0 (min) and 1 (max) each.\n" + "You can clamp the values in the blur texture to a tighter range,though.\n" + "This will increase the precision in the blur textures, but you run the risk of clamping values to your min/max.\n" + "If you use the GetBlur1() .. GetBlur3() functions to sample the blur texture, they will automatically \"unpack\" the values for you in the end!"); + + ImGui::Indent(16.0f); + + ImGui::DragFloatRange2("Blur 1 Range", &_editorPreset.blur1Min, &_editorPreset.blur1Max, 0.01, 0.0, 1.0); + ImGui::DragFloatRange2("Blur 2 Range", &_editorPreset.blur2Min, &_editorPreset.blur2Max, 0.01, 0.0, 1.0); + ImGui::DragFloatRange2("Blur 3 Range", &_editorPreset.blur3Min, &_editorPreset.blur3Max, 0.01, 0.0, 1.0); + ImGui::Spacing(); + ImGui::SliderFloat("Blur 1 Edge Darken", &_editorPreset.blur1EdgeDarken, 0.00f, 1.0f); + + ImGui::Unindent(16.0f); + + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawDefaultWaveformSettings() +{ + if (ImGui::CollapsingHeader("Default Waveform Effect")) + { + DrawWaveformModeSelection(); + + ImGui::TextUnformatted("Drawing Settings"); + + ImGui::Indent(16.0f); + + ImGui::Checkbox("Additive Waves##WaveformAdditive", &_editorPreset.additiveWaves); + DrawHelpTooltip("The wave is drawn additively, saturating the image at white"); + + ImGui::Checkbox("Dots##WaveformDrawDots", &_editorPreset.waveDots); + DrawHelpTooltip("The waveform is drawn as dots (instead of lines)"); + + ImGui::Checkbox("Thick##WaveformDrawThick", &_editorPreset.waveThick); + DrawHelpTooltip("The waveform's lines (or dots) are drawn with double thickness"); + + ImGui::SliderFloat("Scale##DefaultWaveformScale", &_editorPreset.waveScale, 0.00f, 5.0f); + DrawHelpTooltip("Scaling factor of the waveform.\n1 = original size, 2 = twice the size, 0.5 = half the size"); + + ImGui::SliderFloat("Smoothing##DefaultWaveformSmoothing", &_editorPreset.waveSmoothing, 0.00f, 1.0f); + DrawHelpTooltip("Smoothing of the waveform.\n0 = no smoothing, 0.75 = heavy smoothing"); + + ImGui::SliderFloat("Mystery Param##DefaultWaveformParam", &_editorPreset.waveParam, -1.00f, 1.0f); + DrawHelpTooltip("This value does different things depending on the mode.\nFor example, it could control angle at which the waveform was drawn."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Position"); + + ImGui::Indent(16.0f); + + ImGui::SliderFloat("X##DefaultWaveformX", &_editorPreset.waveX, 0.00f, 1.0f); + DrawHelpTooltip("Position of the waveform.\n0 = far left edge of screen, 0.5 = center, 1 = far right"); + + ImGui::SliderFloat("Y##DefaultWaveformY", &_editorPreset.waveY, 0.00f, 1.0f); + DrawHelpTooltip("Position of the waveform.\n0 = very bottom of screen, 0.5 = center, 1 = top"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Color"); + + ImGui::Indent(16.0f); + + ImGui::ColorEdit4("Color##DefaultWaveformColor", &_editorPreset.waveColor.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The color of the waveform"); + + ImGui::Checkbox("Maximize Wave Color", &_editorPreset.maximizeWaveColor); + DrawHelpTooltip("All 3 R/G/B colors will be scaled up until at least one reaches 1.0"); + + ImGui::Checkbox("Modulate Alpha by Volume", &_editorPreset.modWaveAlphaByVolume); + DrawHelpTooltip("Modulate waveform alpha value by audio volume"); + + ImGui::DragFloatRange2("Modulation Range", &_editorPreset.modWaveAlphaStart, &_editorPreset.modWaveAlphaEnd, 0.01, 0.0, 1.0); + DrawHelpTooltip("Clamps alpha modulation 0->1 within this relative volume range."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawWaveformModeSelection() +{ + static const char* waveformModes[] = { + "Circle", + "X/Y Oscillation Spiral", + "Centered Spiro", + "Centered Spiro (Volume)", + "Derivative Line", + "Explosive Hash", + "Line", + "Double Line", + "Spectrum Line", + "Wave 9", + "Wave X", + "Wave 11", + "Wave Skewed", + "Wave Star", + "Wave Flower", + "Wave Lasso"}; + + ImGui::TextUnformatted("Mode"); + + ImGui::Indent(16.0f); + + int selectionIndex = _editorPreset.waveMode % 16; + const char* comboboxPreviewValue = waveformModes[selectionIndex]; + if (ImGui::BeginCombo("##DefaultWaveformMode", comboboxPreviewValue)) + { + for (int index = 0; index < 16; index++) + { + if (index == 0) + { + ImGui::TextDisabled("%s", "-- Original Milkdrop Modes --"); + } + if (index == 9) + { + ImGui::TextDisabled("%s", "-- projectM / Milkdrop 3 Extras --"); + } + const bool isSelected = (selectionIndex == index); + if (ImGui::Selectable(waveformModes[index], isSelected)) + { + _editorPreset.waveMode = index; + } + + if (isSelected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + DrawHelpTooltip("Determines the drawing style of the classic default waveform.\n" + "Selecting any of the \"extra\" waveform modes will wrap around to " + "the original modes in classic Milkdrop versions."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); +} + +void PresetEditorGUI::DrawMotionVectorSettings() +{ + if (ImGui::CollapsingHeader("Motion Vector Grid")) + { + ImGui::TextUnformatted("Layout And Position"); + + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Size X##MotionVectorX", &_editorPreset.mvX, 0.00f, 64.0f); + DrawHelpTooltip("The number of motion vectors in the X direction"); + + ImGui::SliderFloat("size Y##MotionVectorY", &_editorPreset.mvY, 0.00f, 48.0f); + DrawHelpTooltip("The number of motion vectors in the Y direction"); + + ImGui::SliderFloat("Length##MotionVectorLength", &_editorPreset.mvL, 0.00f, 5.0f); + DrawHelpTooltip("The length of the motion vectors\n0=no trail, 1=normal, 2=double..."); + + ImGui::SliderFloat("X Offset##MotionVectorOffsetX", &_editorPreset.mvDX, -1.00f, 1.0f); + DrawHelpTooltip("Horizontal placement offset of the motion vectors"); + + ImGui::SliderFloat("Y Offset##MotionVectorOffsetY", &_editorPreset.mvDY, -1.00f, 1.0f); + DrawHelpTooltip("Vertical placement offset of the motion vectors"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Color"); + + ImGui::Indent(16.0f); + + ImGui::ColorEdit4("Color##MotionVectorColor", &_editorPreset.mvColor.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The color of the motion vector grid"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawMotionCodeSettings() +{ + if (ImGui::CollapsingHeader("Motion Code")) + { + + ImGui::TextUnformatted("Per-Frame Init"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code will only run once when the preset is loaded.\n" + "It can be used to set up the Per-Frame, qXX, regXX and megabuf variables to known initial (non-zero) values.\n" + "Calculations only required once can also be done in this code."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##EditPerFrameInitCode")) + { + EditCode(ExpressionCodeTypes::PerFrameInit, _editorPreset.perFrameInitCode); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(_editorPreset.perFrameInitCode), _editorPreset.perFrameInitCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + + ImGui::TextUnformatted("Per-Frame"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code runs at the very beginning of each rendered frame.\n" + "Any complex/expensive calculations should be done here, eventually passing the results via qXX or regXX vars" + "or gmegabuf to effects later in the rendering process.\n" + "All qXX var values set here are copied to the Per-Vertex code and all custom waveforms and shapes."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##EditPerFrameCode")) + { + EditCode(ExpressionCodeTypes::PerFrame, _editorPreset.perFrameCode); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(_editorPreset.perFrameCode), _editorPreset.perFrameCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + + ImGui::TextUnformatted("Per-Vertex"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code is executed for every grid vertex in the warp mesh.\n" + "Since this code may run several thousand times PER FRAME, it is key to keep it fast and slim.\n" + "Any values which don't depend on the x/y grid coordinates should be pre-calculated in the Per-Frame code" + "and passed in via qXX or regXX vars or using gmegabuf.\n" + "Also known als \"Per-Pixel\" code."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##EditPerVertexCode")) + { + EditCode(ExpressionCodeTypes::PerVertex, _editorPreset.perPixelCode); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(_editorPreset.perPixelCode), _editorPreset.perPixelCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawWarpMotionSettings() +{ + if (ImGui::CollapsingHeader("Warping and Motion")) + { + ImGui::TextUnformatted("Translation"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Horizontal Motion", &_editorPreset.xPush, -1.00f, 1.0f); + DrawHelpTooltip("Controls amount of constant horizontal motion.\n-0.01 = move left 1% per frame, 0=none, 0.01 = move right 1%"); + + ImGui::SliderFloat("Vertical Motion", &_editorPreset.yPush, -1.00f, 1.0f); + DrawHelpTooltip("Controls amount of constant vertical motion.\n-0.01 = move up 1% per frame, 0=none, 0.01 = move down 1%"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Rotation"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Rotation##WarpRotation", &_editorPreset.rot, -1.00f, 1.0f); + DrawHelpTooltip("Controls the amount of rotation.\n0=none, 0.1=slightly right, -0.1=slightly clockwise, 0.1=CCW"); + + ImGui::SliderFloat("Center X##WarpCenterX", &_editorPreset.rotCX, 0.00f, 1.0f); + DrawHelpTooltip("Controls where the center of rotation and stretching is, horizontally.\n0=left, 0.5=center, 1=right"); + + ImGui::SliderFloat("Center Y##WarpCenterY", &_editorPreset.rotCY, 0.00f, 1.0f); + DrawHelpTooltip("Controls where the center of rotation and stretching is, vertically.\n0=top, 0.5=center, 1=bottom"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Scaling"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Stretch X##WarpStretchX", &_editorPreset.stretchX, 0.00f, 2.0f); + DrawHelpTooltip("Controls amount of constant horizontal stretching.\n0.99=shrink 1%, 1=normal, 1.01=stretch 1%"); + + ImGui::SliderFloat("Stretch Y##WarpStretchY", &_editorPreset.stretchY, 0.00f, 2.0f); + DrawHelpTooltip("Controls amount of constant vertical stretching.\n0.99=shrink 1%, 1=normal, 1.01=stretch 1%"); + + ImGui::SliderFloat("Zoom##WarpZoom", &_editorPreset.zoom, 0.00f, 2.0f); + DrawHelpTooltip("Controls inward/outward motion.\n0.9=zoom out 10% per frame, 1.0=no zoom, 1.1=zoom in 10%"); + + ImGui::SliderFloat("Zoom Exponent##Warp", &_editorPreset.zoomExponent, 0.00f, 5.0f); + DrawHelpTooltip("Controls the curvature of the zoom; 1=normal"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Warping"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Warp Amount##WarpAmount", &_editorPreset.warpAmount, 0.00f, 10.0f); + DrawHelpTooltip("Controls the magnitude of the warping.\n0=none, 1=normal, 2=major warping..."); + + ImGui::SliderFloat("Warp Scale##WarpScale", &_editorPreset.warpScale, 0.00f, 10.0f); + DrawHelpTooltip("Controls the scale of the warp effect."); + + ImGui::SliderFloat("Warp Animation Speed##WarpAnimSpeed", &_editorPreset.warpAnimSpeed, 0.00f, 5.0f); + DrawHelpTooltip("Controls the speed of the warp effect."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Options"); + ImGui::Indent(16.0f); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawBorderSettings() +{ + if (ImGui::CollapsingHeader("Border Effect")) + { + ImGui::TextUnformatted("Outer Border"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Thickness##OuterBorderThickness", &_editorPreset.outerBorderSize, 0.0f, 1.0f); + DrawHelpTooltip("Thickness of the outer border drawn at the edges of the screen every frame"); + ImGui::ColorEdit4("Color##OuterBorderColor", &_editorPreset.outerBorderColor.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("Color of the outer border drawn at the edges of the screen every frame"); + + ImGui::Unindent(16.0f); + + ImGui::Spacing(); + + ImGui::TextUnformatted("Inner Border"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("Thickness##InnerBorderThickness", &_editorPreset.innerBorderSize, 0.0f, 1.0f); + DrawHelpTooltip("Thickness of the inner border drawn at the edges of the screen every frame"); + + ImGui::ColorEdit4("Color##InnerBorderColor", &_editorPreset.innerBorderColor.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("Color of the inner border drawn at the edges of the screen every frame"); + + ImGui::Unindent(16.0f); + + ImGui::Spacing(); + } +} + +void PresetEditorGUI::DrawCustomWaveSettings(EditorPreset::Wave& waveform) +{ + std::string idx = std::to_string(waveform.index + 1); + + ImGui::PushID(std::string("CustomWave" + idx).c_str()); + + if (ImGui::CollapsingHeader(std::string("Waveform " + idx).c_str())) + { + ImGui::Checkbox("Enabled", &waveform.enabled); + DrawHelpTooltip("This waveform is only rendered if enabled explicitly."); + + ImGui::Spacing(); + + ImGui::BeginDisabled(!waveform.enabled); + + ImGui::TextUnformatted("Position And Style"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("X", &waveform.x, 0.0f, 1.0f); + DrawHelpTooltip("Horizontal position of the waveform.\n" + "0=left, 0.5=center, 1=right"); + + ImGui::SliderFloat("Y", &waveform.x, 0.0f, 1.0f); + DrawHelpTooltip("Vertical position of the waveform.\n" + "0=bottom, 0.5=center, 1=top"); + + ImGui::SliderInt("Samples", &waveform.samples, 1, 512); + DrawHelpTooltip("Number of waveform points to draw."); + + ImGui::SliderInt("Separation", &waveform.sep, 0, 255); + DrawHelpTooltip("Separation distance of dual waveforms."); + + ImGui::SliderFloat("Scaling", &waveform.scaling, 0.0f, 10.0f); + DrawHelpTooltip("Waveform value scaling factor."); + + ImGui::SliderFloat("Smoothing", &waveform.scaling, 0.0f, 1.0f); + DrawHelpTooltip("Waveform smoothing value.\n" + "0.0=no smoothing, 0.5=default, 1.0=extreme smoothing"); + + ImGui::ColorEdit4("Color", &waveform.color.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The default color of the waveform."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Drawing Flags"); + ImGui::Indent(16.0f); + + ImGui::Checkbox("Spectrum", &waveform.spectrum); + DrawHelpTooltip("Use spectrum instead of oscilloscope data."); + + ImGui::Checkbox("Dots", &waveform.useDots); + DrawHelpTooltip("Draw Waveform as dots instead of lines."); + + ImGui::Checkbox("Thick", &waveform.drawThick); + DrawHelpTooltip("Draw waveform lines or dots twice as thick."); + + ImGui::Checkbox("Additive", &waveform.additive); + DrawHelpTooltip("Use additive color blending when drawing."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Drawing Code"); + ImGui::Indent(16.0f); + + ImGui::TextUnformatted("Initialization"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code will only run once when the preset is loaded.\n" + "It can be used to set up variables specific to this waveform to" + " known initial (non-zero) values. The waveform tX variables can also be initialized here."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##InitCode")) + { + EditCode(ExpressionCodeTypes::CustomWaveInit, waveform.initCode, waveform.index + 1); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(waveform.initCode), waveform.initCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + + ImGui::TextUnformatted("Per-Frame"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code runs once before rendering the waveform.\n" + "Any complex/expensive calculations should be done here, eventually passing the results via qXX/tX vars " + "or (g)megabuf to the per-point code.\n" + "All qXX/tX var values set here are copied to each Per-Point code instance."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##PerFrameCode")) + { + EditCode(ExpressionCodeTypes::CustomWavePerFrame, waveform.perFrameCode, waveform.index + 1); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(waveform.perFrameCode), waveform.perFrameCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + + ImGui::TextUnformatted("Per-Point"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code is executed for every point of the waveform.\n" + "This allows full control over the placement of each vertex of the waveform, " + "enabling drawing arbitrary shapes."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##PerPointCode")) + { + EditCode(ExpressionCodeTypes::CustomWavePerPoint, waveform.perPointCode, waveform.index + 1); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(waveform.perPointCode), waveform.perPointCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::EndDisabled(); + } + else if (waveform.enabled) + { + ImGui::SameLine(); + ImGui::TextDisabled(" " ICON_FA_CHECK " Enabled"); + } + + ImGui::PopID(); +} + +void PresetEditorGUI::DrawCustomShapeSettings(EditorPreset::Shape& shape) +{ + std::string idx = std::to_string(shape.index + 1); + + ImGui::PushID(std::string("CustomShape" + idx).c_str()); + + if (ImGui::CollapsingHeader(std::string("Shape " + idx).c_str())) + { + ImGui::Checkbox("Enabled", &shape.enabled); + DrawHelpTooltip("This shape is only rendered if enabled explicitly."); + + ImGui::Spacing(); + + ImGui::BeginDisabled(!shape.enabled); + + ImGui::TextUnformatted("Position And Style"); + ImGui::Indent(16.0f); + + ImGui::SliderFloat("X", &shape.x, 0.0f, 1.0f); + DrawHelpTooltip("Default horizontal position of the shape.\n" + "0=left, 0.5=center, 1=right"); + + ImGui::SliderFloat("Y", &shape.y, 0.0f, 1.0f); + DrawHelpTooltip("Default vertical position of the shape.\n" + "0=bottom, 0.5=center, 1=top"); + + ImGui::SliderFloat("Radius", &shape.radius, 0.001f, 10.0f); + DrawHelpTooltip("Default radius of the shape."); + + ImGui::SliderFloat("Angle", &shape.angle, 0.0f, 2 * 3.14159265358979323846); + DrawHelpTooltip("Default rotation angle of the shape."); + + ImGui::SliderInt("Sides", &shape.sides, 3, 100); + DrawHelpTooltip("The default number of sides that make up the polygonal shape."); + + ImGui::SliderInt("Instances", &shape.instances, 1, 1024); + DrawHelpTooltip("The total number of instances (the number of times to repeat the per-frame equations for, and draw, this shape)."); + + ImGui::ColorEdit4("Inner Color", &shape.color.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The default color and opacity towards the center of the shape."); + + ImGui::ColorEdit4("Outer Color", &shape.color2.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The default color and opacity towards the outer edge of the shape"); + + ImGui::ColorEdit4("Border Color", &shape.borderColor.red, ImGuiColorEditFlags_Float); + DrawHelpTooltip("The default color and opacity of the border of the shape"); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Texturing"); + ImGui::Indent(16.0f); + + ImGui::Checkbox("Textured", &shape.textured); + DrawHelpTooltip("If enabled, the shape will be textured with the image from the previous frame."); + + ImGui::BeginDisabled(!shape.textured); + + ImGui::SliderFloat("Angle##Texture", &shape.tex_ang, 0.0f, 2 * 3.14159265358979323846); + DrawHelpTooltip("The angle at which to rotate the previous frame's image before applying it to the shape."); + + ImGui::SliderFloat("Zoom##Texture", &shape.tex_zoom, 0.001f, 10.0f); + DrawHelpTooltip("The portion of the previous frame's image to use with the shape."); + + ImGui::EndDisabled(); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Drawing Flags"); + ImGui::Indent(16.0f); + ImGui::Checkbox("Thick Outline", &shape.thickOutline); + DrawHelpTooltip("If enabled, the border will be overdrawn 4 times to make it thicker, bolder, and more visible."); + + ImGui::Checkbox("Additive", &shape.additive); + DrawHelpTooltip("If enabled, the shape will add color to saturate the image toward white; otherwise, it will replace what's there."); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::TextUnformatted("Drawing Code"); + ImGui::Indent(16.0f); + + ImGui::TextUnformatted("Initialization"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code will only run once when the preset is loaded.\n" + "It can be used to set up variables specific to this shape to" + " known initial (non-zero) values. The shape tX variables can ONLY be initialized here."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##InitCode")) + { + EditCode(ExpressionCodeTypes::CustomShapeInit, shape.initCode, shape.index + 1); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(shape.initCode), shape.initCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + + ImGui::TextUnformatted("Per-Frame"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("This code runs once per shape instance.\n" + "Any complex/expensive calculations should be done in the preset per-frame code, eventually passing the results via qXX/tX vars " + "or gmegabuf to the shape per-frame code. The tX variables are reset to their init values for each instance."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##PerFrameCode")) + { + EditCode(ExpressionCodeTypes::CustomShapePerFrame, shape.perFrameCode, shape.index + 1); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(shape.perFrameCode), shape.perFrameCode.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::EndDisabled(); + } + else if (shape.enabled) + { + ImGui::SameLine(); + ImGui::TextDisabled(" " ICON_FA_CHECK " Enabled"); + } + + ImGui::PopID(); +} + +void PresetEditorGUI::DrawShaderCodeSettings() +{ + bool shaderTabDisabled = _editorPreset.presetVersion < 200 || (_editorPreset.warpShaderVersion < 2 && _editorPreset.compositeShaderVersion < 2); + + ImGui::BeginDisabled(shaderTabDisabled); + if (ImGui::CollapsingHeader("Warp / Composite Shaders")) + { + + ImGui::BeginDisabled(_editorPreset.warpShaderVersion < 2); + + ImGui::TextUnformatted("Warp Shader"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("The warp shader is used to draw the warp mesh, after the motion vectors were drawn but before " + "any waveforms or shapes are rendered. It can be used to amend or replace the classic Milkdrop 1 " + "warp effect. It is not necessary to use the previous image at all - this shader may create a new" + "image on each frame, onto which waveforms and shapes are drawn.\n\n" ICON_FA_CODE + " This shader must be written in DirectX HLSL."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##WarpShader")) + { + EditCode(ExpressionCodeTypes::WarpShader, _editorPreset.warpShader); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(_editorPreset.warpShader), _editorPreset.warpShader.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::EndDisabled(); + + ImGui::BeginDisabled(_editorPreset.compositeShaderVersion < 2); + + ImGui::TextUnformatted("Composite Shader"); + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + DrawHelpTooltip("The composite shader runs immediately before displaying the preset image to the user, " + "allowing post-processing of the previous rendering steps or even drawing a whole new image " + "on top of it. The input (sampler_main) to the composite shader is passed on to the next frame, " + "the output of the composite shader will only be displayed on screen and then discarded when " + "the next frame is rendered.\n\n" ICON_FA_CODE " This shader must be written in DirectX HLSL."); + + ImGui::Indent(16.0f); + + if (ImGui::Button(ICON_FA_PENCIL " Edit##CompositeShader")) + { + EditCode(ExpressionCodeTypes::CompositeShader, _editorPreset.compositeShader); + } + ImGui::SameLine(); + ImGui::Text("(%lu lines / %lu characters)", GetLoC(_editorPreset.compositeShader), _editorPreset.compositeShader.length()); + + ImGui::Unindent(16.0f); + ImGui::Spacing(); + + ImGui::EndDisabled(); + } + if (shaderTabDisabled) + { + DrawHelpTooltip("To enable HLSL shaders, open the compatibility settings and set the " + "preset version to 200 or higher and the PS version to 2 or higher."); + } + ImGui::EndDisabled(); + + DrawShaderLossWarning(); +} + +void PresetEditorGUI::DrawShaderLossWarning() const +{ + if (_editorPreset.presetVersion < 200) + { + if (!_editorPreset.warpShader.empty() || !_editorPreset.compositeShader.empty()) + { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::TextWrapped("%s", "WARNING: You have disabled shader support, but the preset still contains shader code.\n" + "The shader code WILL NOT BE SAVED and gets lost once you close this preset in the editor."); + ImGui::PopStyleColor(); + } + } + else + { + if (_editorPreset.warpShaderVersion < 2 && !_editorPreset.warpShader.empty()) + { + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::TextWrapped("%s", "WARNING: You have selected a WARP shader PS Version below 2, but the preset still contains warp shader code.\n" + "The warp shader code WILL NOT BE SAVED and gets lost once you close this preset in the editor."); + ImGui::PopStyleColor(); + } + if (_editorPreset.compositeShaderVersion < 2 && !_editorPreset.compositeShader.empty()) + { + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::TextWrapped("%s", "WARNING: You have selected a COMPOSITE shader PS Version below 2, but the preset still contains composite shader code.\n" + "The composite shader WILL NOT BE SAVED and gets lost once you close this preset in the editor."); + ImGui::PopStyleColor(); + } + } +} + +void PresetEditorGUI::DrawHelpTooltip(const std::string& helpText) +{ + if (ImGui::BeginItemTooltip()) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(helpText.c_str()); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +} // namespace Editor \ No newline at end of file diff --git a/src/gui/preset_editor/PresetEditorGUI.h b/src/gui/preset_editor/PresetEditorGUI.h new file mode 100644 index 0000000..3ce5cfc --- /dev/null +++ b/src/gui/preset_editor/PresetEditorGUI.h @@ -0,0 +1,98 @@ +#pragma once + +#include "EditorMenu.h" +#include "EditorPreset.h" +#include "ExpressionCodeTypes.h" +#include "PresetFile.h" + +#include +#include + +class ProjectMGUI; +class ProjectMSDLApplication; +class ProjectMWrapper; + +namespace Editor { + +class CodeEditorWindow; + +class PresetEditorGUI +{ +public: + explicit PresetEditorGUI(ProjectMGUI& gui); + + ~PresetEditorGUI(); + + /** + * @brief Displays the preset editor screen and associated windows. + * @param presetFile The preset file name to load and edit. + */ + void Show(const std::string& presetFile); + + /** + * @brief Tells the editor UI to close. + * If there are unsaved changes, the user will be asked to save or abort the close. + */ + void Close(); + + /** + * @brief Draws the preset editor. + * @return true if the preset editor is visible and has been drawn, false otherwise. + */ + bool Draw(); + + /** + * @brief Reloads the rendered preview with the current changes. + */ + void UpdatePresetPreview(); + + /** + * @brief Reloads the rendered preview with the current changes. + */ + void CopyToClipboard(); + +private: + void HandleGlobalEditorKeys(); + void TakeProjectMControl(); + void ReleaseProjectMControl(); + + void EditCode(ExpressionCodeTypes type, std::string& code, int index = 0); + + unsigned long GetLoC(const std::string& code); + + void DrawLeftSideBar(); + + void DrawPresetCompatibilitySettings(); + void DrawGeneralParameters(); + void DrawDefaultWaveformSettings(); + void DrawWaveformModeSelection(); + void DrawMotionVectorSettings(); + void DrawMotionCodeSettings(); + void DrawWarpMotionSettings(); + void DrawBorderSettings(); + void DrawCustomWaveSettings(EditorPreset::Wave& waveform); + void DrawCustomShapeSettings(EditorPreset::Shape& shape); + void DrawShaderCodeSettings(); + void DrawShaderLossWarning() const; + + static void DrawHelpTooltip(const std::string& helpText); + + + ProjectMGUI& _gui; //!< Reference to the projectM GUI instance + ProjectMSDLApplication& _application; + ProjectMWrapper& _projectMWrapper; + + bool _wantClose{false}; + bool _visible{false}; //!< true if the editor is visible, false if not. + + EditorMenu _menu; //!< The editor-specific main menu bar. + + std::string _loadedPresetPath; //!< The full path of the currently loaded preset. Can be empty. + PresetFile _presetFile; //!< The raw preset data. + EditorPreset _editorPreset; //!< The preset data in a parsed, strongly-typed container. + + std::unique_ptr _codeEditorWindow; //!< The code editor window. + +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/PresetFile.cpp b/src/gui/preset_editor/PresetFile.cpp new file mode 100644 index 0000000..7d586e7 --- /dev/null +++ b/src/gui/preset_editor/PresetFile.cpp @@ -0,0 +1,590 @@ +#include "PresetFile.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Editor { + +PresetFile PresetFile::EmptyPreset() +{ + PresetFile emptyPreset; + + if (emptyPreset.ReadData(emptyPreset.ExportPreset().str())) + { + return emptyPreset; + } + + return {}; +} + +auto PresetFile::ReadFile(const std::string& presetFile) -> bool +{ + std::ifstream presetStream(presetFile.c_str(), std::ios_base::in | std::ios_base::binary); + return ReadStream(presetStream); +} + +auto PresetFile::ReadData(const std::string& presetData) -> bool +{ + std::istringstream presetStream(presetData); + return ReadStream(presetStream); +} + +auto PresetFile::ReadStream(std::istream& presetStream) -> bool +{ + if (!presetStream.good()) + { + return false; + } + + presetStream.seekg(0, std::istream::end); + auto fileSize = presetStream.tellg(); + presetStream.seekg(0, std::istream::beg); + + if (static_cast(fileSize) > maxFileSize) + { + return false; + } + + std::vector presetFileContents(fileSize); + presetStream.read(presetFileContents.data(), fileSize); + + if (presetStream.fail() || presetStream.bad()) + { + return false; + } + + _presetValues.clear(); + + size_t startPos{0}; // Starting position of current line + size_t pos{0}; // Current read position + + auto parseLineIfDataAvailable = [this, &pos, &startPos, &presetFileContents]() { + if (pos > startPos) + { + auto beg = presetFileContents.begin(); + std::string line(beg + startPos, beg + pos); + ParseLine(line); + } + }; + + while (pos < presetFileContents.size()) + { + switch (presetFileContents[pos]) + { + case '\r': + case '\n': + // EOL, skip over CR/LF + parseLineIfDataAvailable(); + startPos = pos + 1; + break; + + case '\0': + // Null char is not expected. Could be a random binary file. + return false; + } + + ++pos; + } + + parseLineIfDataAvailable(); + + return !_presetValues.empty(); +} + +auto PresetFile::Write(const std::string& presetFile) const -> bool +{ + std::ofstream presetStream(presetFile.c_str(), std::ios_base::out | std::ios_base::binary); + return Write(presetStream); +} + +auto PresetFile::Write(std::ostream& presetStream) const -> bool +{ + if (!presetStream.good()) + { + return false; + } + + auto presetData = ExportPreset(); + presetStream << presetData.rdbuf(); + presetStream.flush(); + + return true; +} + +auto PresetFile::AsString() const -> std::string +{ + return ExportPreset().str(); +} + +auto PresetFile::GetCode(const std::string& keyPrefix) const -> std::string +{ + auto lowerKey = ToLower(keyPrefix); + + std::stringstream code; //!< The parsed code + std::string key(lowerKey.length() + 5, '\0'); // Allocate a string that can hold up to 5 digits. + + key.replace(0, lowerKey.length(), lowerKey); + + for (int index{1}; index <= 99999; ++index) + { + key.replace(lowerKey.length(), 5, std::to_string(index)); + if (_presetValues.find(key) == _presetValues.end()) + { + break; + } + + auto line = _presetValues.at(key); + + // Remove backtick char in shader code + if (!line.empty() && line.at(0) == '`') + { + line.erase(0, 1); + } + code << line << std::endl; + } + + auto codeStr = code.str(); + + return codeStr; +} + +void PresetFile::SetCode(const std::string& keyPrefix, const std::string& code) +{ + auto lowerKeyPrefix = ToLower(keyPrefix); + + bool shaderPrefix = false; + + if (lowerKeyPrefix == "warp_" || lowerKeyPrefix == "comp_") + { + shaderPrefix = true; + } + + // Remove all previous lines for this code block prefix + for (auto it = begin(_presetValues); it != end(_presetValues);) + { + if (it->first.substr(0, lowerKeyPrefix.length()) == lowerKeyPrefix) + { + // Check if line prefix is only followed by numbers + std::string remainder = it->first.substr(lowerKeyPrefix.length()); + bool onlyDigits = true; + for (const auto ch : remainder) + { + if (ch < '0' || ch > '9') + { + onlyDigits = false; + break; + } + } + if (onlyDigits) + { + it = _presetValues.erase(it); + continue; + } + } + ++it; + } + + if (code.empty()) + { + return; + } + + uint32_t lineNumber = 1; + + std::string key(lowerKeyPrefix.length() + 5, '\0'); // Allocate a string that can hold up to 5 digits. + key.replace(0, lowerKeyPrefix.length(), lowerKeyPrefix); + + std::stringstream codeStream(code); + std::string codeLine; + while (std::getline(codeStream, codeLine, '\n')) + { + if (shaderPrefix) + { + codeLine = std::string("`").append(codeLine); + } + + key.replace(lowerKeyPrefix.length(), 5, std::to_string(lineNumber)); + _presetValues[key] = codeLine; + + lineNumber++; + + // Milkdrop doesn't enforce this on export, but stops reading at 99999 lines. + // So just stop writing more lines than necessary if someone tries. + if (lineNumber > 99999) + { + break; + } + } +} + +auto PresetFile::GetInt(const std::string& key, int defaultValue) const -> int +{ + auto lowerKey = ToLower(key); + if (_presetValues.find(lowerKey) != _presetValues.end()) + { + try + { + return std::stoi(_presetValues.at(lowerKey)); + } + catch (std::logic_error&) + { + } + } + + return defaultValue; +} + +void PresetFile::SetInt(const std::string& key, int value) +{ + auto lowerKey = ToLower(key); + _presetValues[lowerKey] = std::to_string(value); +} + +auto PresetFile::GetFloat(const std::string& key, float defaultValue) const -> float +{ + auto lowerKey = ToLower(key); + if (_presetValues.find(lowerKey) != _presetValues.end()) + { + try + { + return std::stof(_presetValues.at(lowerKey)); + } + catch (std::logic_error&) + { + } + } + + return defaultValue; +} + +void PresetFile::SetFloat(const std::string& key, float value) +{ + auto lowerKey = ToLower(key); + _presetValues[lowerKey] = std::to_string(value); +} + +auto PresetFile::GetBool(const std::string& key, bool defaultValue) const -> bool +{ + return GetInt(key, static_cast(defaultValue)) > 0; +} + +void PresetFile::SetBool(const std::string& key, bool value) +{ + auto lowerKey = ToLower(key); + _presetValues[lowerKey] = value ? "1" : "0"; +} + +auto PresetFile::GetString(const std::string& key, const std::string& defaultValue) const -> std::string +{ + auto lowerKey = ToLower(key); + if (_presetValues.find(lowerKey) != _presetValues.end()) + { + return _presetValues.at(lowerKey); + } + + return defaultValue; +} + +void PresetFile::SetString(const std::string& key, const std::string& value) +{ + auto lowerKey = ToLower(key); + _presetValues[lowerKey] = value; +} + +const std::map& PresetFile::PresetValues() const +{ + return _presetValues; +} + +void PresetFile::ParseLine(const std::string& line) +{ + // Search for first delimiter, either space or equal + auto varNameDelimiterPos = line.find_first_of(" ="); + + if (varNameDelimiterPos == std::string::npos || varNameDelimiterPos == 0) + { + // Empty line, delimiter at start of line or no delimiter found, skip. + return; + } + + // Convert key to lower case, as INI functions are not case-sensitive. + std::string varName(ToLower(std::string(line.begin(), line.begin() + varNameDelimiterPos))); + std::string value(line.begin() + varNameDelimiterPos + 1, line.end()); + + // Only add first occurrence to mimic Milkdrop behaviour + if (!varName.empty() && _presetValues.find(varName) == _presetValues.end()) + { + _presetValues.emplace(std::move(varName), std::move(value)); + } +} + +auto PresetFile::ToLower(std::string str) -> std::string +{ + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::tolower(c); }); + + return str; +} + +auto PresetFile::ExportPreset() const -> std::stringstream +{ + std::stringstream presetFileContents; + + int milkdropPresetVersion = GetInt("MILKDROP_PRESET_VERSION", 100); + + // Introduced in Milkdrop 2.0, but can be set to <200. + presetFileContents << "MILKDROP_PRESET_VERSION=" << milkdropPresetVersion << std::endl; + + // Milkdrop 2 Pixel Shader version information. + if (milkdropPresetVersion == 200) + { + presetFileContents << "PSVERSION=" << GetInt("psversion", 0) << std::endl; + } + else if (milkdropPresetVersion > 200) + { + presetFileContents << "PSVERSION_WARP=" << GetInt("psversion_warp", 0) << std::endl; + presetFileContents << "PSVERSION_COMP=" << GetInt("psversion_comp", 0) << std::endl; + } + + presetFileContents << "[preset00]" << std::endl; + + // (Unused) rating value and classic post-processing filter parameters + presetFileContents << std::setprecision(3) << std::fixed; + presetFileContents << "fRating=" << GetFloat("fRating", 3.0f) << std::endl; + presetFileContents << "fGammaAdj=" << GetFloat("fGammaAdj", 2.0f) << std::endl; + presetFileContents << "fDecay=" << GetFloat("fDecay", 0.98f) << std::endl; + presetFileContents << "fVideoEchoZoom=" << GetFloat("fVideoEchoZoom", 2.0f) << std::endl; + presetFileContents << "fVideoEchoAlpha=" << GetFloat("fVideoEchoAlpha", 0.0f) << std::endl; + presetFileContents << "nVideoEchoOrientation=" << GetInt("nVideoEchoOrientation", 0) << std::endl; + + // Flags for classic filters and default waveform parameters + presetFileContents << "nWaveMode=" << GetInt("nWaveMode", 0) << std::endl; + presetFileContents << "bAdditiveWaves=" << GetInt("bAdditiveWaves", 0) << std::endl; + presetFileContents << "bWaveDots=" << GetInt("bWaveDots", 0) << std::endl; + presetFileContents << "bWaveThick=" << GetInt("bWaveThick", 0) << std::endl; + presetFileContents << "bModWaveAlphaByVolume=" << GetInt("bModWaveAlphaByVolume", 0) << std::endl; + presetFileContents << "bMaximizeWaveColor=" << GetInt("bMaximizeWaveColor", 1) << std::endl; + presetFileContents << "bTexWrap=" << GetInt("bTexWrap", 1) << std::endl; + presetFileContents << "bDarkenCenter=" << GetInt("bDarkenCenter", 0) << std::endl; + presetFileContents << "bRedBlueStereo=" << GetInt("bRedBlueStereo", 0) << std::endl; + presetFileContents << "bBrighten=" << GetInt("bBrighten", 0) << std::endl; + presetFileContents << "bDarken=" << GetInt("bDarken", 0) << std::endl; + presetFileContents << "bSolarize=" << GetInt("bSolarize", 0) << std::endl; + presetFileContents << "bInvert=" << GetInt("bInvert", 0) << std::endl; + + // Default waveform and warp animation + presetFileContents << "fWaveAlpha=" << GetFloat("fWaveAlpha", 0.8f) << std::endl; + presetFileContents << "fWaveScale=" << GetFloat("fWaveScale", 1.0f) << std::endl; + presetFileContents << "fWaveSmoothing=" << GetFloat("fWaveSmoothing", 0.75f) << std::endl; + presetFileContents << "fWaveParam=" << GetFloat("fWaveParam", 0.0f) << std::endl; + presetFileContents << "fModWaveAlphaStart=" << GetFloat("fModWaveAlphaStart", 0.75f) << std::endl; + presetFileContents << "fModWaveAlphaEnd=" << GetFloat("fModWaveAlphaEnd", 0.95f) << std::endl; + presetFileContents << "fWarpAnimSpeed=" << GetFloat("fWarpAnimSpeed", 1.0f) << std::endl; + presetFileContents << "fWarpScale=" << GetFloat("fWarpScale", 1.0f) << std::endl; + presetFileContents << std::setprecision(5); + presetFileContents << "fZoomExponent=" << GetFloat("fZoomExponent", 1.0f) << std::endl; + presetFileContents << std::setprecision(3); + presetFileContents << "fShader=" << GetFloat("fShader", 0.0f) << std::endl; + + // Warp parameters and default waveform color/location + presetFileContents << std::setprecision(5); + presetFileContents << "zoom=" << GetFloat("zoom", 1.0f) << std::endl; + presetFileContents << "rot=" << GetFloat("rot", 0.0f) << std::endl; + presetFileContents << std::setprecision(3); + presetFileContents << "cx=" << GetFloat("cx", 0.5f) << std::endl; + presetFileContents << "cy=" << GetFloat("cy", 0.5f) << std::endl; + presetFileContents << std::setprecision(5); + presetFileContents << "dx=" << GetFloat("dx", 0.0f) << std::endl; + presetFileContents << "dy=" << GetFloat("dy", 0.0f) << std::endl; + presetFileContents << "warp=" << GetFloat("warp", 1.0f) << std::endl; + presetFileContents << "sx=" << GetFloat("sx", 1.0f) << std::endl; + presetFileContents << "sy=" << GetFloat("sy", 1.0f) << std::endl; + presetFileContents << std::setprecision(3); + presetFileContents << "wave_r=" << GetFloat("wave_r", 1.0f) << std::endl; + presetFileContents << "wave_g=" << GetFloat("wave_g", 1.0f) << std::endl; + presetFileContents << "wave_b=" << GetFloat("wave_b", 1.0f) << std::endl; + presetFileContents << "wave_x=" << GetFloat("wave_x", 0.5f) << std::endl; + presetFileContents << "wave_y=" << GetFloat("wave_y", 0.5f) << std::endl; + + // Borders and motion vectors + presetFileContents << "ob_size=" << GetFloat("ob_size", 0.01f) << std::endl; + presetFileContents << "ob_r=" << GetFloat("ob_r", 0.0f) << std::endl; + presetFileContents << "ob_g=" << GetFloat("ob_g", 0.0f) << std::endl; + presetFileContents << "ob_b=" << GetFloat("ob_b", 0.0f) << std::endl; + presetFileContents << "ob_a=" << GetFloat("ob_a", 0.0f) << std::endl; + presetFileContents << "ib_size=" << GetFloat("ib_size", 0.01f) << std::endl; + presetFileContents << "ib_r=" << GetFloat("ib_r", 0.25f) << std::endl; + presetFileContents << "ib_g=" << GetFloat("ib_g", 0.25f) << std::endl; + presetFileContents << "ib_b=" << GetFloat("ib_b", 0.25f) << std::endl; + presetFileContents << "ib_a=" << GetFloat("ib_a", 0.0f) << std::endl; + presetFileContents << "nMotionVectorsX=" << GetFloat("nMotionVectorsX", 12.0f) << std::endl; + presetFileContents << "nMotionVectorsY=" << GetFloat("nMotionVectorsY", 9.0f) << std::endl; + presetFileContents << "mv_dx=" << GetFloat("mv_dx", 0.0f) << std::endl; + presetFileContents << "mv_dy=" << GetFloat("mv_dy", 0.0f) << std::endl; + presetFileContents << "mv_l=" << GetFloat("mv_l", 0.9f) << std::endl; + presetFileContents << "mv_r=" << GetFloat("mv_r", 1.0f) << std::endl; + presetFileContents << "mv_g=" << GetFloat("mv_g", 1.0f) << std::endl; + presetFileContents << "mv_b=" << GetFloat("mv_b", 1.0f) << std::endl; + presetFileContents << "mv_a=" << GetFloat("mv_a", 1.0f) << std::endl; + presetFileContents << "b1n=" << GetFloat("b1n", 0.0f) << std::endl; + presetFileContents << "b2n=" << GetFloat("b2n", 0.0f) << std::endl; + presetFileContents << "b3n=" << GetFloat("b3n", 0.0f) << std::endl; + presetFileContents << "b1x=" << GetFloat("b1x", 1.0f) << std::endl; + presetFileContents << "b2x=" << GetFloat("b2x", 1.0f) << std::endl; + presetFileContents << "b3x=" << GetFloat("b3x", 1.0f) << std::endl; + presetFileContents << "b1ed=" << GetFloat("b1ed", 0.25f) << std::endl; + + // Custom waves + for (int index = 0; index < 4; index++) + { + ExportWave(index, presetFileContents); + } + + // Custom shapes + for (int index = 0; index < 4; index++) + { + ExportShape(index, presetFileContents); + } + + // Per-frame code + ExportCodeBlock("per_frame_init_", presetFileContents); + ExportCodeBlock("per_frame_", presetFileContents); + ExportCodeBlock("per_pixel_", presetFileContents); + + // Shaders + if (GetInt("psversion_warp", 0) >= 2) + { + ExportCodeBlock("warp_", presetFileContents); + } + if (GetInt("psversion_comp", 0) >= 2) + { + ExportCodeBlock("comp_", presetFileContents); + } + + return presetFileContents; +} + +void PresetFile::ExportWave(int index, std::stringstream& outputStream) const +{ + auto exportIntLine = [&](const std::string& key, int defaultValue) { + std::string linePrefix = std::string("wavecode_") + .append(std::to_string(index)) + .append("_") + .append(key); + + outputStream << linePrefix << "=" << GetInt(linePrefix, defaultValue) << std::endl; + }; + + auto exportFloatLine = [&](const std::string& key, int precision, float defaultValue) { + std::string linePrefix = std::string("wavecode_") + .append(std::to_string(index)) + .append("_") + .append(key); + + outputStream << std::setprecision(precision) << std::fixed + << linePrefix << "=" << GetFloat(linePrefix, defaultValue) << std::endl; + }; + + exportIntLine("enabled", 0); + exportIntLine("samples", 512); + exportIntLine("sep", 0); + exportIntLine("bSpectrum", 0); + exportIntLine("bUseDots", 0); + exportIntLine("bDrawThick", 0); + exportIntLine("bAdditive", 0); + exportFloatLine("scaling", 5, 1.0f); + exportFloatLine("smoothing", 5, 0.5f); + exportFloatLine("r", 3, 1.0f); + exportFloatLine("g", 3, 1.0f); + exportFloatLine("b", 3, 1.0f); + exportFloatLine("a", 3, 1.0f); + + std::string codeBlockPrefix = std::string("wave_") + .append(std::to_string(index)) + .append("_"); + + ExportCodeBlock(codeBlockPrefix + "init", outputStream); + ExportCodeBlock(codeBlockPrefix + "per_frame", outputStream); + ExportCodeBlock(codeBlockPrefix + "per_point", outputStream); +} + +void PresetFile::ExportShape(int index, std::stringstream& outputStream) const +{ + auto exportIntLine = [&](const std::string& key, int defaultValue) { + std::string linePrefix = std::string("shapecode_") + .append(std::to_string(index)) + .append("_") + .append(key); + + outputStream << linePrefix << "=" << GetInt(linePrefix, defaultValue) << std::endl; + }; + + auto exportFloatLine = [&](const std::string& key, int precision, float defaultValue) { + std::string linePrefix = std::string("shapecode_") + .append(std::to_string(index)) + .append("_") + .append(key); + + outputStream << std::setprecision(precision) << std::fixed + << linePrefix << "=" << GetFloat(linePrefix, defaultValue) << std::endl; + }; + + exportIntLine("enabled", 0); + exportIntLine("sides", 4); + exportIntLine("additive", 0); + exportIntLine("thickOutline", 0); + exportIntLine("textured", 0); + exportIntLine("num_inst", 1); + exportFloatLine("x", 3, 0.5f); + exportFloatLine("y", 3, 0.5f); + exportFloatLine("rad", 5, 0.1f); + exportFloatLine("ang", 5, 0.0f); + exportFloatLine("tex_ang", 5, 0.0f); + exportFloatLine("tex_zoom", 5, 1.0f); + exportFloatLine("r", 3, 1.0f); + exportFloatLine("g", 3, 0.0f); + exportFloatLine("b", 3, 0.0f); + exportFloatLine("a", 3, 1.0f); + exportFloatLine("r2", 3, 1.0f); + exportFloatLine("g2", 3, 0.0f); + exportFloatLine("b2", 3, 0.0f); + exportFloatLine("a2", 3, 0.0f); + exportFloatLine("border_r", 3, 1.0f); + exportFloatLine("border_g", 3, 1.0f); + exportFloatLine("border_b", 3, 1.0f); + exportFloatLine("border_a", 3, 0.1f); + + std::string codeBlockPrefix = std::string("shape_") + .append(std::to_string(index)) + .append("_"); + + ExportCodeBlock(codeBlockPrefix + "init", outputStream); + ExportCodeBlock(codeBlockPrefix + "per_frame", outputStream); +} + +void PresetFile::ExportCodeBlock(const std::string& keyPrefix, std::stringstream& outputStream) const +{ + std::string key(keyPrefix.length() + 5, '\0'); //!< Allocate a string that can hold up to 5 digits. + key.replace(0, keyPrefix.length(), keyPrefix); + + for (int index{1}; index <= 99999; ++index) + { + key.replace(keyPrefix.length(), 5, std::to_string(index)); + if (_presetValues.find(key) == _presetValues.end()) + { + break; + } + + auto line = _presetValues.at(key); + + outputStream << key << "=" << line << std::endl; + } +} + +} // namespace Editor + diff --git a/src/gui/preset_editor/PresetFile.h b/src/gui/preset_editor/PresetFile.h new file mode 100644 index 0000000..21725c7 --- /dev/null +++ b/src/gui/preset_editor/PresetFile.h @@ -0,0 +1,246 @@ +#pragma once + +#include +#include + +namespace Editor { + +/** + * @brief Milkdrop preset file parser/writer + * + * Extended version of the parser class in libprojectM, with added support for exporting presets. + * + * Reads in the file as key/value pairs, where the key is either separated from the value by an equal sign or a space. + * Lines not matching this pattern are simply ignored, e.g. the [preset00] INI section. + * + * Values and code blocks can easily be accessed via the helper functions. It is also possible to access the parsed + * map contents directly if required. + * + * When exporting, missing keys are added using Milkdrop's default values. Exporting an empty/unparsed preset + * will return a default file, which is identical to passing an empty preset to Milkdrop/projectM. + */ +class PresetFile +{ +public: + using ValueMap = std::map; //!< A map with key/value pairs, each representing one line in the preset file. + + static constexpr size_t maxFileSize = 0x100000; //!< Maximum size of a preset file. Used for sanity checks. + + /** + * @brief Returns a PresetFile containing a parsed, empty preset with proper default values already set. + * @return An empty preset with Milkdrop's default value. + */ + static PresetFile EmptyPreset(); + + /** + * @brief Reads the preset file into an internal map to prepare for parsing. + * @param presetFile The file name to read from. + * @return True if the file was parsed successfully, false if an error occurred or no line could be parsed. + */ + [[nodiscard]] auto ReadFile(const std::string& presetFile) -> bool; + + /** + * @brief Reads the preset data into an internal map to prepare for parsing. + * @param presetData The data to read from. + * @return True if the data was parsed successfully, false if an error occurred or no line could be parsed. + */ + [[nodiscard]] auto ReadData(const std::string& presetData) -> bool; + + /** + * @brief Reads the data stream into an internal map to prepare for parsing. + * @param presetStream The stream to read preset data form. + * @return True if the stream was parsed successfully, false if an error occurred or no line could be parsed. + */ + [[nodiscard]] auto ReadStream(std::istream& presetStream) -> bool; + + /** + * @brief Saves the preset file in Milkdrop's write order. + * @param presetFile The preset file name to write into. + */ + [[nodiscard]] auto Write(const std::string& presetFile) const -> bool; + + /** + * @brief Saves the preset file in Milkdrop's write order. + * @param presetStream The preset data stream to write into. + */ + [[nodiscard]] auto Write(std::ostream& presetStream) const -> bool; + + /** + * @brief Returns the preset file as a string in Milkdrop's write order. + * @return A string with the same data which would be saved to a preset file. + */ + [[nodiscard]] auto AsString() const -> std::string; + + /** + * @brief Returns a block of code, ready for parsing or use in shader compilation. + * + * Shaders have a "`" prepended on each line. If a line starts with this character, it's stripped and a newline + * character is added at the end of each line. Equations are returned as a single, long line. + * + * The function appends numbers to the prefix, starting with 1, and stops when a key is missing. This is following + * Milkdrop's behaviour, so any gap in numbers will essentially cut off all code after the gap. + * + * Comments starting with // or \\\\ will be stripped until the end of each line in both equations and shader code. + * + * @param keyPrefix The key prefix for the code block to be returned. + * @return The code that was parsed from the given prefix. Empty if no code was found. + */ + [[nodiscard]] auto GetCode(const std::string& keyPrefix) const -> std::string; + + /** + * @brief Replaces/sets a block of code with the given prefix to the new string. + * + * Calling this method will remove all existing map keys starting with the prefix and generate + * new entries, numbered from 1 to the number of lines in "code". + * + * If keyPrefix is either "comp_" or "warp_", each line will automatically be prefixed with the + * "`" character. + * + * @param keyPrefix The key prefix for the code block to be set. + * @param code The new code block to save. + */ + void SetCode(const std::string& keyPrefix, const std::string& code); + + /** + * @brief Returns the given key value as an integer. + * + * Returns the default value if no value can be parsed or the key doesn't exist. + * + * Any additional text after the number, e.g. a comment, is ignored. + * + * @param key The key to retrieve the value from. + * @param defaultValue The default value to return if key is not found. + * @return The converted value or the default value. + */ + [[nodiscard]] auto GetInt(const std::string& key, int defaultValue) const -> int; + + /** + * @brief Sets the given key to a new integer value. + * @param key The key to set the value for. + * @param value The new value to set. + */ + void SetInt(const std::string& key, int value); + + /** + * @brief Returns the given key value as a floating-point value. + * + * Returns the default value if no value can be parsed or the key doesn't exist. + * + * Any additional text after the number, e.g. a comment, is ignored. + * + * @param key The key to retrieve the value from. + * @param defaultValue The default value to return if key is not found. + * @return The converted value or the default value. + */ + [[nodiscard]] auto GetFloat(const std::string& key, float defaultValue) const -> float; + + /** + * @brief Sets the given key to a new float value. + * @param key The key to set the value for. + * @param value The new value to set. + */ + void SetFloat(const std::string& key, float value); + + /** + * @brief Returns the given key value as a boolean. + * + * Returns the default value if no value can be parsed or the key doesn't exist. + * + * Any additional text after the number, e.g. a comment, is ignored. + * + * @param key The key to retrieve the value from. + * @param defaultValue The default value to return if key is not found. + * @return True if the value is non-zero, false otherwise. + */ + [[nodiscard]] auto GetBool(const std::string& key, bool defaultValue) const -> bool; + + /** + * @brief Sets the given key to a new boolean (0/1) value. + * @param key The key to set the value for. + * @param value The new value to set. + */ + void SetBool(const std::string& key, bool value); + + /** + * @brief Returns the given key value as a string. + * + * Returns the default value if no value can be parsed or the key doesn't exist. + * + * @param key The key to retrieve the value from. + * @param defaultValue The default value to return if key is not found. + * @return the string content of the key, or the default value. + */ + [[nodiscard]] auto GetString(const std::string& key, const std::string& defaultValue) const -> std::string; + + /** + * @brief Sets the given key to a new string value. + * @param key The key to set the value for. + * @param value The new value to set. + */ + void SetString(const std::string& key, const std::string& value); + + /** + * @brief Returns a reference to the internal value map. + * @return A reference to the internal value map. + */ + [[nodiscard]] auto PresetValues() const -> const ValueMap&; + +protected: + /** + * @brief Parses a single line and stores the result in the value map. + * + * The function doesn't really care about invalid lines with random text or comments. The first "word" + * is added as key to the map, but will not be used afterward. + * + * @param line The line to parse. + */ + void ParseLine(const std::string& line); + +private: + /** + * @brief Converts the string to lower-case. + * Only letters A-Z are converted to a-z by default. + * @param str The original string. + * @return The lower-case string. + */ + static auto ToLower(std::string str) -> std::string; + + /** + * @brief Exports the currently stored preset data as a Milkdrop-compatible INI file. + * + * All contents are written in the exact same order and formatting as Milkdrop does. + * This means parsing and exporting an existing preset file will return identical contents. + * + * @return A stringstream containing the preset file data. + */ + [[nodiscard]] auto ExportPreset() const -> std::stringstream; + + /** + * @brief Exports a block for a single custom waveform. + * @param index The index of the custom waveform (0-3). + * @param outputStream The stream to write the exported data into. + */ + void ExportWave(int index, std::stringstream& outputStream) const; + + /** + * @brief Exports a block for a single custom shape. + * @param index The index of the custom shape (0-3). + * @param outputStream The stream to write the exported data into. + */ + void ExportShape(int index, std::stringstream& outputStream) const; + + /** + * @brief Exports a block of code, with one 1-based numbered key per line. + * + * This method will not add the "`" character for shader code. Each code line + * is written as-is. + * + * @param keyPrefix The code block prefix, without line numbers. + * @param outputStream The stream to write the exported data into. + */ + void ExportCodeBlock(const std::string& keyPrefix, std::stringstream& outputStream) const; + + ValueMap _presetValues; //!< Map with preset keys and their value. +}; + +} // namespace Editor diff --git a/src/gui/preset_editor/imgui_color_text_editor/CMakeLists.txt b/src/gui/preset_editor/imgui_color_text_editor/CMakeLists.txt new file mode 100644 index 0000000..05af63d --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(ImGUIColorTextEditor STATIC + TextEditor.cpp + TextEditor.h + ) + +target_link_libraries(ImGUIColorTextEditor + PUBLIC + ImGui + ) + +target_include_directories(ImGUIColorTextEditor + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ) diff --git a/src/gui/preset_editor/imgui_color_text_editor/CONTRIBUTING b/src/gui/preset_editor/imgui_color_text_editor/CONTRIBUTING new file mode 100644 index 0000000..73ab161 --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/CONTRIBUTING @@ -0,0 +1,11 @@ +# Contributing +Pull requests are welcome, feel free to contribute if you have implemented something which might be useful for the general audience of this little piece of software. Apparently, it became kind of a community project now. :) + +Whem contributing, please follow the following guidelines. I will keep it updated as we bump into something which worth doing better. +- Try to follow the same coding and naming conventions you find in the source already. I know that everyone has its own preference/taste in coding, but please keep the source consistent in style. +- Please submit to the 'dev' branch first for testing, and it will be merged to 'main' if it seems to work fine. I would like try keep 'master' in a good working condition, as more and more people are using it. +- Please send your submissions in small, well defined requests, i. e. do not accumulate many unrelated changes in one large pull request. Keep your submissions as small as possible, it will make everyone's life easier. +- Avoid using ImGui internal since it would make the source fragile against internal changes in ImGui. +- Try to keep the perormance high within the render function. Try to avoid doing anything which leads to memory allocations (like using temporary std::string, std::vector variables), or complex algorithm. If you really have to, try to amortise it between frames. + +Thank you. :) diff --git a/src/gui/preset_editor/imgui_color_text_editor/LICENSE b/src/gui/preset_editor/imgui_color_text_editor/LICENSE new file mode 100644 index 0000000..d0e3543 --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 BalazsJako + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/gui/preset_editor/imgui_color_text_editor/README.md b/src/gui/preset_editor/imgui_color_text_editor/README.md new file mode 100644 index 0000000..fae0046 --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/README.md @@ -0,0 +1,33 @@ +# ImGuiColorTextEdit +Syntax highlighting text editor for ImGui + +![Screenshot](https://github.com/BalazsJako/ImGuiColorTextEdit/wiki/ImGuiTextEdit.png "Screenshot") + +Demo project: https://github.com/BalazsJako/ColorTextEditorDemo + +This started as my attempt to write a relatively simple widget which provides text editing functionality with syntax highlighting. Now there are other contributors who provide valuable additions. + +While it relies on Omar Cornut's https://github.com/ocornut/imgui, it does not follow the "pure" one widget - one function approach. Since the editor has to maintain a relatively complex and large internal state, it did not seem to be practical to try and enforce fully immediate mode. It stores its internal state in an object instance which is reused across frames. + +The code is (still) work in progress, please report if you find any issues. + +# Main features + - approximates typical code editor look and feel (essential mouse/keyboard commands work - I mean, the commands _I_ normally use :)) + - undo/redo + - UTF-8 support + - works with both fixed and variable-width fonts + - extensible syntax highlighting for multiple languages + - identifier declarations: a small piece of description can be associated with an identifier. The editor displays it in a tooltip when the mouse cursor is hovered over the identifier + - error markers: the user can specify a list of error messages together the line of occurence, the editor will highligh the lines with red backround and display error message in a tooltip when the mouse cursor is hovered over the line + - large files: there is no explicit limit set on file size or number of lines (below 2GB, performance is not affected when large files are loaded (except syntax coloring, see below) + - color palette support: you can switch between different color palettes, or even define your own + - whitespace indicators (TAB, space) + +# Known issues + - syntax highligthing of most languages - except C/C++ - is based on std::regex, which is diasppointingly slow. Because of that, the highlighting process is amortized between multiple frames. C/C++ has a hand-written tokenizer which is much faster. + +Please post your screenshots if you find this little piece of software useful. :) + +# Contribute + +If you want to contribute, please refer to CONTRIBUTE file. diff --git a/src/gui/preset_editor/imgui_color_text_editor/TextEditor.cpp b/src/gui/preset_editor/imgui_color_text_editor/TextEditor.cpp new file mode 100644 index 0000000..8cc5fb5 --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/TextEditor.cpp @@ -0,0 +1,3746 @@ +#include +#include +#include +#include +#include +#include + +#include "TextEditor.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" // for imGui::GetCurrentWindow() + +#include + +// TODO +// - multiline comments vs single-line: latter is blocking start of a ML + +template +bool equals(InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, BinaryPredicate p) +{ + for (; first1 != last1 && first2 != last2; ++first1, ++first2) + { + if (!p(*first1, *first2)) + return false; + } + return first1 == last1 && first2 == last2; +} + +TextEditor::TextEditor() + : mStartTime(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) +{ + SetPalette(GetDarkPalette()); + SetLanguageDefinition(LanguageDefinition::HLSL()); + mLines.emplace_back(); +} + + +void TextEditor::SetLanguageDefinition(const LanguageDefinition& aLanguageDef) +{ + mLanguageDefinition = aLanguageDef; + mRegexList.clear(); + + for (auto& r : mLanguageDefinition.mTokenRegexStrings) + mRegexList.emplace_back(std::make_pair(std::regex(r.first, std::regex_constants::optimize), r.second)); + + Colorize(); +} + +void TextEditor::SetPalette(const Palette& aValue) +{ + mPaletteBase = aValue; +} + +std::string TextEditor::GetText(const Coordinates& aStart, const Coordinates& aEnd) const +{ + std::string result; + + auto lstart = aStart.mLine; + auto lend = aEnd.mLine; + auto istart = GetCharacterIndex(aStart); + auto iend = GetCharacterIndex(aEnd); + size_t s = 0; + + for (size_t i = lstart; i < lend; i++) + s += mLines[i].size(); + + result.reserve(s + s / 8); + + while (istart < iend || lstart < lend) + { + if (lstart >= static_cast(mLines.size())) + break; + + auto& line = mLines[lstart]; + if (istart < static_cast(line.size())) + { + result += static_cast(line[istart].mChar); + istart++; + } + else + { + istart = 0; + ++lstart; + result += '\n'; + } + } + + return result; +} + +TextEditor::Coordinates TextEditor::GetActualCursorCoordinates() const +{ + return SanitizeCoordinates(mState.mCursorPosition); +} + +TextEditor::Coordinates TextEditor::SanitizeCoordinates(const Coordinates& aValue) const +{ + auto line = aValue.mLine; + auto column = aValue.mColumn; + if (line >= static_cast(mLines.size())) + { + if (mLines.empty()) + { + line = 0; + column = 0; + } + else + { + line = (int) mLines.size() - 1; + column = GetLineMaxColumn(line); + } + return {line, column}; + } + else + { + column = mLines.empty() ? 0 : std::min(column, GetLineMaxColumn(line)); + return {line, column}; + } +} + +// https://en.wikipedia.org/wiki/UTF-8 +// We assume that the char is a standalone character (<128) or a leading byte of an UTF-8 code sequence (non-10xxxxxx code) +static int UTF8CharLength(TextEditor::Char c) +{ + if ((c & 0xFE) == 0xFC) + return 6; + if ((c & 0xFC) == 0xF8) + return 5; + if ((c & 0xF8) == 0xF0) + return 4; + else if ((c & 0xF0) == 0xE0) + return 3; + else if ((c & 0xE0) == 0xC0) + return 2; + return 1; +} + +// "Borrowed" from ImGui source +static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = static_cast(c); + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) + return 0; + buf[0] = static_cast(0xc0 + (c >> 6)); + buf[1] = static_cast(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) + return 0; + buf[0] = static_cast(0xf0 + (c >> 18)); + buf[1] = static_cast(0x80 + ((c >> 12) & 0x3f)); + buf[2] = static_cast(0x80 + ((c >> 6) & 0x3f)); + buf[3] = static_cast(0x80 + ((c) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + + if (buf_size < 3) + return 0; + buf[0] = static_cast(0xe0 + (c >> 12)); + buf[1] = static_cast(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char) (0x80 + ((c) & 0x3f)); + return 3; +} + +void TextEditor::Advance(Coordinates& aCoordinates) const +{ + if (aCoordinates.mLine < (int) mLines.size()) + { + auto& line = mLines[aCoordinates.mLine]; + auto cindex = GetCharacterIndex(aCoordinates); + + if (cindex + 1 < (int) line.size()) + { + auto delta = UTF8CharLength(line[cindex].mChar); + cindex = std::min(cindex + delta, (int) line.size() - 1); + } + else + { + ++aCoordinates.mLine; + cindex = 0; + } + aCoordinates.mColumn = GetCharacterColumn(aCoordinates.mLine, cindex); + } +} + +void TextEditor::DeleteRange(const Coordinates& aStart, const Coordinates& aEnd) +{ + assert(aEnd >= aStart); + assert(!mReadOnly); + + //printf("D(%d.%d)-(%d.%d)\n", aStart.mLine, aStart.mColumn, aEnd.mLine, aEnd.mColumn); + + if (aEnd == aStart) + return; + + auto start = GetCharacterIndex(aStart); + auto end = GetCharacterIndex(aEnd); + + if (aStart.mLine == aEnd.mLine) + { + auto& line = mLines[aStart.mLine]; + auto n = GetLineMaxColumn(aStart.mLine); + if (aEnd.mColumn >= n) + line.erase(line.begin() + start, line.end()); + else + line.erase(line.begin() + start, line.begin() + end); + } + else + { + auto& firstLine = mLines[aStart.mLine]; + auto& lastLine = mLines[aEnd.mLine]; + + firstLine.erase(firstLine.begin() + start, firstLine.end()); + lastLine.erase(lastLine.begin(), lastLine.begin() + end); + + if (aStart.mLine < aEnd.mLine) + firstLine.insert(firstLine.end(), lastLine.begin(), lastLine.end()); + + if (aStart.mLine < aEnd.mLine) + RemoveLine(aStart.mLine + 1, aEnd.mLine + 1); + } + + mTextChanged = true; +} + +int TextEditor::InsertTextAt(Coordinates& /* inout */ aWhere, const char* aValue) +{ + assert(!mReadOnly); + + int cindex = GetCharacterIndex(aWhere); + int totalLines = 0; + while (*aValue != '\0') + { + assert(!mLines.empty()); + + if (*aValue == '\r') + { + // skip + ++aValue; + } + else if (*aValue == '\n') + { + if (cindex < (int) mLines[aWhere.mLine].size()) + { + auto& newLine = InsertLine(aWhere.mLine + 1); + auto& line = mLines[aWhere.mLine]; + newLine.insert(newLine.begin(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.end()); + } + else + { + InsertLine(aWhere.mLine + 1); + } + ++aWhere.mLine; + aWhere.mColumn = 0; + cindex = 0; + ++totalLines; + ++aValue; + } + else + { + auto& line = mLines[aWhere.mLine]; + auto d = UTF8CharLength(*aValue); + while (d-- > 0 && *aValue != '\0') + line.insert(line.begin() + cindex++, Glyph(*aValue++, PaletteIndex::Default)); + ++aWhere.mColumn; + } + + mTextChanged = true; + } + + return totalLines; +} + +void TextEditor::AddUndo(const UndoRecord& aValue) +{ + assert(!mReadOnly); + //printf("AddUndo: (@%d.%d) +\'%s' [%d.%d .. %d.%d], -\'%s', [%d.%d .. %d.%d] (@%d.%d)\n", + // aValue.mBefore.mCursorPosition.mLine, aValue.mBefore.mCursorPosition.mColumn, + // aValue.mAdded.c_str(), aValue.mAddedStart.mLine, aValue.mAddedStart.mColumn, aValue.mAddedEnd.mLine, aValue.mAddedEnd.mColumn, + // aValue.mRemoved.c_str(), aValue.mRemovedStart.mLine, aValue.mRemovedStart.mColumn, aValue.mRemovedEnd.mLine, aValue.mRemovedEnd.mColumn, + // aValue.mAfter.mCursorPosition.mLine, aValue.mAfter.mCursorPosition.mColumn + // ); + + mUndoBuffer.resize(mUndoIndex + 1); + mUndoBuffer.back() = aValue; + ++mUndoIndex; +} + +TextEditor::Coordinates TextEditor::ScreenPosToCoordinates(const ImVec2& aPosition) const +{ + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImVec2 local(aPosition.x - origin.x, aPosition.y - origin.y); + + int lineNo = std::max(0, static_cast(std::floor(local.y / mCharAdvance.y))); + + int columnCoord = 0; + + if (lineNo >= 0 && lineNo < static_cast(mLines.size())) + { + auto& line = mLines.at(lineNo); + + int columnIndex = 0; + float columnX = 0.0f; + + while ((size_t) columnIndex < line.size()) + { + float columnWidth = 0.0f; + + if (line[columnIndex].mChar == '\t') + { + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ").x; + float oldX = columnX; + float newColumnX = (1.0f + std::floor((1.0f + columnX) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + columnWidth = newColumnX - oldX; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX = newColumnX; + columnCoord = (columnCoord / mTabSize) * mTabSize + mTabSize; + columnIndex++; + } + else + { + char buf[7]; + auto d = UTF8CharLength(line[columnIndex].mChar); + int i = 0; + while (i < 6 && d-- > 0) + buf[i++] = static_cast(line[columnIndex++].mChar); + buf[i] = '\0'; + columnWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf).x; + if (mTextStart + columnX + columnWidth * 0.5f > local.x) + break; + columnX += columnWidth; + columnCoord++; + } + } + } + + return SanitizeCoordinates(Coordinates(lineNo, columnCoord)); +} + +TextEditor::Coordinates TextEditor::FindWordStart(const Coordinates& aFrom) const +{ + Coordinates at = aFrom; + if (at.mLine >= (int) mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int) line.size()) + return at; + + while (cindex > 0 && isspace(line[cindex].mChar)) + --cindex; + + auto cstart = (PaletteIndex) line[cindex].mColorIndex; + while (cindex > 0) + { + auto c = line[cindex].mChar; + if ((c & 0xC0) != 0x80) // not UTF code sequence 10xxxxxx + { + if (c <= 32 && isspace(c)) + { + cindex++; + break; + } + if (cstart != (PaletteIndex) line[size_t(cindex - 1)].mColorIndex) + break; + } + --cindex; + } + return {at.mLine, GetCharacterColumn(at.mLine, cindex)}; +} + +TextEditor::Coordinates TextEditor::FindWordEnd(const Coordinates& aFrom) const +{ + Coordinates at = aFrom; + if (at.mLine >= (int) mLines.size()) + return at; + + auto& line = mLines[at.mLine]; + auto cindex = GetCharacterIndex(at); + + if (cindex >= (int) line.size()) + return at; + + bool prevspace = (bool) isspace(line[cindex].mChar); + auto cstart = (PaletteIndex) line[cindex].mColorIndex; + while (cindex < (int) line.size()) + { + auto c = line[cindex].mChar; + auto d = UTF8CharLength(c); + if (cstart != (PaletteIndex) line[cindex].mColorIndex) + break; + + if (prevspace != !!isspace(c)) + { + if (isspace(c)) + while (cindex < (int) line.size() && isspace(line[cindex].mChar)) + ++cindex; + break; + } + cindex += d; + } + return {aFrom.mLine, GetCharacterColumn(aFrom.mLine, cindex)}; +} + +TextEditor::Coordinates TextEditor::FindNextWord(const Coordinates& aFrom) const +{ + Coordinates at = aFrom; + if (at.mLine >= (int) mLines.size()) + return at; + + // skip to the next non-word character + auto cindex = GetCharacterIndex(aFrom); + bool isword = false; + bool skip = false; + if (cindex < (int) mLines[at.mLine].size()) + { + auto& line = mLines[at.mLine]; + isword = isalnum(line[cindex].mChar); + skip = isword; + } + + while (!isword || skip) + { + if (at.mLine >= mLines.size()) + { + auto l = std::max(0, (int) mLines.size() - 1); + return {l, GetLineMaxColumn(l)}; + } + + auto& line = mLines[at.mLine]; + if (cindex < (int) line.size()) + { + isword = isalnum(line[cindex].mChar); + + if (isword && !skip) + return {at.mLine, GetCharacterColumn(at.mLine, cindex)}; + + if (!isword) + skip = false; + + cindex++; + } + else + { + cindex = 0; + ++at.mLine; + skip = false; + isword = false; + } + } + + return at; +} + +int TextEditor::GetCharacterIndex(const Coordinates& aCoordinates) const +{ + if (aCoordinates.mLine >= mLines.size()) + return -1; + auto& line = mLines[aCoordinates.mLine]; + int c = 0; + int i = 0; + for (; i < line.size() && c < aCoordinates.mColumn;) + { + if (line[i].mChar == '\t') + c = (c / mTabSize) * mTabSize + mTabSize; + else + ++c; + i += UTF8CharLength(line[i].mChar); + } + return i; +} + +int TextEditor::GetCharacterColumn(int aLine, int aIndex) const +{ + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + int i = 0; + while (i < aIndex && i < (int) line.size()) + { + auto c = line[i].mChar; + i += UTF8CharLength(c); + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + } + return col; +} + +int TextEditor::GetLineCharacterCount(int aLine) const +{ + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int c = 0; + for (unsigned i = 0; i < line.size(); c++) + i += UTF8CharLength(line[i].mChar); + return c; +} + +int TextEditor::GetLineMaxColumn(int aLine) const +{ + if (aLine >= mLines.size()) + return 0; + auto& line = mLines[aLine]; + int col = 0; + for (unsigned i = 0; i < line.size();) + { + auto c = line[i].mChar; + if (c == '\t') + col = (col / mTabSize) * mTabSize + mTabSize; + else + col++; + i += UTF8CharLength(c); + } + return col; +} + +bool TextEditor::IsOnWordBoundary(const Coordinates& aAt) const +{ + if (aAt.mLine >= (int) mLines.size() || aAt.mColumn == 0) + return true; + + auto& line = mLines[aAt.mLine]; + auto cindex = GetCharacterIndex(aAt); + if (cindex >= (int) line.size()) + return true; + + if (mColorizerEnabled) + return line[cindex].mColorIndex != line[size_t(cindex - 1)].mColorIndex; + + return isspace(line[cindex].mChar) != isspace(line[cindex - 1].mChar); +} + +void TextEditor::IndentSelection(bool aIndent) +{ + std::string tabString = mTabsAsSpaces ? std::string(mTabSize, ' ') : "\t"; + + UndoRecord u; + + u.mBefore = mState; + + auto start = mState.mSelectionStart; + auto end = mState.mSelectionEnd; + + if (start.mLine == end.mLine) + { + // Just tab without selection inserts tab/spaces at cursor position + if (aIndent && mState.mSelectionStart.mColumn == end.mColumn) + { + u.mAdded = tabString; + u.mAddedStart = start; + u.mAddedEnd = Coordinates(start.mLine, start.mColumn + tabString.size()); + + InsertText(tabString); + } + else + { + // Otherwise, (un)indent the whole line + IndentLine(start.mLine, aIndent, tabString, u); + } + } + else + { + // Multiple lines - indent or unindent each (if possible) + // Selection start/end columns are ignored, each "touched" line counts + + // Undo the change of the whole text block + auto undoStart = Coordinates(start.mLine, 0); + auto undoEnd = Coordinates(end.mLine, GetLineCharacterCount(end.mLine)); + + u.mRemoved = GetText(undoStart, undoEnd); + u.mRemovedStart = undoStart; + u.mRemovedEnd = undoEnd; + + UndoRecord dummyUndo; + for (int line = start.mLine; line <= end.mLine; line++) + { + // If the last line is only selected at the beginning, ignore it. + if (line != end.mLine || end.mColumn > 0) + { + IndentLine(line, aIndent, tabString, dummyUndo); + } + } + + undoStart = Coordinates(mState.mSelectionStart.mLine, 0); + undoEnd = Coordinates(mState.mSelectionEnd.mLine, GetLineCharacterCount(mState.mSelectionEnd.mLine)); + + u.mAdded = GetText(undoStart, undoEnd); + u.mAddedStart = undoStart; + u.mAddedEnd = undoEnd; + } + + u.mAfter = mState; + + AddUndo(u); +} + +int TextEditor::IndentLine(int aIndex, bool aIndent, const std::string& aIndentChars, UndoRecord& aUndoRecord) +{ + Coordinates lineStart(aIndex, 0); + Coordinates lineEnd(aIndex, GetLineMaxColumn(aIndex)); + auto text = GetText(lineStart, lineEnd); + + if (!aIndent && text.empty()) + { + return 0; + } + + auto selectionStart = mState.mSelectionStart; + auto selectionEnd = mState.mSelectionEnd; + + int lastWhitespace = 0; + int totalIndentation = 0; + while (lastWhitespace < text.size() && (text.at(lastWhitespace) == ' ' || text.at(lastWhitespace) == '\t')) + { + totalIndentation += text.at(lastWhitespace) == ' ' ? 1 : mTabSize; + lastWhitespace++; + } + + if (aIndent) + { + Coordinates insertCoordinates(aIndex, lastWhitespace); + + InsertTextAt(insertCoordinates, aIndentChars.c_str()); + + if (selectionStart.mLine == aIndex && selectionStart.mColumn >= lastWhitespace - 1) + { + selectionStart.mColumn += aIndentChars.size(); + } + if (selectionEnd.mLine == aIndex && selectionEnd.mColumn >= lastWhitespace - 1) + { + selectionEnd.mColumn += aIndentChars.size(); + } + SetSelection(selectionStart, selectionEnd); + auto pos = GetCursorPosition(); + if (pos.mLine == aIndex && pos.mColumn >= lastWhitespace) + { + pos.mColumn += aIndentChars.size(); + SetCursorPosition(pos); + } + + aUndoRecord.mAdded = aIndentChars; + aUndoRecord.mAddedStart = insertCoordinates; + aUndoRecord.mAddedEnd = Coordinates(insertCoordinates.mLine, insertCoordinates.mColumn + aIndentChars.size()); + + return aIndentChars.size(); + } + + // Easy case: Up to one tab stop - just remove all indentation. + if (totalIndentation <= mTabSize) + { + Coordinates removeEnd(aIndex, lastWhitespace); + + aUndoRecord.mRemoved = GetText(lineStart, removeEnd); + aUndoRecord.mRemovedStart = lineStart; + aUndoRecord.mRemovedEnd = removeEnd; + + auto pos = GetCursorPosition(); + + DeleteRange(lineStart, removeEnd); + + if (selectionStart.mLine == aIndex && selectionStart.mColumn >= removeEnd.mColumn) + { + selectionStart.mColumn -= lastWhitespace; + } + if (selectionEnd.mLine == aIndex && selectionEnd.mColumn >= removeEnd.mColumn) + { + selectionEnd.mColumn -= lastWhitespace; + } + SetSelection(selectionStart, selectionEnd); + if (pos.mLine == aIndex && pos.mColumn >= removeEnd.mColumn) + { + pos.mColumn -= lastWhitespace; + SetCursorPosition(pos); + } + else if (pos.mLine == aIndex && pos.mColumn < lastWhitespace - 1) + { + pos.mColumn = 0; + SetCursorPosition(pos); + } + + return -lastWhitespace; + } + + // Read backwards, gather as many tabs/spaces as needed. + int removeIndentation = 0; + int removeStartPos = lastWhitespace; + for (; removeStartPos > 0 && removeIndentation < mTabSize; removeStartPos--) + { + removeIndentation += text.at(removeStartPos - 1) == ' ' ? 1 : mTabSize; + } + + Coordinates removeStartCoords(aIndex, removeStartPos); + Coordinates removeEndCoords(aIndex, lastWhitespace); + + aUndoRecord.mRemoved = GetText(removeStartCoords, removeEndCoords); + aUndoRecord.mRemovedStart = removeStartCoords; + aUndoRecord.mRemovedEnd = removeEndCoords; + + DeleteRange(removeStartCoords, Coordinates(aIndex, lastWhitespace)); + + int indentChars = removeStartPos - lastWhitespace; + + // Insert spaces if removed size was larger than necessary (e.g. mixed spaces and tabs) + if (removeIndentation > mTabSize) + { + std::string fillString(removeIndentation - mTabSize, ' '); + InsertTextAt(removeStartCoords, fillString.c_str()); + + aUndoRecord.mAdded = fillString; + aUndoRecord.mAddedStart = removeStartCoords; + aUndoRecord.mAddedEnd = Coordinates(removeStartCoords.mLine, removeStartCoords.mColumn + fillString.size()); + + indentChars += fillString.size(); + } + + if (selectionStart.mLine == aIndex && selectionStart.mColumn >= lastWhitespace) + { + selectionStart.mColumn += indentChars; + } + if (selectionEnd.mLine == aIndex && selectionEnd.mColumn >= lastWhitespace) + { + selectionEnd.mColumn += indentChars; + } + SetSelection(selectionStart, selectionEnd); + auto pos = GetCursorPosition(); + if (pos.mLine == aIndex && pos.mColumn >= lastWhitespace) + { + pos.mColumn += indentChars; + SetCursorPosition(pos); + } + + return indentChars; +} + +void TextEditor::RemoveLine(int aStart, int aEnd) +{ + assert(!mReadOnly); + assert(aEnd >= aStart); + assert(mLines.size() > (size_t) (aEnd - aStart)); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + { + ErrorMarkers::value_type e(i.first >= aStart ? i.first - 1 : i.first, i.second); + if (e.first >= aStart && e.first <= aEnd) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + { + if (i >= aStart && i <= aEnd) + continue; + btmp.insert(i >= aStart ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aStart, mLines.begin() + aEnd); + assert(!mLines.empty()); + + mTextChanged = true; +} + +void TextEditor::RemoveLine(int aIndex) +{ + assert(!mReadOnly); + assert(mLines.size() > 1); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + { + ErrorMarkers::value_type e(i.first > aIndex ? i.first - 1 : i.first, i.second); + if (e.first - 1 == aIndex) + continue; + etmp.insert(e); + } + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + { + if (i == aIndex) + continue; + btmp.insert(i >= aIndex ? i - 1 : i); + } + mBreakpoints = std::move(btmp); + + mLines.erase(mLines.begin() + aIndex); + assert(!mLines.empty()); + + mTextChanged = true; +} + +TextEditor::Line& TextEditor::InsertLine(int aIndex) +{ + assert(!mReadOnly); + + auto& result = *mLines.insert(mLines.begin() + aIndex, Line()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first >= aIndex ? i.first + 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + Breakpoints btmp; + for (auto i : mBreakpoints) + btmp.insert(i >= aIndex ? i + 1 : i); + mBreakpoints = std::move(btmp); + + return result; +} + +std::string TextEditor::GetWordUnderCursor() const +{ + auto c = GetCursorPosition(); + return GetWordAt(c); +} + +std::string TextEditor::GetWordAt(const Coordinates& aCoords) const +{ + auto start = FindWordStart(aCoords); + auto end = FindWordEnd(aCoords); + + std::string r; + + auto istart = GetCharacterIndex(start); + auto iend = GetCharacterIndex(end); + + for (auto it = istart; it < iend; ++it) + r.push_back(static_cast(mLines[aCoords.mLine][it].mChar)); + + return r; +} + +ImU32 TextEditor::GetGlyphColor(const Glyph& aGlyph) const +{ + if (!mColorizerEnabled) + return mPalette[static_cast(PaletteIndex::Default)]; + if (aGlyph.mComment) + return mPalette[(int) PaletteIndex::Comment]; + if (aGlyph.mMultiLineComment) + return mPalette[(int) PaletteIndex::MultiLineComment]; + auto const color = mPalette[(int) aGlyph.mColorIndex]; + if (aGlyph.mPreprocessor) + { + const auto ppcolor = mPalette[static_cast(PaletteIndex::Preprocessor)]; + const unsigned int c0 = ((ppcolor & 0xff) + (color & 0xff)) / 2; + const unsigned int c1 = (((ppcolor >> 8) & 0xff) + ((color >> 8) & 0xff)) / 2; + const unsigned int c2 = (((ppcolor >> 16) & 0xff) + ((color >> 16) & 0xff)) / 2; + const unsigned int c3 = (((ppcolor >> 24) & 0xff) + ((color >> 24) & 0xff)) / 2; + return c0 | c1 << 8 | c2 << 16 | c3 << 24; + } + return color; +} + +void TextEditor::HandleKeyboardInputs() +{ + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowFocused()) + { + if (ImGui::IsWindowHovered()) + ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput); + //ImGui::CaptureKeyboardFromApp(true); + + io.WantCaptureKeyboard = true; + io.WantTextInput = true; + + if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Z)) + Undo(); + else if (!IsReadOnly() && !ctrl && !shift && alt && ImGui::IsKeyPressed(ImGuiKey_Backspace)) + Undo(); + else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Y)) + Redo(); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_UpArrow)) + MoveUp(1, shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_DownArrow)) + MoveDown(1, shift); + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) + MoveLeft(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_RightArrow)) + MoveRight(1, shift, ctrl); + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageUp)) + MoveUp(GetPageSize() - 4, shift); + else if (!alt && ImGui::IsKeyPressed(ImGuiKey_PageDown)) + MoveDown(GetPageSize() - 4, shift); + else if (!alt && ctrl && ImGui::IsKeyPressed(ImGuiKey_Home)) + MoveTop(shift); + else if (ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) + MoveBottom(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Home)) + MoveHome(shift); + else if (!ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_End)) + MoveEnd(shift); + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) + Delete(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Backspace)) + Backspace(); + else if (!ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) + mOverwrite ^= true; + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) + Copy(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_C)) + Copy(); + else if (!IsReadOnly() && !ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Insert)) + Paste(); + else if (!IsReadOnly() && ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_V)) + Paste(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_X)) + Cut(); + else if (!ctrl && shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Delete)) + Cut(); + else if (ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_A)) + SelectAll(); + else if (!IsReadOnly() && !ctrl && !shift && !alt && ImGui::IsKeyPressed(ImGuiKey_Enter)) + EnterCharacter('\n', false); + else if (!IsReadOnly() && !ctrl && !alt && ImGui::IsKeyPressed(ImGuiKey_Tab)) + IndentSelection(!shift); + + if (!IsReadOnly() && !io.InputQueueCharacters.empty()) + { + for (int i = 0; i < io.InputQueueCharacters.Size; i++) + { + auto c = io.InputQueueCharacters[i]; + if (c != 0 && (c == '\n' || c >= 32)) + EnterCharacter(c, shift); + } + io.InputQueueCharacters.resize(0); + } + } +} + +void TextEditor::HandleMouseInputs() +{ + ImGuiIO& io = ImGui::GetIO(); + auto shift = io.KeyShift; + auto ctrl = io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl; + auto alt = io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeyAlt; + + if (ImGui::IsWindowHovered()) + { + if (!alt) + { + auto click = ImGui::IsMouseClicked(0); + auto doubleClick = ImGui::IsMouseDoubleClicked(0); + auto t = ImGui::GetTime(); + auto tripleClick = click && !doubleClick && (mLastClick != -1.0f && (t - mLastClick) < io.MouseDoubleClickTime); + + /* + Left mouse button triple click + */ + + if (tripleClick) + { + if (!ctrl) + { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + mSelectionMode = SelectionMode::Line; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = -1.0f; + } + + /* + Left mouse button double click + */ + + else if (doubleClick) + { + if (!ctrl) + { + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + if (mSelectionMode == SelectionMode::Line) + mSelectionMode = SelectionMode::Normal; + else + mSelectionMode = SelectionMode::Word; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + + mLastClick = (float) ImGui::GetTime(); + } + + /* + Left mouse button click + */ + else if (click) + { + if (shift) + { + mInteractiveStart = mState.mSelectionStart; + mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + } + else + mState.mCursorPosition = mInteractiveStart = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + if (ctrl) + mSelectionMode = SelectionMode::Word; + else + mSelectionMode = SelectionMode::Normal; + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + + mLastClick = (float) ImGui::GetTime(); + } + // Mouse left button dragging (=> update selection) + else if (ImGui::IsMouseDragging(0) && ImGui::IsMouseDown(0)) + { + io.WantCaptureMouse = true; + mState.mCursorPosition = mInteractiveEnd = ScreenPosToCoordinates(ImGui::GetMousePos()); + SetSelection(mInteractiveStart, mInteractiveEnd, mSelectionMode); + } + } + } +} + +void TextEditor::Render() +{ + /* Compute mCharAdvance regarding to scaled font size (Ctrl + mouse wheel)*/ + const float fontSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, "#", nullptr, nullptr).x; + mCharAdvance = ImVec2(fontSize, ImGui::GetTextLineHeightWithSpacing() * mLineSpacing); + + /* Update palette with the current alpha from style */ + for (int i = 0; i < (int) PaletteIndex::Max; ++i) + { + auto color = ImGui::ColorConvertU32ToFloat4(mPaletteBase[i]); + color.w *= ImGui::GetStyle().Alpha; + mPalette[i] = ImGui::ColorConvertFloat4ToU32(color); + } + + assert(mLineBuffer.empty()); + + auto contentSize = ImGui::GetWindowContentRegionMax(); + auto drawList = ImGui::GetWindowDrawList(); + float longest(mTextStart); + + if (mScrollToTop) + { + mScrollToTop = false; + ImGui::SetScrollY(0.f); + } + + ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos(); + auto scrollX = ImGui::GetScrollX(); + auto scrollY = ImGui::GetScrollY(); + + auto lineNo = static_cast(std::floor(scrollY / mCharAdvance.y)); + auto globalLineMax = static_cast(mLines.size()); + auto lineMax = std::max(0, std::min(static_cast(mLines.size()) - 1, lineNo + static_cast(std::floor((scrollY + contentSize.y) / mCharAdvance.y)))); + + // Deduce mTextStart by evaluating mLines size (global lineMax) plus two spaces as text width + char buf[16]; + snprintf(buf, 16, " %d ", globalLineMax); + mTextStart = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x + static_cast(mLeftMargin); + + if (!mLines.empty()) + { + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; + + while (lineNo <= lineMax) + { + ImVec2 lineStartScreenPos = ImVec2(cursorScreenPos.x, cursorScreenPos.y + static_cast(lineNo) * mCharAdvance.y); + ImVec2 textScreenPos = ImVec2(lineStartScreenPos.x + mTextStart, lineStartScreenPos.y); + + auto& line = mLines[lineNo]; + longest = std::max(mTextStart + TextDistanceToLineStart(Coordinates(lineNo, GetLineMaxColumn(lineNo))), longest); + auto columnNo = 0; + Coordinates lineStartCoord(lineNo, 0); + Coordinates lineEndCoord(lineNo, GetLineMaxColumn(lineNo)); + + // Draw selection for the current line + float sstart = -1.0f; + float ssend = -1.0f; + + assert(mState.mSelectionStart <= mState.mSelectionEnd); + if (mState.mSelectionStart <= lineEndCoord) + sstart = mState.mSelectionStart > lineStartCoord ? TextDistanceToLineStart(mState.mSelectionStart) : 0.0f; + if (mState.mSelectionEnd > lineStartCoord) + ssend = TextDistanceToLineStart(mState.mSelectionEnd < lineEndCoord ? mState.mSelectionEnd : lineEndCoord); + + if (mState.mSelectionEnd.mLine > lineNo) + ssend += mCharAdvance.x; + + if (sstart != -1 && ssend != -1 && sstart < ssend) + { + ImVec2 vstart(lineStartScreenPos.x + mTextStart + sstart, lineStartScreenPos.y); + ImVec2 vend(lineStartScreenPos.x + mTextStart + ssend, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(vstart, vend, mPalette[(int) PaletteIndex::Selection]); + } + + // Draw breakpoints + auto start = ImVec2(lineStartScreenPos.x + scrollX, lineStartScreenPos.y); + + if (mBreakpoints.count(lineNo + 1) != 0) + { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int) PaletteIndex::Breakpoint]); + } + + // Draw error markers + auto errorIt = mErrorMarkers.find(lineNo + 1); + if (errorIt != mErrorMarkers.end()) + { + auto end = ImVec2(lineStartScreenPos.x + contentSize.x + 2.0f * scrollX, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int) PaletteIndex::ErrorMarker]); + + if (ImGui::IsMouseHoveringRect(lineStartScreenPos, end)) + { + ImGui::BeginTooltip(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.2f, 0.2f, 1.0f)); + ImGui::Text("Error at line %d:", errorIt->first); + ImGui::PopStyleColor(); + ImGui::Separator(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 0.2f, 1.0f)); + ImGui::Text("%s", errorIt->second.c_str()); + ImGui::PopStyleColor(); + ImGui::EndTooltip(); + } + } + + // Draw line number (right aligned) + snprintf(buf, 16, "%d ", lineNo + 1); + + auto lineNoWidth = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf, nullptr, nullptr).x; + drawList->AddText(ImVec2(lineStartScreenPos.x + mTextStart - lineNoWidth, lineStartScreenPos.y), mPalette[(int) PaletteIndex::LineNumber], buf); + + if (mState.mCursorPosition.mLine == lineNo) + { + auto focused = ImGui::IsWindowFocused(); + + // Highlight the current line (where the cursor is) + if (!HasSelection()) + { + auto end = ImVec2(start.x + contentSize.x + scrollX, start.y + mCharAdvance.y); + drawList->AddRectFilled(start, end, mPalette[(int) (focused ? PaletteIndex::CurrentLineFill : PaletteIndex::CurrentLineFillInactive)]); + drawList->AddRect(start, end, mPalette[(int) PaletteIndex::CurrentLineEdge], 1.0f); + } + + // Render the cursor + if (focused) + { + auto timeEnd = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto elapsed = timeEnd - mStartTime; + if (elapsed > 400) + { + float width = 1.0f; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + float cx = TextDistanceToLineStart(mState.mCursorPosition); + + if (mOverwrite && cindex < (int) line.size()) + { + auto c = line[cindex].mChar; + if (c == '\t') + { + auto x = (1.0f + std::floor((1.0f + cx) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + width = x - cx; + } + else + { + char buf2[2]; + buf2[0] = static_cast(line[cindex].mChar); + buf2[1] = '\0'; + width = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, buf2).x; + } + } + ImVec2 cstart(textScreenPos.x + cx, lineStartScreenPos.y); + ImVec2 cend(textScreenPos.x + cx + width, lineStartScreenPos.y + mCharAdvance.y); + drawList->AddRectFilled(cstart, cend, mPalette[(int) PaletteIndex::Cursor]); + if (elapsed > 800) + mStartTime = timeEnd; + } + } + } + + // Render colorized text + auto prevColor = line.empty() ? mPalette[(int) PaletteIndex::Default] : GetGlyphColor(line[0]); + ImVec2 bufferOffset; + + for (int i = 0; i < line.size();) + { + auto& glyph = line[i]; + auto color = GetGlyphColor(glyph); + + if ((color != prevColor || glyph.mChar == '\t' || glyph.mChar == ' ') && !mLineBuffer.empty()) + { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + auto textSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, mLineBuffer.c_str(), nullptr, nullptr); + bufferOffset.x += textSize.x; + mLineBuffer.clear(); + } + prevColor = color; + + if (glyph.mChar == '\t') + { + auto oldX = bufferOffset.x; + bufferOffset.x = (1.0f + std::floor((1.0f + bufferOffset.x) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + ++i; + + if (mShowWhitespaces) + { + const auto s = ImGui::GetFontSize(); + const auto x1 = textScreenPos.x + oldX + 1.0f; + const auto x2 = textScreenPos.x + bufferOffset.x - 1.0f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + const ImVec2 p1(x1, y); + const ImVec2 p2(x2, y); + const ImVec2 p3(x2 - s * 0.2f, y - s * 0.2f); + const ImVec2 p4(x2 - s * 0.2f, y + s * 0.2f); + drawList->AddLine(p1, p2, 0x90909090); + drawList->AddLine(p2, p3, 0x90909090); + drawList->AddLine(p2, p4, 0x90909090); + } + } + else if (glyph.mChar == ' ') + { + if (mShowWhitespaces) + { + const auto s = ImGui::GetFontSize(); + const auto x = textScreenPos.x + bufferOffset.x + spaceSize * 0.5f; + const auto y = textScreenPos.y + bufferOffset.y + s * 0.5f; + drawList->AddCircleFilled(ImVec2(x, y), 1.5f, 0x80808080, 4); + } + bufferOffset.x += spaceSize; + i++; + } + else + { + auto l = UTF8CharLength(glyph.mChar); + while (l-- > 0) + mLineBuffer.push_back(static_cast(line[i++].mChar)); + } + ++columnNo; + } + + if (!mLineBuffer.empty()) + { + const ImVec2 newOffset(textScreenPos.x + bufferOffset.x, textScreenPos.y + bufferOffset.y); + drawList->AddText(newOffset, prevColor, mLineBuffer.c_str()); + mLineBuffer.clear(); + } + + ++lineNo; + } + + // Draw a tooltip on known identifiers/preprocessor symbols + if (ImGui::IsWindowHovered() && ImGui::IsMousePosValid()) + { + auto id = GetWordAt(ScreenPosToCoordinates(ImGui::GetMousePos())); + if (!id.empty()) + { + if (!mLanguageDefinition.mCaseSensitive) + { + std::transform(id.begin(), id.end(), id.begin(), ::tolower); + } + auto it = mLanguageDefinition.mIdentifiers.find(id); + if (it != mLanguageDefinition.mIdentifiers.end()) + { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(it->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + else + { + auto pi = mLanguageDefinition.mPreprocIdentifiers.find(id); + if (pi != mLanguageDefinition.mPreprocIdentifiers.end()) + { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(pi->second.mDeclaration.c_str()); + ImGui::EndTooltip(); + } + } + } + } + } + + + ImGui::Dummy(ImVec2((longest + 2), static_cast(mLines.size()) * mCharAdvance.y)); + + if (mScrollToCursor) + { + EnsureCursorVisible(); + ImGui::SetWindowFocus(); + mScrollToCursor = false; + } +} + +void TextEditor::Render(const char* aTitle, const ImVec2& aSize, bool aBorder) +{ + mWithinRender = true; + mTextChanged = false; + mCursorPositionChanged = false; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(mPalette[(int) PaletteIndex::Background])); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f)); + if (!mIgnoreImGuiChild) + ImGui::BeginChild(aTitle, aSize, aBorder, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NoMove); + + if (mHandleKeyboardInputs) + { + HandleKeyboardInputs(); + ImGui::PushTabStop(true); + } + + if (mHandleMouseInputs) + HandleMouseInputs(); + + ColorizeInternal(); + Render(); + + if (mHandleKeyboardInputs) + ImGui::PopTabStop(); + + if (!mIgnoreImGuiChild) + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + mWithinRender = false; +} + +void TextEditor::SetText(const std::string& aText) +{ + mLines.clear(); + mLines.emplace_back(); + for (auto chr : aText) + { + if (chr == '\r') + { + // ignore the carriage return character + } + else if (chr == '\n') + mLines.emplace_back(); + else + { + mLines.back().emplace_back(chr, PaletteIndex::Default); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::SetTextLines(const std::vector& aLines) +{ + mLines.clear(); + + if (aLines.empty()) + { + mLines.emplace_back(); + } + else + { + mLines.resize(aLines.size()); + + for (size_t i = 0; i < aLines.size(); ++i) + { + const std::string& aLine = aLines[i]; + + mLines[i].reserve(aLine.size()); + for (char j : aLine) + mLines[i].emplace_back(j, PaletteIndex::Default); + } + } + + mTextChanged = true; + mScrollToTop = true; + + mUndoBuffer.clear(); + mUndoIndex = 0; + + Colorize(); +} + +void TextEditor::EnterCharacter(ImWchar aChar, bool aShift) +{ + assert(!mReadOnly); + + UndoRecord u; + + u.mBefore = mState; + + if (HasSelection()) + { + if (aChar == '\t' && mState.mSelectionStart.mLine != mState.mSelectionEnd.mLine) + { + + auto start = mState.mSelectionStart; + auto end = mState.mSelectionEnd; + auto originalEnd = end; + + if (start > end) + std::swap(start, end); + start.mColumn = 0; + // end.mColumn = end.mLine < mLines.size() ? mLines[end.mLine].size() : 0; + if (end.mColumn == 0 && end.mLine > 0) + --end.mLine; + if (end.mLine >= (int) mLines.size()) + end.mLine = mLines.empty() ? 0 : (int) mLines.size() - 1; + end.mColumn = GetLineMaxColumn(end.mLine); + + //if (end.mColumn >= GetLineMaxColumn(end.mLine)) + // end.mColumn = GetLineMaxColumn(end.mLine) - 1; + + u.mRemovedStart = start; + u.mRemovedEnd = end; + u.mRemoved = GetText(start, end); + + bool modified = false; + + for (int i = start.mLine; i <= end.mLine; i++) + { + auto& line = mLines[i]; + if (aShift) + { + if (!line.empty()) + { + if (line.front().mChar == '\t') + { + line.erase(line.begin()); + modified = true; + } + else + { + for (int j = 0; j < mTabSize && !line.empty() && line.front().mChar == ' '; j++) + { + line.erase(line.begin()); + modified = true; + } + } + } + } + else + { + line.insert(line.begin(), Glyph('\t', TextEditor::PaletteIndex::Background)); + modified = true; + } + } + + if (modified) + { + start = Coordinates(start.mLine, GetCharacterColumn(start.mLine, 0)); + Coordinates rangeEnd; + if (originalEnd.mColumn != 0) + { + end = Coordinates(end.mLine, GetLineMaxColumn(end.mLine)); + rangeEnd = end; + u.mAdded = GetText(start, end); + } + else + { + end = Coordinates(originalEnd.mLine, 0); + rangeEnd = Coordinates(end.mLine - 1, GetLineMaxColumn(end.mLine - 1)); + u.mAdded = GetText(start, rangeEnd); + } + + u.mAddedStart = start; + u.mAddedEnd = rangeEnd; + u.mAfter = mState; + + mState.mSelectionStart = start; + mState.mSelectionEnd = end; + AddUndo(u); + + mTextChanged = true; + + EnsureCursorVisible(); + } + + return; + } // c == '\t' + else + { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + } // HasSelection + + auto coord = GetActualCursorCoordinates(); + u.mAddedStart = coord; + + assert(!mLines.empty()); + + if (aChar == '\n') + { + InsertLine(coord.mLine + 1); + auto& line = mLines[coord.mLine]; + auto& newLine = mLines[coord.mLine + 1]; + + if (mLanguageDefinition.mAutoIndentation) + for (size_t it = 0; it < line.size() && isascii(line[it].mChar) && isblank(line[it].mChar); ++it) + newLine.push_back(line[it]); + + const size_t whitespaceSize = newLine.size(); + auto cindex = GetCharacterIndex(coord); + newLine.insert(newLine.end(), line.begin() + cindex, line.end()); + line.erase(line.begin() + cindex, line.begin() + static_cast(line.size())); + SetCursorPosition(Coordinates(coord.mLine + 1, GetCharacterColumn(coord.mLine + 1, static_cast(whitespaceSize)))); + u.mAdded = static_cast(aChar); + } + else + { + char buf[7]; + int e = ImTextCharToUtf8(buf, 7, aChar); + if (e > 0) + { + buf[e] = '\0'; + auto& line = mLines[coord.mLine]; + auto cindex = GetCharacterIndex(coord); + + if (mOverwrite && cindex < static_cast(line.size())) + { + auto d = UTF8CharLength(line[cindex].mChar); + + u.mRemovedStart = mState.mCursorPosition; + u.mRemovedEnd = Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex + d)); + + while (d-- > 0 && cindex < static_cast(line.size())) + { + u.mRemoved += static_cast(line[cindex].mChar); + line.erase(line.begin() + cindex); + } + } + + for (auto p = buf; *p != '\0'; p++, ++cindex) + line.insert(line.begin() + cindex, Glyph(*p, PaletteIndex::Default)); + u.mAdded = buf; + + SetCursorPosition(Coordinates(coord.mLine, GetCharacterColumn(coord.mLine, cindex))); + } + else + return; + } + + mTextChanged = true; + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + + AddUndo(u); + + Colorize(coord.mLine - 1, 3); + EnsureCursorVisible(); +} + +void TextEditor::SetReadOnly(bool aValue) +{ + mReadOnly = aValue; +} + +void TextEditor::SetColorizerEnable(bool aValue) +{ + mColorizerEnabled = aValue; +} + +void TextEditor::SetCursorPosition(const Coordinates& aPosition) +{ + if (mState.mCursorPosition != aPosition) + { + mState.mCursorPosition = aPosition; + mCursorPositionChanged = true; + EnsureCursorVisible(); + } +} + +void TextEditor::SetSelectionStart(const Coordinates& aPosition) +{ + mState.mSelectionStart = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelectionEnd(const Coordinates& aPosition) +{ + mState.mSelectionEnd = SanitizeCoordinates(aPosition); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); +} + +void TextEditor::SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode) +{ + auto oldSelStart = mState.mSelectionStart; + auto oldSelEnd = mState.mSelectionEnd; + + mState.mSelectionStart = SanitizeCoordinates(aStart); + mState.mSelectionEnd = SanitizeCoordinates(aEnd); + if (mState.mSelectionStart > mState.mSelectionEnd) + std::swap(mState.mSelectionStart, mState.mSelectionEnd); + + switch (aMode) + { + case SelectionMode::Normal: + break; + case SelectionMode::Word: { + mState.mSelectionStart = FindWordStart(mState.mSelectionStart); + if (!IsOnWordBoundary(mState.mSelectionEnd)) + mState.mSelectionEnd = FindWordEnd(FindWordStart(mState.mSelectionEnd)); + break; + } + case SelectionMode::Line: { + const auto lineNo = mState.mSelectionEnd.mLine; + mState.mSelectionStart = Coordinates(mState.mSelectionStart.mLine, 0); + mState.mSelectionEnd = Coordinates(lineNo, GetLineMaxColumn(lineNo)); + break; + } + default: + break; + } + + if (mState.mSelectionStart != oldSelStart || + mState.mSelectionEnd != oldSelEnd) + mCursorPositionChanged = true; +} + +void TextEditor::SetTabSize(int aValue) +{ + mTabSize = std::max(0, std::min(32, aValue)); +} + +void TextEditor::SetTabsAsSpaces(bool aValue) +{ + mTabsAsSpaces = aValue; +} + +void TextEditor::InsertText(const std::string& aValue) +{ + InsertText(aValue.c_str()); +} + +void TextEditor::InsertText(const char* aValue) +{ + if (aValue == nullptr) + return; + + auto pos = GetActualCursorCoordinates(); + auto start = std::min(pos, mState.mSelectionStart); + int totalLines = pos.mLine - start.mLine; + + totalLines += InsertTextAt(pos, aValue); + + SetSelection(pos, pos); + SetCursorPosition(pos); + Colorize(start.mLine - 1, totalLines + 2); +} + +void TextEditor::DeleteSelection() +{ + assert(mState.mSelectionEnd >= mState.mSelectionStart); + + if (mState.mSelectionEnd == mState.mSelectionStart) + return; + + DeleteRange(mState.mSelectionStart, mState.mSelectionEnd); + + SetSelection(mState.mSelectionStart, mState.mSelectionStart); + SetCursorPosition(mState.mSelectionStart); + Colorize(mState.mSelectionStart.mLine, 1); +} + +void TextEditor::MoveUp(int aAmount, bool aSelect) +{ + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = std::max(0, mState.mCursorPosition.mLine - aAmount); + if (oldPos != mState.mCursorPosition) + { + if (aSelect) + { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else + { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +void TextEditor::MoveDown(int aAmount, bool aSelect) +{ + assert(mState.mCursorPosition.mColumn >= 0); + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition.mLine = std::max(0, std::min((int) mLines.size() - 1, mState.mCursorPosition.mLine + aAmount)); + + if (mState.mCursorPosition != oldPos) + { + if (aSelect) + { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else + { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + + EnsureCursorVisible(); + } +} + +static bool IsUTFSequence(char c) +{ + return (c & 0xC0) == 0x80; +} + +void TextEditor::MoveLeft(int aAmount, bool aSelect, bool aWordMode) +{ + if (mLines.empty()) + return; + + auto oldPos = mState.mCursorPosition; + mState.mCursorPosition = GetActualCursorCoordinates(); + auto line = mState.mCursorPosition.mLine; + auto cindex = GetCharacterIndex(mState.mCursorPosition); + + while (aAmount-- > 0) + { + if (cindex == 0) + { + if (line > 0) + { + --line; + if ((int) mLines.size() > line) + cindex = (int) mLines[line].size(); + else + cindex = 0; + } + } + else + { + --cindex; + if (cindex > 0) + { + if (static_cast(mLines.size()) > line) + { + while (cindex > 0 && IsUTFSequence(static_cast(mLines[line][cindex].mChar))) + --cindex; + } + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + if (aWordMode) + { + mState.mCursorPosition = FindWordStart(mState.mCursorPosition); + cindex = GetCharacterIndex(mState.mCursorPosition); + } + } + + mState.mCursorPosition = Coordinates(line, GetCharacterColumn(line, cindex)); + + assert(mState.mCursorPosition.mColumn >= 0); + if (aSelect) + { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else + { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveRight(int aAmount, bool aSelect, bool aWordMode) +{ + auto oldPos = mState.mCursorPosition; + + if (mLines.empty() || oldPos.mLine >= mLines.size()) + return; + + auto cindex = GetCharacterIndex(mState.mCursorPosition); + while (aAmount-- > 0) + { + auto lindex = mState.mCursorPosition.mLine; + auto& line = mLines[lindex]; + + if (cindex >= line.size()) + { + if (mState.mCursorPosition.mLine < mLines.size() - 1) + { + mState.mCursorPosition.mLine = std::max(0, std::min((int) mLines.size() - 1, mState.mCursorPosition.mLine + 1)); + mState.mCursorPosition.mColumn = 0; + } + else + return; + } + else + { + cindex += UTF8CharLength(line[cindex].mChar); + mState.mCursorPosition = Coordinates(lindex, GetCharacterColumn(lindex, cindex)); + if (aWordMode) + mState.mCursorPosition = FindNextWord(mState.mCursorPosition); + } + } + + if (aSelect) + { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = SanitizeCoordinates(mState.mCursorPosition); + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else + { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd, aSelect && aWordMode ? SelectionMode::Word : SelectionMode::Normal); + + EnsureCursorVisible(); +} + +void TextEditor::MoveTop(bool aSelect) +{ + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(0, 0)); + + if (mState.mCursorPosition != oldPos) + { + if (aSelect) + { + mInteractiveEnd = oldPos; + mInteractiveStart = mState.mCursorPosition; + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::TextEditor::MoveBottom(bool aSelect) +{ + auto oldPos = GetCursorPosition(); + auto newPos = Coordinates((int) mLines.size() - 1, 0); + SetCursorPosition(newPos); + if (aSelect) + { + mInteractiveStart = oldPos; + mInteractiveEnd = newPos; + } + else + mInteractiveStart = mInteractiveEnd = newPos; + SetSelection(mInteractiveStart, mInteractiveEnd); +} + +void TextEditor::MoveHome(bool aSelect) +{ + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, 0)); + + if (mState.mCursorPosition != oldPos) + { + if (aSelect) + { + if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else + { + mInteractiveStart = mState.mCursorPosition; + mInteractiveEnd = oldPos; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::MoveEnd(bool aSelect) +{ + auto oldPos = mState.mCursorPosition; + SetCursorPosition(Coordinates(mState.mCursorPosition.mLine, GetLineMaxColumn(oldPos.mLine))); + + if (mState.mCursorPosition != oldPos) + { + if (aSelect) + { + if (oldPos == mInteractiveEnd) + mInteractiveEnd = mState.mCursorPosition; + else if (oldPos == mInteractiveStart) + mInteractiveStart = mState.mCursorPosition; + else + { + mInteractiveStart = oldPos; + mInteractiveEnd = mState.mCursorPosition; + } + } + else + mInteractiveStart = mInteractiveEnd = mState.mCursorPosition; + SetSelection(mInteractiveStart, mInteractiveEnd); + } +} + +void TextEditor::Delete() +{ + assert(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) + { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } + else + { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + auto& line = mLines[pos.mLine]; + + if (pos.mColumn == GetLineMaxColumn(pos.mLine)) + { + if (pos.mLine == (int) mLines.size() - 1) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + Advance(u.mRemovedEnd); + + auto& nextLine = mLines[pos.mLine + 1]; + line.insert(line.end(), nextLine.begin(), nextLine.end()); + RemoveLine(pos.mLine + 1); + } + else + { + auto cindex = GetCharacterIndex(pos); + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + u.mRemovedEnd.mColumn++; + u.mRemoved = GetText(u.mRemovedStart, u.mRemovedEnd); + + auto d = UTF8CharLength(line[cindex].mChar); + while (d-- > 0 && cindex < (int) line.size()) + line.erase(line.begin() + cindex); + } + + mTextChanged = true; + + Colorize(pos.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::Backspace() +{ + assert(!mReadOnly); + + if (mLines.empty()) + return; + + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) + { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + DeleteSelection(); + } + else + { + auto pos = GetActualCursorCoordinates(); + SetCursorPosition(pos); + + if (mState.mCursorPosition.mColumn == 0) + { + if (mState.mCursorPosition.mLine == 0) + return; + + u.mRemoved = '\n'; + u.mRemovedStart = u.mRemovedEnd = Coordinates(pos.mLine - 1, GetLineMaxColumn(pos.mLine - 1)); + Advance(u.mRemovedEnd); + + auto& line = mLines[mState.mCursorPosition.mLine]; + auto& prevLine = mLines[mState.mCursorPosition.mLine - 1]; + auto prevSize = GetLineMaxColumn(mState.mCursorPosition.mLine - 1); + prevLine.insert(prevLine.end(), line.begin(), line.end()); + + ErrorMarkers etmp; + for (auto& i : mErrorMarkers) + etmp.insert(ErrorMarkers::value_type(i.first - 1 == mState.mCursorPosition.mLine ? i.first - 1 : i.first, i.second)); + mErrorMarkers = std::move(etmp); + + RemoveLine(mState.mCursorPosition.mLine); + --mState.mCursorPosition.mLine; + mState.mCursorPosition.mColumn = prevSize; + } + else + { + auto& line = mLines[mState.mCursorPosition.mLine]; + auto cindex = GetCharacterIndex(pos) - 1; + auto cend = cindex + 1; + while (cindex > 0 && IsUTFSequence(static_cast(line[cindex].mChar))) + --cindex; + + //if (cindex > 0 && UTF8CharLength(line[cindex].mChar) > 1) + // --cindex; + + u.mRemovedStart = u.mRemovedEnd = GetActualCursorCoordinates(); + --u.mRemovedStart.mColumn; + --mState.mCursorPosition.mColumn; + + while (cindex < line.size() && cend-- > cindex) + { + u.mRemoved += static_cast(line[cindex].mChar); + line.erase(line.begin() + cindex); + } + } + + mTextChanged = true; + + EnsureCursorVisible(); + Colorize(mState.mCursorPosition.mLine, 1); + } + + u.mAfter = mState; + AddUndo(u); +} + +void TextEditor::SelectWordUnderCursor() +{ + auto c = GetCursorPosition(); + SetSelection(FindWordStart(c), FindWordEnd(c)); +} + +void TextEditor::SelectAll() +{ + SetSelection(Coordinates(0, 0), Coordinates((int) mLines.size(), 0)); +} + +bool TextEditor::HasSelection() const +{ + return mState.mSelectionEnd > mState.mSelectionStart; +} + +void TextEditor::Copy() +{ + if (HasSelection()) + { + ImGui::SetClipboardText(GetSelectedText().c_str()); + } + else + { + if (!mLines.empty()) + { + std::string str; + auto& line = mLines[GetActualCursorCoordinates().mLine]; + for (auto& g : line) + str.push_back(static_cast(g.mChar)); + ImGui::SetClipboardText(str.c_str()); + } + } +} + +void TextEditor::Cut() +{ + if (IsReadOnly()) + { + Copy(); + } + else + { + if (HasSelection()) + { + UndoRecord u; + u.mBefore = mState; + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + + Copy(); + DeleteSelection(); + + u.mAfter = mState; + AddUndo(u); + } + } +} + +void TextEditor::Paste() +{ + if (IsReadOnly()) + return; + + auto clipText = ImGui::GetClipboardText(); + if (clipText != nullptr && strlen(clipText) > 0) + { + UndoRecord u; + u.mBefore = mState; + + if (HasSelection()) + { + u.mRemoved = GetSelectedText(); + u.mRemovedStart = mState.mSelectionStart; + u.mRemovedEnd = mState.mSelectionEnd; + DeleteSelection(); + } + + u.mAdded = clipText; + u.mAddedStart = GetActualCursorCoordinates(); + + InsertText(clipText); + + u.mAddedEnd = GetActualCursorCoordinates(); + u.mAfter = mState; + AddUndo(u); + } +} + +bool TextEditor::CanUndo() const +{ + return !mReadOnly && mUndoIndex > 0; +} + +bool TextEditor::CanRedo() const +{ + return !mReadOnly && mUndoIndex < (int) mUndoBuffer.size(); +} + +void TextEditor::Undo(int aSteps) +{ + while (CanUndo() && aSteps-- > 0) + mUndoBuffer[--mUndoIndex].Undo(this); +} + +void TextEditor::Redo(int aSteps) +{ + while (CanRedo() && aSteps-- > 0) + mUndoBuffer[mUndoIndex++].Redo(this); +} + +const TextEditor::Palette& TextEditor::GetDarkPalette() +{ + const static Palette p = {{ + 0xff7f7f7f, // Default + 0xffd69c56, // Keyword + 0xff00ff00, // Number + 0xff7070e0, // String + 0xff70a0e0, // Char literal + 0xffffffff, // Punctuation + 0xff408080, // Preprocessor + 0xffaaaaaa, // Identifier + 0xff9bc64d, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff206020, // Comment (single line) + 0xff406020, // Comment (multi line) + 0xff101010, // Background + 0xffe0e0e0, // Cursor + 0x80a06020, // Selection + 0x800020ff, // ErrorMarker + 0x40f08000, // Breakpoint + 0xff707000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40a0a0a0, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetLightPalette() +{ + const static Palette p = {{ + 0xff7f7f7f, // None + 0xffff0c06, // Keyword + 0xff008000, // Number + 0xff2020a0, // String + 0xff304070, // Char literal + 0xff000000, // Punctuation + 0xff406060, // Preprocessor + 0xff404040, // Identifier + 0xff606010, // Known identifier + 0xffc040a0, // Preproc identifier + 0xff205020, // Comment (single line) + 0xff405020, // Comment (multi line) + 0xffffffff, // Background + 0xff000000, // Cursor + 0x80600000, // Selection + 0xa00010ff, // ErrorMarker + 0x80f08000, // Breakpoint + 0xff505000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + +const TextEditor::Palette& TextEditor::GetRetroBluePalette() +{ + const static Palette p = {{ + 0xff00ffff, // None + 0xffffff00, // Keyword + 0xff00ff00, // Number + 0xff808000, // String + 0xff808000, // Char literal + 0xffffffff, // Punctuation + 0xff008000, // Preprocessor + 0xff00ffff, // Identifier + 0xffffffff, // Known identifier + 0xffff00ff, // Preproc identifier + 0xff808080, // Comment (single line) + 0xff404040, // Comment (multi line) + 0xff800000, // Background + 0xff0080ff, // Cursor + 0x80ffff00, // Selection + 0xa00000ff, // ErrorMarker + 0x80ff8000, // Breakpoint + 0xff808000, // Line number + 0x40000000, // Current line fill + 0x40808080, // Current line fill (inactive) + 0x40000000, // Current line edge + }}; + return p; +} + + +std::string TextEditor::GetText() const +{ + return GetText(Coordinates(), Coordinates((int) mLines.size(), 0)); +} + +std::vector TextEditor::GetTextLines() const +{ + std::vector result; + + result.reserve(mLines.size()); + + for (auto& line : mLines) + { + std::string text; + + text.resize(line.size()); + + for (size_t i = 0; i < line.size(); ++i) + text[i] = static_cast(line[i].mChar); + + result.emplace_back(std::move(text)); + } + + return result; +} + +std::string TextEditor::GetSelectedText() const +{ + return GetText(mState.mSelectionStart, mState.mSelectionEnd); +} + +std::string TextEditor::GetCurrentLineText() const +{ + auto lineLength = GetLineMaxColumn(mState.mCursorPosition.mLine); + return GetText( + Coordinates(mState.mCursorPosition.mLine, 0), + Coordinates(mState.mCursorPosition.mLine, lineLength)); +} + +void TextEditor::ProcessInputs() +{ +} + +void TextEditor::Colorize(int aFromLine, int aLines) +{ + int toLine = aLines == -1 ? (int) mLines.size() : std::min((int) mLines.size(), aFromLine + aLines); + mColorRangeMin = std::min(mColorRangeMin, aFromLine); + mColorRangeMax = std::max(mColorRangeMax, toLine); + mColorRangeMin = std::max(0, mColorRangeMin); + mColorRangeMax = std::max(mColorRangeMin, mColorRangeMax); + mCheckComments = true; +} + +void TextEditor::ColorizeRange(int aFromLine, int aToLine) +{ + if (mLines.empty() || aFromLine >= aToLine) + return; + + std::string buffer; + std::cmatch results; + std::string id; + + int endLine = std::max(0, std::min((int) mLines.size(), aToLine)); + for (int i = aFromLine; i < endLine; ++i) + { + auto& line = mLines[i]; + + if (line.empty()) + continue; + + buffer.resize(line.size()); + for (size_t j = 0; j < line.size(); ++j) + { + auto& col = line[j]; + buffer[j] = static_cast(col.mChar); + col.mColorIndex = PaletteIndex::Default; + } + + const char* bufferBegin = &buffer.front(); + const char* bufferEnd = bufferBegin + buffer.size(); + + auto last = bufferEnd; + + for (auto first = bufferBegin; first != last;) + { + const char* token_begin = nullptr; + const char* token_end = nullptr; + PaletteIndex token_color = PaletteIndex::Default; + + bool hasTokenizeResult = false; + + if (mLanguageDefinition.mTokenize != nullptr) + { + if (mLanguageDefinition.mTokenize(first, last, token_begin, token_end, token_color)) + hasTokenizeResult = true; + } + + if (hasTokenizeResult == false) + { + // todo : remove + //printf("using regex for %.*s\n", first + 10 < last ? 10 : int(last - first), first); + + for (auto& p : mRegexList) + { + if (std::regex_search(first, last, results, p.first, std::regex_constants::match_continuous)) + { + hasTokenizeResult = true; + + auto& v = *results.begin(); + token_begin = v.first; + token_end = v.second; + token_color = p.second; + break; + } + } + } + + if (hasTokenizeResult == false) + { + first++; + } + else + { + const size_t token_length = token_end - token_begin; + + if (token_color == PaletteIndex::Identifier) + { + id.assign(token_begin, token_end); + + // todo : allmost all language definitions use lower case to specify keywords, so shouldn't this use ::tolower ? + if (!mLanguageDefinition.mCaseSensitive) + std::transform(id.begin(), id.end(), id.begin(), ::tolower); + + if (!line[first - bufferBegin].mPreprocessor) + { + if (mLanguageDefinition.mKeywords.count(id) != 0) + token_color = PaletteIndex::Keyword; + else if (mLanguageDefinition.mIdentifiers.count(id) != 0) + token_color = PaletteIndex::KnownIdentifier; + else if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } + else + { + if (mLanguageDefinition.mPreprocIdentifiers.count(id) != 0) + token_color = PaletteIndex::PreprocIdentifier; + } + } + + for (size_t j = 0; j < token_length; ++j) + line[(token_begin - bufferBegin) + j].mColorIndex = token_color; + + first = token_end; + } + } + } +} + +void TextEditor::ColorizeInternal() +{ + if (mLines.empty() || !mColorizerEnabled) + return; + + if (mCheckComments) + { + auto endLine = mLines.size(); + auto endIndex = 0; + auto commentStartLine = endLine; + auto commentStartIndex = endIndex; + auto withinString = false; + auto withinSingleLineComment = false; + auto withinPreproc = false; + auto firstChar = true; // there is no other non-whitespace characters in the line before + auto concatenate = false; // '\' on the very end of the line + auto currentLine = 0; + auto currentIndex = 0; + while (currentLine < endLine || currentIndex < endIndex) + { + auto& line = mLines[currentLine]; + + if (currentIndex == 0 && !concatenate) + { + withinSingleLineComment = false; + withinPreproc = false; + firstChar = true; + } + + concatenate = false; + + if (!line.empty()) + { + auto& g = line[currentIndex]; + auto c = g.mChar; + + if (c != mLanguageDefinition.mPreprocChar && !isspace(c)) + firstChar = false; + + if (currentIndex == (int) line.size() - 1 && line[line.size() - 1].mChar == '\\') + concatenate = true; + + bool inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + + if (withinString) + { + line[currentIndex].mMultiLineComment = inComment; + + if (c == '\"') + { + if (currentIndex + 1 < (int) line.size() && line[currentIndex + 1].mChar == '\"') + { + currentIndex += 1; + if (currentIndex < (int) line.size()) + line[currentIndex].mMultiLineComment = inComment; + } + else + withinString = false; + } + else if (c == '\\') + { + currentIndex += 1; + if (currentIndex < (int) line.size()) + line[currentIndex].mMultiLineComment = inComment; + } + } + else + { + if (firstChar && c == mLanguageDefinition.mPreprocChar) + withinPreproc = true; + + if (c == '\"') + { + withinString = true; + line[currentIndex].mMultiLineComment = inComment; + } + else + { + auto pred = [](const char& a, const Glyph& b) { return a == b.mChar; }; + auto from = line.begin() + currentIndex; + auto& startStr = mLanguageDefinition.mCommentStart; + auto& singleStartStr = mLanguageDefinition.mSingleLineComment; + + if (!singleStartStr.empty() && + currentIndex + singleStartStr.size() <= line.size() && + equals(singleStartStr.begin(), singleStartStr.end(), from, from + static_cast(singleStartStr.size()), pred)) + { + withinSingleLineComment = true; + } + else if (!withinSingleLineComment && currentIndex + startStr.size() <= line.size() && + equals(startStr.begin(), startStr.end(), from, from + static_cast(startStr.size()), pred)) + { + commentStartLine = currentLine; + commentStartIndex = currentIndex; + } + + inComment = inComment = (commentStartLine < currentLine || (commentStartLine == currentLine && commentStartIndex <= currentIndex)); + + line[currentIndex].mMultiLineComment = inComment; + line[currentIndex].mComment = withinSingleLineComment; + + auto& endStr = mLanguageDefinition.mCommentEnd; + if (currentIndex + 1 >= static_cast(endStr.size()) && + equals(endStr.begin(), endStr.end(), from + 1 - static_cast(endStr.size()), from + 1, pred)) + { + commentStartIndex = endIndex; + commentStartLine = endLine; + } + } + } + line[currentIndex].mPreprocessor = withinPreproc; + currentIndex += UTF8CharLength(c); + if (currentIndex >= static_cast(line.size())) + { + currentIndex = 0; + ++currentLine; + } + } + else + { + currentIndex = 0; + ++currentLine; + } + } + mCheckComments = false; + } + + if (mColorRangeMin < mColorRangeMax) + { + const int increment = (mLanguageDefinition.mTokenize == nullptr) ? 10 : 10000; + const int to = std::min(mColorRangeMin + increment, mColorRangeMax); + ColorizeRange(mColorRangeMin, to); + mColorRangeMin = to; + + if (mColorRangeMax == mColorRangeMin) + { + mColorRangeMin = std::numeric_limits::max(); + mColorRangeMax = 0; + } + return; + } +} + +float TextEditor::TextDistanceToLineStart(const Coordinates& aFrom) const +{ + auto& line = mLines[aFrom.mLine]; + float distance = 0.0f; + float spaceSize = ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, " ", nullptr, nullptr).x; + int colIndex = GetCharacterIndex(aFrom); + for (size_t it = 0u; it < line.size() && it < colIndex;) + { + if (line[it].mChar == '\t') + { + distance = (1.0f + std::floor((1.0f + distance) / (float(mTabSize) * spaceSize))) * (float(mTabSize) * spaceSize); + ++it; + } + else + { + auto d = UTF8CharLength(line[it].mChar); + char tempCString[7]; + int i = 0; + for (; i < 6 && d-- > 0 && it < (int) line.size(); i++, it++) + tempCString[i] = static_cast(line[it].mChar); + + tempCString[i] = '\0'; + distance += ImGui::GetFont()->CalcTextSizeA(ImGui::GetFontSize(), FLT_MAX, -1.0f, tempCString, nullptr, nullptr).x; + } + } + + return distance; +} + +void TextEditor::EnsureCursorVisible() +{ + if (!mWithinRender) + { + mScrollToCursor = true; + return; + } + + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); + + auto height = ImGui::GetWindowHeight(); + auto width = ImGui::GetWindowWidth(); + + auto top = 1 + static_cast(std::ceil(scrollY / mCharAdvance.y)); + auto bottom = static_cast(std::ceil((scrollY + height) / mCharAdvance.y)); + + auto left = static_cast(std::ceil(scrollX / mCharAdvance.x)); + auto right = static_cast(std::ceil((scrollX + width) / mCharAdvance.x)); + + auto pos = GetActualCursorCoordinates(); + auto len = TextDistanceToLineStart(pos); + + if (pos.mLine < top) + ImGui::SetScrollY(std::max(0.0f, static_cast(pos.mLine - 1) * mCharAdvance.y)); + if (pos.mLine > bottom - 4) + ImGui::SetScrollY(std::max(0.0f, static_cast(pos.mLine + 4) * mCharAdvance.y - height)); + if (len + mTextStart < static_cast(left) + 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart - 4)); + if (len + mTextStart > static_cast(right) - 4) + ImGui::SetScrollX(std::max(0.0f, len + mTextStart + 4 - width)); +} + +int TextEditor::GetPageSize() const +{ + auto height = ImGui::GetWindowHeight() - 20.0f; + return static_cast(std::floor(height / mCharAdvance.y)); +} + +TextEditor::UndoRecord::UndoRecord( + std::string aAdded, + const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, + std::string aRemoved, + const TextEditor::Coordinates aRemovedStart, + const TextEditor::Coordinates aRemovedEnd, + const TextEditor::EditorState& aBefore, + const TextEditor::EditorState& aAfter) + : mAdded(std::move(aAdded)) + , mAddedStart(aAddedStart) + , mAddedEnd(aAddedEnd) + , mRemoved(std::move(aRemoved)) + , mRemovedStart(aRemovedStart) + , mRemovedEnd(aRemovedEnd) + , mBefore(aBefore) + , mAfter(aAfter) +{ + assert(mAddedStart <= mAddedEnd); + assert(mRemovedStart <= mRemovedEnd); +} + +void TextEditor::UndoRecord::Undo(TextEditor* aEditor) +{ + if (!mAdded.empty()) + { + aEditor->DeleteRange(mAddedStart, mAddedEnd); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); + } + + if (!mRemoved.empty()) + { + auto start = mRemovedStart; + aEditor->InsertTextAt(start, mRemoved.c_str()); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); + } + + aEditor->mState = mBefore; + aEditor->EnsureCursorVisible(); +} + +void TextEditor::UndoRecord::Redo(TextEditor* aEditor) +{ + if (!mRemoved.empty()) + { + aEditor->DeleteRange(mRemovedStart, mRemovedEnd); + aEditor->Colorize(mRemovedStart.mLine - 1, mRemovedEnd.mLine - mRemovedStart.mLine + 2); + } + + if (!mAdded.empty()) + { + auto start = mAddedStart; + aEditor->InsertTextAt(start, mAdded.c_str()); + aEditor->Colorize(mAddedStart.mLine - 1, mAddedEnd.mLine - mAddedStart.mLine + 2); + } + + aEditor->mState = mAfter; + aEditor->EnsureCursorVisible(); +} + +static bool TokenizeCStyleString(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if (*p == '"') + { + p++; + + while (p < in_end) + { + // handle end of string + if (*p == '"') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + + // handle escape character for " + if (*p == '\\' && p + 1 < in_end && p[1] == '"') + p++; + + p++; + } + } + + return false; +} + +static bool TokenizeCStyleCharacterLiteral(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if (*p == '\'') + { + p++; + + // handle escape characters + if (p < in_end && *p == '\\') + p++; + + if (p < in_end) + p++; + + // handle end of character literal + if (p < in_end && *p == '\'') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + } + + return false; +} + +static bool TokenizeCStyleIdentifier(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || *p == '_') + { + p++; + + while ((p < in_end) && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == '_')) + p++; + + out_begin = in_begin; + out_end = p; + return true; + } + + return false; +} + +static bool TokenizeCStyleNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + const bool startsWithNumber = *p >= '0' && *p <= '9'; + + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; + + p++; + + bool hasNumber = startsWithNumber; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasNumber = true; + + p++; + } + + if (hasNumber == false) + return false; + + bool isFloat = false; + bool isHex = false; + bool isBinary = false; + + if (p < in_end) + { + if (*p == '.') + { + isFloat = true; + + p++; + + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } + else if (*p == 'x' || *p == 'X') + { + // hex formatted integer of the type 0xef80 + + isHex = true; + + p++; + + while (p < in_end && ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F'))) + p++; + } + else if (*p == 'b' || *p == 'B') + { + // binary formatted integer of the type 0b01011101 + + isBinary = true; + + p++; + + while (p < in_end && (*p >= '0' && *p <= '1')) + p++; + } + } + + if (isHex == false && isBinary == false) + { + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) + { + isFloat = true; + + p++; + + if (p < in_end && (*p == '+' || *p == '-')) + p++; + + bool hasDigits = false; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; + + p++; + } + + if (hasDigits == false) + return false; + } + + // single precision floating point type + if (p < in_end && *p == 'f') + p++; + } + + if (isFloat == false) + { + // integer size type + while (p < in_end && (*p == 'u' || *p == 'U' || *p == 'l' || *p == 'L')) + p++; + } + + out_begin = in_begin; + out_end = p; + return true; +} + +static bool TokenizeCStylePunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + (void) in_end; + + switch (*in_begin) + { + case '[': + case ']': + case '{': + case '}': + case '!': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '~': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + case '.': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + default: + return false; + } +} + +static bool TokenizeMilkdropConstant(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + if (*p != '$') + { + return false; + } + + p++; + + // Ordinal constant: $'n' + if (*p == '\'') + { + if (p + 2 < in_end && *(p + 2) == '\'') + { + out_begin = in_begin; + out_end = p + 3; + return true; + } + } + + // PI or PHI constant: $PI and $PHI + if (*p == 'p' || *p == 'P' && p + 1 < in_end) + { + if (*(p + 1) == 'i' || *(p + 1) == 'I') + { + out_begin = in_begin; + out_end = p + 2; + return true; + } + if ((*(p + 1) == 'h' || *(p + 1) == 'H') && p + 2 < in_end && (*(p + 2) == 'i' || *(p + 2) == 'I')) + { + out_begin = in_begin; + out_end = p + 3; + return true; + } + } + + // E constant: $E + if (*p == 'e' || *p == 'E') + { + out_begin = in_begin; + out_end = p + 1; + return true; + } + + // Only hex constant left to check: $XAABBCCDD + if (*p != 'x' & *p != 'X') + { + return false; + } + + p++; + + while (p < in_end) + { + // handle end of hex constant + static const char hexChars[] = "0123456789aAbBcCdDeEfF"; + + bool digitValid = false; + for (auto hexDigit : hexChars) + { + if (*p == hexDigit) + { + digitValid = true; + break; + } + } + + if (!digitValid) + { + break; + } + + p++; + } + + // Only found $X, but no other valid digits -> don't recognize as constant. + if (p == in_begin + 2) + { + return false; + } + + out_begin = in_begin; + out_end = p; + return true; +} + +static bool TokenizeMilkdropNumber(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + const char* p = in_begin; + + const bool startsWithNumber = *p >= '0' && *p <= '9'; + + if (*p != '+' && *p != '-' && !startsWithNumber) + return false; + + p++; + + bool hasNumber = startsWithNumber; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasNumber = true; + + p++; + } + + if (hasNumber == false) + return false; + + + if (p < in_end) + { + if (*p == '.') + { + p++; + + while (p < in_end && (*p >= '0' && *p <= '9')) + p++; + } + } + + // floating point exponent + if (p < in_end && (*p == 'e' || *p == 'E')) + { + p++; + + if (p < in_end && (*p == '+' || *p == '-')) + p++; + + bool hasDigits = false; + + while (p < in_end && (*p >= '0' && *p <= '9')) + { + hasDigits = true; + + p++; + } + + if (hasDigits == false) + return false; + } + + out_begin = in_begin; + out_end = p; + return true; +} + +static bool TokenizeMilkdropPunctuation(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end) +{ + (void) in_end; + + switch (*in_begin) + { + case '[': + case ']': + case '!': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '+': + case '=': + case '|': + case '<': + case '>': + case '?': + case ':': + case '/': + case ';': + case ',': + out_begin = in_begin; + out_end = in_begin + 1; + return true; + default: + return false; + } +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::CPlusPlus() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const cppKeywords[] = { + "alignas", "alignof", "and", "and_eq", "asm", "atomic_cancel", "atomic_commit", "atomic_noexcept", "auto", "bitand", "bitor", "bool", "break", "case", "catch", "char", "char16_t", "char32_t", "class", + "compl", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "float", + "for", "friend", "goto", "if", "import", "inline", "int", "long", "module", "mutable", "namespace", "new", "noexcept", "not", "not_eq", "nullptr", "operator", "or", "or_eq", "private", "protected", "public", + "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "synchronized", "template", "this", "thread_local", + "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", "xor", "xor_eq"}; + for (auto& k : cppKeywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "printf", "sprintf", "snprintf", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper", + "std", "string", "vector", "map", "unordered_map", "set", "unordered_set", "min", "max"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "C++"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::MilkdropExpression() +{ + static bool inited = false; + static LanguageDefinition milkdropLangDef; + if (!inited) + { + static const char* const nseel2Keywords[] = { + "if", "loop", "while", "bnot", "equal", "below", "above", "assign", "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "sqr", "sqrt", "pow", "exp", "_neg", "log", "log10", "abs", "min", + "max", "sign", "rand", "floor", "int", "ceil", "invsqrt", "sigmoid", "band", "bor", "exec2", "exec3", "_mem", "megabuf", "gmem", "gmegabuf", "freembuf", "memcpy", "memset", + "_if", "_and", "_or", "_not", "_equal", "_noteq", "_below", "_above", "_beleq", "_aboeq", "_set", "_add", "_sub", "_mul", "_div", "_mod", "_mulop", "_divop", "_orop", "_andop", "_subop", + "_modop", "_powop", "_neg", "_gmem"}; + for (auto& k : nseel2Keywords) + milkdropLangDef.mKeywords.insert(k); + + milkdropLangDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeMilkdropConstant(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeMilkdropNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + milkdropLangDef.mCommentStart = "/*"; + milkdropLangDef.mCommentEnd = "*/"; + milkdropLangDef.mSingleLineComment = "//"; + + milkdropLangDef.mCaseSensitive = false; + milkdropLangDef.mAutoIndentation = true; + + milkdropLangDef.mName = "Milkdrop Expression"; + + inited = true; + } + return milkdropLangDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::HLSL() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "AppendStructuredBuffer", + "asm", + "asm_fragment", + "BlendState", + "bool", + "break", + "Buffer", + "ByteAddressBuffer", + "case", + "cbuffer", + "centroid", + "class", + "column_major", + "compile", + "compile_fragment", + "CompileShader", + "const", + "continue", + "ComputeShader", + "ConsumeStructuredBuffer", + "default", + "DepthStencilState", + "DepthStencilView", + "discard", + "do", + "double", + "DomainShader", + "dword", + "else", + "export", + "extern", + "false", + "float", + "for", + "fxgroup", + "GeometryShader", + "groupshared", + "half", + "Hullshader", + "if", + "in", + "inline", + "inout", + "InputPatch", + "int", + "interface", + "line", + "lineadj", + "linear", + "LineStream", + "matrix", + "min16float", + "min10float", + "min16int", + "min12int", + "min16uint", + "namespace", + "nointerpolation", + "noperspective", + "NULL", + "out", + "OutputPatch", + "packoffset", + "pass", + "pixelfragment", + "PixelShader", + "point", + "PointStream", + "precise", + "RasterizerState", + "RenderTargetView", + "return", + "register", + "row_major", + "RWBuffer", + "RWByteAddressBuffer", + "RWStructuredBuffer", + "RWTexture1D", + "RWTexture1DArray", + "RWTexture2D", + "RWTexture2DArray", + "RWTexture3D", + "sample", + "sampler", + "SamplerState", + "SamplerComparisonState", + "shared", + "snorm", + "stateblock", + "stateblock_state", + "static", + "string", + "struct", + "switch", + "StructuredBuffer", + "tbuffer", + "technique", + "technique10", + "technique11", + "texture", + "Texture1D", + "Texture1DArray", + "Texture2D", + "Texture2DArray", + "Texture2DMS", + "Texture2DMSArray", + "Texture3D", + "TextureCube", + "TextureCubeArray", + "true", + "typedef", + "triangle", + "triangleadj", + "TriangleStream", + "uint", + "uniform", + "unorm", + "unsigned", + "vector", + "vertexfragment", + "VertexShader", + "void", + "volatile", + "while", + "bool1", + "bool2", + "bool3", + "bool4", + "double1", + "double2", + "double3", + "double4", + "float1", + "float2", + "float3", + "float4", + "int1", + "int2", + "int3", + "int4", + "in", + "out", + "inout", + "uint1", + "uint2", + "uint3", + "uint4", + "dword1", + "dword2", + "dword3", + "dword4", + "half1", + "half2", + "half3", + "half4", + "float1x1", + "float2x1", + "float3x1", + "float4x1", + "float1x2", + "float2x2", + "float3x2", + "float4x2", + "float1x3", + "float2x3", + "float3x3", + "float4x3", + "float1x4", + "float2x4", + "float3x4", + "float4x4", + "half1x1", + "half2x1", + "half3x1", + "half4x1", + "half1x2", + "half2x2", + "half3x2", + "half4x2", + "half1x3", + "half2x3", + "half3x3", + "half4x3", + "half1x4", + "half2x4", + "half3x4", + "half4x4", + }; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "all", "AllMemoryBarrier", "AllMemoryBarrierWithGroupSync", "any", "asdouble", "asfloat", "asin", "asint", "asint", "asuint", + "asuint", "atan", "atan2", "ceil", "CheckAccessFullyMapped", "clamp", "clip", "cos", "cosh", "countbits", "cross", "D3DCOLORtoUBYTE4", "ddx", + "ddx_coarse", "ddx_fine", "ddy", "ddy_coarse", "ddy_fine", "degrees", "determinant", "DeviceMemoryBarrier", "DeviceMemoryBarrierWithGroupSync", + "distance", "dot", "dst", "errorf", "EvaluateAttributeAtCentroid", "EvaluateAttributeAtSample", "EvaluateAttributeSnapped", "exp", "exp2", + "f16tof32", "f32tof16", "faceforward", "firstbithigh", "firstbitlow", "floor", "fma", "fmod", "frac", "frexp", "fwidth", "GetRenderTargetSampleCount", + "GetRenderTargetSamplePosition", "GroupMemoryBarrier", "GroupMemoryBarrierWithGroupSync", "InterlockedAdd", "InterlockedAnd", "InterlockedCompareExchange", + "InterlockedCompareStore", "InterlockedExchange", "InterlockedMax", "InterlockedMin", "InterlockedOr", "InterlockedXor", "isfinite", "isinf", "isnan", + "ldexp", "length", "lerp", "lit", "log", "log10", "log2", "mad", "max", "min", "modf", "msad4", "mul", "noise", "normalize", "pow", "printf", + "Process2DQuadTessFactorsAvg", "Process2DQuadTessFactorsMax", "Process2DQuadTessFactorsMin", "ProcessIsolineTessFactors", "ProcessQuadTessFactorsAvg", + "ProcessQuadTessFactorsMax", "ProcessQuadTessFactorsMin", "ProcessTriTessFactorsAvg", "ProcessTriTessFactorsMax", "ProcessTriTessFactorsMin", + "radians", "rcp", "reflect", "refract", "reversebits", "round", "rsqrt", "saturate", "sign", "sin", "sincos", "sinh", "smoothstep", "sqrt", "step", + "tan", "tanh", "tex1D", "tex1D", "tex1Dbias", "tex1Dgrad", "tex1Dlod", "tex1Dproj", "tex2D", "tex2D", "tex2Dbias", "tex2Dgrad", "tex2Dlod", "tex2Dproj", + "tex3D", "tex3D", "tex3Dbias", "tex3Dgrad", "tex3Dlod", "tex3Dproj", "texCUBE", "texCUBE", "texCUBEbias", "texCUBEgrad", "texCUBElod", "texCUBEproj", "transpose", "trunc"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(L?\"(\\.|[^\"])*\")", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(\'\\?[^\']\')", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "HLSL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::GLSL() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local"}; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair("[ \\t]*#[ \\t]*[a-zA-Z_]+", PaletteIndex::Preprocessor)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(L?\"(\\.|[^\"])*\")", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(\'\\?[^\']\')", PaletteIndex::CharLiteral)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "GLSL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::C() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long", "register", "restrict", "return", "short", + "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while", "_Alignas", "_Alignof", "_Atomic", "_Bool", "_Complex", "_Generic", "_Imaginary", + "_Noreturn", "_Static_assert", "_Thread_local"}; + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abort", "abs", "acos", "asin", "atan", "atexit", "atof", "atoi", "atol", "ceil", "clock", "cosh", "ctime", "div", "exit", "fabs", "floor", "fmod", "getchar", "getenv", "isalnum", "isalpha", "isdigit", "isgraph", + "ispunct", "isspace", "isupper", "kbhit", "log10", "log2", "log", "memcmp", "modf", "pow", "putchar", "putenv", "puts", "rand", "remove", "rename", "sinh", "sqrt", "srand", "strcat", "strcmp", "strerror", "time", "tolower", "toupper"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenize = [](const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex) -> bool { + paletteIndex = PaletteIndex::Max; + + while (in_begin < in_end && isascii(*in_begin) && isblank(*in_begin)) + in_begin++; + + if (in_begin == in_end) + { + out_begin = in_end; + out_end = in_end; + paletteIndex = PaletteIndex::Default; + } + else if (TokenizeCStyleString(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::String; + else if (TokenizeCStyleCharacterLiteral(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::CharLiteral; + else if (TokenizeCStyleIdentifier(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Identifier; + else if (TokenizeCStyleNumber(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Number; + else if (TokenizeCStylePunctuation(in_begin, in_end, out_begin, out_end)) + paletteIndex = PaletteIndex::Punctuation; + + return paletteIndex != PaletteIndex::Max; + }; + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "C"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::SQL() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "add", "except", "percent", "all", "exec", "plan", "alter", "execute", "precision", "and", "exists", "primary", "any", "exit", "print", "as", "fetch", "proc", "asc", "file", "procedure", + "authorization", "fillfactor", "public", "backup", "for", "raiserror", "begin", "foreign", "read", "between", "freetext", "readtext", "break", "freetexttable", "reconfigure", + "browse", "from", "references", "bulk", "full", "replication", "by", "function", "restore", "cascade", "goto", "restrict", "case", "grant", "return", "check", "group", "revoke", + "checkpoint", "having", "right", "close", "holdlock", "rollback", "clustered", "identity", "rowcount", "coalesce", "identity_insert", "rowguidcol", "collate", "identitycol", "rule", + "column", "if", "save", "commit", "in", "schema", "compute", "index", "select", "constraint", "inner", "session_user", "contains", "insert", "set", "containstable", "intersect", "setuser", + "continue", "into", "shutdown", "convert", "is", "some", "create", "join", "statistics", "cross", "key", "system_user", "current", "kill", "table", "current_date", "left", "textsize", + "current_time", "like", "then", "current_timestamp", "lineno", "to", "current_user", "load", "top", "cursor", "national", "tran", "database", "nocheck", "transaction", + "dbcc", "nonclustered", "trigger", "deallocate", "not", "truncate", "declare", "null", "tsequal", "default", "nullif", "union", "delete", "of", "unique", "deny", "off", "update", + "desc", "offsets", "updatetext", "disk", "on", "use", "distinct", "open", "user", "distributed", "opendatasource", "values", "double", "openquery", "varying", "drop", "openrowset", "view", + "dummy", "openxml", "waitfor", "dump", "option", "when", "else", "or", "where", "end", "order", "while", "errlvl", "outer", "with", "escape", "over", "writetext"}; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "abs", "acos", "add_months", "ascii", "asciistr", "asin", "atan", "atan2", "avg", "bfilename", "bin_to_num", "bitand", "cardinality", "case", "cast", "ceil", + "chartorowid", "chr", "coalesce", "compose", "concat", "convert", "corr", "cos", "cosh", "count", "covar_pop", "covar_samp", "cume_dist", "current_date", + "current_timestamp", "dbtimezone", "decode", "decompose", "dense_rank", "dump", "empty_blob", "empty_clob", "exp", "extract", "first_value", "floor", "from_tz", "greatest", + "group_id", "hextoraw", "initcap", "instr", "instr2", "instr4", "instrb", "instrc", "lag", "last_day", "last_value", "lead", "least", "length", "length2", "length4", + "lengthb", "lengthc", "listagg", "ln", "lnnvl", "localtimestamp", "log", "lower", "lpad", "ltrim", "max", "median", "min", "mod", "months_between", "nanvl", "nchr", + "new_time", "next_day", "nth_value", "nullif", "numtodsinterval", "numtoyminterval", "nvl", "nvl2", "power", "rank", "rawtohex", "regexp_count", "regexp_instr", + "regexp_replace", "regexp_substr", "remainder", "replace", "round", "rownum", "rpad", "rtrim", "sessiontimezone", "sign", "sin", "sinh", + "soundex", "sqrt", "stddev", "substr", "sum", "sys_context", "sysdate", "systimestamp", "tan", "tanh", "to_char", "to_clob", "to_date", "to_dsinterval", "to_lob", + "to_multi_byte", "to_nclob", "to_number", "to_single_byte", "to_timestamp", "to_timestamp_tz", "to_yminterval", "translate", "trim", "trunc", "tz_offset", "uid", "upper", + "user", "userenv", "var_pop", "var_samp", "variance", "vsize "}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(L?\"(\\.|[^\"])*\")", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(\'[^\']*\')", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = false; + langDef.mAutoIndentation = false; + + langDef.mName = "SQL"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::AngelScript() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "and", "abstract", "auto", "bool", "break", "case", "cast", "class", "const", "continue", "default", "do", "double", "else", "enum", "false", "final", "float", "for", + "from", "funcdef", "function", "get", "if", "import", "in", "inout", "int", "interface", "int8", "int16", "int32", "int64", "is", "mixin", "namespace", "not", + "null", "or", "out", "override", "private", "protected", "return", "set", "shared", "super", "switch", "this ", "true", "typedef", "uint", "uint8", "uint16", "uint32", + "uint64", "void", "while", "xor"}; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "cos", "sin", "tab", "acos", "asin", "atan", "atan2", "cosh", "sinh", "tanh", "log", "log10", "pow", "sqrt", "abs", "ceil", "floor", "fraction", "closeTo", "fpFromIEEE", "fpToIEEE", + "complex", "opEquals", "opAddAssign", "opSubAssign", "opMulAssign", "opDivAssign", "opAdd", "opSub", "opMul", "opDiv"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(L?\"(\\.|[^\"])*\")", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(\'\\?[^\']\')", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[0-7]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "/*"; + langDef.mCommentEnd = "*/"; + langDef.mSingleLineComment = "//"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = true; + + langDef.mName = "AngelScript"; + + inited = true; + } + return langDef; +} + +const TextEditor::LanguageDefinition& TextEditor::LanguageDefinition::Lua() +{ + static bool inited = false; + static LanguageDefinition langDef; + if (!inited) + { + static const char* const keywords[] = { + "and", "break", "do", "", "else", "elseif", "end", "false", "for", "function", "if", "in", "", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}; + + for (auto& k : keywords) + langDef.mKeywords.insert(k); + + static const char* const identifiers[] = { + "assert", "collectgarbage", "dofile", "error", "getmetatable", "ipairs", "loadfile", "load", "loadstring", "next", "pairs", "pcall", "print", "rawequal", "rawlen", "rawget", "rawset", + "select", "setmetatable", "tonumber", "tostring", "type", "xpcall", "_G", "_VERSION", "arshift", "band", "bnot", "bor", "bxor", "btest", "extract", "lrotate", "lshift", "replace", + "rrotate", "rshift", "create", "resume", "running", "status", "wrap", "yield", "isyieldable", "debug", "getuservalue", "gethook", "getinfo", "getlocal", "getregistry", "getmetatable", + "getupvalue", "upvaluejoin", "upvalueid", "setuservalue", "sethook", "setlocal", "setmetatable", "setupvalue", "traceback", "close", "flush", "input", "lines", "open", "output", "popen", + "read", "tmpfile", "type", "write", "close", "flush", "lines", "read", "seek", "setvbuf", "write", "__gc", "__tostring", "abs", "acos", "asin", "atan", "ceil", "cos", "deg", "exp", "tointeger", + "floor", "fmod", "ult", "log", "max", "min", "modf", "rad", "random", "randomseed", "sin", "sqrt", "string", "tan", "type", "atan2", "cosh", "sinh", "tanh", + "pow", "frexp", "ldexp", "log10", "pi", "huge", "maxinteger", "mininteger", "loadlib", "searchpath", "seeall", "preload", "cpath", "path", "searchers", "loaded", "module", "require", "clock", + "date", "difftime", "execute", "exit", "getenv", "remove", "rename", "setlocale", "time", "tmpname", "byte", "char", "dump", "find", "format", "gmatch", "gsub", "len", "lower", "match", "rep", + "reverse", "sub", "upper", "pack", "packsize", "unpack", "concat", "maxn", "insert", "pack", "unpack", "remove", "move", "sort", "offset", "codepoint", "char", "len", "codes", "charpattern", + "coroutine", "table", "io", "os", "string", "utf8", "bit32", "math", "debug", "package"}; + for (auto& k : identifiers) + { + Identifier id; + id.mDeclaration = "Built-in function"; + langDef.mIdentifiers.insert(std::make_pair(std::string(k), id)); + } + + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(L?\"(\\.|[^\"])*\")", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"(\'[^\']*\')", PaletteIndex::String)); + langDef.mTokenRegexStrings.push_back(std::make_pair("0[xX][0-9a-fA-F]+[uU]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][+-]?[0-9]+)?[fF]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[+-]?[0-9]+[Uu]?[lL]?[lL]?", PaletteIndex::Number)); + langDef.mTokenRegexStrings.push_back(std::make_pair("[a-zA-Z_][a-zA-Z0-9_]*", PaletteIndex::Identifier)); + langDef.mTokenRegexStrings.push_back(std::make_pair(R"([\[\]\{\}\!\%\^\&\*\(\)\-\+\=\~\|\<\>\?\/\;\,\.])", PaletteIndex::Punctuation)); + + langDef.mCommentStart = "--[["; + langDef.mCommentEnd = "]]"; + langDef.mSingleLineComment = "--"; + + langDef.mCaseSensitive = true; + langDef.mAutoIndentation = false; + + langDef.mName = "Lua"; + + inited = true; + } + return langDef; +} diff --git a/src/gui/preset_editor/imgui_color_text_editor/TextEditor.h b/src/gui/preset_editor/imgui_color_text_editor/TextEditor.h new file mode 100644 index 0000000..0787905 --- /dev/null +++ b/src/gui/preset_editor/imgui_color_text_editor/TextEditor.h @@ -0,0 +1,476 @@ +#pragma once + +#include "imgui.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class TextEditor +{ +public: + enum class PaletteIndex + { + Default, + Keyword, + Number, + String, + CharLiteral, + Punctuation, + Preprocessor, + Identifier, + KnownIdentifier, + PreprocIdentifier, + Comment, + MultiLineComment, + Background, + Cursor, + Selection, + ErrorMarker, + Breakpoint, + LineNumber, + CurrentLineFill, + CurrentLineFillInactive, + CurrentLineEdge, + Max + }; + + enum class SelectionMode + { + Normal, + Word, + Line + }; + + struct Breakpoint { + int mLine; + bool mEnabled; + std::string mCondition; + + Breakpoint() + : mLine(-1) + , mEnabled(false) + { + } + }; + + // Represents a character coordinate from the user's point of view, + // i. e. consider an uniform grid (assuming fixed-width font) on the + // screen as it is rendered, and each cell has its own coordinate, starting from 0. + // Tabs are counted as [1..mTabSize] count empty spaces, depending on + // how many space is necessary to reach the next tab stop. + // For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4, + // because it is rendered as " ABC" on the screen. + struct Coordinates { + int mLine, mColumn; + Coordinates() + : mLine(0) + , mColumn(0) + { + } + Coordinates(int aLine, int aColumn) + : mLine(aLine) + , mColumn(aColumn) + { + assert(aLine >= 0); + assert(aColumn >= 0); + } + static Coordinates Invalid() + { + static Coordinates invalid(-1, -1); + return invalid; + } + + bool operator==(const Coordinates& o) const + { + return mLine == o.mLine && + mColumn == o.mColumn; + } + + bool operator!=(const Coordinates& o) const + { + return mLine != o.mLine || + mColumn != o.mColumn; + } + + bool operator<(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn < o.mColumn; + } + + bool operator>(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn > o.mColumn; + } + + bool operator<=(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine < o.mLine; + return mColumn <= o.mColumn; + } + + bool operator>=(const Coordinates& o) const + { + if (mLine != o.mLine) + return mLine > o.mLine; + return mColumn >= o.mColumn; + } + }; + + struct Identifier { + Coordinates mLocation; + std::string mDeclaration; + }; + + typedef std::string String; + typedef std::unordered_map Identifiers; + typedef std::unordered_set Keywords; + typedef std::map ErrorMarkers; + typedef std::unordered_set Breakpoints; + typedef std::array Palette; + typedef uint8_t Char; + + struct Glyph { + Char mChar; + PaletteIndex mColorIndex = PaletteIndex::Default; + bool mComment : 1; + bool mMultiLineComment : 1; + bool mPreprocessor : 1; + + Glyph(Char aChar, PaletteIndex aColorIndex) + : mChar(aChar) + , mColorIndex(aColorIndex) + , mComment(false) + , mMultiLineComment(false) + , mPreprocessor(false) + { + } + }; + + typedef std::vector Line; + typedef std::vector Lines; + + struct LanguageDefinition { + typedef std::pair TokenRegexString; + typedef std::vector TokenRegexStrings; + typedef bool (*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex); + + std::string mName; + Keywords mKeywords; + Identifiers mIdentifiers; + Identifiers mPreprocIdentifiers; + std::string mCommentStart, mCommentEnd, mSingleLineComment; + char mPreprocChar; + bool mAutoIndentation; + + TokenizeCallback mTokenize; + + TokenRegexStrings mTokenRegexStrings; + + bool mCaseSensitive; + + LanguageDefinition() + : mPreprocChar('#') + , mAutoIndentation(true) + , mTokenize(nullptr) + , mCaseSensitive(true) + { + } + + static const LanguageDefinition& CPlusPlus(); + static const LanguageDefinition& MilkdropExpression(); + static const LanguageDefinition& HLSL(); + static const LanguageDefinition& GLSL(); + static const LanguageDefinition& C(); + static const LanguageDefinition& SQL(); + static const LanguageDefinition& AngelScript(); + static const LanguageDefinition& Lua(); + }; + + TextEditor(); + ~TextEditor() = default; + + void SetLanguageDefinition(const LanguageDefinition& aLanguageDef); + const LanguageDefinition& GetLanguageDefinition() const + { + return mLanguageDefinition; + } + + const Palette& GetPalette() const + { + return mPaletteBase; + } + void SetPalette(const Palette& aValue); + + void SetErrorMarkers(const ErrorMarkers& aMarkers) + { + mErrorMarkers = aMarkers; + } + void SetBreakpoints(const Breakpoints& aMarkers) + { + mBreakpoints = aMarkers; + } + + void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false); + void SetText(const std::string& aText); + std::string GetText() const; + + void SetTextLines(const std::vector& aLines); + std::vector GetTextLines() const; + + std::string GetSelectedText() const; + std::string GetCurrentLineText() const; + + int GetTotalLines() const + { + return (int) mLines.size(); + } + bool IsOverwrite() const + { + return mOverwrite; + } + + void SetReadOnly(bool aValue); + bool IsReadOnly() const + { + return mReadOnly; + } + bool IsTextChanged() const + { + return mTextChanged; + } + bool IsCursorPositionChanged() const + { + return mCursorPositionChanged; + } + + bool IsColorizerEnabled() const + { + return mColorizerEnabled; + } + void SetColorizerEnable(bool aValue); + + Coordinates GetCursorPosition() const + { + return GetActualCursorCoordinates(); + } + void SetCursorPosition(const Coordinates& aPosition); + + inline void SetHandleMouseInputs(bool aValue) + { + mHandleMouseInputs = aValue; + } + inline bool IsHandleMouseInputsEnabled() const + { + return mHandleKeyboardInputs; + } + + inline void SetHandleKeyboardInputs(bool aValue) + { + mHandleKeyboardInputs = aValue; + } + inline bool IsHandleKeyboardInputsEnabled() const + { + return mHandleKeyboardInputs; + } + + inline void SetImGuiChildIgnored(bool aValue) + { + mIgnoreImGuiChild = aValue; + } + inline bool IsImGuiChildIgnored() const + { + return mIgnoreImGuiChild; + } + + inline void SetShowWhitespaces(bool aValue) + { + mShowWhitespaces = aValue; + } + inline bool IsShowingWhitespaces() const + { + return mShowWhitespaces; + } + + void SetTabSize(int aValue); + inline int GetTabSize() const + { + return mTabSize; + } + + void SetTabsAsSpaces(bool aValue); + inline bool GetTabsAsSpaces() const + { + return mTabsAsSpaces; + } + + void InsertText(const std::string& aValue); + void InsertText(const char* aValue); + + void MoveUp(int aAmount = 1, bool aSelect = false); + void MoveDown(int aAmount = 1, bool aSelect = false); + void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false); + void MoveTop(bool aSelect = false); + void MoveBottom(bool aSelect = false); + void MoveHome(bool aSelect = false); + void MoveEnd(bool aSelect = false); + + void SetSelectionStart(const Coordinates& aPosition); + void SetSelectionEnd(const Coordinates& aPosition); + void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal); + void SelectWordUnderCursor(); + void SelectAll(); + bool HasSelection() const; + + void Copy(); + void Cut(); + void Paste(); + void Delete(); + + bool CanUndo() const; + bool CanRedo() const; + void Undo(int aSteps = 1); + void Redo(int aSteps = 1); + + static const Palette& GetDarkPalette(); + static const Palette& GetLightPalette(); + static const Palette& GetRetroBluePalette(); + +private: + typedef std::vector> RegexList; + + struct EditorState { + Coordinates mSelectionStart; + Coordinates mSelectionEnd; + Coordinates mCursorPosition; + }; + + class UndoRecord + { + public: + UndoRecord() + { + } + ~UndoRecord() + { + } + + UndoRecord( + std::string aAdded, + const TextEditor::Coordinates aAddedStart, + const TextEditor::Coordinates aAddedEnd, + + std::string aRemoved, + const TextEditor::Coordinates aRemovedStart, + const TextEditor::Coordinates aRemovedEnd, + + const TextEditor::EditorState& aBefore, + const TextEditor::EditorState& aAfter); + + void Undo(TextEditor* aEditor); + void Redo(TextEditor* aEditor); + + std::string mAdded; + Coordinates mAddedStart; + Coordinates mAddedEnd; + + std::string mRemoved; + Coordinates mRemovedStart; + Coordinates mRemovedEnd; + + EditorState mBefore; + EditorState mAfter; + }; + + typedef std::vector UndoBuffer; + + void ProcessInputs(); + void Colorize(int aFromLine = 0, int aCount = -1); + void ColorizeRange(int aFromLine = 0, int aToLine = 0); + void ColorizeInternal(); + float TextDistanceToLineStart(const Coordinates& aFrom) const; + void EnsureCursorVisible(); + int GetPageSize() const; + std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const; + Coordinates GetActualCursorCoordinates() const; + Coordinates SanitizeCoordinates(const Coordinates& aValue) const; + void Advance(Coordinates& aCoordinates) const; + void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd); + int InsertTextAt(Coordinates& aWhere, const char* aValue); + void AddUndo(const UndoRecord& aValue); + Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const; + Coordinates FindWordStart(const Coordinates& aFrom) const; + Coordinates FindWordEnd(const Coordinates& aFrom) const; + Coordinates FindNextWord(const Coordinates& aFrom) const; + int GetCharacterIndex(const Coordinates& aCoordinates) const; + int GetCharacterColumn(int aLine, int aIndex) const; + int GetLineCharacterCount(int aLine) const; + int GetLineMaxColumn(int aLine) const; + bool IsOnWordBoundary(const Coordinates& aAt) const; + void IndentSelection(bool aIndent); + int IndentLine(int aIndex, bool aIndent, const std::string& aIndentChars, UndoRecord& aUndoRecord); + void RemoveLine(int aStart, int aEnd); + void RemoveLine(int aIndex); + Line& InsertLine(int aIndex); + void EnterCharacter(ImWchar aChar, bool aShift); + void Backspace(); + void DeleteSelection(); + std::string GetWordUnderCursor() const; + std::string GetWordAt(const Coordinates& aCoords) const; + ImU32 GetGlyphColor(const Glyph& aGlyph) const; + + void HandleKeyboardInputs(); + void HandleMouseInputs(); + void Render(); + + float mLineSpacing{1.0f}; + Lines mLines; + EditorState mState; + UndoBuffer mUndoBuffer; + int mUndoIndex{0}; + + int mTabSize{4}; + bool mTabsAsSpaces{false}; + bool mOverwrite{false}; + bool mReadOnly{false}; + bool mWithinRender{false}; + bool mScrollToCursor{false}; + bool mScrollToTop{false}; + bool mTextChanged{false}; + bool mColorizerEnabled{true}; + float mTextStart{20.0f}; // position (in pixels) where a code line starts relative to the left of the TextEditor. + int mLeftMargin{10}; + bool mCursorPositionChanged{false}; + int mColorRangeMin{0}; + int mColorRangeMax{0}; + SelectionMode mSelectionMode{SelectionMode::Normal}; + bool mHandleKeyboardInputs{true}; + bool mHandleMouseInputs{true}; + bool mIgnoreImGuiChild{false}; + bool mShowWhitespaces{true}; + + Palette mPaletteBase{{}}; + Palette mPalette{{}}; + LanguageDefinition mLanguageDefinition; + RegexList mRegexList; + + bool mCheckComments{true}; + Breakpoints mBreakpoints; + ErrorMarkers mErrorMarkers; + ImVec2 mCharAdvance; + Coordinates mInteractiveStart, mInteractiveEnd; + std::string mLineBuffer; + uint64_t mStartTime{0}; + + float mLastClick{-1.0f}; +}; diff --git a/src/notifications/CMakeLists.txt b/src/notifications/CMakeLists.txt index 582f24c..3cf8e90 100644 --- a/src/notifications/CMakeLists.txt +++ b/src/notifications/CMakeLists.txt @@ -1,7 +1,13 @@ add_library(ProjectMSDL-Notifications STATIC DisplayToastNotification.cpp DisplayToastNotification.h - QuitNotification.cpp QuitNotification.h PlaybackControlNotification.cpp PlaybackControlNotification.h UpdateWindowTitleNotification.cpp UpdateWindowTitleNotification.h) + PlaybackControlNotification.cpp + PlaybackControlNotification.h + QuitNotification.cpp + QuitNotification.h + UpdateWindowTitleNotification.cpp + UpdateWindowTitleNotification.h + ) target_include_directories(ProjectMSDL-Notifications PRIVATE diff --git a/src/notifications/UpdateWindowTitleNotification.h b/src/notifications/UpdateWindowTitleNotification.h index d6cb0a4..96a1d43 100644 --- a/src/notifications/UpdateWindowTitleNotification.h +++ b/src/notifications/UpdateWindowTitleNotification.h @@ -2,11 +2,21 @@ #include +#include + /** * @brief Informs the application that the window title should be updated. */ class UpdateWindowTitleNotification : public Poco::Notification { public: + UpdateWindowTitleNotification() = default; + + explicit UpdateWindowTitleNotification(std::string customTitle) + : _customTitle(std::move(customTitle)) + {} + std::string name() const override; + + std::string _customTitle; }; diff --git a/src/resources/FontAwesome License.txt b/src/resources/FontAwesome License.txt new file mode 100644 index 0000000..45063c1 --- /dev/null +++ b/src/resources/FontAwesome License.txt @@ -0,0 +1,165 @@ +Fonticons, Inc. (https://fontawesome.com) + +-------------------------------------------------------------------------------- + +Font Awesome Free License + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +-------------------------------------------------------------------------------- + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) + +The Font Awesome Free download is licensed under a Creative Commons +Attribution 4.0 International License and applies to all icons packaged +as SVG and JS file types. + +-------------------------------------------------------------------------------- + +# Fonts: SIL OFL 1.1 License + +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +Copyright (c) 2025 Fonticons, Inc. (https://fontawesome.com) +with Reserved Font Name: "Font Awesome". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +-------------------------------------------------------------------------------- + +# Code: MIT License (https://opensource.org/licenses/MIT) + +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +Copyright 2025 Fonticons, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +# Attribution + +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +-------------------------------------------------------------------------------- + +# Brand Icons + +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/src/resources/fa-regular-400.ttf b/src/resources/fa-regular-400.ttf new file mode 100644 index 0000000..ac7fe8d Binary files /dev/null and b/src/resources/fa-regular-400.ttf differ diff --git a/src/resources/fa-solid-900.ttf b/src/resources/fa-solid-900.ttf new file mode 100644 index 0000000..60d024b Binary files /dev/null and b/src/resources/fa-solid-900.ttf differ diff --git a/vcpkg.json b/vcpkg.json index 924009d..cf19f06 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "dependencies": [ + "expat", "glew", "sdl2", { @@ -9,7 +10,8 @@ "util" ] }, + "freetype", "projectm", - "freetype" + "projectm-eval" ] } \ No newline at end of file