🎉 begin project

This commit is contained in:
Maurice Grönwoldt 2025-05-29 13:21:19 +02:00
commit 8da6ddc689
29 changed files with 1261 additions and 0 deletions

20
.clang-format Normal file
View file

@ -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

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
*.h text eol=lf
*.cpp text eol=lf
*.glsl text eol=lf
*.ts text eol=lf
*.js text eol=lf

21
.gitignore vendored Normal file
View file

@ -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

25
CMake/CMakeLists.txt Normal file
View file

@ -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()

34
CMakeLists.txt Normal file
View file

@ -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)

112
CMakePresets.json Normal file
View file

@ -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"
]
}
]
}

19
LICENSE Normal file
View file

@ -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.

74
README.md Normal file
View file

@ -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
Heres a simple example of using VUI:
```cpp
#include <vui/VUI.h>
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.

5
Source/CMakeLists.txt Normal file
View file

@ -0,0 +1,5 @@
add_subdirectory(Core)
add_subdirectory(Input)
add_subdirectory(Platform)
add_subdirectory(Types)
add_module_sources(VUI.cppm)

View file

@ -0,0 +1,5 @@
add_module_sources(
VUI.cppm
Window.cppm
WindowManager.cppm
)

36
Source/Core/VUI.cppm Normal file
View file

@ -0,0 +1,36 @@
module;
#include <cstdint>
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<double>(GetTimerValue() - s_TimerOffset) /
static_cast<double>(GetTimerFrequency());
};
export uint64_t GetTimeNs() {
return static_cast<uint64_t>(
static_cast<double>(GetTimerValue() - s_TimerOffset) * 1e9 /
static_cast<double>(GetTimerFrequency()));
}
export void SetTime(const uint64_t time) {
const auto dTimerValue = static_cast<double>(GetTimerValue());
const auto dTime = static_cast<double>(time);
const auto dFrequency = static_cast<double>(GetTimerFrequency());
s_TimerOffset = static_cast<uint64_t>(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

54
Source/Core/Window.cppm Normal file
View file

@ -0,0 +1,54 @@
module;
#include <cstdint>
#include <string>
#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

View file

@ -0,0 +1,91 @@
module;
#include <cstdint>
#include <string>
#include <vector>
export module VUI:WindowManager;
import :Window;
import :PlatformWindow;
import :Time;
namespace VUI {
export struct WindowManager {
static Ref<Window> Create(uint32_t width, uint32_t height,
const std::string &title);
static Ref<Window> Create(const WindowSpecification &);
static void Manage(const Ref<Window> &window);
static void Update();
static void Close(Ref<Window> &);
static void CloseByTitle(const std::string &title);
static void CloseAll();
static bool HasOpenWindows();
protected:
std::vector<Ref<Window>> m_Windows{};
friend Window;
};
static WindowManager s_WindowManager = {};
Ref<Window> WindowManager::Create(uint32_t width, uint32_t height,
const std::string &title) {
return Create(WindowSpecification{width, height, title});
}
Ref<Window> 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> &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> &window) { return !window->isOpen(); });
}
void WindowManager::Close(Ref<Window> &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> &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

View file

@ -0,0 +1,5 @@
add_module_sources(
InputCodes.cppm
Input.cppm
Keys.cppm
)

59
Source/Input/Input.cppm Normal file
View file

@ -0,0 +1,59 @@
module;
#include <array>
#include <cassert>
#include <utility>
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<uint8_t, std::to_underlying(KeyCodes::KEYLAST)> s_Keys{};
static constexpr std::array<KeyCodes, Keys> 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

View file

@ -0,0 +1,124 @@
module;
#include <cstdint>
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

120
Source/Input/Keys.cppm Normal file
View file

@ -0,0 +1,120 @@
module;
#include "../PlatformDetection.h"
#include <array>
#include <utility>
export module VUI:Keys;
import :InputCodes;
namespace VUI {
export constexpr int Keys = VUI_OS_VALUE(136, 0x15C + 1, 128);
export constexpr std::array<KeyCodes, Keys> InitKeys() {
auto keys = std::array<KeyCodes, Keys>{};
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

View file

@ -0,0 +1,5 @@
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_subdirectory(Linux)
elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_subdirectory(Windows)
endif ()

View file

@ -0,0 +1,4 @@
add_module_sources(
Time.cppm
Window.cppm
)

View file

@ -0,0 +1,19 @@
module;
#include <cstdint>
#include "Windows.h"
export module VUI:TimeInternal;
namespace VUI {
export uint64_t GetTimerValue() {
uint64_t value{0};
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&value));
return value;
}
export uint64_t GetTimerFrequency() {
uint64_t frequency{0};
QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER *>(&frequency));
return frequency;
}
} // namespace VUI

View file

@ -0,0 +1,160 @@
module;
#include <Windows.h>
#include <windowsx.h>
#include <stdexcept>
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<Window> CreateWindowRef() { return Ref<Window>(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<int>(m_WindowPosition.x),
static_cast<int>(m_WindowPosition.y),
static_cast<int>(m_Specification.width),
static_cast<int>(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<CREATESTRUCT *>(lParam);
SetWindowLongPtr(hwnd, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(
static_cast<Win32Window *>(pCreate->lpCreateParams)));
return TRUE;
}
auto impl =
reinterpret_cast<Win32Window *>(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<UINT>(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<float>(GET_X_LPARAM(lParam)),
static_cast<float>(GET_Y_LPARAM(lParam)));
break;
}
case WM_CLOSE: {
this->m_IsOpen = false;
break;
}
case WM_DESTROY: PostQuitMessage(0);
default: break;
}
}
} // namespace VUI::Windows

View file

@ -0,0 +1,25 @@
#pragma once
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define VUI_WINDOWS 1
#elif __APPLE__
#include <TargetConditionals.h>
#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

View file

@ -0,0 +1,4 @@
add_module_sources(
Geometry.cppm
Ref.cppm
)

View file

@ -0,0 +1,45 @@
module;
#include <cstdint>
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

137
Source/Types/Ref.cppm Normal file
View file

@ -0,0 +1,137 @@
module;
#include <atomic>
#include <cstdint>
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<uint32_t> m_RefCount = 0;
};
template <class T>
concept IsRefCounted = std::is_base_of_v<RefCounted, T>;
export template <typename T> class Ref {
public:
static_assert(IsRefCounted<T>, "Should be RefCounted");
Ref() : m_Instance(nullptr) {}
explicit Ref(std::nullptr_t) : m_Instance(nullptr) {}
explicit Ref(T *instance) : m_Instance(instance) { incRef(); }
template <typename T2> explicit Ref(const Ref<T2> &other) {
m_Instance = static_cast<T *>(other.m_Instance);
incRef();
}
template <typename T2> explicit Ref(Ref<T2> &&other) {
m_Instance = static_cast<T *>(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 <typename T2> Ref &operator=(const Ref<T2> &other) {
other.incRef();
decRef();
m_Instance = other.m_Instance;
return *this;
}
template <typename T2> Ref &operator=(Ref<T2> &&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 <typename T2> Ref<T2> as() const { return Ref<T2>(*this); }
template <typename... Args> static Ref Create(Args &&...args) {
return Ref(new T(std::forward<Args>(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 <typename T2> friend class Ref;
mutable T *m_Instance;
};
} // namespace VUI

15
Source/VUI.cppm Normal file
View file

@ -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;

16
main.cppm Normal file
View file

@ -0,0 +1,16 @@
import VUI;
#include <iostream>
#include <ostream>
struct Me final : VUI::RefCounted {
};
int main() {
VUI::WindowManager::Create(1280, 720, "VUI");
while (VUI::WindowManager::HasOpenWindows()) {
VUI::WindowManager::Update();
}
return 0;
}

14
vcpkg-configuration.json Normal file
View file

@ -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"
}
]
}

8
vcpkg.json Normal file
View file

@ -0,0 +1,8 @@
{
"name": "vui",
"version": "2025.1.1",
"dependencies": [
"vulkan",
"vulkan-memory-allocator"
]
}