commit 8da6ddc689ea41c4f7aa14fe6b7abdf835000410 Author: versustunez Date: Thu May 29 13:21:19 2025 +0200 πŸŽ‰ begin project diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c8133d9 --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +AlignAfterOpenBracket: Align +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: Never +BinPackArguments: true +BinPackParameters: true +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +FixNamespaceComments: true +IncludeBlocks: Regroup +SortIncludes: Never \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..30c10e4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.h text eol=lf +*.cpp text eol=lf +*.glsl text eol=lf +*.ts text eol=lf +*.js text eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb6eb81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +/cmake-build-debug/ +/cmake-build-release/ +/build/ +/build-release/ +/build-debug/ +/cmake-build* +.idea/ +.vscode/ +.vs/ +Binaries/ +Intermediates/ +_audioConfig.ini +MonoDebugger.log +mono_crash.*.json +VeAuralize.log +.vqjs +.cache +bin/ +Vendor/ +lib/ +TODO.md \ No newline at end of file diff --git a/CMake/CMakeLists.txt b/CMake/CMakeLists.txt new file mode 100644 index 0000000..5044988 --- /dev/null +++ b/CMake/CMakeLists.txt @@ -0,0 +1,25 @@ +macro(add_sources) + get_property(tmp GLOBAL PROPERTY SRCS) + file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + foreach (_src ${ARGN}) + if (_relPath) + list(APPEND tmp "${_relPath}/${_src}") + else () + list(APPEND tmp "${_src}") + endif () + endforeach () + set_property(GLOBAL PROPERTY SRCS ${tmp}) +endmacro() + +macro(add_module_sources) + get_property(tmp GLOBAL PROPERTY MODULE_SRCS) + file(RELATIVE_PATH _relPath "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + foreach (_src ${ARGN}) + if (_relPath) + list(APPEND tmp "${_relPath}/${_src}") + else () + list(APPEND tmp "${_src}") + endif () + endforeach () + set_property(GLOBAL PROPERTY MODULE_SRCS ${tmp}) +endmacro() \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..48bb639 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.31) +project(VUI LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib/${CMAKE_SYSTEM_NAME}/) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib/${CMAKE_SYSTEM_NAME}/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin/${CMAKE_SYSTEM_NAME}/) +set(IS_DEBUG_BUILD CMAKE_BUILD_TYPE STREQUAL "Debug") + +# AVX :) +if (MSVC) + add_definitions(/MP /arch:AVX2 /W4) +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + add_definitions(-mavx2 -Wall -Wextra) +endif () + +add_subdirectory(CMake) +add_subdirectory(Source) + +get_property(source_files GLOBAL PROPERTY SRCS) +get_property(module_source_files GLOBAL PROPERTY MODULE_SRCS) +add_library(VUI STATIC ${source_files}) +target_sources(VUI + PUBLIC + FILE_SET CXX_MODULES FILES + ${module_source_files} +) +set_property(TARGET VUI PROPERTY COMPILE_WARNING_AS_ERROR ON) +target_compile_features(VUI PUBLIC cxx_std_23) + + +add_executable(VUI_EX main.cppm) +target_link_libraries(VUI_EX PRIVATE VUI) +target_compile_features(VUI_EX PUBLIC cxx_std_23) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..cbb91fa --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,112 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "ninja-multi-vcpkg", + "displayName": "Ninja Multi-Config", + "description": "Configure with vcpkg toolchain and generate Ninja project files for all configurations", + "binaryDir": "${sourceDir}/builds/${presetName}", + "generator": "Ninja Multi-Config", + "toolchainFile": "${sourceDir}/vcpkg/scripts/buildsystems/vcpkg.cmake" + } + ], + "buildPresets": [ + { + "name": "ninja-vcpkg-debug", + "configurePreset": "ninja-multi-vcpkg", + "displayName": "Build (Debug)", + "description": "Build with Ninja/vcpkg (Debug)", + "configuration": "Debug" + }, + { + "name": "ninja-vcpkg-release", + "configurePreset": "ninja-multi-vcpkg", + "displayName": "Build (Release)", + "description": "Build with Ninja/vcpkg (Release)", + "configuration": "Release" + }, + { + "name": "ninja-vcpkg", + "configurePreset": "ninja-multi-vcpkg", + "displayName": "Build", + "description": "Build with Ninja/vcpkg", + "hidden": true + }, + { + "name": "ninja-vcpkg-check-format", + "configurePreset": "ninja-multi-vcpkg", + "displayName": "Build", + "targets": [ + "check-format" + ], + "description": "Build with Ninja/vcpkg check-format" + }, + { + "name": "ninja-vcpkg-format", + "configurePreset": "ninja-multi-vcpkg", + "displayName": "Build", + "targets": [ + "format" + ], + "description": "Build with Ninja/vcpkg format" + } + ], + "workflowPresets": [ + { + "name": "format", + "steps": [ + { + "name": "ninja-multi-vcpkg", + "type": "configure" + }, + { + "name": "ninja-vcpkg-format", + "type": "build" + } + ] + }, + { + "name": "check-format", + "steps": [ + { + "name": "ninja-multi-vcpkg", + "type": "configure" + }, + { + "name": "ninja-vcpkg-check-format", + "type": "build" + } + ] + } + ], + "testPresets": [ + { + "name": "test-ninja-vcpkg", + "configurePreset": "ninja-multi-vcpkg", + "hidden": true + }, + { + "name": "test-debug", + "description": "Test (Debug)", + "displayName": "Test (Debug)", + "configuration": "Debug", + "inherits": [ + "test-ninja-vcpkg" + ] + }, + { + "name": "test-release", + "description": "Test (Release)", + "displayName": "Test (Release)", + "configuration": "Release", + "inherits": [ + "test-ninja-vcpkg" + ] + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56c9b4f --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +zlib License + +Copyright (C) 2025 VersusTuneZ + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ec98b4 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# UI – Versus UI +VUI (short for Versus UI) is a lightweight and modern UI framework written in C++. It provides a clean and intuitive API for building native user interfaces with a focus on simplicity, performance, and extensibility. + +Inspired by RGFW and GLFW + +> ![Warning] +> Work in Progress +> VUI is currently in early development and is not yet production-ready. + + +# ✨ Features +- Basic windowing support +- Event handling (keyboard, mouse, etc.) +- Modular and easy-to-use API +- Lightweight and efficient + +# πŸ–₯️ Platform Support +- βœ… Windows (currently supported) +- ⏳ Linux (planned) + +> macOS and other platforms may be considered in the future. + +# 🚧 Project Status +VUI is a heavy Work In Progress. Many components are still being developed, and APIs may change frequently. Your feedback and contributions are welcome to help shape the future of the project! + +# πŸ“¦ Getting Started +> Note: This project currently targets Windows only. + +Prerequisites +- A C++23 (or later) compatible compiler +- CMake 3.16+ + +# Build Instructions +```bash +git clone https://git.vstz.dev/vstz/VUI.git +cd vui +mkdir build && cd build +cmake .. +cmake --build . +``` + +# πŸ› οΈ Usage +Here’s a simple example of using VUI: + +```cpp +#include + +int main() { + VUI::WindowManager::Create(800, 600, "Example Window"); + while (VUI::WindowManager::HasOpen()) { + VUI::WindowManager::Update(); + double dt = VUI::GetDeltaTime(); + // ... + if (VUI::Input::IsKeyPressed(VUI::KeyCodes::ESCAPE)) { + break; + } + } + return 0; +} +``` +More examples and documentation will be added as the project matures. + +# πŸ“… Roadmap +- Basic window creation +- Event system (keyboard/mouse) +- Linux support +- Layout system +- Widgets (buttons, sliders, etc.) +- Theming & styling +- High-DPI support + +# 🀝 Contributing +Contributions are welcome! If you're interested in helping build VUI, feel free to open issues, submit pull requests, or discuss ideas. + diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt new file mode 100644 index 0000000..87b5ef9 --- /dev/null +++ b/Source/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(Core) +add_subdirectory(Input) +add_subdirectory(Platform) +add_subdirectory(Types) +add_module_sources(VUI.cppm) \ No newline at end of file diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt new file mode 100644 index 0000000..63fe875 --- /dev/null +++ b/Source/Core/CMakeLists.txt @@ -0,0 +1,5 @@ +add_module_sources( + VUI.cppm + Window.cppm + WindowManager.cppm +) \ No newline at end of file diff --git a/Source/Core/VUI.cppm b/Source/Core/VUI.cppm new file mode 100644 index 0000000..21787bc --- /dev/null +++ b/Source/Core/VUI.cppm @@ -0,0 +1,36 @@ +module; +#include + +export module VUI:Time; + +import :TimeInternal; + +static uint64_t s_TimerOffset{0}; +static double s_LastTime{0}; +static double s_DeltaTime{0}; + +namespace VUI { +export double GetDeltaTime() { return s_DeltaTime; }; +export double GetTime() { + return static_cast(GetTimerValue() - s_TimerOffset) / + static_cast(GetTimerFrequency()); +}; +export uint64_t GetTimeNs() { + return static_cast( + static_cast(GetTimerValue() - s_TimerOffset) * 1e9 / + static_cast(GetTimerFrequency())); +} +export void SetTime(const uint64_t time) { + const auto dTimerValue = static_cast(GetTimerValue()); + const auto dTime = static_cast(time); + const auto dFrequency = static_cast(GetTimerFrequency()); + s_TimerOffset = static_cast(dTimerValue - dTime * dFrequency); +}; +export uint64_t GetTimeOffset() { return s_TimerOffset; } + +export void UpdateDeltaTime() { + const auto time = GetTime(); + s_DeltaTime = time - s_LastTime; + s_LastTime = time; +}; +} // namespace VUI \ No newline at end of file diff --git a/Source/Core/Window.cppm b/Source/Core/Window.cppm new file mode 100644 index 0000000..8896409 --- /dev/null +++ b/Source/Core/Window.cppm @@ -0,0 +1,54 @@ +module; + +#include +#include + +#include "../PlatformDetection.h" + +export module VUI:Window; + +import :Ref; +import :Geometry; +import :Input; + +namespace VUI { +struct WindowManager; +std::uint64_t s_Handle = 0; + +export struct WindowSpecification { + uint32_t width{1280}; + uint32_t height{720}; + std::string title{"vui"}; +}; + +export struct Window : RefCounted { + virtual void pullEvents() = 0; + virtual void close() = 0; + + virtual void resize(uint32_t width, uint32_t height) = 0; + virtual void updateTitle(const std::string &title) = 0; + + bool isOpen() const { return m_IsOpen; } + [[nodiscard]] uint32_t getWidth() const { return m_Specification.width; } + [[nodiscard]] uint32_t getHeight() const { return m_Specification.height; } + [[nodiscard]] std::string getTitle() const { return m_Specification.title; } + [[nodiscard]] uint64_t getHandle() const { return m_WindowHandle; } + +protected: + static void UpdateKey(uint32_t key, bool isDown); + virtual void createWindow() = 0; + bool m_IsOpen{false}; + std::uint64_t m_WindowHandle{0}; + WindowSpecification m_Specification{}; + + UPoint m_WindowPosition{}; + + Window() = default; + friend Ref; + friend WindowManager; +}; + +void Window::UpdateKey(const uint32_t key, const bool isDown) { + Input::UpdateKey(key, isDown); +} +} // namespace VUI \ No newline at end of file diff --git a/Source/Core/WindowManager.cppm b/Source/Core/WindowManager.cppm new file mode 100644 index 0000000..29a9c26 --- /dev/null +++ b/Source/Core/WindowManager.cppm @@ -0,0 +1,91 @@ +module; + +#include +#include +#include + +export module VUI:WindowManager; + +import :Window; +import :PlatformWindow; +import :Time; + +namespace VUI { + +export struct WindowManager { + static Ref Create(uint32_t width, uint32_t height, + const std::string &title); + static Ref Create(const WindowSpecification &); + static void Manage(const Ref &window); + + static void Update(); + static void Close(Ref &); + static void CloseByTitle(const std::string &title); + static void CloseAll(); + static bool HasOpenWindows(); + +protected: + std::vector> m_Windows{}; + friend Window; +}; + +static WindowManager s_WindowManager = {}; + +Ref WindowManager::Create(uint32_t width, uint32_t height, + const std::string &title) { + return Create(WindowSpecification{width, height, title}); +} + +Ref WindowManager::Create(const WindowSpecification &specification) { + if (GetTimeOffset() == 0) + SetTime(0); + + auto window = Windowing::CreateWindowRef(); + window->m_Specification = specification; + window->m_WindowHandle = s_Handle++; + window->createWindow(); + Manage(window); + return window; +} + +void WindowManager::Manage(const Ref &window) { + s_WindowManager.m_Windows.push_back(window); +} + +void WindowManager::Update() { + Input::PostFrameUpdate(); + UpdateDeltaTime(); + for (auto &window : s_WindowManager.m_Windows) { + window->pullEvents(); + } + std::erase_if(s_WindowManager.m_Windows, + [](Ref &window) { return !window->isOpen(); }); +} + +void WindowManager::Close(Ref &refWindow) { + if (!refWindow) + return; + refWindow->close(); + std::erase_if(s_WindowManager.m_Windows, [&refWindow](auto &window) { + return !window->isOpen() || window == refWindow; + }); +} +void WindowManager::CloseByTitle(const std::string &title) { + std::erase_if(s_WindowManager.m_Windows, [&title](Ref &window) { + if (window->getTitle() == title) { + window->close(); + } + return !window->isOpen(); + }); +} +void WindowManager::CloseAll() { + for (auto &window : s_WindowManager.m_Windows) { + window->close(); + } + s_WindowManager.m_Windows.clear(); +} +bool WindowManager::HasOpenWindows() { + return !s_WindowManager.m_Windows.empty(); +} + +} // namespace VUI \ No newline at end of file diff --git a/Source/Input/CMakeLists.txt b/Source/Input/CMakeLists.txt new file mode 100644 index 0000000..43b3dd4 --- /dev/null +++ b/Source/Input/CMakeLists.txt @@ -0,0 +1,5 @@ +add_module_sources( + InputCodes.cppm + Input.cppm + Keys.cppm +) \ No newline at end of file diff --git a/Source/Input/Input.cppm b/Source/Input/Input.cppm new file mode 100644 index 0000000..555bdb0 --- /dev/null +++ b/Source/Input/Input.cppm @@ -0,0 +1,59 @@ +module; + +#include +#include +#include + +export module VUI:Input; + +import :InputCodes; +import :Keys; + +namespace VUI { +struct WindowManager; +struct Window; +constexpr uint32_t DownMask = 1; +constexpr uint32_t RepeatMask = 1 << 2; + +static std::array s_Keys{}; + +static constexpr std::array InputKeys = InitKeys(); + +export struct Input { + static bool IsKeyDown(const KeyCodes key) { + return s_Keys[std::to_underlying(key)] & DownMask; + } + + static bool IsKeyPressed(const KeyCodes key) { + const auto value = s_Keys[std::to_underlying(key)]; + return value & DownMask && !(value & RepeatMask); + } + + static bool IsKeyRepeat(const KeyCodes key) { + return s_Keys[std::to_underlying(key)] & RepeatMask; + } + +private: + static void UpdateKey(uint32_t key, bool isDown); + static void PostFrameUpdate(); + friend WindowManager; + friend Window; +}; + +void Input::UpdateKey(const uint32_t key, const bool isDown) { + assert(key < InputKeys.size() && "Keycode out of range"); + const uint8_t keyCode = std::to_underlying(InputKeys[key]); + uint8_t value = s_Keys[keyCode]; + if (isDown) { + value |= DownMask; + } else { + value &= ~(DownMask | RepeatMask); + } + s_Keys[keyCode] = value; +} + +void Input::PostFrameUpdate() { + for (uint8_t &s_key : s_Keys) + s_key |= s_key & DownMask ? RepeatMask : 0; +} +} // namespace VUI \ No newline at end of file diff --git a/Source/Input/InputCodes.cppm b/Source/Input/InputCodes.cppm new file mode 100644 index 0000000..665ad1c --- /dev/null +++ b/Source/Input/InputCodes.cppm @@ -0,0 +1,124 @@ +module; +#include + +export module VUI:InputCodes; + +namespace VUI { +export enum class KeyCodes : uint8_t { + keyNULL, + ESCAPE, + BACKTICK, + N0, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + + MINUS, + EQUALS, + BACKSPACE, + TAB, + SPACE, + + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + PERIOD, + COMMA, + SLASH, + BRACKET, + CLOSEBRACKET, + SEMICOLON, + APOSTROPHE, + BACKSLASH, + RETURN, + DELETE_KEY, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + CAPSLOCK, + SHIFT_L, + CONTROL_L, + ALT_L, + SUPER_L, + SHIFT_R, + CONTROL_R, + ALT_R, + SUPER_R, + UP, + DOWN, + LEFT, + RIGHT, + + INSERT, + END, + HOME, + PAGEUP, + PAGEDOWN, + + NUMLOCK, + KP_SLASH, + MULTIPLY, + KP_MINUS, + KP_1, + KP_2, + KP_3, + KP_4, + KP_5, + KP_6, + KP_7, + KP_8, + KP_9, + KP_0, + KP_PERIOD, + KP_RETURN, + KEYLAST, +}; + +export enum class MouseCodes { + LEFT, + RIGHT, + MIDDLE, + WHEEL_UP, + WHEEL_DOWN, +}; +} // namespace VUI \ No newline at end of file diff --git a/Source/Input/Keys.cppm b/Source/Input/Keys.cppm new file mode 100644 index 0000000..6330a37 --- /dev/null +++ b/Source/Input/Keys.cppm @@ -0,0 +1,120 @@ +module; +#include "../PlatformDetection.h" + +#include +#include + +export module VUI:Keys; + +import :InputCodes; + +namespace VUI { +export constexpr int Keys = VUI_OS_VALUE(136, 0x15C + 1, 128); + +export constexpr std::array InitKeys() { + auto keys = std::array{}; + keys[VUI_OS_VALUE(49, 0x029, 50)] = KeyCodes::BACKTICK; + keys[VUI_OS_VALUE(19, 0x00B, 29)] = KeyCodes::N0; + keys[VUI_OS_VALUE(10, 0x002, 18)] = KeyCodes::N1; + keys[VUI_OS_VALUE(11, 0x003, 19)] = KeyCodes::N2; + keys[VUI_OS_VALUE(12, 0x004, 20)] = KeyCodes::N3; + keys[VUI_OS_VALUE(13, 0x005, 21)] = KeyCodes::N4; + keys[VUI_OS_VALUE(14, 0x006, 23)] = KeyCodes::N5; + keys[VUI_OS_VALUE(15, 0x007, 22)] = KeyCodes::N6; + keys[VUI_OS_VALUE(16, 0x008, 26)] = KeyCodes::N7; + keys[VUI_OS_VALUE(17, 0x009, 28)] = KeyCodes::N8; + keys[VUI_OS_VALUE(18, 0x00A, 25)] = KeyCodes::N9; + keys[VUI_OS_VALUE(65, 0x039, 49)] = KeyCodes::SPACE; + keys[VUI_OS_VALUE(38, 0x01E, 0)] = KeyCodes::A; + keys[VUI_OS_VALUE(56, 0x030, 11)] = KeyCodes::B; + keys[VUI_OS_VALUE(54, 0x02E, 8)] = KeyCodes::C; + keys[VUI_OS_VALUE(40, 0x020, 2)] = KeyCodes::D; + keys[VUI_OS_VALUE(26, 0x012, 14)] = KeyCodes::E; + keys[VUI_OS_VALUE(41, 0x021, 3)] = KeyCodes::F; + keys[VUI_OS_VALUE(42, 0x022, 5)] = KeyCodes::G; + keys[VUI_OS_VALUE(43, 0x023, 4)] = KeyCodes::H; + keys[VUI_OS_VALUE(31, 0x017, 34)] = KeyCodes::I; + keys[VUI_OS_VALUE(44, 0x024, 38)] = KeyCodes::J; + keys[VUI_OS_VALUE(45, 0x025, 40)] = KeyCodes::K; + keys[VUI_OS_VALUE(46, 0x026, 37)] = KeyCodes::L; + keys[VUI_OS_VALUE(58, 0x032, 46)] = KeyCodes::M; + keys[VUI_OS_VALUE(57, 0x031, 45)] = KeyCodes::N; + keys[VUI_OS_VALUE(32, 0x018, 31)] = KeyCodes::O; + keys[VUI_OS_VALUE(33, 0x019, 35)] = KeyCodes::P; + keys[VUI_OS_VALUE(24, 0x010, 12)] = KeyCodes::Q; + keys[VUI_OS_VALUE(27, 0x013, 15)] = KeyCodes::R; + keys[VUI_OS_VALUE(39, 0x01F, 1)] = KeyCodes::S; + keys[VUI_OS_VALUE(28, 0x014, 17)] = KeyCodes::T; + keys[VUI_OS_VALUE(30, 0x016, 32)] = KeyCodes::U; + keys[VUI_OS_VALUE(55, 0x02F, 9)] = KeyCodes::V; + keys[VUI_OS_VALUE(25, 0x011, 13)] = KeyCodes::W; + keys[VUI_OS_VALUE(53, 0x02D, 7)] = KeyCodes::X; + keys[VUI_OS_VALUE(29, 0x015, 16)] = KeyCodes::Y; + keys[VUI_OS_VALUE(52, 0x02C, 6)] = KeyCodes::Z; + keys[VUI_OS_VALUE(60, 0x034, 47)] = KeyCodes::PERIOD; + keys[VUI_OS_VALUE(59, 0x033, 43)] = KeyCodes::COMMA; + keys[VUI_OS_VALUE(61, 0x035, 44)] = KeyCodes::SLASH; + keys[VUI_OS_VALUE(34, 0x01A, 33)] = KeyCodes::BRACKET; + keys[VUI_OS_VALUE(35, 0x01B, 30)] = KeyCodes::CLOSEBRACKET; + keys[VUI_OS_VALUE(47, 0x027, 41)] = KeyCodes::SEMICOLON; + keys[VUI_OS_VALUE(48, 0x028, 39)] = KeyCodes::APOSTROPHE; + keys[VUI_OS_VALUE(51, 0x02B, 42)] = KeyCodes::BACKSLASH; + keys[VUI_OS_VALUE(36, 0x01C, 36)] = KeyCodes::RETURN; + keys[VUI_OS_VALUE(119, 0x153, 118)] = KeyCodes::DELETE_KEY; + keys[VUI_OS_VALUE(77, 0x145, 72)] = KeyCodes::NUMLOCK; + keys[VUI_OS_VALUE(106, 0x135, 82)] = KeyCodes::KP_SLASH; + keys[VUI_OS_VALUE(63, 0x037, 76)] = KeyCodes::MULTIPLY; + keys[VUI_OS_VALUE(82, 0x04A, 67)] = KeyCodes::KP_MINUS; + keys[VUI_OS_VALUE(87, 0x04F, 84)] = KeyCodes::KP_1; + keys[VUI_OS_VALUE(88, 0x050, 85)] = KeyCodes::KP_2; + keys[VUI_OS_VALUE(89, 0x051, 86)] = KeyCodes::KP_3; + keys[VUI_OS_VALUE(83, 0x04B, 87)] = KeyCodes::KP_4; + keys[VUI_OS_VALUE(84, 0x04C, 88)] = KeyCodes::KP_5; + keys[VUI_OS_VALUE(85, 0x04D, 89)] = KeyCodes::KP_6; + keys[VUI_OS_VALUE(79, 0x047, 90)] = KeyCodes::KP_7; + keys[VUI_OS_VALUE(80, 0x048, 92)] = KeyCodes::KP_8; + keys[VUI_OS_VALUE(81, 0x049, 93)] = KeyCodes::KP_9; + keys[VUI_OS_VALUE(90, 0x052, 83)] = KeyCodes::KP_0; + keys[VUI_OS_VALUE(91, 0x053, 65)] = KeyCodes::KP_PERIOD; + keys[VUI_OS_VALUE(104, 0x11C, 77)] = KeyCodes::KP_RETURN; + keys[VUI_OS_VALUE(20, 0x00C, 27)] = KeyCodes::MINUS; + keys[VUI_OS_VALUE(21, 0x00D, 24)] = KeyCodes::EQUALS; + keys[VUI_OS_VALUE(22, 0x00E, 51)] = KeyCodes::BACKSPACE; + keys[VUI_OS_VALUE(23, 0x00F, 48)] = KeyCodes::TAB; + keys[VUI_OS_VALUE(66, 0x03A, 57)] = KeyCodes::CAPSLOCK; + keys[VUI_OS_VALUE(50, 0x02A, 56)] = KeyCodes::SHIFT_L; + keys[VUI_OS_VALUE(37, 0x01D, 59)] = KeyCodes::CONTROL_L; + keys[VUI_OS_VALUE(64, 0x038, 58)] = KeyCodes::ALT_L; + keys[VUI_OS_VALUE(133, 0x15B, 55)] = KeyCodes::SUPER_L; +#if !defined(VUI_APPLE) + keys[VUI_OS_VALUE(105, 0x11D, 59)] = KeyCodes::CONTROL_R; + keys[VUI_OS_VALUE(135, 0x15C, 55)] = KeyCodes::SUPER_R; + keys[VUI_OS_VALUE(62, 0x036, 56)] = KeyCodes::SHIFT_R; + keys[VUI_OS_VALUE(108, 0x138, 58)] = KeyCodes::ALT_R; +#endif + keys[VUI_OS_VALUE(67, 0x03B, 127)] = KeyCodes::F1; + keys[VUI_OS_VALUE(68, 0x03C, 121)] = KeyCodes::F2; + keys[VUI_OS_VALUE(69, 0x03D, 100)] = KeyCodes::F3; + keys[VUI_OS_VALUE(70, 0x03E, 119)] = KeyCodes::F4; + keys[VUI_OS_VALUE(71, 0x03F, 97)] = KeyCodes::F5; + keys[VUI_OS_VALUE(72, 0x040, 98)] = KeyCodes::F6; + keys[VUI_OS_VALUE(73, 0x041, 99)] = KeyCodes::F7; + keys[VUI_OS_VALUE(74, 0x042, 101)] = KeyCodes::F8; + keys[VUI_OS_VALUE(75, 0x043, 102)] = KeyCodes::F9; + keys[VUI_OS_VALUE(76, 0x044, 110)] = KeyCodes::F10; + keys[VUI_OS_VALUE(95, 0x057, 104)] = KeyCodes::F11; + keys[VUI_OS_VALUE(96, 0x058, 112)] = KeyCodes::F12; + keys[VUI_OS_VALUE(111, 0x148, 126)] = KeyCodes::UP; + keys[VUI_OS_VALUE(116, 0x150, 125)] = KeyCodes::DOWN; + keys[VUI_OS_VALUE(113, 0x14B, 123)] = KeyCodes::LEFT; + keys[VUI_OS_VALUE(114, 0x14D, 124)] = KeyCodes::RIGHT; + keys[VUI_OS_VALUE(118, 0x152, 115)] = KeyCodes::INSERT; + keys[VUI_OS_VALUE(115, 0x14F, 120)] = KeyCodes::END; + keys[VUI_OS_VALUE(112, 0x149, 117)] = KeyCodes::PAGEUP; + keys[VUI_OS_VALUE(117, 0x151, 122)] = KeyCodes::PAGEDOWN; + keys[VUI_OS_VALUE(9, 0x001, 53)] = KeyCodes::ESCAPE; + keys[VUI_OS_VALUE(110, 0x147, 116)] = KeyCodes::HOME; + + return keys; +} +} // namespace VUI \ No newline at end of file diff --git a/Source/Platform/CMakeLists.txt b/Source/Platform/CMakeLists.txt new file mode 100644 index 0000000..f65c881 --- /dev/null +++ b/Source/Platform/CMakeLists.txt @@ -0,0 +1,5 @@ +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_subdirectory(Linux) +elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + add_subdirectory(Windows) +endif () \ No newline at end of file diff --git a/Source/Platform/Windows/CMakeLists.txt b/Source/Platform/Windows/CMakeLists.txt new file mode 100644 index 0000000..b7bc40b --- /dev/null +++ b/Source/Platform/Windows/CMakeLists.txt @@ -0,0 +1,4 @@ +add_module_sources( + Time.cppm + Window.cppm +) \ No newline at end of file diff --git a/Source/Platform/Windows/Time.cppm b/Source/Platform/Windows/Time.cppm new file mode 100644 index 0000000..1fe124d --- /dev/null +++ b/Source/Platform/Windows/Time.cppm @@ -0,0 +1,19 @@ +module; +#include +#include "Windows.h" + +export module VUI:TimeInternal; + +namespace VUI { +export uint64_t GetTimerValue() { + uint64_t value{0}; + QueryPerformanceCounter(reinterpret_cast(&value)); + return value; +} + +export uint64_t GetTimerFrequency() { + uint64_t frequency{0}; + QueryPerformanceFrequency(reinterpret_cast(&frequency)); + return frequency; +} +} // namespace VUI \ No newline at end of file diff --git a/Source/Platform/Windows/Window.cppm b/Source/Platform/Windows/Window.cppm new file mode 100644 index 0000000..843f6ce --- /dev/null +++ b/Source/Platform/Windows/Window.cppm @@ -0,0 +1,160 @@ +module; + +#include +#include +#include + +export module VUI:PlatformWindow; + +import :Window; +import :Ref; + +namespace VUI::Windowing { +const char *CLASS_NAME = "VUI"; +bool s_windowInit = false; + +struct Win32Window : Window { + Win32Window(); + ~Win32Window() override; + + void pullEvents() override; + void close() override; + void resize(uint32_t width, uint32_t height) override; + void updateTitle(const std::string &title) override; + static LRESULT CALLBACK WindowProc(HWND, UINT uMsg, WPARAM, LPARAM); + +protected: + void createWindow() override; + void handleEvent(HWND, UINT message, WPARAM, LPARAM); + +private: + HWND m_Window{nullptr}; + HINSTANCE m_Instance{nullptr}; + friend Window; +}; + +export Ref CreateWindowRef() { return Ref(new Win32Window()); } + +Win32Window::Win32Window() : Window() { m_Instance = GetModuleHandle(nullptr); } +Win32Window::~Win32Window() { DestroyWindow(m_Window); } + +void Win32Window::pullEvents() { + MSG msg; + while (PeekMessage(&msg, m_Window, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } +} + +void Win32Window::close() { + CloseWindow(m_Window); + m_IsOpen = false; +} + +void Win32Window::resize(uint32_t width, uint32_t height) { + m_Specification.width = width; + m_Specification.height = height; + + SetWindowPos(m_Window, nullptr, static_cast(m_WindowPosition.x), + static_cast(m_WindowPosition.y), + static_cast(m_Specification.width), + static_cast(m_Specification.height), 0); +} + +void Win32Window::updateTitle(const std::string &title) { + m_Specification.title = title; + SetWindowText(m_Window, title.c_str()); +} + +LRESULT CALLBACK Win32Window::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + + if (uMsg == WM_CREATE) { + auto *pCreate = reinterpret_cast(lParam); + SetWindowLongPtr(hwnd, GWLP_USERDATA, + reinterpret_cast( + static_cast(pCreate->lpCreateParams))); + return TRUE; + } + + auto impl = + reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (impl != nullptr) { + impl->handleEvent(hwnd, uMsg, wParam, lParam); + return DefWindowProcA(hwnd, uMsg, wParam, lParam); + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +void registerClass(HINSTANCE hInstance) { + WNDCLASS wc = {}; + + wc.lpfnWndProc = Win32Window::WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + + RegisterClass(&wc); +} + +void Win32Window::createWindow() { + if (!s_windowInit) { + registerClass(m_Instance); + s_windowInit = true; + } + + m_Window = CreateWindowEx(0, CLASS_NAME, m_Specification.title.c_str(), + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, + m_Instance, this); + + m_IsOpen = m_Window != nullptr; + if (m_Window == nullptr) { + throw std::runtime_error("Failed to create window."); + } + resize(m_Specification.width, m_Specification.height); + ShowWindow(m_Window, SW_NORMAL); +} +void Win32Window::handleEvent(HWND, UINT message, WPARAM wParam, + LPARAM lParam) { + switch (message) { + case WM_MOVE: { + m_WindowPosition.x = LOWORD(lParam); + m_WindowPosition.y = HIWORD(lParam); + break; + } + case WM_SIZE: { + m_Specification.width = LOWORD(lParam); + m_Specification.height = HIWORD(lParam); + break; + } + case WM_KEYDOWN: + case WM_KEYUP: { + uint32_t scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + if (scancode == 0) + scancode = MapVirtualKeyW(static_cast(wParam), MAPVK_VK_TO_VSC); + + switch (scancode) { + case 0x54: scancode = 0x137; break; + case 0x146: scancode = 0x45; break; + case 0x136: scancode = 0x36; break; + default: break; + } + UpdateKey(scancode, message == WM_KEYDOWN); + break; + } + case WM_MOUSEMOVE: { + // TODO: Handle MouseMove + Point rect(static_cast(GET_X_LPARAM(lParam)), + static_cast(GET_Y_LPARAM(lParam))); + break; + } + case WM_CLOSE: { + this->m_IsOpen = false; + break; + } + case WM_DESTROY: PostQuitMessage(0); + default: break; + } +} + +} // namespace VUI::Windows \ No newline at end of file diff --git a/Source/PlatformDetection.h b/Source/PlatformDetection.h new file mode 100644 index 0000000..15f1d1f --- /dev/null +++ b/Source/PlatformDetection.h @@ -0,0 +1,25 @@ +#pragma once +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#define VUI_WINDOWS 1 +#elif __APPLE__ +#include +#define VUI_APPLE 1 +#elif __ANDROID__ +#define VUI_ANDROID 1 +#elif __linux__ +#define VUI_LINUX 1 +#elif __unix__ // all unices not caught above +#define VUI_UNIX 1 +#elif defined(_POSIX_VERSION) +#define VUI_POSIX 1 +#else +#error "Unknown compiler" +#endif + +#if defined(VUI_LINUX) +#define VUI_OS_VALUE(l, w, m) l +#elif defined(VUI_WINDOWS) +#define VUI_OS_VALUE(l, w, m) w +#elif defined(VUI_APPLE) +#define VUI_OS_VALUE(l, w, m) m +#endif \ No newline at end of file diff --git a/Source/Types/CMakeLists.txt b/Source/Types/CMakeLists.txt new file mode 100644 index 0000000..fee31ce --- /dev/null +++ b/Source/Types/CMakeLists.txt @@ -0,0 +1,4 @@ +add_module_sources( + Geometry.cppm + Ref.cppm +) \ No newline at end of file diff --git a/Source/Types/Geometry.cppm b/Source/Types/Geometry.cppm new file mode 100644 index 0000000..eee613d --- /dev/null +++ b/Source/Types/Geometry.cppm @@ -0,0 +1,45 @@ +module; +#include + +export module VUI:Geometry; + +namespace VUI { +export struct Point { + float x{0}; + float y{0}; +}; + +export struct UPoint { + uint32_t x{0}; + uint32_t y{0}; +}; + +export struct URect { + uint32_t x{0}; + uint32_t y{0}; + uint32_t width{0}; + uint32_t height{0}; +}; + +export struct Rect { + float x{0}; + float y{0}; + float width{0}; + float height{0}; +}; + +// replace with glm +export struct Vec3 { + float x{0}; + float y{0}; + float z{0}; +}; + +export struct Vec4 { + float x{0}; + float y{0}; + float z{0}; + float w{0}; +}; + +} // namespace VUI \ No newline at end of file diff --git a/Source/Types/Ref.cppm b/Source/Types/Ref.cppm new file mode 100644 index 0000000..1952f0f --- /dev/null +++ b/Source/Types/Ref.cppm @@ -0,0 +1,137 @@ +module; +#include +#include + +export module VUI:Ref; + +namespace VUI { + +export struct RefCounted { + virtual ~RefCounted() = default; + + void incrementRefCount() const { ++m_RefCount; } + void decrementRefCount() const { --m_RefCount; } + + [[nodiscard]] std::uint32_t getRefCount() const { return m_RefCount.load(); } + +private: + mutable std::atomic m_RefCount = 0; +}; + +template +concept IsRefCounted = std::is_base_of_v; + +export template class Ref { +public: + static_assert(IsRefCounted, "Should be RefCounted"); + Ref() : m_Instance(nullptr) {} + + explicit Ref(std::nullptr_t) : m_Instance(nullptr) {} + + explicit Ref(T *instance) : m_Instance(instance) { incRef(); } + + template explicit Ref(const Ref &other) { + m_Instance = static_cast(other.m_Instance); + incRef(); + } + + template explicit Ref(Ref &&other) { + m_Instance = static_cast(other.m_Instance); + other.m_Instance = nullptr; + } + + ~Ref() { decRef(); } + + Ref(const Ref &other) : m_Instance(other.m_Instance) { incRef(); } + + Ref &operator=(std::nullptr_t) { + decRef(); + m_Instance = nullptr; + return *this; + } + + Ref &operator=(const Ref &other) { + if (this == &other) + return *this; + + other.incRef(); + decRef(); + + m_Instance = other.m_Instance; + return *this; + } + + template Ref &operator=(const Ref &other) { + other.incRef(); + decRef(); + + m_Instance = other.m_Instance; + return *this; + } + + template Ref &operator=(Ref &&other) { + decRef(); + + m_Instance = other.m_Instance; + other.m_Instance = nullptr; + return *this; + } + + operator bool() { return m_Instance != nullptr; } + operator bool() const { return m_Instance != nullptr; } + + T *operator->() { return m_Instance; } + const T *operator->() const { return m_Instance; } + + T &operator*() { return *m_Instance; } + const T &operator*() const { return *m_Instance; } + + T *value() { return m_Instance; } + const T *value() const { return m_Instance; } + + void reset(T *instance = nullptr) { + decRef(); + m_Instance = instance; + } + + template Ref as() const { return Ref(*this); } + + template static Ref Create(Args &&...args) { + return Ref(new T(std::forward(args)...)); + } + + bool operator==(const Ref &other) const { + return m_Instance == other.m_Instance; + } + + bool operator!=(const Ref &other) const { return !(*this == other); } + + bool equals(const Ref &other) { + if (!m_Instance || !other.m_Instance) + return false; + + return m_Instance == other.m_Instance; + } + +private: + void incRef() const { + if (m_Instance) { + m_Instance->incrementRefCount(); + } + } + + void decRef() const { + if (m_Instance) { + m_Instance->decrementRefCount(); + + if (m_Instance->getRefCount() == 0) { + delete m_Instance; + m_Instance = nullptr; + } + } + } + + template friend class Ref; + mutable T *m_Instance; +}; +} // namespace VUI \ No newline at end of file diff --git a/Source/VUI.cppm b/Source/VUI.cppm new file mode 100644 index 0000000..31e78b7 --- /dev/null +++ b/Source/VUI.cppm @@ -0,0 +1,15 @@ +export module VUI; + +// Core +// Window +export import :Time; +export import :Window; +export import :WindowManager; + +// Input Handling +export import :Input; +export import :InputCodes; + +// Types +export import :Geometry; +export import :Ref; \ No newline at end of file diff --git a/main.cppm b/main.cppm new file mode 100644 index 0000000..5ec8162 --- /dev/null +++ b/main.cppm @@ -0,0 +1,16 @@ +import VUI; + +#include +#include + +struct Me final : VUI::RefCounted { + +}; + +int main() { + VUI::WindowManager::Create(1280, 720, "VUI"); + while (VUI::WindowManager::HasOpenWindows()) { + VUI::WindowManager::Update(); + } + return 0; +} \ No newline at end of file diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..303e8f5 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,14 @@ +{ + "default-registry": { + "kind": "git", + "baseline": "47364fbc300756f64f7876b549d9422d5f3ec0d3", + "repository": "https://github.com/microsoft/vcpkg" + }, + "registries": [ + { + "kind": "artifact", + "location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip", + "name": "microsoft" + } + ] +} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..83996ba --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "vui", + "version": "2025.1.1", + "dependencies": [ + "vulkan", + "vulkan-memory-allocator" + ] +}