Compare commits

...

193 commits

Author SHA1 Message Date
1b1d3797a0 Added scaffolding work for the OQuery Lexer 2026-06-22 11:34:49 +02:00
bc1b5b5e53 Started work on Database code 2026-06-22 06:52:04 +02:00
2788a63495 Fixed include bug 2026-06-21 05:29:16 +02:00
4a4f5a0748 Added uncapped mode to ostd::StepTimer 2026-06-16 07:22:54 +02:00
08daf0cea1 Added new functionality to Serial class 2026-06-08 06:24:43 +02:00
068adbe282 Added TextEdit::HexadecimalFilter 2026-06-02 06:16:51 +02:00
ddcc74f176 Added new ostd::Serial class 2026-05-17 14:36:33 +02:00
5f87625d23 Sync push 2026-05-17 06:46:17 +02:00
14cb854387 First draft of new ostd::Serial class 2026-05-16 13:00:47 +02:00
72f2a0285f Merge branch 'main' of github.com:OmniaX-dev/OmniaFramework 2026-05-16 04:20:15 +02:00
5318ae6b41 Fixed install script on MacOS 2026-05-16 04:19:19 +02:00
28380f94d9 Added Drag and Drop for Color Picker 2026-05-15 08:42:06 +02:00
6fb05f77ab Bug fixes 2026-05-15 08:29:56 +02:00
fb9876ad22 Added ColorPicker Widget 2026-05-13 10:04:35 +02:00
999cf30c21 Sync push 2026-05-11 21:10:41 +02:00
04e595ca9c Polished TextEdit widget 2026-05-08 07:17:51 +02:00
d5bf65465a Added Undo/Redo to TextBuffer 2026-05-08 01:46:57 +02:00
abc04ec132 Actually fixed pointer conversion bug 2026-05-08 00:58:46 +02:00
3ca1c3314d Fixed pointer conversion bug 2026-05-08 00:53:32 +02:00
ede7db738c Finished core TextEdit widget 2026-05-07 17:50:12 +02:00
a3eb4ba3cc Started work on TextEdit widget 2026-05-07 06:39:43 +02:00
655d7b1f98 Started work on text widgets 2026-05-05 06:39:46 +02:00
424574a2e6 Added caching for string dimensions in BasicRenderer2D 2026-05-04 19:51:15 +02:00
012f26f5f5 Sync push 2026-05-04 06:43:49 +02:00
d6fc5d3303 Sync push 2026-05-04 06:42:27 +02:00
9cfa49d691 Finished function-style macros functionality in Stylesheet 2026-05-04 05:35:22 +02:00
13fe2bb05b Sync push 2026-05-03 22:13:38 +02:00
fa638d2556 Added macros to stylesheet 2026-05-03 13:49:30 +02:00
203a305633 Added FocusManager 2026-05-03 06:49:44 +02:00
67a1dd605d Updated copyright notice 2026-05-03 02:13:46 +02:00
16e78d07b1 Various bugfixes 2026-05-01 21:17:23 +02:00
796b03f08c Added TreeView widget 2026-05-01 05:06:21 +02:00
2de45908a9 Added Context Menu ANimation 2026-04-30 20:36:45 +02:00
68bf336530 Added ComboBox widget 2026-04-28 13:02:14 +02:00
4fb5e24bb9 Started work on ComboBox 2026-04-28 06:48:47 +02:00
3663ac69fd Added layouts 2026-04-28 06:00:33 +02:00
a7265febdf Fixed scaling related bug 2026-04-27 07:21:23 +02:00
36c9c62e6d Addressed a few bugs 2026-04-26 12:38:55 +02:00
858ee269bd Added StatusBar 2026-04-26 06:47:48 +02:00
74c14791ee Finished first stage of ToolBar 2026-04-26 03:28:40 +02:00
c47782280b Sync push 2026-04-25 18:11:48 +02:00
f00f30004c Started work on ToolBar 2026-04-25 06:08:24 +02:00
d8e5b2e9be Added MenuBar 2026-04-25 03:28:28 +02:00
22ed3c0744 Added Event Enablers to Widget class 2026-04-24 23:48:09 +02:00
812260b969 Updated TODO 2026-04-24 13:19:29 +02:00
b38874c78a Finished Context Menu 2026-04-24 13:12:52 +02:00
eb27fa42d1 Polished first stage of Context Menu 2026-04-24 11:29:05 +02:00
99886e8c62 Started work on Context Menu 2026-04-24 06:43:26 +02:00
4bbf6a9b98 Added ostd::StaticHashMap 2026-04-24 04:42:12 +02:00
94e298394c Fixed a ListView dangling pointer bug 2026-04-24 03:01:43 +02:00
183df32659 Added ListView widget 2026-04-23 13:41:09 +02:00
69e7fb7f1d Started work on ListView widget 2026-04-23 03:28:47 +02:00
19f64f6af3 Added Slider widget 2026-04-23 00:46:10 +02:00
327582c463 Added ProgressBar widget 2026-04-22 18:34:47 +02:00
1c9d0b5ab9 Added RadioButton widget + RadioButtonGroup 2026-04-22 17:05:25 +02:00
ffe9d7ca1a Added tooltips 2026-04-22 05:34:59 +02:00
83f4687198 Added save/open file dialogs 2026-04-21 15:28:39 +02:00
8c174fb863 Added Image/Animation to Button widget 2026-04-21 12:36:35 +02:00
32ed5099f1 Updated TODO 2026-04-21 07:17:52 +02:00
cda204aec9 Added TabPanel widget 2026-04-21 05:46:45 +02:00
5491e5d4b3 Started work on TabPanel 2026-04-20 14:45:40 +02:00
27feac8fb7 Implemented ImageLabel widget 2026-04-20 08:50:38 +02:00
67a2b5f10e Added anim() to stylesheet + refactored Widget.hpp 2026-04-20 06:49:00 +02:00
0cf7478c24 Added a null texture slot to FontGlyphAtlas 2026-04-19 22:15:51 +02:00
a20e5849f5 Fixed Linux specific rendering bug 2026-04-19 11:26:33 +02:00
63d00adf96 Added ScrollableWidget class 2026-04-19 06:50:42 +02:00
510ce50684 Added Horizontal scrollbar 2026-04-19 05:42:03 +02:00
891e17948a Started work on horizontal scrollbar 2026-04-18 22:08:05 +02:00
d8475ecb48 Added smooth scrolling + horizontal scrolling support 2026-04-18 08:26:45 +02:00
d618de0981 Sync push 2026-04-18 07:27:46 +02:00
e2287c06f0 Added scrolwheel delta to mouse event 2026-04-18 05:06:44 +02:00
ad7b3e6bed Fixed a MacOS bug in the stylesheet parser 2026-04-18 04:54:43 +02:00
ed3c21bd36 Reworked the Animation class 2026-04-17 17:04:19 +02:00
91fc3e75b9 Added gradient and file() functions to stylesheet 2026-04-17 08:11:52 +02:00
7e37210e82 Sync push 2026-04-17 06:44:55 +02:00
d521c609b3 Added gradient functionality to BasicRenderer2D 2026-04-17 05:59:05 +02:00
44354a80c2 Made VerticalScrollbar a top-most widget 2026-04-17 03:55:26 +02:00
7d8a5be286 Added scale factor to WindowCore 2026-04-17 03:17:34 +02:00
fde961c78e Added scrollbar widget 2026-04-16 21:01:15 +02:00
777124579f Added nested selectors to stylesheets 2026-04-16 17:44:19 +02:00
40b51bf3dc Added scroll functionality to Panel widget 2026-04-16 15:32:49 +02:00
8128baa107 Added default theme 2026-04-16 00:13:18 +02:00
959034df62 Added hide/show functionality to widgets 2026-04-15 23:01:42 +02:00
84c4c2eb88 Added Panel title 2026-04-15 21:00:39 +02:00
e8d13b931e Added triangle drawing functions to BasicRenderer2D 2026-04-15 17:15:34 +02:00
10ecd04747 A bit more refactoring 2026-04-15 16:50:48 +02:00
6ecf220891 Switched to Rust-style numeric type names + other refactors 2026-04-15 16:18:16 +02:00
faafd67015 Fixed a few bugs 2026-04-14 13:03:01 +02:00
17fdf08a20 Implemented batched text rendering 2026-04-14 06:46:31 +02:00
ceb25e8e38 Fixed a Windows bug 2026-04-14 01:23:11 +02:00
c9ea18d586 Sync push 2026-04-14 00:58:05 +02:00
64cd4598f2 Implemented Font GLyph Atlas overflow 2026-04-13 15:11:25 +02:00
20c40d87e4 Started work on Font Glyph Atlas 2026-04-13 06:48:19 +02:00
15d9d1265f Implemented new BasicRenderer2D with native SFML3 (and remove SDL3_gfx
dependency)
2026-04-12 14:48:24 +02:00
9a77ec20bc Added CheckBox widget 2026-04-12 06:50:09 +02:00
b21717e0e3 Implemended scrollwheel events for widgets 2026-04-12 05:38:03 +02:00
fb02eed8cc Added some default variables for gui window stylesheet 2026-04-10 05:43:44 +02:00
0407f55071 Sync push 2026-04-09 21:33:09 +02:00
37ded4be60 Sync push 2026-04-08 11:01:06 +02:00
c28829bd72 Added const variables to stylesheet 2026-04-07 14:00:37 +02:00
da0d6e0c19 Sync push 2026-04-07 11:33:03 +02:00
c98dd97670 Added File/Text Drag and Drop from the OS to the widgets 2026-04-07 04:16:39 +02:00
d4b4df1e51 Added drag and drop functionality for Widgets 2026-04-06 14:39:51 +02:00
260c7054bc Added Basic Panel widget 2026-04-06 06:48:33 +02:00
c695c0ad85 Started work on Panel Widget 2026-04-05 14:12:16 +02:00
25e06eba0a Added __i_stringeable interface to streamline the string conversion
protocol
2026-04-05 08:41:20 +02:00
fcb90c0916 Added functionality to allow multiple IDs per Widget 2026-04-05 06:48:58 +02:00
234aa60ddd Added more qualifiers to the Stylesheet system 2026-04-05 04:52:16 +02:00
aebf9c1d14 Fixed .clangd file 2026-04-05 02:37:17 +02:00
91bbfaf36f Added selector groups to stylesheets 2026-04-04 14:13:58 +02:00
39c9798760 Extended Stylesheet theming functionality 2026-04-04 12:56:56 +02:00
f33b6de559 Added the ability to load Gui themes from a file 2026-04-04 07:16:24 +02:00
45c36339ec Added ogfx::KeyCode struct 2026-04-04 03:10:53 +02:00
41abab36aa Streamlined the Signal System 2026-04-04 01:38:44 +02:00
Sylar
92089ed177 Fixed build on MacOS 2026-04-03 06:10:38 +02:00
f70f66608a cmake modifications for macos 2026-04-03 03:36:59 +02:00
1e22cf8de6 Fixed Windows related problems 2026-04-03 03:10:03 +02:00
2764741cc3 Merge remote-tracking branch 'origin/main' 2026-04-03 00:32:30 +02:00
b092d5a265 Older changes 2026-04-03 00:30:45 +02:00
924da0dba6 Started work on gui Widget system 2026-04-02 06:19:20 +02:00
f0f644102b Added centralized SDL initialization 2026-04-01 10:20:33 +02:00
23e69bfdfc Added possibility to load fonts from memory + added UbuntuMonoRegular as
default embedded font
2026-03-31 07:32:37 +02:00
1d071786d5 Reworked the ogfx Window subsystem 2026-03-31 04:55:17 +02:00
2bec8e1b39 Migrated SDL2 to SDL3 2026-03-31 02:31:51 +02:00
6f1899b1a6 Restructured the ogfx module 2026-03-31 00:05:59 +02:00
01a77d722d Implemented cursor functionality into ostd::ConsoleOutputHandler 2026-03-23 04:50:13 +01:00
f0bd0c5d09 Fixed coordinate order in ostd::BasicConsole::setConsoleCursorPosition()
function
2026-03-22 18:50:58 +01:00
7659dd6846 added ostd::BasicConsole::getConsoleCursorPosition() function 2026-03-22 18:39:55 +01:00
77c2cff1ca Renamed data_types sub-folder to data 2026-03-20 23:21:32 +01:00
e04ae605a0 Big refactor, removed ostd::Utils class and split all functionality into
more specific files
2026-03-20 22:44:10 +01:00
5e89ce586c Expanded ostd::FileSystem functionality 2026-03-20 21:20:18 +01:00
c1011f1233 Added licence text to <FileSystem.hpp> 2026-03-20 01:06:28 +01:00
b0b660f5a7 moved FileSystem functionality headers from <Utils.hpp> to dedicated
<FileSystem.hpp>
2026-03-20 01:05:35 +01:00
367ad5c92a Added ostd::AsyncJob class 2026-03-10 00:54:14 +01:00
42a2ee330b Added ostd::JsonFile::isLoaded method 2026-03-09 00:36:27 +01:00
9ce48e6e3f Added ogfx::WindowBase::setIcon function 2026-03-08 23:31:26 +01:00
0556e6806c Fixed release script 2026-03-08 23:13:56 +01:00
a110b0912f [0.3.2000] - Big file restructure 2026-03-07 17:13:40 +01:00
1b09fc172f Fixed an #include directive in <PixelRenderer.hpp> 2026-03-05 22:56:00 +01:00
bf70b4dbed Added PixelRenderer::Font class 2026-03-05 22:07:12 +01:00
5a7a90c395 Ported RawTextRenderer from DragonVM (refactored to
PixelRenderer::TextRenderer)
2026-03-05 21:53:05 +01:00
26d32eb059 Ported PixelRenderer class from DragonVM 2026-03-05 21:45:05 +01:00
8fea21fb58 Reworked ogfx::WindowBase update loop logic 2026-01-21 06:46:20 +01:00
1583e72849 Fixed a bug in ostd::Utils::printByteStream where the last ascii line
wasn't printed
2026-01-21 05:39:19 +01:00
df81456588 Sync push 2026-01-09 04:32:54 +01:00
025d089bcb Added ogfx::WindowBaseOutputHandler class 2026-01-06 06:23:07 +01:00
3990a50b00 Added ostd::OutputHandlerBase::tab() virtual function 2026-01-05 22:57:37 +01:00
1c7e2a974b Fixed a small bug in ostd::ConsoleColors 2026-01-05 22:54:08 +01:00
2416d2bc15 Fixed a small bug in ogfx::TTFRenderer that caused errors when more than
one instance of TTFRenderer was present
2026-01-05 20:45:50 +01:00
5878d69b5e Backported some utility functionality from the KeyLight project 2026-01-04 17:29:32 +01:00
53e578a0a6 Added ostd/Midi.hpp file + added midifile library as included dependency 2026-01-04 16:45:51 +01:00
f343c10018 Added ostd/Json.hpp file + added nlohmann Json library as included
dependency
2026-01-04 16:12:26 +01:00
Sylar
7f26511175 Sync push 2026-01-03 01:15:14 +01:00
020b11bf36 Fixed an include directive 2026-01-02 23:03:41 +01:00
Sylar
b17ddf6f7e Added MacOS support 2026-01-02 22:31:17 +01:00
b73a360893 Fixed a bug in ostd::Random that occurred when min > max 2025-11-12 13:29:55 +01:00
c1b86b5553 Added <ostd/Spline.hpp> class 2025-11-11 19:13:13 +01:00
339d077d50 Moved all time-related functionality in <ostd/Utils.hpp> to the new
<ostd/Time.hpp> header
2025-11-11 14:50:25 +01:00
09722f5990 Added Utils::getWorkingDirPath() function 2025-11-06 18:41:44 +01:00
bfa1ae048b Sync push 2025-11-02 19:17:23 +01:00
3a13b22b11 Added ability to read ostd::Timer without stopping it 2025-11-02 19:08:59 +01:00
af3ad601dc Fixed bug in Geometry.hpp 2025-11-01 01:08:15 +01:00
b49e2d9543 Removed bad #include directive in Utils.cpp 2025-10-29 12:55:13 +01:00
371bc25c8c Fixed CMakeLists.txt bug 2025-10-29 02:36:26 +01:00
36ac11dd31 New README File + consolidated build script 2025-10-28 11:50:07 +01:00
12b2af3a1c Changed Utils::md5 function 2025-09-07 08:04:14 +02:00
03b1d7b5f7 Added ogfx::Image class 2024-10-08 23:42:33 +02:00
055aa4f01c Added ogfx::WindowBase::enableResizable function 2024-09-30 12:03:01 +02:00
a622db4b1a Added color mode parameter to ostd::Color::asInteger() 2024-09-15 09:05:03 +02:00
f92b90ae9a Updated install instructions for Ubuntu 2024-09-09 03:13:21 +02:00
76cfbf9341 Updated readme file 2024-09-08 06:38:46 +02:00
6a93250fe7 Added install script 2024-09-06 04:20:26 +02:00
d66dd33d29 Added install script 2024-09-06 04:19:45 +02:00
efe39a6e3d Added build instructions for Fedora 2024-09-06 02:56:19 +02:00
af2eff4ce3 Added ostd::Utils::md5 function 2024-09-05 00:41:50 +02:00
9b7873eda1 Added multi-threaded compilation to make_and_run script 2024-09-02 16:34:50 +02:00
db0fa0b67c Added multi-threaded compilation to compile script 2024-09-02 16:27:02 +02:00
9892dc7745 Fixed a bug in CONVERT_1D_2D() macro (in Defines.hpp) 2024-09-02 09:58:37 +02:00
d81b8a1d99 Fixed a trim-related bug in ostd::String::tokenize 2024-09-02 05:54:44 +02:00
b50bd53c0d Fixed bug with Windows compilation 2024-09-02 02:33:41 +02:00
c298d7614b Added ogfx::gui::RawTextInput 2024-08-31 14:11:47 +02:00
b9e85f20f5 Sync Push 2024-07-13 17:28:35 +02:00
d63db08081 Added ostd::Utils::getHomeDirPath function to <FileSystem.hpp> 2024-05-08 12:47:53 +02:00
7ca2fbf3f1 Added timer functions with less overhead to ostd::Timer 2024-03-30 23:30:39 +01:00
e813d9d1a6 Fixed README 2024-03-29 22:52:10 +01:00
d7fa13d875 Started work on new <ogfx> library 2024-03-29 22:36:16 +01:00
1663e6db7c [0.1.1823] - Added more Builtin Signals 2024-01-26 11:34:57 +01:00
4b1a8598f6 Fixed a bug in ostd::ConsoleOutputHandler::flush() method 2024-01-13 00:41:39 +01:00
e8849a4b49 [0.1.1818] - Added KeyboardController 2024-01-12 17:28:45 +01:00
e520c829ef Actually removed Release folder 2023-12-30 17:14:18 +01:00
052e41c4c9 Removed Release folder 2023-12-30 17:12:44 +01:00
d07117a102 [0.1.1807] - New ostd::String class + refactor 2023-12-30 17:05:31 +01:00
925d3dc5df Better build script 2023-12-28 01:33:40 +01:00
255 changed files with 92525 additions and 5324 deletions

4
.clangd Normal file
View file

@ -0,0 +1,4 @@
CompileFlags:
CompilationDatabase: bin
Diagnostics:
MissingIncludes: None

7
.gitignore vendored
View file

@ -1,5 +1,2 @@
bin
Debug
Build
tools/win-release-tools
win-release
.cache
bin/

View file

@ -1,38 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${default}",
"${workspaceRoot}/src/vendor/GLAD",
"${workspaceRoot}/src/vendor",
"${workspaceRoot}/src/ostd",
"${workspaceRoot}/src"
],
"defines": [],
"compilerPath": "/usr/bin/clang++",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "linux-clang-x64"
//"compileCommands": "${workspaceFolder}/compile_commands.json"
},
{
"name": "Windows",
"includePath": [
"${default}",
"${workspaceRoot}/src/vendor/GLAD",
"${workspaceRoot}/src/vendor",
"${workspaceRoot}/src/ostd",
"${workspaceRoot}/src"
],
"defines": [],
"compilerPath": "C:\\msys64\\ucrt64\\bin\\clang++",
"cStandard": "c17",
"cppStandard": "c++20",
"intelliSenseMode": "windows-clang-x64"
//"compileCommands": "${workspaceFolder}/compile_commands.json"
}
],
"version": 4
}

143
.vscode/settings.json vendored
View file

@ -1,143 +0,0 @@
{
"C_Cpp.errorSquiggles": "Enabled",
"files.associations": {
"functional": "cpp",
"thread": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"csetjmp": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"hash_map": "cpp",
"barrier": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"cinttypes": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"cstdint": "cpp",
"cuchar": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"latch": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"scoped_allocator": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"syncstream": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp",
"*.rh": "cpp",
"hash_set": "cpp",
"strstream": "cpp",
"expected": "cpp",
"spanstream": "cpp",
"stacktrace": "cpp",
"*.ipp": "cpp",
"format": "cpp",
"stdfloat": "cpp"
},
"workbench.colorCustomizations": {
"editor.background": "#131313",
"tab.hoverBackground": "#111",
"tab.inactiveBackground": "#000",
"tab.activeBackground": "#1b1b1b",
"tab.activeBorder": "#bb5400",
"editorIndentGuide.background": "#252525",
"editorWhitespace.foreground": "#683700",
"editor.lineHighlightBorder": "#1a1a1a",
"editor.lineHighlightBackground": "#490050",
"editorBracketMatch.background": "#444",
"editorBracketMatch.border": "#b80318",
"editor.selectionBackground": "#3b3b3b",
"editor.selectionHighlightBackground": "#702900",
"statusBar.background": "#2e2850",
"minimap.background" : "#1b1b1b",
"sideBar.background": "#1b1b1b"
},
"workbench.editor.wrapTabs": true,
"todohighlight.isEnable": true,
"todohighlight.keywords": [
{
"text": "TODO:",
"color": "red",
"backgroundColor": "rgba(0, 0, 0, 0)"
},
{
"text": "INFO:",
"color": "blue",
"backgroundColor": "rgba(0, 0, 0, 0)"
},
{
"text": "HACK:",
"color": "purple",
"backgroundColor": "rgba(0, 0, 0, 0)"
}
],
"editor.insertSpaces": false,
"workbench.list.openMode": "doubleClick"
}

29
.zed/tasks.json Normal file
View file

@ -0,0 +1,29 @@
[
{
"label": "build",
"command": "./build",
"args": ["debug"],
"cwd": "$ZED_WORKTREE_ROOT",
"use_new_terminal": false,
"allow_concurrent_runs": false,
"reveal": "always",
},
{
"label": "build run",
"command": "./build",
"args": ["run"],
"cwd": "$ZED_WORKTREE_ROOT",
"use_new_terminal": false,
"allow_concurrent_runs": false,
"reveal": "always",
},
{
"label": "build dbg",
"command": "./build",
"args": ["dbg"],
"cwd": "$ZED_WORKTREE_ROOT",
"use_new_terminal": false,
"allow_concurrent_runs": false,
"reveal": "always",
},
]

View file

@ -2,77 +2,213 @@
#-----------------------------------------------------------------------------------------
set(PROJ_NAME omnia-framework)
set(MAJOR_VER 0)
set(MINOR_VER 1)
set(OMNIA_STD_LIB ostd)
set(RUN_CONFIG O_F_DEBUG)
set(MINOR_VER 3)
#-----------------------------------------------------------------------------------------
#Setup
#-----------------------------------------------------------------------------------------
set(CMAKE_CXX_COMPILER "clang++")
set(CMAKE_C_COMPILER "clang")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake_minimum_required(VERSION 3.18)
project(${PROJ_NAME} LANGUAGES C CXX)
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 20)
file(STRINGS "./build.nr" BUILD_NUMBER)
set(CMAKE_CXX_STANDARD 23)
file(STRINGS "./other/build.nr" BUILD_NUMBER)
#-----------------------------------------------------------------------------------------
message("** Building ${PROJ_NAME} ${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
message(STATUS "Building ${PROJ_NAME} ${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug Configuration")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
message(STATUS "Release Configuration")
endif()
#Sources
#-----------------------------------------------------------------------------------------
list(APPEND INCLUDE_DIRS
${CMAKE_CURRENT_LIST_DIR}/src
${CMAKE_CURRENT_LIST_DIR}/src/ostd
${CMAKE_CURRENT_LIST_DIR}/src/ogfx
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/nlohmann
)
list(APPEND OSTD_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/src/ostd/BaseObject.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Color.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Console.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Errors.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/File.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/FileSystem.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Geometry.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Logger.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Logic.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/OutputHandlers.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/PathFinder.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/QuadTree.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/ShuntingYard.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Signals.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Random.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Serial.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/SineWave.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/StringEditor.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/TextStyleParser.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/Utils.cpp
# MidiFile
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/Binasc.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/MidiEvent.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/MidiEventList.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/MidiFile.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/MidiMessage.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/vendor/midifile/Options.cpp
# data
${CMAKE_CURRENT_LIST_DIR}/src/ostd/data/BaseObject.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/data/Color.cpp
# io
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Console.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Errors.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/File.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/FileSystem.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Json.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Keyboard.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Logger.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Midi.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/OutputHandlers.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Serial.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Memory.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/io/Stylesheet.cpp
# math
${CMAKE_CURRENT_LIST_DIR}/src/ostd/math/ShuntingYard.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/math/Random.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/math/SineWave.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/math/Spline.cpp
# string
${CMAKE_CURRENT_LIST_DIR}/src/ostd/string/String.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/string/TextStyleParser.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/string/utf8.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/string/TextBuffer.cpp
# utils
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/Logic.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/Hash.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/PathFinder.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/QuadTree.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/Signals.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ostd/utils/Time.cpp
)
list(APPEND OGFX_SOURCE_FILES
# gui
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/RawTextInput.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Window.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/WindowOutputHandler.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/WidgetManager.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ContextMenu.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/MenuBar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/ToolBar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/Layout.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/FocusManager.cpp
# gui/widgets
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Widget.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Containers.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Label.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/RootWidget.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/CheckBox.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Scrollbar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Button.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/RadioButton.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/ProgressBar.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Slider.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/ComboBox.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/TreeView.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/Text.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/ColorPicker.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/gui/widgets/ColorPickerSkins.cpp
# render
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/BasicRenderer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/PixelRenderer.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/render/FontGlyphAtlas.cpp
# resources
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/resources/Image.cpp
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/resources/UbuntuTTF.cpp
# utils
${CMAKE_CURRENT_LIST_DIR}/src/ogfx/utils/Animation.cpp
)
list(APPEND TEST_SOURCE_FILES
# ${CMAKE_CURRENT_LIST_DIR}/src/test/GraphicsWindowTest.cpp
${CMAKE_CURRENT_LIST_DIR}/src/test/GuiTest.cpp
# ${CMAKE_CURRENT_LIST_DIR}/src/test/TextBufferUnitTest.cpp
)
#-----------------------------------------------------------------------------------------
#Targets
#-----------------------------------------------------------------------------------------
set(OMNIA_STD_LIB ostd)
set(OMNIA_GFX_LIB ogfx)
set(TEST_TARGET ostd_test)
add_library(${OMNIA_STD_LIB} SHARED ${OSTD_SOURCE_FILES})
add_library(${OMNIA_GFX_LIB} SHARED ${OGFX_SOURCE_FILES})
add_executable(${TEST_TARGET} ${TEST_SOURCE_FILES})
target_compile_definitions(${OMNIA_STD_LIB} PUBLIC ${RUN_CONFIG} BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
target_compile_definitions(${OMNIA_STD_LIB} PUBLIC BUILD_CONFIG="${CMAKE_BUILD_TYPE}" BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
target_compile_definitions(${OMNIA_GFX_LIB} PUBLIC BUILD_CONFIG="${CMAKE_BUILD_TYPE}" BUILD_NR=${BUILD_NUMBER} MAJ_V=${MAJOR_VER} MIN_V=${MINOR_VER} VERSION_STR="${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
target_include_directories(${OMNIA_STD_LIB} PUBLIC ${INCLUDE_DIRS})
target_include_directories(${OMNIA_GFX_LIB} PUBLIC ${INCLUDE_DIRS})
target_include_directories(${TEST_TARGET} PUBLIC ${INCLUDE_DIRS})
#TODO: Different flags for Release/Debug
add_compile_options(-O3 -m32 -MMD -MP -Wall -ggdb -fsanitize=address)
if (UNIX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='$ORIGIN'")
target_link_libraries(${OMNIA_STD_LIB} PUBLIC tinfo)
endif (UNIX)
if (WIN32)
set(MSYS2_UCRT64 "C:/msys64/ucrt64")
target_include_directories(${OMNIA_STD_LIB} PRIVATE
"${MSYS2_UCRT64}/include"
"${MSYS2_UCRT64}/include/c++/13.2.0"
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
)
set(MSYS2_UCRT64 "C:/msys64/ucrt64")
target_include_directories(${OMNIA_GFX_LIB} PRIVATE
"${MSYS2_UCRT64}/include"
"${MSYS2_UCRT64}/include/c++/13.2.0"
"${MSYS2_UCRT64}/include/c++/13.2.0/x86_64-w64-mingw32"
)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-O3 -MMD -MP -Wall -ggdb -fsanitize=address)
target_compile_definitions(${OMNIA_STD_LIB} PUBLIC OX_DEBUG_BUILD)
target_compile_definitions(${OMNIA_GFX_LIB} PUBLIC OX_DEBUG_BUILD)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
add_compile_options(-MMD -MP -Wall -latomic)
target_compile_definitions(${OMNIA_STD_LIB} PUBLIC OX_RELEASE_BUILD)
target_compile_definitions(${OMNIA_GFX_LIB} PUBLIC OX_RELEASE_BUILD)
endif()
#-----------------------------------------------------------------------------------------
#Finalize
#Linking
#-----------------------------------------------------------------------------------------
add_custom_command(TARGET ${OMNIA_STD_LIB} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/extra/ $<TARGET_FILE_DIR:${OMNIA_STD_LIB}>
VERBATIM
)
add_custom_target(FinalMessage ALL ${CMAKE_COMMAND} -E cmake_echo_color --cyan "[100%] Built ${PROJ_NAME} ${MAJOR_VER}.${MINOR_VER}.${BUILD_NUMBER}")
add_dependencies(FinalMessage ${OMNIA_STD_LIB})
if (WIN32)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC mingw32)
target_link_libraries(${OMNIA_STD_LIB} PUBLIC mingw32)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC SDL3 SDL3_ttf SDL3_image)
endif (WIN32)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC ${OMNIA_STD_LIB})
target_link_libraries(${TEST_TARGET} PUBLIC ${OMNIA_STD_LIB})
target_link_libraries(${TEST_TARGET} PUBLIC ${OMNIA_GFX_LIB})
if (APPLE)
cmake_policy(SET CMP0167 OLD)
find_package(Boost REQUIRED COMPONENTS regex)
target_link_libraries(${OMNIA_STD_LIB} PUBLIC Boost::regex)
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL3 REQUIRED sdl3)
pkg_check_modules(SDL3_IMAGE REQUIRED sdl3-image)
pkg_check_modules(SDL3_TTF REQUIRED sdl3-ttf)
target_include_directories(${OMNIA_GFX_LIB} PUBLIC
${SDL3_INCLUDE_DIRS}
${SDL3_IMAGE_INCLUDE_DIRS}
${SDL3_TTF_INCLUDE_DIRS}
)
target_link_directories(${OMNIA_GFX_LIB} PUBLIC
${SDL3_LIBRARY_DIRS}
${SDL3_IMAGE_LIBRARY_DIRS}
${SDL3_TTF_LIBRARY_DIRS}
)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC
${SDL3_LIBRARIES}
${SDL3_IMAGE_LIBRARIES}
${SDL3_TTF_LIBRARIES}
)
elseif (UNIX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath,'$ORIGIN',-rpath,/usr/lib,-rpath,/usr/local/lib")
target_link_libraries(${OMNIA_STD_LIB} PUBLIC tinfo boost_regex)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC xcb xcb-randr)
target_link_libraries(${OMNIA_GFX_LIB} PUBLIC SDL3 SDL3_ttf SDL3_image)
endif ()
#-----------------------------------------------------------------------------------------

674
LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

173
README.md
View file

@ -1,53 +1,160 @@
# ⚠️ !!! THIS PROJECT IS STILL IN EARLY DEVELOPMENT !!! ⚠️
# OmniaFramework
A collection of C++ classes and tools that I have created over the years
A modular C++ utility library containing a collection of general purpose tools and classses.
### <u>Build instructions - Windows</u>
## Build instructions - Windows
**Step 1:**
download MSYS2 from https://www.msys2.org/ and install it
##### Step 1: Install MSYS2
**Step 2:**
run MSYS2, and in the terminal run:
```
Download and install [**MSYS2**](https://www.msys2.org/), a minimal Unix-like environment for Windows that includes Clang, Make, and package management.
> ⚠️ **Recommended install path:**
> Leave the default location `C:\msys64` unchanged.
> If you install MSYS2 elsewhere, you may need to manually adjust paths in scripts and build commands throughout the build process.
Once installed, launch the **MSYS2 UCRT64 terminal**; this is the environment OmniaFramework is designed to build in.
##### Step 2: Prepare build environment
Once you have the **MSYS2 UCRT64 terminal** open, run theese two commands to prepare the build environment and install dependencies:
```bash
pacman -Syuu
pacman -S --needed base-devel mingw-w64-ucrt-x86_64-clang mingw-w64-ucrt-x86_64-gdb mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost
pacman -S --needed base-devel mingw-w64-ucrt-x86_64-clang mingw-w64-ucrt-x86_64-gdb mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost mingw-w64-ucrt-x86_64-sdl3 mingw-w64-ucrt-x86_64-sdl3-image mingw-w64-ucrt-x86_64-sdl3-ttf
```
**Step 3:**
open a UCRT64/MSYS2 command prompt inside the root directory of the project
#### Step 3: build OmniaFramework
**Step 4:**
execute this command:
```
./compile
```
## Build instructions - Linux (Arch)
**Step 1:**
open a terminal and run:
```
sudo pacman -Syuu
sudo pacman -S --needed base-devel clang gdb cmake make boost
Still in the **UCRT64** terminal, run the following commands to download, compile and install OmniaFramework:
```bash
git clone https://github.com/OmniaX-dev/OmniaFramework.git
cd OmniaFramework
./build release
./build install
```
**Step 2:**
open a terminal inside the root directory of the project and run this command:
##### Build options
Once **MSYS2** is installed and your environment is set up, you can use the `./build` script to compile OmniaFramework in various modes other than release:
###### <u>Debug build</u>
```bash
./build debug
```
./compile
Compiles OmniaFramework with debug symbols and no optimization, ideal for development and troubleshooting.
###### <u>Incremental build (uses last configuration)</u>
```bash
./build
```
## Build instructions - Linux (Ubuntu)
**Step 1:**
open a terminal and run:
Rebuilds only the modified source files using **whichever build configuration was last used** (`debug` or `release`).
This is ideal for fast iteration without switching modes.
###### <u>Run after build</u>
```bash
./build run
```
Same as `./build`, but immediately launches `ostd_test.exe` after building the changes.
###### <u>Windows release packaging</u>
```bash
./build install
```
Installs the library MSYS2's UCRT64 profile.
> ⚠️ **Important:**
> The `/build` script assumes MSYS2 is installed at `C:/msys64`.
> If your installation is in a different location, you must manually update the `MSYS_ROOT` variable at the top of `other/create_release.sh` and `other/install_release.sh`.
###
### <u>Build instructions - Linux</u>
##### Step 1: Install dependencies
- **<u>Arch Linux</u>**
```bash
sudo pacman -S --needed base-devel clang gdb cmake make boost sdl3 sdl3_image sdl3_ttf
```
- **<u>Ubuntu</u>**
```bash
sudo apt update && sudo apt upgrade
sudo apt install clang gdb make cmake libboost-all-dev
sudo apt install build-essential dkms linux-headers-$(uname -r) clang gdb make cmake libssl-dev libboost-all-dev libsdl3-dev libsdl3-image-dev libsdl3-ttf-dev libxcb-randr0-dev
```
**Step 2:**
open a terminal inside the root directory of the project and run this command:
```
./compile
- **<u>Fedora</u>**
```bash
sudo dnf update
sudo dnf install gcc gcc-c++ make clang gdb cmake clang-tools-extra boost boost-devel SDL3 SDL3_image SDL3_ttf SDL3-devel SDL3_image-devel SDL3_ttf-devel
```
#### For other Linux distros, install the dependencies using your package manager.
> **NOTE**: For other Linux distros, you need to install the same dependencies through your package manager.
##### Step 2: Build OmniaFramework
```bash
git clone https://github.com/OmniaX-dev/OmniaFramework.git
cd OmniaFramework
./build release
./build install
```
> **<u>NOTE</u>**: The same build options for the `./build` script apply here, as explained in the **Windows** section.
### <u>Build instructions - MacOS</u>
##### Step 1: Install HomeBrew
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
##### Step 2: Add HomeBrew to PATH
After installing HomeBrew, it should automatically tell you how to add it to path, in general it should loo something. like this:
- **For Apple Silicon**
```bash
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
```
- **For Intel Mac**
```bash
echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/usr/local/bin/brew shellenv)"
```
##### Step 3: Install dependencies
```bash
brew install git llvm gdb cmake make boost sdl3 sdl3_image sdl3_ttf pkg-config
```
##### Step 4: Build OmniaFramework
```bash
git clone https://github.com/OmniaX-dev/OmniaFramework.git
cd OmniaFramework
./build release
./build install
```
> **<u>NOTE</u>**: The same build options for the `./build` script apply here, as explained in the **Windows** section.

64
build Executable file
View file

@ -0,0 +1,64 @@
#!/bin/bash
set -e
MAKE_COMMAND=make
MAKEFILE_TYPE="Unix Makefiles"
if [[ "$(uname)" == "Darwin" ]]; then
NJOBS=-j"$(sysctl -n hw.ncpu)"
else
NJOBS=-j$(nproc)
fi
if [[ "$(uname -s)" == MINGW64_NT* ]]; then
MAKE_COMMAND=mingw32-make.exe
MAKEFILE_TYPE="MinGW Makefiles"
fi
if [[ "$1" == "debug" || "$1" == "release" ]]; then
BUILD_TYPE="$(tr '[:lower:]' '[:upper:]' <<< ${1:0:1})${1:1}"
rm -rf bin
cmake -B bin -S ./ -G "$MAKEFILE_TYPE" -DCMAKE_BUILD_TYPE=$BUILD_TYPE
$MAKE_COMMAND -C bin $NJOBS --no-print-directory
mkdir -p extra
find extra -mindepth 1 -maxdepth 1 -exec cp -r {} bin/ \;
build_number=$(cat other/build.nr)
build_number=$((build_number + 1))
truncate -s 0 other/build.nr
echo $build_number >> other/build.nr
cd other
./create_release.sh
cd ..
elif [[ "$1" == "install" ]]; then
cd other
./install_release.sh
cd ..
else
find extra -mindepth 1 -maxdepth 1 -exec cp -r {} bin/ \;
printf "\n\033[0;32mBuilding Changes...\n\n\033[0m"
$MAKE_COMMAND -C bin $NJOBS --no-print-directory
if [[ "$1" == "run" ]]; then
printf "\n\033[0;32mRunning Test Application...\n\n\033[0m"
cd bin
./ostd_test
cd ..
elif [[ "$1" == "dbg" ]]; then
printf "\n\033[0;32mDebugging Test Application...\n\n\033[0m"
cd bin
gdb -batch \
-ex 'set pagination off' \
-ex 'set print pretty on' \
-ex 'set logging file gdb-last.log' \
-ex 'set logging overwrite on' \
-ex 'set logging on' \
-ex 'handle SIGPIPE nostop noprint pass' \
-ex run \
-ex 'bt full' \
-ex 'info locals' \
-ex 'info args' \
-ex quit \
--args ./ostd_test
cd ..
fi
fi

View file

@ -1 +0,0 @@
1796

49
compile
View file

@ -1,49 +0,0 @@
#!/bin/bash
rm -r bin
if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
cmake -B bin -S ./
cd bin
make
cd ..
if [ $? -eq 0 ]; then
mkdir -p ./Release/lib
cp ./bin/libostd.so ./Release/lib
mkdir -p ./Release/include/ostd/vendor
find ./src/ostd -maxdepth 1 -name "*.hpp" -exec cp {} ./Release/include/ostd \;
find ./src/ostd/vendor -maxdepth 1 -name "*.hpp" -exec cp {} ./Release/include/ostd/vendor \;
cp -r ./licences ./Release
typeset -i build_number=$(cat build.nr)
((build_number++))
truncate -s 0 build.nr
echo $build_number >> build.nr
fi
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
cmake -B bin -S ./ -G "MinGW Makefiles"
cd bin
mingw32-make.exe
cd ..
if [ $? -eq 0 ]; then
mkdir -p ./Release/lib
cp ./bin/libostd.dll ./Release/lib
cp C:/msys64/ucrt64/bin/libgcc_s_seh-1.dll ./Release/lib
cp C:/msys64/ucrt64/bin/libstdc++-6.dll ./Release/lib
cp C:/msys64/ucrt64/bin/libwinpthread-1.dll ./Release/lib
mkdir -p ./Release/include/ostd/vendor
find ./src/ostd -maxdepth 1 -name "*.hpp" -exec cp {} ./Release/include/ostd \;
find ./src/ostd/vendor -maxdepth 1 -name "*.hpp" -exec cp {} ./Release/include/ostd/vendor \;
cp -r ./licences ./Release
typeset -i build_number=$(cat build.nr)
((build_number++))
truncate -s 0 build.nr
echo $build_number >> build.nr
fi
fi

332
extra/DefaultTheme.oss Normal file
View file

@ -0,0 +1,332 @@
% ================== DefaultTheme.oss ==================
% ==== Variables ====
const $accentColor = #DC143CFF
const $textColor = #111111FF
const $accentColorLight = #F16A85FF
const $accentColorDark = #6B0A1DFF
% ===================
% ====== Root Widget ======
(window) {
backgroundColor = rgba(160, 160, 160, 255)
(tooltip) {
backgroundColor = #FFF7D6FF
borderColor = rgb(30, 30, 30)
textColor = rgb(30, 30, 30)
borderWidth = 1
fontSize = 20
}
}
% =========================
% ====== Panel ======
(panel) {
backgroundColor = #AAAAAAFF
titleColor = $accentColorDark
titlebarColor = #999999FF
titlebarBorderColor = #909090FF
borderColor = #909090FF
borderRadius = 0
borderWidth = 2
titlebarBorderWidth = 2
showBorder = true
showBackground = true
titlebarType = full
titlebarHeight = 18
titlebarFontSize = 18
titlebarTextAlign = text_left
scrollSpeed = 1.2
scrollSmoothFactor = 0.7
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== TabPanel ======
(tabPanel) {
backgroundColor = #AAAAAAFF
borderColor = #666666FF
borderRadius = 0
borderWidth = 2
showBorder = true
showBackground = true
margin = rect(0, 0, 0, 0)
textColor = $textColor
fontSize = 18
(tabBar) {
height = 35.0
backgroundColor = #888888FF
borderColor = #909090FF
borderWidth = 1
sidePadding = 20.0
}
}
(@panel_tab panel) {
}
% ======================
% ====== Scrollbar ======
(scrollbar) {
(thumb) {
color = rgb(140, 140, 140)
borderRadius = 30.0
borderColor = rgb(150, 150, 150)
showBorder = true
}
(track) {
color = rgb(90, 90, 90)
}
width = 18.0
}
(scrollbar:hover) {
(thumb) {
color = #999999FF
}
}
(scrollbar:pressed) {
(thumb) {
color = #999999FF
}
(track) {
color = rgb(70, 70, 70)
}
}
% =======================
% ====== CheckBox ======
(checkbox) {
checkBorderColor = $accentColorDark
checkBoxColor = $accentColor
textColor = $textColor
backgroundColor = color_transparent
borderColor = color_transparent
fontSize = 20
borderRadius = 0
borderWidth = 0
checkBorderRadius = 6
checkBorderWidth = 2
showBackground = false
showBorder = false
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
(checkbox:hover) {
checkBorderColor = $accentColorLight
}
(checkbox:pressed) {
checkBoxColor = $accentColor
textColor = $accentColorDark
checkBorderColor = $accentColor
}
(checkbox:active) {
checkBoxColor = $accentColor
checkBorderColor = $accentColor
textColor = $accentColorDark
}
% ======================
% ====== RadioButton ======
(radioButton) {
innerCircleColor = $accentColorDark
outerCircleColor = $accentColor
outerCircleBorderWidth = 2
textColor = $textColor
fontSize = 20
showBackground = false
showBorder = false
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
(radioButton:hover) {
outerCircleColor = $accentColorLight
}
(radioButton:pressed) {
outerCircleColor = $accentColor
textColor = $accentColorDark
innerCircleColor = $accentColor
}
(radioButton:active) {
innerCircleColor = $accentColor
outerCircleColor = $accentColor
textColor = $accentColorDark
}
% =========================
% ====== Label ======
(label) {
textColor = $textColor
backgroundColor = color_transparent
borderColor = color_transparent
fontSize = 20
borderRadius = 0
borderWidth = 0
showBackground = false
showBorder = false
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== Button ======
(button) {
textColor = $textColor
backgroundColor = #999999FF
backgroundGradient = gradientV(rgb(160, 160, 160) - rgb(120, 120, 120))
borderColor = $accentColorDark
fontSize = 20
borderRadius = 0
borderWidth = 1
showBackground = true
showBorder = true
useBackgroundGradient = true
padding = rect(5, 5, 5, 5)
margin = rect(0, 0, 0, 0)
showIcon = false
(icon) {
}
}
(button:hover) {
borderColor = $accentColor
backgroundColor = #BBBBBBFF
}
(button:pressed) {
borderColor = $accentColorDark
textColor = #BBBBBBFF
backgroundGradient = gradientV(rgb(120, 120, 120) - rgb(160, 160, 160))
}
% ====================
% ====== Image ======
(image) {
}
% ===================
% ====== Image ======
(progressbar) {
backgroundColor = #400000FF
progressColor = #AA0000FF
borderColor = $accentColor
showDecimal = false
showText = true
useBackgroundGradient = true
backgroundGradient = gradientV(#400A1DFF - $accentColorDark)
useProgressGradient = true
progressGradient = gradientV(#C21135FF - #820B23FF)
}
% ===================
% ====== Slider ======
(slider) {
(handle) {
color = $accentColor
size = vec2(10, 18)
}
(track) {
color = #500000FF
progressColor = #AA0000FF
width = 6.0
}
showStepTicks = true
stepTickHeight = 6.0
stepTickColor = $accentColor
padding = rect(5, 5, 5, 5)
}
% ===================
% ====== Context Menu ======
(context) {
padding = rect(16, 0, 16, 0)
itemSpacing = 6.0
fontSize = 18
backgroundColor = #CCAAAAFF
selectionColor = $accentColor
selectionTextColor = #FFFFFFFF
separatorLineColor = #700000FF
textColor = $textColor
submenuIndicatorColor = $accentColorDark
borderColor = #400000FF
useSelectionGradient = true
selectionGradient = gradientV(#C21135FF - #820B23FF)
}
% ==========================
% ====== MenuBar ======
(menubar) {
height = 24.0
itemPadding = rect(0, 0, 15, 0)
fontSize = 16
backgroundColor = #CCAAAAFF
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = #FFFFFFFF
borderColor = #400000FF
}
% =====================
% ====== MenuBar ======
(toolbar) {
height = 30.0
fontSize = 16
backgroundColor = #AA9999FF
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = #FFFFFFFF
borderColor = #400000FF
}
% =====================
(@tool_button button) {
fontSize = 18
padding = rect(5, 0, 5, 0)
margin = rect(5, 0, 0, 0)
showBackground = true
backgroundColor = #FFAAAAAA
useBackgroundGradient = false
showBorder = true
borderColor = color_black
showIcon = true
(icon) {
tint = color_crimson
}
}
(@tool_button button:hover) {
borderColor = color_white
backgroundColor = rgb(128, 0, 0, 128)
(icon) {
tint = color_white
}
}
(@tool_button button:pressed) {
borderColor = $accentColorDark
(icon) {
tint = $accentColorDark
}
}

424
extra/DefaultThemeDark.oss Normal file
View file

@ -0,0 +1,424 @@
% ================== DefaultTheme.oss ==================
% ==== Variables ====
const $accentColor = #2FB000FF
const $textColor = #CCEECCFF
const $borderColor = #111111FF
const $darkColor = #223322FF
const $accentColorLight = #87FF5CFF
const $accentColorDark = #3B7500FF
const $backgroundColor = rgba(30, 30, 30, 255)
const $crimsonAccent = rgba(220, 20, 60, 150)
const $blueAccent = #0075C7
% ===================
macro set_focus_border(color, width = 1, show = true) {
focusBorderColor = $color;
showFocusBorder = $show;
focusBorderWidth = $width;
}
macro set_common_values(_borderColor = $borderColor, _fontSize = 18, _showBorder = true) {
borderColor = $_borderColor;
fontSize = $_fontSize;
showBorder = $_showBorder;
}
% ====== Root Widget ======
(window) {
backgroundColor = $backgroundColor
(tooltip) {
backgroundColor = #FFF7D6FF
borderColor = rgb(30, 30, 30)
textColor = rgb(30, 30, 30)
borderWidth = 1
fontSize = 20
}
}
% =========================
% ====== Panel ======
(panel) {
set_common_values()
backgroundColor = $backgroundColor
titleColor = $accentColorDark
titlebarColor = $borderColor
titlebarBorderColor = $borderColor
borderRadius = 0
borderWidth = 2
titlebarBorderWidth = 2
showBackground = true
titlebarType = full
titlebarHeight = 35
titlebarFontSize = 18
titlebarTextAlign = text_left
scrollSpeed = 1.2
scrollSmoothFactor = 0.7
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== TabPanel ======
(tabPanel) {
set_common_values()
backgroundColor = $backgroundColor
borderRadius = 0
borderWidth = 2
showBackground = true
margin = rect(0, 0, 0, 0)
textColor = $textColor
(tabBar) {
height = 35.0
backgroundColor = $darkColor
borderColor = $borderColor
borderWidth = 1
sidePadding = 20.0
}
}
(@panel_tab panel) {
}
% ======================
% ====== Scrollbar ======
(scrollbar) {
(thumb) {
color = $darkColor
borderRadius = 30.0
borderColor = $borderColor
showBorder = true
}
(track) {
color = #111111FF
}
width = 18.0
}
(scrollbar:hover) {
(thumb) {
color = $accentColorDark
}
}
(scrollbar:pressed) {
(thumb) {
color = #225500FF
}
(track) {
color = #001100FF
}
}
% =======================
% ====== CheckBox ======
(checkbox) {
set_common_values($borderColor, 18, false)
checkBorderColor = $accentColorDark
checkBoxColor = $accentColor
textColor = $textColor
borderRadius = 0
borderWidth = 0
checkBorderRadius = 6
checkBorderWidth = 2
showBackground = false
padding = rect(4, 4, 4, 4)
margin = rect(0, 0, 0, 0)
set_focus_border($blueAccent)
}
(checkbox:hover) {
checkBorderColor = $accentColorLight
}
(checkbox:pressed) {
checkBoxColor = $accentColor
textColor = $accentColorDark
checkBorderColor = $accentColor
}
(checkbox:active) {
checkBoxColor = $accentColor
checkBorderColor = $accentColor
textColor = $accentColorDark
}
% ======================
% ====== RadioButton ======
(radioButton) {
set_common_values($borderColor, 18, false)
innerCircleColor = $accentColorDark
outerCircleColor = $accentColor
outerCircleBorderWidth = 2
textColor = $textColor
showBackground = false
padding = rect(4, 4, 4, 4)
margin = rect(0, 0, 0, 0)
set_focus_border($blueAccent)
}
(radioButton:hover) {
outerCircleColor = $accentColorLight
}
(radioButton:pressed) {
outerCircleColor = $accentColor
textColor = $accentColorDark
innerCircleColor = $accentColor
}
(radioButton:active) {
innerCircleColor = $accentColor
outerCircleColor = $accentColor
textColor = $accentColorDark
}
% =========================
% ====== Label ======
(label) {
set_common_values($borderColor, 18, false)
textColor = $textColor
borderRadius = 0
borderWidth = 0
showBackground = false
padding = rect(15, 15, 15, 15)
margin = rect(0, 0, 0, 0)
}
% ===================
% ====== Button ======
(button) {
set_common_values($accentColorDark)
textColor = $textColor
backgroundGradient = gradientV($accentColor - $accentColorDark)
borderRadius = 0
borderWidth = 1
showBackground = false
useBackgroundGradient = true
padding = rect(5, 5, 5, 5)
margin = rect(0, 0, 0, 0)
showIcon = false
(icon) {
}
set_focus_border($blueAccent)
}
(button:hover) {
borderColor = $accentColorLight
}
(button:pressed) {
borderColor = $accentColorDark
textColor = #BBBBBBFF
backgroundGradient = gradientV($accentColorDark - $accentColor)
}
% ====================
% ====== Image ======
(image) {
}
% ===================
% ====== ProgressBar ======
(progressbar) {
set_common_values($accentColor)
borderWidth = 1
showDecimal = false
showText = true
useBackgroundGradient = true
backgroundGradient = gradientV($darkColor - $accentColorDark)
useProgressGradient = true
progressGradient = gradientV($accentColor - $accentColorDark)
}
% =========================
% ====== Slider ======
(slider) {
(handle) {
color = $accentColor
size = vec2(10, 18)
}
(track) {
color = $accentColorDark
progressColor = $accentColor
width = 6.0
}
showStepTicks = true
stepTickHeight = 6.0
stepTickColor = $accentColor
padding = rect(5, 5, 5, 5)
set_focus_border($blueAccent)
}
% ===================
% ====== Context Menu ======
(context) {
set_common_values()
padding = rect(16, 0, 16, 0)
itemSpacing = 6.0
backgroundColor = $darkColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
separatorLineColor = $borderColor
textColor = $textColor
submenuIndicatorColor = $accentColorDark
useSelectionGradient = true
selectionGradient = gradientV($accentColor - $accentColorDark)
animateOpen = true
animationDelay = 120
}
% ==========================
% ====== MenuBar ======
(menubar) {
set_common_values($borderColor, 16, false)
height = 24.0
itemPadding = rect(0, 0, 15, 0)
backgroundColor = $darkColor
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
}
% =====================
% ====== ToolBar ======
(toolbar) {
set_common_values()
height = 30.0
backgroundColor = $darkColor
textColor = $textColor
selectionColor = $accentColor
selectionTextColor = $accentColorLight
showBackground = true
}
(@tool_button button) {
set_common_values(color_black, 18, false)
padding = rect(5, 0, 5, 0)
margin = rect(5, 0, 0, 0)
showBackground = true
backgroundColor = $darkColor
useBackgroundGradient = false
showIcon = true
(icon) {
tint = $accentColorLight
}
set_focus_border($blueAccent)
}
(@tool_button button:hover) {
borderColor = $textColor
backgroundColor = $accentColorDark
showBorder = true
(icon) {
tint = $textColor
}
}
(@tool_button button:pressed) {
borderColor = $accentColorDark
showBorder = true
(icon) {
tint = $accentColorLight
}
}
% =====================
% ====== ComboBox ======
(combo) {
set_common_values($accentColor)
backgroundColor = $darkColor
borderWidth = 1
showBackground = true
triangleIndicatorColor = $accentColor
triangleIndicatorPadding = 8.0
textColor = $textColor
}
(combo:hover) {
triangleIndicatorColor = $accentColorLight
borderColor = $accentColorLight
}
(combo:active) {
triangleIndicatorColor = $accentColorLight
borderColor = $accentColorLight
}
% ======================
% ====== TreeView ======
(tree) {
set_common_values($borderColor, 16, true)
linePadding = rect(5, 5, 0, 0)
selectionColor = $accentColor
selectionTextColor = color_white
iconSpacing = 0.0
iconInset = 4
showIcons = true
textColor = $textColor
backgroundColor = $darkColor
backgroundColor2 = $darkColor.lighten(0.06)
showAlternatingBackground = true
separatorLineColor = $borderColor
showSeparatorLine = false
iconTintColor = $accentColorLight
indentWidth = 16.0
chevronColor = $accentColorDark
chevronInset = 4.0
branchLineColor = $textColor
showBranchLines = true
branchLineThickness = 1.0
set_focus_border($blueAccent)
}
% ======================
% ====== TextEdit ======
(textEdit) {
set_common_values()
set_focus_border($blueAccent)
backgroundColor = $darkColor
textColor = $textColor
cursorBlink = true
cursorWidth = 2
cursorColor = $crimsonAccent
selectionColor = $blueAccent
cursorStyle = "line"
}
% ======================
% ====== ColorPicker ======
(colorPicker) {
set_common_values()
set_focus_border($blueAccent)
backgroundColor = $darkColor
padding = rect(8, 8, 8, 8)
stripWidth = 20
regionSpacing = 8
cursorRadius = 6
}
(colorButton) {
set_common_values()
set_focus_border($blueAccent)
}
% =========================

BIN
extra/FreeSans.ttf Normal file

Binary file not shown.

BIN
extra/icons.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
extra/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

View file

@ -1,9 +0,0 @@
#!/bin/bash
printf "\n============================================[ Building Library ]============================================\n\n"
\cp -r ../extra/* ./
if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
make
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
mingw32-make.exe
fi

51
extra/testQuery.oq Normal file
View file

@ -0,0 +1,51 @@
GET UNIQUE name AS "Name", age, country, children # * can be used to get all columns; UNIQUE is optional
IN test_table
FOR ([age == 20] OR [year <= 1998]) AND # FOR conditions should support all regular comparison operators (==, !=, <, <=, >, >=) when applicable
([name == "Alberto"] OR [name MATCHES "regex"]) AND
[country IN {"Spain", "Italy", "Germany"}] AND
NOT [children BETWEEN 2,4 INCL] # Possible values: INCL (default, implicit), EXCL, INCL_L, INCL_R
CROSS test_table_2 MATCH # Possible values: MATCH (default, implicit), LEFT, RIGHT, FULL
IF test_table.id == test_table_2.user_id # Debating if this should support only equality, or more complex conditions, maybe even matching FOR conditions
ORDER age, year DESC; # Possible values: ASC (default, implicit), DESC
I start by scanning a character: if that character is a letter
or an underscore, I go into keyword/identifier mode, and accumulate until I encounter
anything that is not a letter, number, underscore or dot, then check to see if
it's a keyword, otherwise it's an identifier (since performance is quite
critical in this particular context, I am thinking of fast-tracking directly to
identifier by setting a bool flag if either: I am starting or ending
with an underscore, or I encounter a dot or a number ...I am going to allow
for keyword check if the underscore is found inside, because I might want to
have that in the future) ...if a dot is on the end, I error.
If the initial character is a double quote, I go into string literal mode and
accumulate until a non-escaped double quote (inclusive). If I find a
new line or the end of the query before that, I error.
If the initial character is a number, I go into number literal mode and accumulate
until I find something that is not a number or a dot. If more than
one dot is found, I error. (here I could polish a bit, removing leading zeros if no
dot is present and adding a trailing zero if the dot is the last
character ...again, is this the job of the lexer? ...maybe not, but maybe it is worth
sending cleaner data to the parser, if the fix is this simple
....I'll wait to hear what you think)
If the initial character is any of: =, <, >, !, I check the next character and do
the operator-logic you described, and if that doesn't apply then I'll
put the single character as a symbol (not used now, but more robust for the future...
though I wonder if I should have the parser error on it or error
directly in the lexer instead of putting it into a symbol)
If the initial character is any of the current symbol list: ( ) [ ] { } , * ; I put
it as a symbol
If the initial character is anything else, I error
when I reach the end, I emit an EndOfInput token

121
extra/testTheme.oss Normal file
View file

@ -0,0 +1,121 @@
% ================== testTheme.txt ==================
% ---- Variables ----
$accentColor = rgba(255, 0, 255, 255)
const $color2 = #0000FFFF
% -------------------
window.backgroundColor = rgba(160, 160, 160, 255)
$accentColor = rgb(255, 0, 0)
(label) {
textColor = $accentColor
backgroundColor = Color(#000000FF)
borderColor = $accentColor
fontSize = 20
borderRadius = 0
borderWidth = 2
showBackground = true
showBorder = true
padding = Rect(15, 15, 15, 15)
}
(checkbox) {
textColor = color_black
fontSize = 28
showBackground = false
showBorder = false
borderRadius = 0
borderWidth = 2
checkBorderRadius = 8
checkBorderWidth = 2
checkBorderColor = color_skyblue
checkBoxColor = color_skyblue
padding = Rect(15, 15, 15, 15)
}
(checkbox:hover) {
checkBorderColor = color_white
}
(checkbox:pressed) {
checkBoxColor = color_lightgray
textColor = color_darkgray
}
(checkbox:active) {
textColor = #000050FF
}
(panel) {
backgroundColor = #AAAAAAFF
borderColor = color_black
borderRadius = 10
borderWidth = 2
showBorder = true
showBackground = true
padding = Rect(15, 15, 15, 15)
titleColor = #444444FF
titlebarColor = #999999FF
titlebarBorderColor = #909090FF
titlebarBorderWidth = 2
titlebarType = full
titlebarHeight = 18
titlebarFontSize = 18
titlebarTextAlign = text_left
}
(@testLabel label) {
textColor = $color2
backgroundColor = #000000FF
fontSize = 50
}
(@testLabel2 label) {
showBackground = false
margin = Rect(30, 0, 30, 30)
}
(@testLabel2 label:hover) {
borderColor = #208080FF
}
(@testLabel2 label:pressed) {
showBackground = true
}
(label:hover) {
textColor = color_green
borderColor = rgb(0, 255, 0)
}
(label:pressed) {
textColor = color_firebrick
}
(@testLabel label:hover) {
borderColor = $color2
}
(@testLabel label:pressed) {
backgroundColor = color_firebrick
textColor = #505050FF
}
(label:disabled) {
backgroundColor = #EEEEEEFF
%textColor = #505050FF
%borderColor = #000000FF
}
(@label3 label) {
textColor = $color2
backgroundColor = #000000FF
borderColor = #FF0000FF
borderWidth = 4
borderRadius = 15
}
% ===================================================

View file

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

21
licences/JSON-LICENCE.txt Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-2025 Niels Lohmann
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.

17
licences/SDL3-LICENSE.txt Normal file
View file

@ -0,0 +1,17 @@
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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.

View file

@ -0,0 +1,17 @@
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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.

View file

@ -0,0 +1,17 @@
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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.

View file

@ -0,0 +1,96 @@
-------------------------------
UBUNTU FONT LICENCE Version 1.0
-------------------------------
PREAMBLE
This licence allows the licensed fonts to be used, studied, modified and
redistributed freely. The fonts, including any derivative works, can be
bundled, embedded, and redistributed provided the terms of this licence
are met. The fonts and derivatives, however, cannot be released under
any other licence. The requirement for fonts to remain under this
licence does not require any document created using the fonts or their
derivatives to be published under this licence, as long as the primary
purpose of the document is not to be a vehicle for the distribution of
the fonts.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this licence and clearly marked as such. This may
include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components
as received under this licence.
"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.
"Copyright Holder(s)" refers to all individuals and companies who have a
copyright ownership of the Font Software.
"Substantially Changed" refers to Modified Versions which can be easily
identified as dissimilar to the Font Software by users of the Font
Software comparing the Original Version with the Modified Version.
To "Propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification and with or without charging
a redistribution fee), making available to the public, and in some
countries other activities as well.
PERMISSION & CONDITIONS
This licence does not grant any rights under trademark law and all such
rights are reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to propagate the Font Software, subject to
the below conditions:
1) Each copy of the Font Software must contain the above copyright
notice and this licence. 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.
2) The font name complies with the following:
(a) The Original Version must retain its name, unmodified.
(b) Modified Versions which are Substantially Changed must be renamed to
avoid use of the name of the Original Version or similar names entirely.
(c) Modified Versions which are not Substantially Changed must be
renamed to both (i) retain the name of the Original Version and (ii) add
additional naming elements to distinguish the Modified Version from the
Original Version. The name of such Modified Versions must be the name of
the Original Version, with "derivative X" where X represents the name of
the new work, appended to that name.
3) The name(s) of the Copyright Holder(s) and any contributor to the
Font Software shall not be used to promote, endorse or advertise any
Modified Version, except (i) as required by this licence, (ii) to
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
their explicit written permission.
4) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this licence, and must not be distributed
under any other licence. The requirement for fonts to remain under this
licence does not affect any document created using the Font Software,
except any version of the Font Software extracted from a document
created using the Font Software may only be distributed under this
licence.
TERMINATION
This licence 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.

320
licences/libgcc-LICENCE.txt Normal file
View file

@ -0,0 +1,320 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
License
There are two licenses affecting GNU libstdc++: one for the code, and one for the documentation.
There is a license section in the FAQ regarding common questions. If you have more questions, ask the FSF or the gcc mailing list.
The Code: GPL
The source code is distributed under the GNU General Public License version 3, with the addition under section 7 of an exception described in the “GCC Runtime Library Exception, version 3.1” as follows (or see the file COPYING.RUNTIME):
GCC RUNTIME LIBRARY EXCEPTION
Version 3.1, 31 March 2009
Copyright (C) 2009 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
This GCC Runtime Library Exception ("Exception") is an additional
permission under section 7 of the GNU General Public License, version
3 ("GPLv3"). It applies to a given file (the "Runtime Library") that
bears a notice placed by the copyright holder of the file stating that
the file is governed by GPLv3 along with this Exception.
When you use GCC to compile a program, GCC may combine portions of
certain GCC header files and runtime libraries with the compiled
program. The purpose of this Exception is to allow compilation of
non-GPL (including proprietary) programs to use, in this way, the
header files and runtime libraries covered by this Exception.
0. Definitions.
A file is an "Independent Module" if it either requires the Runtime
Library for execution after a Compilation Process, or makes use of an
interface provided by the Runtime Library, but is not otherwise based
on the Runtime Library.
"GCC" means a version of the GNU Compiler Collection, with or without
modifications, governed by version 3 (or a specified later version) of
the GNU General Public License (GPL) with the option of using any
subsequent versions published by the FSF.
"GPL-compatible Software" is software whose conditions of propagation,
modification and use would permit combination with GCC in accord with
the license of GCC.
"Target Code" refers to output from any compiler for a real or virtual
target processor architecture, in executable form or suitable for
input to an assembler, loader, linker and/or execution
phase. Notwithstanding that, Target Code does not include data in any
format that is used as a compiler intermediate representation, or used
for producing a compiler intermediate representation.
The "Compilation Process" transforms code entirely represented in
non-intermediate languages designed for human-written code, and/or in
Java Virtual Machine byte code, into Target Code. Thus, for example,
use of source code generators and preprocessors need not be considered
part of the Compilation Process, since the Compilation Process can be
understood as starting with the output of the generators or
preprocessors.
A Compilation Process is "Eligible" if it is done using GCC, alone or
with other GPL-compatible software, or if it is done without using any
work based on GCC. For example, using non-GPL-compatible Software to
optimize any GCC intermediate representations would not qualify as an
Eligible Compilation Process.
1. Grant of Additional Permission.
You have permission to propagate a work of Target Code formed by
combining the Runtime Library with Independent Modules, even if such
propagation would otherwise violate the terms of GPLv3, provided that
all Target Code was generated by Eligible Compilation Processes. You
may then convey such a combination under terms of your choice,
consistent with the licensing of the Independent Modules.
2. No Weakening of GCC Copyleft.
The availability of this Exception does not imply any general
presumption that third-party software is unaffected by the copyleft
requirements of the license of GCC.
Hopefully that text is self-explanatory. If it isn't, you need to speak to your lawyer, or the Free Software Foundation.
The Documentation: GPL, FDL
The documentation shipped with the library and made available over the web, excluding the pages generated from source comments, are copyrighted by the Free Software Foundation, and placed under the GNU Free Documentation License version 1.3. There are no Front-Cover Texts, no Back-Cover Texts, and no Invariant Sections.
For documentation generated by doxygen or other automated tools via processing source code comments and markup, the original source code license applies to the generated files. Thus, the doxygen documents are licensed GPL.
If you plan on making copies of the documentation, please let us know. We can probably offer suggestions.

View file

@ -0,0 +1,320 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on the Program.
To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work.
A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”.
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
License
There are two licenses affecting GNU libstdc++: one for the code, and one for the documentation.
There is a license section in the FAQ regarding common questions. If you have more questions, ask the FSF or the gcc mailing list.
The Code: GPL
The source code is distributed under the GNU General Public License version 3, with the addition under section 7 of an exception described in the “GCC Runtime Library Exception, version 3.1” as follows (or see the file COPYING.RUNTIME):
GCC RUNTIME LIBRARY EXCEPTION
Version 3.1, 31 March 2009
Copyright (C) 2009 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
This GCC Runtime Library Exception ("Exception") is an additional
permission under section 7 of the GNU General Public License, version
3 ("GPLv3"). It applies to a given file (the "Runtime Library") that
bears a notice placed by the copyright holder of the file stating that
the file is governed by GPLv3 along with this Exception.
When you use GCC to compile a program, GCC may combine portions of
certain GCC header files and runtime libraries with the compiled
program. The purpose of this Exception is to allow compilation of
non-GPL (including proprietary) programs to use, in this way, the
header files and runtime libraries covered by this Exception.
0. Definitions.
A file is an "Independent Module" if it either requires the Runtime
Library for execution after a Compilation Process, or makes use of an
interface provided by the Runtime Library, but is not otherwise based
on the Runtime Library.
"GCC" means a version of the GNU Compiler Collection, with or without
modifications, governed by version 3 (or a specified later version) of
the GNU General Public License (GPL) with the option of using any
subsequent versions published by the FSF.
"GPL-compatible Software" is software whose conditions of propagation,
modification and use would permit combination with GCC in accord with
the license of GCC.
"Target Code" refers to output from any compiler for a real or virtual
target processor architecture, in executable form or suitable for
input to an assembler, loader, linker and/or execution
phase. Notwithstanding that, Target Code does not include data in any
format that is used as a compiler intermediate representation, or used
for producing a compiler intermediate representation.
The "Compilation Process" transforms code entirely represented in
non-intermediate languages designed for human-written code, and/or in
Java Virtual Machine byte code, into Target Code. Thus, for example,
use of source code generators and preprocessors need not be considered
part of the Compilation Process, since the Compilation Process can be
understood as starting with the output of the generators or
preprocessors.
A Compilation Process is "Eligible" if it is done using GCC, alone or
with other GPL-compatible software, or if it is done without using any
work based on GCC. For example, using non-GPL-compatible Software to
optimize any GCC intermediate representations would not qualify as an
Eligible Compilation Process.
1. Grant of Additional Permission.
You have permission to propagate a work of Target Code formed by
combining the Runtime Library with Independent Modules, even if such
propagation would otherwise violate the terms of GPLv3, provided that
all Target Code was generated by Eligible Compilation Processes. You
may then convey such a combination under terms of your choice,
consistent with the licensing of the Independent Modules.
2. No Weakening of GCC Copyleft.
The availability of this Exception does not imply any general
presumption that third-party software is unaffected by the copyleft
requirements of the license of GCC.
Hopefully that text is self-explanatory. If it isn't, you need to speak to your lawyer, or the Free Software Foundation.
The Documentation: GPL, FDL
The documentation shipped with the library and made available over the web, excluding the pages generated from source comments, are copyrighted by the Free Software Foundation, and placed under the GNU Free Documentation License version 1.3. There are no Front-Cover Texts, no Back-Cover Texts, and no Invariant Sections.
For documentation generated by doxygen or other automated tools via processing source code comments and markup, the original source code license applies to the generated files. Thus, the doxygen documents are licensed GPL.
If you plan on making copies of the documentation, please let us know. We can probably offer suggestions.

View file

@ -0,0 +1,5 @@
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.

View file

@ -0,0 +1,23 @@
Copyright (c) 1999-2018, Craig Stuart Sapp
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,25 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
version 1.3.2, February 17th, 2026
Copyright (C) 1995-2026 Jean-loup Gailly and Mark Adler
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.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
*/

116
other/TODO.txt Normal file
View file

@ -0,0 +1,116 @@
***Add Color constants to ostd::Color
***Add MouseEntered/MouseExited callbacks
***Fix mouse detection currently using local coordinates
***implement file theme loading
***Implemenmt MouseDragged callback
***Add scissoring to only draw inside the bounds of containers
***Implement Drag And Drop
***Implement a way to determine on what widget (in the hierarchy chain) and event must stop
***Implement const in stylesheet
***Implement color constants in stylesheet
***Implement margin for widgets
***Implement mouse scroll callback
***Fix: Text rendering bug in labels
***Implement callback for state changes in checkbox
***FIX: WindowOutputHandler text rendering
***Implement wrappers in Renderer2D to take a center and size for rects
***find a way to add fixed update to gui::Window
***Add triangle drawing functions to BasicRenderer2D
***Implement Panel title
***Implement show/hide functionality for widgets
***Create reliable default stylesheet
***Implement global scale (probably query desktop scale and adjust accordingly)
***Add Dark Mode Default theme
***FIX: Optimize ListView
***FIX: Limit FPS even when continuous events happen
***FIX: Float values in stylesheet needing to have decimal point to be read as float
***Make parsing of vec2 and rect in stylesheet, consistent with other value functions like gradients and animations
***FIX: ToolBar::AddButton not consistent order
***FIX: KeyPress crash on TreeView
***FIX: Animated icons not working in TreeView
***Check if possible, then change all WindowCore& refs to Window&
***Add utf-8 codepoint walking functions to String
***Change the stored text in KeyEventData from a single char to the full utf-8 string
***Check if Text Entered events are enabled via SDL
***Add modifiers to Event.keyboard path
***Implement max length in TextEdit
***Make label auto-size
***Implement shift-selection in TextEdit (mouse/keyboard)
***Implement ctrl-selection/navigation in TextEdit
***Implement home/end and pg up/pg down in TextEdit
***Implement Undo stack in TextBuffer
***Resolve lifetime issue for character filter in TextEdit
Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent changes
See if I can query OS theme mode to saelect the right theme
Add buttons to scroll tabs in TabPanel
Add Font class, to handle different font attributes
Add multi-selection to TreeView
Cache getStringDimensions() call in TabPanel::draw_tabs
Add gradient to TreeView selection
Add gradient to menubar background and selection
Add cursor stack
Add MacOS global menu integration
FIX: Tooltips
XML Layout file?
Add Hex Editor widget
Add Message Boxes
FIX: Widget Content extents increasing negatively not showing scrollbars
FIX: Button state getting hung after showing an openFileDialog
FIX: TreeView last item is covered by scrollbar
FIX: TreeView last item not clickable even when no horizontal scrollbar present
FIX: Refresh display text in ComboBox when font size changes and also on theme reload
Add setSelectedMenuItem() function to ComboBox
FIX: ContextMenu rendering partly outside the screen when showing it close to the right Window border
Add ToolBar::addButton overload that takes a ogfx::Icon wrapper
Redesign theme overrides to be more robust
Implement a font-fallback chain in FontGlyphAtlas
Implement appendText in TextBuffer and TextEdit
Implement per-glyph caching in BasicRenderer2D
Added double-click event to widgets
Implement notification system
Fix cursor not advancing when selection is present and left arrow is pressed in TextEdit
Check widgets/layouts interactions
Add per-widget font
Implement following widgets:
***Label
***Checkbox
***Panel / Container
***Title Label
***ScrollView
***Image / Icon
***Tab Panel
***Button
***Image/Icon
***Save/Open File Dialogs (and folder dialogs)
***Tooltips
***Radio Button
***Grouping
***Progressbar
***Horizontal Slider
***Vertical Slider
***ListBox
***Context Menu
***MenuBar
***Toolbar
***StatusBar
***Layouts
***Vertical
***Horizontal
***Grid
***Fill
***ComboBox
***TreeView
***Text Input
***Numeric Field
***Password Field
Text Area
Color Picker
Calendar

1
other/build.nr Normal file
View file

@ -0,0 +1 @@
2097

69
other/create_release.sh Executable file
View file

@ -0,0 +1,69 @@
#!/bin/bash
MSYS_ROOT=c:/msys64
set -e
RELEASE_DIR=
if [[ "$(uname)" == "Darwin" ]]; then
RELEASE_DIR=../bin/OmniaFramework_macos
mkdir -p "$RELEASE_DIR/lib"
# macOS shared libraries (.dylib)
cp ../bin/libostd.dylib "$RELEASE_DIR/lib"
cp ../bin/libogfx.dylib "$RELEASE_DIR/lib"
elif [[ "$(uname -s)" == Linux* ]]; then
RELEASE_DIR=../bin/OmniaFramework_linux64
mkdir -p $RELEASE_DIR/lib
cp ../bin/libostd.so $RELEASE_DIR/lib
cp ../bin/libogfx.so $RELEASE_DIR/lib
elif [[ "$(uname -s)" == MINGW64_NT* ]]; then
RELEASE_DIR=../bin/OmniaFramework_w64
mkdir -p $RELEASE_DIR/lib
cp ../bin/libostd.dll $RELEASE_DIR/lib
cp ../bin/libostd.dll.a $RELEASE_DIR/lib
cp ../bin/libogfx.dll $RELEASE_DIR/lib
cp ../bin/libogfx.dll.a $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/libgcc_s_seh-1.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/libstdc++-6.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/libwinpthread-1.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/SDL2.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/SDL2_image.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/SDL2_ttf.dll $RELEASE_DIR/lib
cp $MSYS_ROOT/ucrt64/bin/libSDL2_gfx-1-0-0.dll $RELEASE_DIR/lib
fi
cp -r ../licences $RELEASE_DIR
cp ../LICENSE $RELEASE_DIR/licences/OmniaFramework-LICENCE.txt
# OSTD
mkdir -p $RELEASE_DIR/include/ostd/vendor/midifile
mkdir -p $RELEASE_DIR/include/ostd/data
mkdir -p $RELEASE_DIR/include/ostd/io
mkdir -p $RELEASE_DIR/include/ostd/math
mkdir -p $RELEASE_DIR/include/ostd/string
mkdir -p $RELEASE_DIR/include/ostd/utils
find ../src/ostd -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd \;
find ../src/ostd/data -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/data \;
find ../src/ostd/io -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/io \;
find ../src/ostd/math -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/math \;
find ../src/ostd/string -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/string \;
find ../src/ostd/utils -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/utils \;
find ../src/ostd/vendor -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/vendor \;
find ../src/ostd/vendor/midifile -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ostd/vendor/midifile \;
cp -r ../src/ostd/vendor/nlohmann $RELEASE_DIR/include/ostd/vendor/
# OGFX
mkdir -p $RELEASE_DIR/include/ogfx/gui
mkdir -p $RELEASE_DIR/include/ogfx/gui/widgets
mkdir -p $RELEASE_DIR/include/ogfx/render
mkdir -p $RELEASE_DIR/include/ogfx/resources
mkdir -p $RELEASE_DIR/include/ogfx/utils
find ../src/ogfx -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx \;
find ../src/ogfx/gui -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx/gui \;
find ../src/ogfx/gui/widgets -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx/gui/widgets \;
find ../src/ogfx/render -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx/render \;
find ../src/ogfx/resources -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx/resources \;
find ../src/ogfx/utils -maxdepth 1 -name "*.h*" -exec cp {} $RELEASE_DIR/include/ogfx/utils \;

54
other/install_release.sh Executable file
View file

@ -0,0 +1,54 @@
#!/bin/bash
MSYS_ROOT=c:/msys64
set -e
RELEASE_DIR=
if [[ "$(uname)" == "Darwin" ]]; then
if [[ "$(uname -m)" == "arm64" ]]; then
PREFIX=/opt/homebrew
else
PREFIX=/usr/local
fi
RELEASE_DIR=../bin/OmniaFramework_macos
# Remove old versions
sudo rm -rf /usr/local/include/ostd 2> /dev/null
sudo rm -rf /usr/local/include/ogfx 2> /dev/null
sudo rm -f "$PREFIX/lib/libostd.dylib" 2> /dev/null
sudo rm -f "$PREFIX/lib/libogfx.dylib" 2> /dev/null
# Install headers
sudo cp -r "$RELEASE_DIR/include/ostd" /usr/local/include/
sudo cp -r "$RELEASE_DIR/include/ogfx" /usr/local/include/
# Install libraries
sudo cp "$RELEASE_DIR/lib/libostd.dylib" "$PREFIX/lib/"
sudo cp "$RELEASE_DIR/lib/libogfx.dylib" "$PREFIX/lib/"
elif [[ "$(uname -s)" == Linux* ]]; then
RELEASE_DIR=../bin/OmniaFramework_linux64
sudo rm -rf /usr/include/ostd 2> /dev/null
sudo rm -rf /usr/include/ogfx 2> /dev/null
sudo rm -rf /usr/lib/libostd.so 2> /dev/null
sudo rm -rf /usr/lib/libogfx.so 2> /dev/null
sudo cp -r $RELEASE_DIR/include/ostd /usr/include/
sudo cp -r $RELEASE_DIR/include/ogfx /usr/include/
sudo cp -r $RELEASE_DIR/lib/libostd.so /usr/lib/
sudo cp -r $RELEASE_DIR/lib/libogfx.so /usr/lib/
elif [[ "$(uname -s)" == MINGW64_NT* ]]; then
RELEASE_DIR=../bin/OmniaFramework_w64
rm -rf $MSYS_ROOT/ucrt64/include/ostd 2> /dev/null
rm -rf $MSYS_ROOT/ucrt64/include/ogfx 2> /dev/null
rm -rf $MSYS_ROOT/ucrt64/bin/libostd.dll 2> /dev/null
rm -rf $MSYS_ROOT/ucrt64/lib/libostd.dll.a 2> /dev/null
rm -rf $MSYS_ROOT/ucrt64/bin/libogfx.dll 2> /dev/null
rm -rf $MSYS_ROOT/ucrt64/lib/libogfx.dll.a 2> /dev/null
cp -r $RELEASE_DIR/include/ostd $MSYS_ROOT/ucrt64/include/
cp -r $RELEASE_DIR/include/ogfx $MSYS_ROOT/ucrt64/include/
cp -r $RELEASE_DIR/lib/libostd.dll $MSYS_ROOT/ucrt64/bin/
cp -r $RELEASE_DIR/lib/libostd.dll.a $MSYS_ROOT/ucrt64/lib/
cp -r $RELEASE_DIR/lib/libogfx.dll $MSYS_ROOT/ucrt64/bin/
cp -r $RELEASE_DIR/lib/libogfx.dll.a $MSYS_ROOT/ucrt64/lib/
fi

85
other/oss description.txt Normal file
View file

@ -0,0 +1,85 @@
Value Types:
Color:
#RRGGBB - Hex representation
#RRGGBBAA - Hex representation
rgb(r, g, b) - r, g and b are u8 values
rgb(r, g, b, a) - r, g, b and a are u8 values
color(...) - where ... can be any of the forms above. This form is already kind of deprecated, since it's useless, but supported anyways
All of the color representations support .lighten(f32) and .darken(f32) postfixes (This doesn't apply to the outer parenthesis of color(...), only to the raw hex/rgba representations))
Rect:
rect(f32, f32, f32, f32)
Vec2:
vec2(f32, f32)
Color Gradients:
gradienth(color - color - ...) - Where each <color> can be in any of the Color value-types described above
gradientv(color - color - ...) - Same as gradienth
w_gradienth(color - weight - color - ...) - same as gradienth/gradientv but between every two colors there is an f32 weight, the weights count is always one less than the colors count.
w_gradientv(color - weight - color - ...) - Same as w_gradienth
File:
file("file/path/string") - containing string literal of a file path
Animation:
anim(paramName: paramVal, paramName: paramVal, ...) - Where paramNames correspond to arbitrary parameters of the animation, and paramValue can either be an integer, an f32 or a bool
Integer/Float:
numeric literals like 1 or 1.0 for float
Bool:
true/false
String:
"String Literal"
Variables:
They are pattern-matched replaces, and are declared outside group selectors, in one of two forms:
$var1 = someValue - Where someValue is of a valid value-type
const $var2 = someValue - same as above, but non-mutable
They can be referenced anywhere a value-type can appear
Macros:
Parametrized function-like macros that expand at parse-time into their body.
They are declared as follows:
macro someMacro(param1, param2, param3 = someVal) {
someProperty = $param1;
someProperty2 = $param2;
}
They can have default values for parameters, and those default values can be of any value-type (including global variables).
They can also be inlined, like: macro test(param1, param2 = 4) { someValue = $param1; someOtherValue = $param2; }
Group Selectors:
They define theming blocks, and they are defined as follows:
([@theming_id] name[:qualifier]) {
someField = someValue
some_macro_call()
some_macro_call2(someParam1, someParam2)
someField2 = someValue2
(innerSelector) {
someInnerProperty = someValue
}
}
The square brackets are there only to signify optionality, and are not part of the syntax.
The body of the theme block is new-line-based, making the block non-inlineable.
Macro calls can only appear here, and as full lines in the body.
innerSelectors are only defined using a name, and don't support theme_id or qualifier
The stylesheet supports \ (backslash) truncated lines
Comments are inline and are defined using the % symbol
The language is case-insensitive

View file

@ -0,0 +1,470 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ContextMenu.hpp"
#include "../render/BasicRenderer.hpp"
#include "Window.hpp"
namespace ogfx
{
namespace gui
{
void ContextMenu::Entry::update_size(BasicRenderer2D& gfx, i32 fontSize)
{
size = { 0, 0 };
for (auto& entry : submenus)
{
auto s = gfx.getStringDimensions(entry.text, fontSize);
if (s.x > size.x)
size.x = s.x + (entry.submenus.size() > 0 ? 40 : 0);
size.y += s.y;
entry.update_size(gfx, fontSize);
}
}
ContextMenu& ContextMenu::create(void)
{
return *this;
}
void ContextMenu::applyTheme(const ostd::Stylesheet& theme)
{
setPadding(theme.get<Rectangle>("context.padding", getPadding(), {}, {}));
setItemSpacing(theme.get<f32>("context.itemSpacing", getItemSpacing(), {}, {}));
setFontSize(theme.get<i32>("context.fontSize", getFontSize(), {}, {}));
setBackgroundColor(theme.get<Color>("context.backgroundColor", getBackgroundColor(), {}, {}));
setSelectionColor(theme.get<Color>("context.selectionColor", getSelectionColor(), {}, {}));
setSelectionTextColor(theme.get<Color>("context.selectionTextColor", getSelectionTextColor(), {}, {}));
setSeparatorLineColor(theme.get<Color>("context.separatorLineColor", getSeparatorLineColor(), {}, {}));
setTextColor(theme.get<Color>("context.textColor", getTextColor(), {}, {}));
setSubmenuIndicatorColor(theme.get<Color>("context.submenuIndicatorColor", getSubmenuIndicatorColor(), {}, {}));
setBorderColor(theme.get<Color>("context.borderColor", getBorderColor(), {}, {}));
enableSelectionGradient(theme.get<bool>("context.useSelectionGradient", isSelectionGradientEnabled(), {}, {}));
setSelectionGradient(theme.get<ColorGradient>("context.selectionGradient", getSelectionGradient(), {}, {}));
enableOpenAnimation(theme.get<bool>("context.animateOpen", isOpenAnimationEnabled(), {}, {}));
setAnimationDelayMS(theme.get<i32>("context.animationDelay", getAnimationDelayMS(), {}, {}));
}
void ContextMenu::draw(BasicRenderer2D& gfx)
{
if (!m_visible) return;
for (const auto& panel : m_panels)
{
// Apply easing for a more natural feel
f32 t = panel.animProgress;
f32 eased = 1.0f - (1.0f - t) * (1.0f - t); // ease-out quadratic
f32 visibleHeight = panel.size.y * eased;
Rectangle clip {
panel.position.x,
panel.position.y,
panel.size.x,
visibleHeight
};
gfx.pushClippingRect(clip);
draw_panel(gfx, panel);
gfx.popClippingRect();
}
}
void ContextMenu::update(void)
{
if (!m_visible) return;
// Advance animation for any panel that isn't fully open yet.
f64 deltaMs = m_animClock.read(); // milliseconds since last update
m_animClock.restart(ostd::eTimeUnits::Milliseconds);
for (auto& panel : m_panels)
{
if (panel.animProgress < 1.0f)
{
panel.animProgress += (f32)(deltaMs / (f64)OpenAnimDurationMs);
if (panel.animProgress > 1.0f) panel.animProgress = 1.0f;
}
}
if (m_pendingOpenPanelDepth >= 0 && m_hoverOpenTimerActive && m_hoverOpenTimer.read() >= m_animationDelayMS)
{
Panel& parent = m_panels[m_pendingOpenPanelDepth];
i32 entryIdx = m_pendingOpenEntryIndex;
Entry& e = (*parent.entries)[entryIdx];
pop_to_depth(m_pendingOpenPanelDepth);
Rectangle r = entry_rect(parent, entryIdx);
Vec2 anchor { parent.position.x + parent.size.x, r.y };
push_panel(e.submenus, anchor, false);
m_panels[m_pendingOpenPanelDepth].openedSubmenuIndex = entryIdx;
m_hoverOpenTimer.endCount();
m_hoverOpenTimerActive = false;
m_pendingOpenPanelDepth = -1;
m_pendingOpenEntryIndex = -1;
}
if (m_hoverCloseTimerActive && m_hoverCloseTimer.read() >= SubmenuCloseDelayMs)
{
i32 keepDepth = -1;
for (i32 i = 0; i < (i32)m_panels.size(); ++i)
{
Rectangle r { m_panels[i].position, m_panels[i].size };
if (r.contains(m_mousePos, true)) keepDepth = i;
}
if (keepDepth >= 0)
{
pop_to_depth(keepDepth);
m_panels[keepDepth].openedSubmenuIndex = -1;
}
m_hoverCloseTimer.endCount();
m_hoverCloseTimerActive = false;
}
}
void ContextMenu::onMouseReleased(const Event& event)
{
if (!m_visible) return;
Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
for (i32 i = (i32)m_panels.size() - 1; i >= 0; --i)
{
i32 idx = entry_index_at(m_panels[i], mousePos);
if (idx >= 0)
{
Entry& e = (*m_panels[i].entries)[idx];
if (e.submenus.empty())
{
if (m_data.onActivate && e.id >= 0)
m_data.onActivate(e);
hide();
event.handle();
hide();
}
else
{
pop_to_depth(i);
Rectangle r = entry_rect(m_panels[i], idx);
Vec2 anchor { m_panels[i].position.x + m_panels[i].size.x, r.y };
push_panel(e.submenus, anchor, false);
m_panels[i].openedSubmenuIndex = idx;
}
event.handle();
return;
}
}
// hide();
event.handle();
}
void ContextMenu::onMouseMoved(const Event& event)
{
if (!m_visible) return;
Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
m_mousePos = mousePos;
i32 hitPanel = -1;
i32 hitEntry = -1;
for (i32 i = (i32)m_panels.size() - 1; i >= 0; --i)
{
i32 idx = entry_index_at(m_panels[i], mousePos);
if (idx >= 0) { hitPanel = i; hitEntry = idx; break; }
Rectangle panelRect { m_panels[i].position, m_panels[i].size };
if (panelRect.contains(mousePos, true)) { hitPanel = i; hitEntry = -1; break; }
}
for (i32 i = 0; i < (i32)m_panels.size(); ++i)
m_panels[i].hoveredIndex = (i == hitPanel ? hitEntry : -1);
if (hitPanel < 0)
{
if (!m_hoverCloseTimerActive)
{
m_hoverCloseTimer.startCount(ostd::eTimeUnits::Milliseconds);
m_hoverCloseTimerActive = true;
}
if (m_hoverOpenTimerActive)
{
m_hoverOpenTimer.endCount();
m_hoverOpenTimerActive = false;
}
m_pendingOpenPanelDepth = -1;
event.handle();
return;
}
if ((i32)m_panels.size() - 1 > hitPanel)
{
if (hitEntry != m_panels[hitPanel].openedSubmenuIndex)
{
if (!m_hoverCloseTimerActive)
{
m_hoverCloseTimer.startCount(ostd::eTimeUnits::Milliseconds);
m_hoverCloseTimerActive = true;
}
}
else
{
if (m_hoverCloseTimerActive)
{
m_hoverCloseTimer.endCount();
m_hoverCloseTimerActive = false;
}
}
}
else
{
if (m_hoverCloseTimerActive)
{
m_hoverCloseTimer.endCount();
m_hoverCloseTimerActive = false;
}
}
if (hitEntry >= 0)
{
Entry& e = (*m_panels[hitPanel].entries)[hitEntry];
bool hasChildren = e.submenus.size() > 0;
bool alreadyOpen = (m_panels[hitPanel].openedSubmenuIndex == hitEntry) && ((i32)m_panels.size() - 1 > hitPanel);
if (hasChildren && !alreadyOpen)
{
if (m_pendingOpenPanelDepth != hitPanel || m_pendingOpenEntryIndex != hitEntry)
{
m_pendingOpenPanelDepth = hitPanel;
m_pendingOpenEntryIndex = hitEntry;
m_hoverOpenTimer.startCount(ostd::eTimeUnits::Milliseconds);
m_hoverOpenTimerActive = true;
}
}
else
{
if (m_hoverOpenTimerActive)
{
m_hoverOpenTimer.endCount();
m_hoverOpenTimerActive = false;
}
m_pendingOpenPanelDepth = -1;
}
}
event.handle();
}
void ContextMenu::onMousePressed(const Event& event)
{
if (!m_visible) return;
Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
for (i32 i = (i32)m_panels.size() - 1; i >= 0; --i)
{
i32 idx = entry_index_at(m_panels[i], mousePos);
if (idx >= 0)
{
event.handle();
return;
}
}
hide();
event.handle();
}
void ContextMenu::onMouseScrolled(const Event& event)
{
event.handle();
}
void ContextMenu::show(const Vec2& pos)
{
m_panels.clear();
push_panel(m_data.entries, pos, false);
m_visible = true;
m_animClock.startCount(ostd::eTimeUnits::Milliseconds);
m_window.getFocusManager().clearFocus();
}
void ContextMenu::hide(void)
{
m_panels.clear();
m_hoverOpenTimer.endCount();
m_hoverCloseTimer.endCount();
m_hoverOpenTimerActive = false;
m_hoverCloseTimerActive = false;
m_pendingOpenPanelDepth = -1;
m_pendingOpenEntryIndex = -1;
m_animClock.endCount();
m_visible = false;
}
void ContextMenu::setInstance(const Instance& instance)
{
m_data = instance;
m_panels.clear();
m_pendingOpenPanelDepth = -1;
m_pendingOpenEntryIndex = -1;
m_hoverOpenTimer.endCount();
m_hoverCloseTimer.endCount();
if (m_visible)
push_panel(m_data.entries, getPosition(), false);
}
void ContextMenu::setFontSize(i32 size)
{
m_fontSize = size;
relayout_panels();
}
void ContextMenu::push_panel(stdvec<Entry>& entries, const Vec2& anchorTopLeft, bool flipLeft)
{
Panel p;
p.entries = &entries;
p.position = anchorTopLeft;
compute_panel_size(p);
f32 windowW = (f32)m_window.getWindowWidth();
f32 windowH = (f32)m_window.getWindowHeight();
if (flipLeft && !m_panels.empty())
{
// anchorTopLeft was computed assuming "open right"; flip to open left
p.position.x = m_panels.back().position.x - p.size.x;
}
else if (p.position.x + p.size.x > windowW && !m_panels.empty())
{
p.position.x = m_panels.back().position.x - p.size.x;
}
if (p.position.y + p.size.y > windowH)
p.position.y = windowH - p.size.y;
if (p.position.y < 0) p.position.y = 0;
if (p.position.x < 0) p.position.x = 0;
p.animProgress = m_animateOpen ? 0.0f : 1.0f;
m_panels.push_back(p);
}
void ContextMenu::compute_panel_size(Panel& panel)
{
panel.size = { 0, 0 };
auto& gfx = m_window.getGFX();
for (auto& entry : *panel.entries)
{
auto s = gfx.getStringDimensions(entry.text, getFontSize());
f32 extra = (entry.submenus.size() > 0 ? s.y : 0);
if (s.x + extra > panel.size.x)
panel.size.x = s.x + extra;
panel.size.y += s.y + m_spacing;
panel.entryHeight = s.y + m_spacing;
}
panel.size.x += m_padding.x + m_padding.w;
panel.size.y += m_padding.y + m_padding.h;
// Enforce minimum width from the Instance — applies to root only. // NEW
// Submenus sit naturally to the right; forcing them wider tends to look // NEW
// odd and can cause needless edge-flipping. // NEW
if (panel.entries == &m_data.entries && panel.size.x < m_data.minWidth) // NEW
panel.size.x = m_data.minWidth; // NEW
}
void ContextMenu::pop_to_depth(size_t depth)
{
if (depth + 1 < m_panels.size())
m_panels.resize(depth + 1);
}
Rectangle ContextMenu::entry_rect(const Panel& panel, i32 index) const
{
return {
panel.position.x,
panel.position.y + m_padding.y + (index * panel.entryHeight),
panel.size.x,
panel.entryHeight
};
}
i32 ContextMenu::entry_index_at(const Panel& panel, const Vec2& mousePos) const
{
Rectangle panelRect { panel.position, panel.size };
if (!panelRect.contains(mousePos, true)) return -1;
for (i32 i = 0; i < (i32)panel.entries->size(); ++i)
{
if (entry_rect(panel, i).contains(mousePos, true))
return i;
}
return -1;
}
void ContextMenu::relayout_panels(void)
{
for (auto& panel : m_panels)
compute_panel_size(panel);
}
void ContextMenu::draw_panel(BasicRenderer2D& gfx, const Panel& panel)
{
gfx.fillRect({ panel.position, panel.size }, getBackgroundColor());
const f32 triPad = 14;
f32 y = 0;
i32 i = 0;
auto& entries = *panel.entries;
for (auto& entry : entries)
{
Vec2 entryPos = panel.position + Vec2 { m_padding.x, y + m_padding.y };
Rectangle rect = { entryPos - Vec2 { m_padding.x, 0 }, panel.size.x, panel.entryHeight };
bool highlighted = (panel.hoveredIndex == i)
|| (panel.openedSubmenuIndex == i);
if (highlighted)
{
if (isSelectionGradientEnabled())
gfx.fillGradientRect(rect, m_selectionGradient);
else
gfx.fillRect(rect, getSelectionColor());
gfx.drawRect(rect, getSeparatorLineColor(), 1, false, false, i != entries.size() , false);
gfx.drawVCenteredString(entry.text, { entryPos, panel.size.x, panel.entryHeight }, getSelectionTextColor(), getFontSize());
}
else
{
gfx.drawRect(rect, getSeparatorLineColor(), 1, false, false, i != entries.size() , false);
gfx.drawVCenteredString(entry.text, { entryPos, panel.size.x, panel.entryHeight }, getTextColor(), getFontSize());
}
if (entry.submenus.size() > 0)
{
Triangle tri {
{ rect.x + rect.w - panel.entryHeight + triPad, rect.y + (triPad * 0.5f) },
{ rect.x + rect.w - (triPad * 0.5f), rect.y + (rect.h * 0.5f) },
{ rect.x + rect.w - panel.entryHeight + triPad, rect.y + rect.h - (triPad * 0.5f) }
};
gfx.fillTriangle(tri, getSubmenuIndicatorColor());
}
y += panel.entryHeight;
i++;
}
gfx.drawRect({ panel.position, panel.size }, getBorderColor(), 2);
}
}
}

View file

@ -0,0 +1,194 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class ContextMenu : public Rectangle
{
public: struct Entry
{
inline Entry(const String& t, i32 id = -1, const stdvec<Entry>& sub = {}) { text = t; this->id = id; submenus = sub; }
inline Entry(const String& t, const stdvec<Entry>& sub) { text = t; id = -1; submenus = sub; }
String text { "" };
i32 id { -1 };
stdvec<Entry> submenus;
private:
Vec2 size;
void update_size(BasicRenderer2D& gfx, i32 fontSize);
friend class ContextMenu;
};
public: struct Instance
{
using Callback = std::function<void(const Entry&)>;
stdvec<Entry> entries;
Callback onActivate { nullptr };
f32 minWidth { 0 };
// Remove first entry whose text matches. Returns true if something was removed.
inline bool removeByText(const String& text, bool recursive = true)
{
return remove_if(entries, [&](const Entry& e) { return e.text == text; }, recursive);
}
// Remove first entry whose id matches. ID -1 is treated as "no id" and is skipped.
inline bool removeById(i32 id, bool recursive = true)
{
if (id < 0) return false;
return remove_if(entries, [&](const Entry& e) { return e.id == id; }, recursive);
}
// Remove ALL matches (handy if you reuse text/ids across submenus).
inline size_t removeAllByText(const String& text, bool recursive = true)
{
return remove_all_if(entries, [&](const Entry& e) { return e.text == text; }, recursive);
}
inline size_t removeAllById(i32 id, bool recursive = true)
{
if (id < 0) return 0;
return remove_all_if(entries, [&](const Entry& e) { return e.id == id; }, recursive);
}
private:
template <typename Pred>
static bool remove_if(stdvec<Entry>& list, Pred pred, bool recursive)
{
for (auto it = list.begin(); it != list.end(); ++it)
{
if (pred(*it)) { list.erase(it); return true; }
}
if (!recursive) return false;
for (auto& e : list)
{
if (remove_if(e.submenus, pred, true)) return true;
}
return false;
}
template <typename Pred>
static size_t remove_all_if(stdvec<Entry>& list, Pred pred, bool recursive)
{
size_t count = 0;
for (auto it = list.begin(); it != list.end(); )
{
if (pred(*it)) { it = list.erase(it); ++count; }
else ++it;
}
if (recursive)
{
for (auto& e : list)
count += remove_all_if(e.submenus, pred, true);
}
return count;
}
};
private: struct Panel
{
stdvec<Entry>* entries { nullptr };
Vec2 position { 0, 0 };
Vec2 size { 0, 0 };
f32 entryHeight { 0 };
i32 hoveredIndex { -1 };
i32 openedSubmenuIndex { -1 };
f32 animProgress { 0 }; // 0.0 = collapsed, 1.0 = fully open
};
public:
inline ContextMenu(Window& window) : m_window(window) { create(); }
ContextMenu& create(void);
void applyTheme(const ostd::Stylesheet& theme);
void draw(BasicRenderer2D& gfx);
void update(void);
void onMouseReleased(const Event& event);
void onMouseMoved(const Event& event);
void onMousePressed(const Event& event);
void onMouseScrolled(const Event& event);
void show(const Vec2& pos);
void hide(void);
void setInstance(const Instance& instance);
void setFontSize(i32 size);
inline bool isVisible(void) const { return m_visible; }
inline i32 getFontSize(void) const { return m_fontSize; }
OSTD_PARAM_GETSET(Rectangle, Padding, m_padding);
OSTD_PARAM_GETSET(f32, ItemSpacing, m_spacing);
OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor);
OSTD_PARAM_GETSET(Color, SelectionColor, m_selectionColor);
OSTD_PARAM_GETSET(Color, SelectionTextColor, m_selectionTextColor);
OSTD_PARAM_GETSET(Color, SeparatorLineColor, m_separatorLineColor);
OSTD_PARAM_GETSET(Color, TextColor, m_textColor);
OSTD_PARAM_GETSET(Color, SubmenuIndicatorColor, m_submenuIndicatorColor);
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
OSTD_PARAM_GETSET(ColorGradient, SelectionGradient, m_selectionGradient);
OSTD_BOOL_PARAM_GETSET_E(SelectionGradient, m_useSelectionGradient);
OSTD_BOOL_PARAM_GETSET_E(OpenAnimation, m_animateOpen);
OSTD_PARAM_GETSET(u64, AnimationDelayMS, m_animationDelayMS);
private:
void push_panel(stdvec<Entry>& entries, const Vec2& anchorTopLeft, bool flipLeft = false);
void pop_to_depth(size_t depth);
void compute_panel_size(Panel& panel);
i32 entry_index_at(const Panel& panel, const Vec2& mousePos) const;
Rectangle entry_rect(const Panel& panel, i32 index) const;
void draw_panel(BasicRenderer2D& gfx, const Panel& panel);
void relayout_panels(void);
private:
Window& m_window;
bool m_visible { false };
Instance m_data;
f32 m_entryHeight { 0 };
Vec2 m_mousePos { 0, 0 };
ostd::Counter m_animClock;
stdvec<Panel> m_panels;
i32 m_pendingOpenPanelDepth { -1 };
i32 m_pendingOpenEntryIndex { -1 };
ostd::Counter m_hoverOpenTimer;
ostd::Counter m_hoverCloseTimer;
bool m_hoverOpenTimerActive { false };
bool m_hoverCloseTimerActive { false };
Rectangle m_padding { 16, 0, 16, 0 };
f32 m_spacing { 8 };
i32 m_fontSize { 18 };
Color m_backgroundColor { "#6B0A1DFF" };
Color m_selectionColor { "#DC143CFF" };
Color m_selectionTextColor { "#F16A85FF" };
Color m_separatorLineColor { "#700000FF" };
Color m_textColor { "#F16A85FF" };
Color m_submenuIndicatorColor { "#111111FF" };
Color m_borderColor { "#400000FF" };
bool m_useSelectionGradient { true };
ColorGradient m_selectionGradient { { "#C21135FF", "#820B23FF" }, { 1.0f } };
bool m_animateOpen { true };
u64 m_animationDelayMS { 300 };
static constexpr u64 SubmenuCloseDelayMs { 50 };
static constexpr u64 OpenAnimDurationMs { 120 };
friend class Instance;
};
}
}

181
src/ogfx/gui/Events.hpp Normal file
View file

@ -0,0 +1,181 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/BaseObject.hpp>
#include <ostd/math/Geometry.hpp>
#include <ogfx/utils/Keycodes.hpp>
namespace ogfx
{
class WindowCore;
namespace gui
{
class Widget;
}
class WindowResizedData : public ostd::BaseObject
{
public:
inline WindowResizedData(WindowCore& parent, i32 _oldx, i32 _oldy, i32 _newx, i32 _newy) : parentWindow(parent), old_width(_oldx), old_height(_oldy), new_width(_newx), new_height(_newy)
{
setTypeName("ogfx::WindowResizedData");
validate();
}
public:
i32 new_width;
i32 new_height;
i32 old_width;
i32 old_height;
WindowCore& parentWindow;
};
class MouseEventData : public ostd::BaseObject
{
public: enum class eButton { None = 0, Left, Middle, Right };
public: enum class eScrollDirection { None = 0, Up, Down, Left, Right };
public:
inline MouseEventData(WindowCore& parent, f32 mousex, f32 mousey, eButton btn) : parentWindow(parent), position_x(mousex), position_y(mousey), button(btn)
{
setTypeName("ogfx::MouseEventData");
validate();
}
public:
f32 position_x;
f32 position_y;
eButton button;
eScrollDirection scroll { eScrollDirection::None };
Vec2 scrollAmount { 0 };
gui::Widget* mousePressedOnWidget { nullptr };
WindowCore& parentWindow;
};
class KeyEventData : public ostd::BaseObject
{
public: struct KeyModifiers
{
bool lshift { false };
bool rshift { false };
bool ctrl { false };
bool alt { false };
bool meta { false }; // Win/Cmd/Super
inline bool any(void) const { return rshift || lshift || ctrl || alt || meta; }
inline bool anyShift(void) const { return lshift || rshift; }
inline bool shiftOnly(void) const { return (lshift || rshift) && !ctrl && !alt && !meta; }
inline bool ctrlOnly(void) const { return ctrl && !lshift && !rshift && !alt && !meta; }
inline bool metaOnly(void) const { return meta && !lshift && !rshift && !alt && !ctrl; }
inline bool altOnly(void) const { return alt && !lshift && !rshift && !meta && !ctrl; }
inline bool primaryOnly(void) const
{
#ifdef MAC_OS
return metaOnly();
#else
return ctrlOnly();
#endif
}
inline bool primaryShiftOnly(void) const
{
#ifdef MAC_OS
return meta && anyShift() && !ctrl && !alt;
#else
return ctrl && anyShift() && !meta && !alt;
#endif
}
inline bool primaryAltOnly(void) const
{
#ifdef MAC_OS
return meta && alt && !ctrl && !anyShift();
#else
return ctrl && alt && !ctrl && !anyShift();
#endif
}
inline bool shiftAltOnly(void) const
{
return alt && anyShift() && !ctrl && !meta;
}
inline bool primary(void) const
{
#ifdef MAC_OS
return meta;
#else
return ctrl;
#endif
}
};
public: enum class eKeyEvent { Pressed = 0, Released, Text };
public:
inline KeyEventData(WindowCore& parent, i32 key, const String& _text, eKeyEvent evt, const KeyModifiers& mod) : parentWindow(parent), keyCode(key), text(_text), eventType(evt), modifiers(mod)
{
setTypeName("ogfx::KeyEventData");
validate();
}
inline bool isCopy(void) const { return eventType == eKeyEvent::Pressed && modifiers.primaryOnly() && keyCode == KeyCode::C; }
inline bool isCut(void) const { return eventType == eKeyEvent::Pressed && modifiers.primaryOnly() && keyCode == KeyCode::X; }
inline bool isPaste(void) const { return eventType == eKeyEvent::Pressed && modifiers.primaryOnly() && keyCode == KeyCode::V; }
inline bool isSelectAll(void) const { return eventType == eKeyEvent::Pressed && modifiers.primaryOnly() && keyCode == KeyCode::A; }
inline bool isUndo(void) const { return eventType == eKeyEvent::Pressed && modifiers.primaryOnly() && keyCode == KeyCode::Z; }
inline bool isRedo(void) const { return eventType == eKeyEvent::Pressed && ((modifiers.primaryShiftOnly() && keyCode == KeyCode::Z) || (modifiers.primaryOnly() && keyCode == KeyCode::Y)); }
public:
i32 keyCode;
String text;
eKeyEvent eventType;
KeyModifiers modifiers;
WindowCore& parentWindow;
};
class DropEventData : public ostd::BaseObject
{
public: enum class eDropType { None = 0, File, Text, InApp };
public:
inline DropEventData(WindowCore& parent, eDropType type) : parentWindow(parent), dropType(type)
{
setTypeName("ogfx::DropEventData");
validate();
}
public:
eDropType dropType;
WindowCore& parentWindow;
String textOrFilePath { "" };
ostd::BaseObject* userObject { nullptr };
};
namespace gui
{
class Event
{
public:
inline Event(WindowCore& _window) : window(_window), drop(window, DropEventData::eDropType::None) { }
inline void handle(void) const { m_handled = true; }
inline bool isHandled(void) const { return m_handled; }
public:
WindowCore& window;
WindowResizedData* windowResized { nullptr };
MouseEventData* mouse { nullptr };
KeyEventData* keyboard { nullptr };
DropEventData drop;
u32 __original_signal_id { 0 };
private:
mutable bool m_handled { false };
};
}
}

View file

@ -0,0 +1,165 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "FocusManager.hpp"
#include "Window.hpp"
#include "../utils/Keycodes.hpp"
namespace ogfx
{
namespace gui
{
FocusManager::FocusManager(Window& window) : m_window(window)
{
}
bool FocusManager::requestFocus(Widget& widget)
{
if (&widget == m_focused) return true;
if (!widget.isFocusEnabled()) return false;
if (m_window.getToolBar().isVisible())
{
if (requestFocus(widget, m_window.getToolBar()))
return true;
}
if (m_window.getStatusBar().isVisible())
{
if (requestFocus(widget, m_window.getStatusBar()))
return true;
}
return requestFocus(widget, m_window.m_rootWidget);
}
bool FocusManager::requestFocus(Widget& widget, Widget& root)
{
if (&widget == m_focused) return true;
if (!widget.isFocusEnabled()) return false;
if (widget.isInvalid()) return false;
if (!widget.isVisible()) return false;
if (!root.hasChildren() || !root.isChildrenEnabled())
return false;
if (!root.getChildren().hasWidget(widget))
{
for (auto& child : root.getChildren().m_widgetList)
{
if (child && requestFocus(widget, *child))
return true;
}
return false;
}
if (m_focused)
{
m_focused->m_focused = false;
if (m_focused->callback_onFocusLost)
m_focused->callback_onFocusLost(Event(m_window));
m_focused->onFocusLost(Event(m_window));
m_focused->setThemeQualifier("focused", false);
}
widget.m_focused = true;
if (widget.callback_onFocusGained)
widget.callback_onFocusGained(Event(m_window));
widget.onFocusGained(Event(m_window));
widget.setThemeQualifier("focused", true);
m_focused = &widget;
return true;
}
Widget* FocusManager::focusNext(void)
{
if (m_tabIndexMap.empty())
return nullptr;
i32 startIndex = -1, smallest = i32Range::max();
if (m_focused)
startIndex = m_focused->m_tabIndex;
i32 nextIndex = i32Range::max();
Widget* nextPtr = nullptr;
Widget* smallestPtr = nullptr;
for (auto&[wptr, index] : m_tabIndexMap)
{
if (index < smallest)
{
smallest = index;
smallestPtr = wptr;
}
if (index > startIndex && index < nextIndex)
{
nextIndex = index;
nextPtr = wptr;
}
}
Widget* w = nullptr;
if (!nextPtr)
{
if (!smallestPtr)
return nullptr;
w = smallestPtr;
}
else
w = nextPtr;
requestFocus(*w);
return w;
}
bool FocusManager::registerTabIndex(Widget& widget, i32 index)
{
if (index >= 0)
{
m_tabIndexMap[&widget] = index;
return true;
}
return false;
}
void FocusManager::unregisterTabIndex(Widget& widget)
{
if (m_tabIndexMap.count(&widget) > 0)
m_tabIndexMap.erase(m_tabIndexMap.find(&widget));
}
void FocusManager::clearFocus(void)
{
if (m_focused)
{
m_focused->m_focused = false;
m_focused = nullptr;
}
}
void FocusManager::onKeyReleased(const Event& event)
{
if (isTabNavigationEnabled() && event.keyboard->keyCode == KeyCode::Tab)
focusNext();
else if (event.keyboard->keyCode == KeyCode::Space || event.keyboard->keyCode == KeyCode::Return || event.keyboard->keyCode == KeyCode::Return2 || event.keyboard->keyCode == KeyCode::KpEnter)
{
if (!m_focused)
return;
if (m_focused->callback_onActionPerformed)
m_focused->callback_onActionPerformed(event);
}
}
}
}

View file

@ -0,0 +1,55 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class Window;
class FocusManager
{
public:
FocusManager(Window& window);
bool requestFocus(Widget& widget, Widget& root);
bool requestFocus(Widget& widget);
Widget* focusNext(void);
bool registerTabIndex(Widget& widget, i32 index);
void unregisterTabIndex(Widget& widget);
void clearFocus(void);
void onKeyReleased(const Event& event);
inline Widget* getFocused(void) { return m_focused; }
OSTD_BOOL_PARAM_GETSET_E(TabNavigation, m_tabNavigationEnabled);
private:
Window& m_window;
Widget* m_focused { nullptr };
bool m_tabNavigationEnabled { true };
stdumap<Widget*, i32> m_tabIndexMap;
};
}
}

332
src/ogfx/gui/Layout.cpp Normal file
View file

@ -0,0 +1,332 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Layout.hpp"
#include "widgets/Widget.hpp"
#include <algorithm>
namespace ogfx
{
namespace gui
{
// ============================================================================
// Layout — shared helpers
// ============================================================================
Vec2 Layout::resolvePreferred(const Widget& child)
{
const auto& h = child.layoutHint();
Vec2 pref;
pref.x = (h.preferred.x < 0) ? child.getw() : h.preferred.x;
pref.y = (h.preferred.y < 0) ? child.geth() : h.preferred.y;
return pref;
}
f32 Layout::applyMinMax(f32 value, f32 minV, f32 maxV)
{
if (value < minV) value = minV;
if (maxV >= 0 && value > maxV) value = maxV;
return value;
}
f32 Layout::alignOffset(eAlign a, f32 available, f32 size)
{
switch (a)
{
case eAlign::Center: return (available - size) * 0.5f;
case eAlign::End: return available - size;
case eAlign::Start:
case eAlign::Stretch: // when stretched the caller already used `available`
default: return 0.0f;
}
}
// Iterate the children we actually care about: visible, valid, not ignored,
// not "ignore-scroll" (scrollbars and friends), and not manually drawn.
// This is a small inline helper to avoid repeating the predicate across
// every layout subclass below.
namespace
{
template<typename F>
void for_each_laid_out(const Widget& parent, F&& fn)
{
auto& mgr = const_cast<Widget&>(parent).getChildren();
for (auto* c : mgr.getWidgets())
{
if (!c) continue;
if (c->isInvalid()) continue;
if (!c->isVisible()) continue;
if (c->isIgnoreScrollEnabled()) continue; // scrollbars etc.
if (c->layoutHint().ignored) continue;
fn(*c);
}
}
}
// ============================================================================
// FillLayout
// ============================================================================
void FillLayout::arrange(Widget& parent)
{
// IMPORTANT: child positions are LOCAL to the parent's content frame.
// getGlobalPosition() automatically adds the parent's padding and
// contentOffset, so we must NOT include them here or they'd be
// applied twice. We only use the SIZE from getContentBounds().
const Vec2 size = parent.getContentBounds().getSize();
for_each_laid_out(parent, [&](Widget& c) {
c.setPosition({ 0, 0 });
c.setSize(size);
c.onBoundsChanged(size);
});
}
Vec2 FillLayout::measure(const Widget& parent) const
{
// Fill wants whatever the parent gives it; report the largest child
// preferred size as a hint for nested measure passes.
Vec2 result { 0, 0 };
for_each_laid_out(parent, [&](Widget& c) {
Vec2 pref = resolvePreferred(c);
result.x = std::max(result.x, pref.x);
result.y = std::max(result.y, pref.y);
});
return result;
}
// ============================================================================
// BoxLayout
// ============================================================================
void BoxLayout::arrange(Widget& parent)
{
const bool horiz = (m_orientation == Orientation::Horizontal);
// IMPORTANT: child positions are LOCAL to the parent's content frame.
// getGlobalPosition() auto-adds parent's padding and contentOffset,
// so we use only the SIZE from getContentBounds() and start child
// positions at (0, 0). Adding area.x/area.y here would double-count.
const Vec2 areaSize = parent.getContentBounds().getSize();
const f32 totalMain = horiz ? areaSize.x : areaSize.y;
const f32 totalCross = horiz ? areaSize.y : areaSize.x;
// --- Pass 1: gather children, classify as "fixed" vs "stretching",
// and accumulate fixed-size + total stretch weight.
struct Slot { Widget* w; f32 main; f32 cross; bool stretches; f32 stretch;
f32 mainMin, mainMax; f32 crossMin, crossMax; };
stdvec<Slot> slots;
slots.reserve(8);
f32 fixedMain = 0.0f;
f32 stretchTotal = 0.0f;
for_each_laid_out(parent, [&](Widget& c) {
const auto& h = c.layoutHint();
const Vec2 pref = resolvePreferred(c);
Slot s;
s.w = &c;
s.main = horiz ? pref.x : pref.y;
s.cross = horiz ? pref.y : pref.x;
s.stretches = (h.stretch > 0.0f);
s.stretch = h.stretch;
s.mainMin = horiz ? h.minSize.x : h.minSize.y;
s.mainMax = horiz ? h.maxSize.x : h.maxSize.y;
s.crossMin = horiz ? h.minSize.y : h.minSize.x;
s.crossMax = horiz ? h.maxSize.y : h.maxSize.x;
if (!s.stretches)
{
s.main = applyMinMax(s.main, s.mainMin, s.mainMax);
fixedMain += s.main;
}
else
{
stretchTotal += s.stretch;
}
slots.push_back(s);
});
if (slots.empty())
return;
const f32 spacingTotal = m_spacing * std::max<f32>(0, (f32)slots.size() - 1);
f32 leftover = std::max(0.0f, totalMain - fixedMain - spacingTotal);
// --- Pass 2: distribute leftover among stretching children.
// We may need multiple rounds: if a stretcher hits its maxSize cap,
// we lock it and redistribute the residual among the rest.
stdvec<bool> locked(slots.size(), false);
for (size_t i = 0; i < slots.size(); ++i)
if (!slots[i].stretches) locked[i] = true;
while (stretchTotal > 0.0f && leftover > 0.0f)
{
bool changed = false;
f32 newLeftover = leftover;
f32 newStretchTotal = stretchTotal;
for (size_t i = 0; i < slots.size(); ++i)
{
if (locked[i]) continue;
f32 share = leftover * (slots[i].stretch / stretchTotal);
f32 capped = applyMinMax(share, slots[i].mainMin, slots[i].mainMax);
if (capped < share) // hit the cap
{
slots[i].main = capped;
newLeftover -= capped;
newStretchTotal -= slots[i].stretch;
locked[i] = true;
changed = true;
}
else
{
slots[i].main = share; // tentative; may be re-revised next round
}
}
if (!changed) break; // no caps hit -> stable
leftover = newLeftover;
stretchTotal = newStretchTotal;
}
// --- Pass 3: place children.
f32 cursor = 0.0f; // local to parent's content frame
for (auto& s : slots)
{
const auto& h = s.w->layoutHint();
// Cross size: stretch fills, otherwise use child preferred (clamped).
f32 crossSize;
if (h.alignCross == eAlign::Stretch)
crossSize = totalCross;
else
crossSize = applyMinMax(s.cross, s.crossMin, s.crossMax);
crossSize = std::min(crossSize, totalCross);
const f32 crossOff = alignOffset(h.alignCross, totalCross, crossSize);
if (horiz)
{
s.w->setPosition(cursor, crossOff);
s.w->setSize(s.main, crossSize);
}
else
{
s.w->setPosition(crossOff, cursor);
s.w->setSize(crossSize, s.main);
}
s.w->onBoundsChanged(s.w->getSize());
cursor += s.main + m_spacing;
}
}
Vec2 BoxLayout::measure(const Widget& parent) const
{
const bool horiz = (m_orientation == Orientation::Horizontal);
f32 mainSum = 0, crossMax = 0;
i32 count = 0;
for_each_laid_out(parent, [&](Widget& c) {
Vec2 pref = resolvePreferred(c);
f32 m = horiz ? pref.x : pref.y;
f32 cr = horiz ? pref.y : pref.x;
mainSum += m;
crossMax = std::max(crossMax, cr);
++count;
});
mainSum += m_spacing * std::max(0, count - 1);
return horiz ? Vec2 { mainSum, crossMax } : Vec2 { crossMax, mainSum };
}
// ============================================================================
// GridLayout
// ============================================================================
void GridLayout::arrange(Widget& parent)
{
if (m_rows == 0 || m_cols == 0) return;
// IMPORTANT: child positions are LOCAL to the parent's content frame.
// getGlobalPosition() auto-adds parent's padding and contentOffset,
// so we use only the SIZE from getContentBounds() here.
const Vec2 areaSize = parent.getContentBounds().getSize();
const f32 cellW = (areaSize.x - m_spacing * (m_cols - 1)) / (f32)m_cols;
const f32 cellH = (areaSize.y - m_spacing * (m_rows - 1)) / (f32)m_rows;
u32 cellIndex = 0;
const u32 maxCells = m_rows * m_cols;
for_each_laid_out(parent, [&](Widget& c) {
if (cellIndex >= maxCells) return; // overflow — drop
const auto& h = c.layoutHint();
const u32 col = cellIndex % m_cols;
const u32 row = cellIndex / m_cols;
const u32 colSpan = std::max<u32>(1, h.colSpan);
const u32 rowSpan = std::max<u32>(1, h.rowSpan);
const f32 slotW = cellW * colSpan + m_spacing * (colSpan - 1);
const f32 slotH = cellH * rowSpan + m_spacing * (rowSpan - 1);
const f32 slotX = col * (cellW + m_spacing); // local to content frame
const f32 slotY = row * (cellH + m_spacing);
// Apply alignment within the cell slot if the child isn't stretching.
Vec2 size { slotW, slotH };
if (h.alignMain != eAlign::Stretch || h.alignCross != eAlign::Stretch)
{
Vec2 pref = resolvePreferred(c);
if (h.alignMain != eAlign::Stretch) size.x = std::min(pref.x, slotW);
if (h.alignCross != eAlign::Stretch) size.y = std::min(pref.y, slotH);
}
const f32 offX = (h.alignMain == eAlign::Stretch) ? 0 : alignOffset(h.alignMain, slotW, size.x);
const f32 offY = (h.alignCross == eAlign::Stretch) ? 0 : alignOffset(h.alignCross, slotH, size.y);
c.setPosition(slotX + offX, slotY + offY);
c.setSize(size);
c.onBoundsChanged(size);
cellIndex += colSpan;
});
}
Vec2 GridLayout::measure(const Widget& parent) const
{
// Find the largest preferred size among children; report grid total.
f32 maxW = 0, maxH = 0;
for_each_laid_out(parent, [&](Widget& c) {
Vec2 pref = resolvePreferred(c);
maxW = std::max(maxW, pref.x);
maxH = std::max(maxH, pref.y);
});
const f32 totalW = maxW * m_cols + m_spacing * (m_cols - 1);
const f32 totalH = maxH * m_rows + m_spacing * (m_rows - 1);
return { totalW, totalH };
}
}
}

145
src/ogfx/gui/Layout.hpp Normal file
View file

@ -0,0 +1,145 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/LayoutHint.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/utils/Defines.hpp>
namespace ogfx
{
namespace gui
{
class Widget;
// ============================================================================
// Layout — abstract base.
//
// A Layout is owned by a Widget (the "container") and is responsible for
// positioning and sizing that widget's children inside the container's
// content bounds (i.e. inside padding, after content offset).
//
// Two responsibilities:
// - arrange(parent): set position + size on every laid-out child.
// - measure(parent): report how much space this layout *wants* given
// its current children. Used so a Layout can drive
// a parent's contentExtents (and therefore scrollbars),
// and so a parent Layout can ask a child container
// "how big would you like to be?".
// ============================================================================
class Layout
{
public:
virtual ~Layout(void) = default;
// Place children. Called from Widget::relayout().
virtual void arrange(Widget& parent) = 0;
// Compute desired size of this layout's content. Should NOT mutate
// children. Used by Widget::getContentExtents() override below and
// by parent layouts asking for preferred size.
virtual Vec2 measure(const Widget& parent) const = 0;
// Spacing between adjacent items along the main axis (Box) or
// between cells (Grid). FillLayout ignores it.
inline f32 getSpacing(void) const { return m_spacing; }
inline void setSpacing(f32 s) { m_spacing = s; }
protected:
// Helpers used by concrete layouts to honor a child's hint
// without each subclass re-implementing the same min/max/preferred
// resolution logic.
static Vec2 resolvePreferred(const Widget& child);
static f32 applyMinMax(f32 value, f32 minV, f32 maxV);
static f32 alignOffset(eAlign a, f32 available, f32 size);
f32 m_spacing { 0.0f };
};
// ============================================================================
// FillLayout — every laid-out child is sized to fill the parent's content
// area. Useful as a default (a "single slot" container) and as a building
// block for things like SplitView panels.
// ============================================================================
class FillLayout : public Layout
{
public:
void arrange(Widget& parent) override;
Vec2 measure(const Widget& parent) const override;
};
// ============================================================================
// BoxLayout — arrange children in a single row (Horizontal) or column
// (Vertical) with stretch factors and spacing.
//
// Algorithm (per axis = "main"):
// 1. Sum preferred sizes of non-stretching children + spacing.
// 2. The remainder is distributed among stretching children proportional
// to their stretch factor.
// 3. min/max are clamped per child; if a stretching child hits its max,
// the leftover is redistributed among the rest in a second pass.
//
// Cross axis: each child either fills (Stretch, default) or uses its
// preferred size aligned per alignCross.
// ============================================================================
class BoxLayout : public Layout
{
public:
enum class Orientation { Horizontal, Vertical };
explicit BoxLayout(Orientation orientation) : m_orientation(orientation) {}
void arrange(Widget& parent) override;
Vec2 measure(const Widget& parent) const override;
inline Orientation getOrientation(void) const { return m_orientation; }
inline void setOrientation(Orientation o) { m_orientation = o; }
private:
Orientation m_orientation;
};
// ============================================================================
// GridLayout — fixed rows × cols. Cell sizes are uniform (rows = h/N,
// cols = w/M). Children are placed in row-major insertion order.
// LayoutHint::colSpan / rowSpan let a child cover multiple cells.
// ============================================================================
class GridLayout : public Layout
{
public:
GridLayout(u32 rows, u32 cols) : m_rows(rows), m_cols(cols) {}
void arrange(Widget& parent) override;
Vec2 measure(const Widget& parent) const override;
inline u32 getRows(void) const { return m_rows; }
inline u32 getCols(void) const { return m_cols; }
inline void setDimensions(u32 rows, u32 cols) { m_rows = rows; m_cols = cols; }
private:
u32 m_rows;
u32 m_cols;
};
}
}

View file

@ -0,0 +1,80 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/math/Geometry.hpp>
#include <ostd/utils/Defines.hpp>
namespace ogfx
{
namespace gui
{
// How a child wants to align itself within the slot a Layout gives it.
enum class eAlign : u8
{
Start, // top / left
Center, // center along the relevant axis
End, // bottom / right
Stretch // fill the available space along that axis
};
// Per-child metadata read by the parent's Layout when it arranges children.
// All fields have sane defaults so unset hints behave like "use my current size,
// no stretching, top-left, span 1".
struct LayoutHint
{
// 0 = use the widget's current/preferred size on the main axis.
// > 0 = take a share of leftover space proportional to this value.
// e.g. two children with stretch=1.0 split leftover space 50/50;
// stretch=2 vs stretch=1 splits it 2/3 vs 1/3.
f32 stretch { 0.0f };
// Alignment along the layout's main axis (only meaningful when the child
// is smaller than the slot the layout assigned it — i.e. when stretch=0
// and the slot ended up larger than preferred). Mostly relevant for Grid
// and for FillLayout in single-child mode.
eAlign alignMain { eAlign::Start };
// Alignment perpendicular to the main axis. Stretch (default) makes the
// child fill the cross dimension of its slot — usually what you want.
eAlign alignCross { eAlign::Stretch };
// Hard limits the layout will respect. minSize is honored even if it
// breaks stretch ratios; maxSize component < 0 means "unbounded".
Vec2 minSize { 0, 0 };
Vec2 maxSize { -1, -1 };
// If a component is < 0, the layout uses the widget's current size
// (getw()/geth()) for that axis. Otherwise this overrides it.
Vec2 preferred { -1, -1 };
// Grid-only. How many cells the child occupies. Ignored by Box/Fill.
u8 colSpan { 1 };
u8 rowSpan { 1 };
// If true, the layout skips this child entirely. Useful for floating
// overlays, drag-ghosts, or scrollbars (which manage their own bounds).
// Note: ScrollableWidget already excludes its scrollbars via the
// IgnoreScroll flag, but this gives finer control for other cases.
bool ignored { false };
};
}
}

203
src/ogfx/gui/MenuBar.cpp Normal file
View file

@ -0,0 +1,203 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "MenuBar.hpp"
#include "../render/BasicRenderer.hpp"
#include "Window.hpp"
namespace ogfx
{
namespace gui
{
MenuBar& MenuBar::create(void)
{
setSize(m_window.getWindowWidth(), m_height);
setPosition(0, 0);
return *this;
}
MenuBar& MenuBar::addMenu(const String& label, const ContextMenu::Instance& instance)
{
m_slots.push_back({ label, instance, {} });
recompute_layout();
return *this;
}
MenuBar& MenuBar::addMenu(const Menu& menu)
{
return addMenu(menu.label, menu.instance);
}
void MenuBar::clearMenus(void)
{
close_menu();
m_slots.clear();
}
void MenuBar::onWindowResized(const Event& event)
{
setSize(m_window.getWindowWidth(), m_height);
setPosition(0, 0);
recompute_layout();
}
void MenuBar::applyTheme(const ostd::Stylesheet& theme)
{
setHeight(theme.get<f32>("menubar.height", getHeight(), {}, {}));
setItemPadding(theme.get<Rectangle>("menubar.itemPadding", getItemPadding(), {}, {}));
setFontSize(theme.get<i32>("menubar.fontSize", getFontSize(), {}, {}));
setBackgroundColor(theme.get<Color>("menubar.backgroundColor", getBackgroundColor(), {}, {}));
setTextColor(theme.get<Color>("menubar.textColor", getTextColor(), {}, {}));
setSelectionColor(theme.get<Color>("menubar.selectionColor", getSelectionColor(), {}, {}));
setSelectionTextColor(theme.get<Color>("menubar.selectionTextColor", getSelectionTextColor(), {}, {}));
setBorderColor(theme.get<Color>("menubar.borderColor", getBorderColor(), {}, {}));
enableBorder(theme.get<bool>("menubar.showBorder", isBorderEnabled(), {}, {}));
setSize(m_window.getWindowWidth(), m_height);
recompute_layout();
}
void MenuBar::recompute_layout(void)
{
auto& gfx = m_window.getGFX();
f32 x = m_itemPadding.x;
for (auto& slot : m_slots)
{
Vec2 textSize = gfx.getStringDimensions(slot.label, m_fontSize);
f32 slotW = textSize.x + m_itemPadding.x + m_itemPadding.w;
slot.rect = { x, 0, slotW, m_height };
x += slotW;
}
}
i32 MenuBar::slot_index_at(const Vec2& mousePos) const
{
for (i32 i = 0; i < (i32)m_slots.size(); ++i)
{
if (m_slots[i].rect.contains(mousePos, true))
return i;
}
return -1;
}
void MenuBar::open_menu(i32 index)
{
if (index < 0 || index >= (i32)m_slots.size()) return;
const Slot& slot = m_slots[index];
// Anchor menu just below the label
Vec2 anchor { slot.rect.x, slot.rect.y + slot.rect.h };
m_window.showContextMenu(slot.instance, anchor);
m_openIndex = index;
m_active = true;
}
void MenuBar::close_menu(void)
{
if (m_window.isContextMenuVisible())
m_window.hideContextMenu();
m_openIndex = -1;
m_active = false;
}
void MenuBar::draw(BasicRenderer2D& gfx)
{
if (!m_visible) return;
gfx.fillRect(*this, m_backgroundColor);
for (i32 i = 0; i < (i32)m_slots.size(); ++i)
{
const Slot& slot = m_slots[i];
bool highlighted = (i == m_hoveredIndex) || (i == m_openIndex);
if (highlighted)
{
gfx.fillRect(slot.rect, m_selectionColor);
gfx.drawCenteredString(slot.label, slot.rect, m_selectionTextColor, m_fontSize);
}
else
{
gfx.drawCenteredString(slot.label, slot.rect, m_textColor, m_fontSize);
}
}
// Bottom border line
if (isBorderEnabled())
gfx.drawLine({ { getx(), gety() + m_height }, { getx() + getw(), gety() + m_height } }, m_borderColor);
}
void MenuBar::update(void)
{
if (!m_visible) return;
// If we're in active mode but the context menu got dismissed
// (item clicked, click-outside, escape, etc.), drop out of active mode.
if (m_active && !m_window.isContextMenuVisible())
{
m_openIndex = -1;
m_active = false;
}
}
void MenuBar::onMousePressed(const Event& event)
{
if (!m_visible) return;
Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
i32 idx = slot_index_at(mousePos);
if (idx < 0)
{
// Press outside the bar — let the context menu's own click-outside
// handling close it; we'll catch up via update().
return;
}
if (m_openIndex == idx)
{
// Clicking the open menu's own label closes it (toggle).
close_menu();
}
else
{
open_menu(idx);
}
event.handle();
}
void MenuBar::onMouseMoved(const Event& event)
{
if (!m_visible) return;
Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
m_hoveredIndex = slot_index_at(mousePos);
// Hover-switching: in active mode, moving onto a different label
// instantly switches the open menu.
if (m_active && m_hoveredIndex >= 0 && m_hoveredIndex != m_openIndex)
{
open_menu(m_hoveredIndex);
event.handle();
}
}
}
}

101
src/ogfx/gui/MenuBar.hpp Normal file
View file

@ -0,0 +1,101 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/ContextMenu.hpp>
namespace ogfx
{
namespace gui
{
class Window;
class MenuBar : public Rectangle
{
public: struct Menu
{
String label;
ContextMenu::Instance instance;
};
public:
inline MenuBar(Window& window) : m_window(window) { create(); }
MenuBar& create(void);
MenuBar& addMenu(const String& label, const ContextMenu::Instance& instance);
MenuBar& addMenu(const Menu& menu);
void clearMenus(void);
void onWindowResized(const Event& event);
void applyTheme(const ostd::Stylesheet& theme);
void draw(BasicRenderer2D& gfx);
void update(void);
void onMousePressed(const Event& event);
void onMouseMoved(const Event& event);
inline bool isVisible(void) const { return m_visible; }
inline void show(bool v = true) { m_visible = v; }
inline void hide(void) { show(false); m_active = false; m_openIndex = -1; }
inline bool isActive(void) const { return m_active; }
OSTD_PARAM_GETSET(f32, Height, m_height);
OSTD_PARAM_GETSET(Rectangle, ItemPadding, m_itemPadding);
OSTD_PARAM_GETSET(i32, FontSize, m_fontSize);
OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor);
OSTD_PARAM_GETSET(Color, TextColor, m_textColor);
OSTD_PARAM_GETSET(Color, SelectionColor, m_selectionColor);
OSTD_PARAM_GETSET(Color, SelectionTextColor, m_selectionTextColor);
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
OSTD_BOOL_PARAM_GETSET_E(Border, m_showBorder);
private:
struct Slot
{
String label;
ContextMenu::Instance instance;
Rectangle rect; // screen-space, recomputed on layout
};
void recompute_layout(void);
i32 slot_index_at(const Vec2& mousePos) const;
void open_menu(i32 index);
void close_menu(void);
private:
Window& m_window;
stdvec<Slot> m_slots;
i32 m_openIndex { -1 };
i32 m_hoveredIndex { -1 };
bool m_active { false };
bool m_visible { false };
f32 m_height { 26 };
Rectangle m_itemPadding { 12, 0, 12, 0 }; // x = left, y = top, w = right, h = bottom
i32 m_fontSize { 16 };
bool m_showBorder { false };
Color m_backgroundColor { "#6B0A1DFF" };
Color m_textColor { "#F16A85FF" };
Color m_selectionColor { "#DC143CFF" };
Color m_selectionTextColor { "#F16A85FF" };
Color m_borderColor { "#400000FF" };
};
}
}

View file

@ -0,0 +1,308 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RawTextInput.hpp"
#include "../gui/Window.hpp"
#include "../../io/Logger.hpp"
#include "../utils/Keycodes.hpp"
namespace ogfx
{
namespace gui
{
RawTextInput::EventListener::EventListener(RawTextInput& _parent) : parent(_parent)
{
connectSignal(ostd::BuiltinSignals::KeyPressed);
connectSignal(ostd::BuiltinSignals::KeyReleased);
connectSignal(ostd::BuiltinSignals::TextEntered);
connectSignal(ostd::BuiltinSignals::MouseDragged);
connectSignal(ostd::BuiltinSignals::MouseMoved);
connectSignal(ostd::BuiltinSignals::MousePressed);
connectSignal(ostd::BuiltinSignals::MouseReleased);
connectSignal(ostd::BuiltinSignals::OnGuiEvent);
connectSignal(ostd::BuiltinSignals::WindowResized);
}
void RawTextInput::EventListener::handleSignal(ostd::Signal& signal)
{
if (signal.ID == ostd::BuiltinSignals::TextEntered)
{
if (m_lastEvent != eEventType::TextEntered)
{
m_lastEvent = eEventType::TextEntered;
// parent.m_keyRepeatCounter.start();
}
auto& data = (ogfx::KeyEventData&)signal.userData;
if (parent.m_charFilter == nullptr)
{
OX_ERROR("Invalid character filter in RawTextInput event listener.");
return; //TODO: Better error
}
if (parent.m_charFilter->isValidChar(data.text[0]))
{
if (parent.m_lastChar == data.text[0] && parent.m_keyRepeatCounter.isCounting())
{
onSignalHandled(signal);
return;
}
String s1 = parent.m_text.new_substr(0, parent.m_cursorPosition);
String s2 = "";
if (parent.m_cursorPosition < parent.m_text.len())
s2 = parent.m_text.new_substr(parent.m_cursorPosition);
s1.addChar(data.text[0]).add(s2);
parent.m_text = s1;
parent.m_cursorPosition++;
parent.m_lastChar = data.text[0];
parent.m_keyRepeatCounter.start();
if (parent.m_theme.cursorBlink)
parent.m_theme.cursorBlinkCounter.start();
parent.m_cursorState = true;
}
}
else if (signal.ID == ostd::BuiltinSignals::KeyPressed)
{
if (m_lastEvent != eEventType::KeyPressed)
{
m_lastEvent = eEventType::KeyPressed;
// parent.m_keyRepeatCounter.start();
}
auto& data = (ogfx::KeyEventData&)signal.userData;
if (parent.m_lastKeyCode == data.keyCode && parent.m_keyRepeatCounter.isCounting())
{
onSignalHandled(signal);
return;
}
if (data.keyCode == KeyCode::Backspace)
{
parent.m_keyRepeatCounter.start();
if (parent.m_theme.cursorBlink)
parent.m_theme.cursorBlinkCounter.start();
parent.m_cursorState = true;
parent.m_lastKeyCode = data.keyCode;
if (parent.m_cursorPosition == 0 || parent.m_text == "")
{
onSignalHandled(signal);
return;
}
String s1 = parent.m_text.new_substr(0, parent.m_cursorPosition - 1);
String s2 = "";
if (parent.m_cursorPosition < parent.m_text.len())
s2 = parent.m_text.new_substr(parent.m_cursorPosition);
parent.m_text = s1 + s2;
parent.m_cursorPosition--;
}
else if (data.keyCode == KeyCode::Left)
{
parent.m_keyRepeatCounter.start();
if (parent.m_theme.cursorBlink)
parent.m_theme.cursorBlinkCounter.start();
parent.m_cursorState = true;
parent.m_lastKeyCode = data.keyCode;
if (parent.m_cursorPosition > 0)
parent.m_cursorPosition--;
}
else if (data.keyCode == KeyCode::Right)
{
parent.m_keyRepeatCounter.start();
if (parent.m_theme.cursorBlink)
parent.m_theme.cursorBlinkCounter.start();
parent.m_cursorState = true;
parent.m_lastKeyCode = data.keyCode;
if (parent.m_cursorPosition < parent.m_text.len())
parent.m_cursorPosition++;
}
else if (data.keyCode == KeyCode::Return)
{
parent.m_keyRepeatCounter.start();
parent.m_lastKeyCode = data.keyCode;
ActionEventData aed(parent, parent.getName(), eActionEventType::Enter, ostd::BaseObject::InvalidRef());
ostd::SignalHandler::emitSignal(RawTextInput::actionEventSignalID, ostd::Signal::Priority::RealTime, aed);
}
else if (data.keyCode == KeyCode::Tab)
{
parent.m_keyRepeatCounter.start();
parent.m_lastKeyCode = data.keyCode;
ActionEventData aed(parent, parent.getName(), eActionEventType::Tab, ostd::BaseObject::InvalidRef());
ostd::SignalHandler::emitSignal(RawTextInput::actionEventSignalID, ostd::Signal::Priority::RealTime, aed);
}
}
else if (signal.ID == ostd::BuiltinSignals::MouseMoved)
{
auto& data = (ogfx::MouseEventData&)signal.userData;
if (parent.contains((f32)data.position_x, (f32)data.position_y))
parent.m_mouseInside = true;
else
parent.m_mouseInside = false;
}
else if (signal.ID == ostd::BuiltinSignals::MousePressed)
{
auto& data = (ogfx::MouseEventData&)signal.userData;
if (data.button == ogfx::MouseEventData::eButton::Left && parent.m_gfx != nullptr && parent.m_mouseInside)
{
String text = parent.m_text;
Vec2 relativePosition = { (f32)data.position_x, (f32)data.position_y };
relativePosition -= (parent.getPosition() + parent.m_paddingX + parent.m_theme.extraPaddingLeft);
if (text.len() > 0)
{
String tmpStr1 = "";
String tmpStr2 = "";
bool found = false;
for (i32 i = 0; i < text.len(); i++)
{
tmpStr2 = "";
char c = text[i];
tmpStr1 += c;
i32 strWidth1 = parent.m_gfx->getStringDimensions(tmpStr1, parent.m_fontSize).x;
if (relativePosition.x > strWidth1)
continue;
found = true;
tmpStr2 = tmpStr1.new_substr(0, tmpStr1.len() - 1);
i32 strWidth2 = (tmpStr2.len() > 0 ? parent.m_gfx->getStringDimensions(tmpStr2, parent.m_fontSize).x : 0);
i32 d1 = (i32)std::abs(relativePosition.x - strWidth2);
i32 d2 = (i32)std::abs(strWidth1 - relativePosition.x);
if (d1 > d2)
parent.m_cursorPosition = tmpStr1.len();
else
parent.m_cursorPosition = tmpStr2.len();
break;
}
if (!found)
parent.m_cursorPosition = text.len();
}
parent.m_cursorState = true;
}
}
onSignalHandled(signal);
}
RawTextInput& RawTextInput::create(const Vec2& position, const Vec2& size, const String& name)
{
setPosition(position);
setSize(size);
m_name = name;
m_eventListener = new EventListener(*this); //TODO: Delete -- Memory Leak
m_charFilter = new CharacterFilter(); //TODO: Delete -- Memory Leak
m_theme = tDefaultThemes::DefaultTheme;
if (m_theme.cursorBlink)
m_theme.cursorBlinkCounter.start();
return *this;
}
void RawTextInput::render(ogfx::BasicRenderer2D& gfx)
{
m_gfx = &gfx;
f64 text_size_scale = 0.66666666666666;
m_paddingX = (4.0f * geth()) / 30.0f;
m_fontSize = (i32)(geth() * 0.66);
f32 cursor_height_scale = 0.75f;
IPoint strSize { 0, 0 };
if (m_cursorPosition > 0 && m_text != "")
{
String s1 = m_text.new_substr(0, m_cursorPosition);
strSize = gfx.getStringDimensions(s1, m_fontSize);
}
gfx.outlinedRect(*this, m_theme.backgroundColor, m_theme.borderColor, 2);
if (m_text.len() > 0)
gfx.drawString(m_text, getPosition() + Vec2 { m_paddingX + m_theme.extraPaddingLeft, m_paddingX + m_theme.extraPaddingTop}, m_theme.textColor, m_fontSize);
if (m_cursorState || !m_theme.cursorBlink)
gfx.fillRect({ getx() + m_paddingX + m_theme.extraPaddingLeft + strSize.x - 1, gety() + m_paddingX + m_theme.extraPaddingTop, (f32)m_theme.cursorWidth, (f32)m_fontSize * cursor_height_scale }, m_theme.cursorColor);
onRender(gfx);
}
void RawTextInput::update(void)
{
m_keyRepeatCounter.update();
onUpdate();
}
void RawTextInput::fixedUpdate(void)
{
if (m_theme.cursorBlink)
{
m_theme.cursorBlinkCounter.update();
if (m_theme.cursorBlinkCounter.isDone())
{
m_cursorState = !m_cursorState;
m_theme.cursorBlinkCounter.start();
}
}
onFixedUpdate();
}
void RawTextInput::setText(const String& text)
{
m_text = text;
m_cursorPosition = m_text.len();
}
void RawTextInput::appendText(const String& text)
{
m_text.add(text);
m_cursorPosition = m_text.len();
}
void RawTextInput::setCursorPosition(u16 cursorPos)
{
if (cursorPos > m_text.len())
cursorPos = m_text.len();
m_cursorPosition = m_text.len();
}
void RawTextInput::setTheme(Theme theme)
{
m_theme = theme;
if (m_theme.cursorBlink)
m_theme.cursorBlinkCounter.start();
else
m_theme.cursorBlinkCounter.stop();
}
void RawTextInputEventListener::onSignalHandled(ostd::Signal& signal)
{
if (signal.ID == ostd::BuiltinSignals::MouseMoved)
{
auto& data = (ogfx::MouseEventData&)signal.userData;
if (getParent().isMouseInside())
{
if (!m_ibeamCursorSet)
window.setCursor(ogfx::WindowCore::eCursor::Text);
m_ibeamCursorSet = true;
}
else
{
if (m_ibeamCursorSet)
window.setCursor(ogfx::WindowCore::eCursor::Default);
m_ibeamCursorSet = false;
}
}
}
}
}

View file

@ -0,0 +1,186 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/render/BasicRenderer.hpp>
#include <ostd/utils/Time.hpp>
namespace ogfx
{
namespace gui
{
class RawTextInput : public Rectangle
{
public: class EventListener : public ostd::BaseObject
{
public: enum class eEventType { None = 0, TextEntered, KeyPressed, KeyReleased };
public:
EventListener(RawTextInput& _parent);
virtual void handleSignal(ostd::Signal& signal) override;
inline virtual void onSignalHandled(ostd::Signal& signal) { }
inline RawTextInput& getParent(void) { return parent; }
private:
RawTextInput& parent;
eEventType m_lastEvent { eEventType::None };
};
public: class CharacterFilter
{
public:
inline virtual bool isValidChar(char c) { return true; }
};
public: class Theme
{
public:
Color textColor { 0, 0, 0, 0 };
Color borderColor { 0, 0, 0, 0 };
Color backgroundColor { 0, 0, 0, 0 };
Color cursorColor { 0, 0, 0, 0 };
u8 cursorWidth { 0 };
u8 extraPaddingTop { 0 };
u8 extraPaddingLeft { 0 };
bool cursorBlink { true };
ostd::BasicCounter cursorBlinkCounter { 30 };
};
public: struct tDefaultThemes
{
inline static const Theme DebugTheme {
{ 220, 220, 255 }, //Text Color
{ 10, 20, 120 }, //Border Color
{ 0, 0, 22 }, //Background Color
{ 220, 10, 10 }, //Cursor Color
2, //Cursor Width
0, //Extra Padding Top
0, //Extra Padding Left
true, //Cursor Blink
{ 30 } //Cursor Blink Timer
};
inline static const Theme DefaultTheme {
{ 120, 120, 180 }, //Text Color
{ 10, 20, 120 }, //Border Color
{ 0, 2, 10 }, //Background Color
{ 20, 100, 255 }, //Cursor Color
2, //Cursor Width
0, //Extra Padding Top
0, //Extra Padding Left
true, //Cursor Blink
{ 30 } //Cursor Blink Timer
};
};
public: enum eActionEventType { None = 0, Enter, Tab };
public: class ActionEventData : public ostd::BaseObject
{
public:
inline ActionEventData(RawTextInput& _sender, const String& _senderName, eActionEventType _eventType, ostd::BaseObject& _userData) :
sender(_sender),
senderName(_senderName),
eventType(_eventType),
userData(_userData)
{
setTypeName("ogfx::gui::RawTextInput::ActionEventData");
validate();
}
public:
RawTextInput& sender;
String senderName { "" };
eActionEventType eventType { eActionEventType::None };
ostd::BaseObject& userData { ostd::BaseObject::InvalidRef() };
};
public:
inline RawTextInput(void) { create({ 0.0f, 0.0f }, { 200.0f, 30.0f }, "UnnamedRawTextInput"); }
inline RawTextInput(const Vec2& position, const Vec2& size, const String& name) { create(position, size, name); }
RawTextInput& create(const Vec2& position, const Vec2& size, const String& name);
virtual void render(ogfx::BasicRenderer2D& gfx);
virtual void update(void);
virtual void fixedUpdate(void);
virtual inline void onRender(ogfx::BasicRenderer2D& gfx) { }
virtual inline void onUpdate(void) { }
virtual inline void onFixedUpdate(void) { }
void setText(const String& text);
void appendText(const String& text);
void setCursorPosition(u16 cursorPos);
void setTheme(Theme theme);
inline void setEventListener(EventListener& evtl) { m_eventListener = &evtl; }
inline void setCharacterFilter(CharacterFilter& chrf) { m_charFilter = &chrf; }
inline void setName(const String& name) { m_name = name; }
inline EventListener* getEventListener(void) const { return m_eventListener; }
inline CharacterFilter* getCharacterFilter(void) const { return m_charFilter; }
inline String getText(void) const { return m_text; }
inline u16 getCursorPosition(void) const { return m_cursorPosition; }
inline Theme& getTheme(void) { return m_theme; }
inline String getName(void) const { return m_name; }
inline bool isMouseInside(void) const { return m_mouseInside; }
private:
EventListener* m_eventListener { nullptr };
CharacterFilter* m_charFilter { nullptr };
ogfx::BasicRenderer2D* m_gfx { nullptr };
i32 m_fontSize { 20 };
f32 m_paddingX { 0 };
protected:
String m_name { "" };
String m_text { "" };
u16 m_cursorPosition { 0 };
bool m_cursorState { true };
ostd::BasicCounter m_keyRepeatCounter { 1 };
char m_lastChar { 0 };
i32 m_lastKeyCode { 0 };
Theme m_theme;
bool m_mouseInside { false };
public:
inline static const u32 actionEventSignalID { ostd::SignalHandler::newCustomSignal(11400) };
};
class RawTextInputEventListener : public RawTextInput::EventListener
{
public:
inline RawTextInputEventListener(ogfx::gui::RawTextInput& _parent, ogfx::WindowCore& _window) : ogfx::gui::RawTextInput::EventListener::EventListener(_parent), window(_window) { }
void onSignalHandled(ostd::Signal& signal) override;
public:
ogfx::WindowCore& window;
bool m_ibeamCursorSet { false };
};
class RawTextInputNumberCharacterFilter : public RawTextInput::CharacterFilter
{
public:
inline bool isValidChar(char c) override { return c >= '0' && c <= '9'; }
};
}
}

117
src/ogfx/gui/ToolBar.cpp Normal file
View file

@ -0,0 +1,117 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ToolBar.hpp"
#include "../render/BasicRenderer.hpp"
#include "Window.hpp"
namespace ogfx
{
namespace gui
{
ToolBar::ToolBar(Window& window, bool statusbar) : Widget({ 0, 0, 0, 0 }, window)
{
create(statusbar);
}
ToolBar& ToolBar::create(bool statusbar)
{
setRootChild();
setStylesheetCategoryName("toolbar");
setTypeName("ogfx::gui::ToolBar");
auto& win = getWindow();
w = win.getWindowWidth();
h = m_height;
disableBorder();
setAsStatusBar(statusbar);
hide();
if (isStatusBar())
setPosition(0, win.getWindowHeight() - m_height);
else if (win.isMenuBarVisible())
setPosition(0, win.getMenuBar().getHeight());
else
setPosition(0, 0);
return *this;
}
Button& ToolBar::addButton(const String& iconPath, const String& text, EventCallback callback)
{
m_buttons.push_back({ getWindow() });
auto& btn = m_buttons.back();
btn.addThemeID("tool_button");
btn.setIcon(iconPath);
btn.setText(text);
btn.setSizeMode(eSizeMode::LayoutManaged);
btn.setCallback(ogfx::gui::Widget::eCallback::ActionPerformed, callback);
btn.enableIconOnly();
btn.layoutHint().preferred = { m_height, m_height };
addWidget(btn);
return btn;
}
void ToolBar::onWindowResized(const Event& event)
{
auto& win = getWindow();
w = win.getWindowWidth();
h = m_height;
f32 offset_y = 0;
if (isStatusBar())
offset_y = win.getWindowHeight() - m_height;
else if (win.getMenuBar().isVisible())
offset_y += win.getMenuBar().geth();
sety(offset_y);
}
void ToolBar::applyTheme(const ostd::Stylesheet& theme)
{
auto& win = getWindow();
setHeight(getThemeValue<f32>(theme, "height", getHeight()));
setSize(win.getWindowWidth(), m_height);
enableBottomBorder(isBorderEnabled());
disableBorder();
}
void ToolBar::afterDraw(BasicRenderer2D& gfx)
{
// Bottom border line
if (isBottomBorderEnabled())
{
f32 lineOffset = (isStatusBar() ? 0.0f : m_height);
gfx.drawLine({ { getx(), gety() + lineOffset }, { getx() + getw(), gety() + lineOffset } }, getBorderColor());
}
}
void ToolBar::onUpdate(void)
{
bool iconsOnly = !isButtonTextEnabled();
for (auto& btn : m_buttons)
btn.enableIconOnly(iconsOnly);
}
void ToolBar::refresh_size(void)
{
for (auto& btn : m_buttons)
{
btn.setIconSize({ m_height, m_height });
}
}
}
}

65
src/ogfx/gui/ToolBar.hpp Normal file
View file

@ -0,0 +1,65 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Button.hpp>
#include <deque>
namespace ogfx
{
namespace gui
{
class Window;
class ToolBar : public Widget
{
public:
ToolBar(Window& window, bool statusbar = false);
ToolBar& create(bool statusbar = false);
Button& addButton(const String& iconPath, const String& text = "", EventCallback callback = nullptr);
void onWindowResized(const Event& event) override;
void applyTheme(const ostd::Stylesheet& theme) override;
void afterDraw(BasicRenderer2D& gfx) override;
void onUpdate(void) override;
inline void setw(f32 ww) override { }
inline void seth(f32 hh) override { h = hh; refresh_size(); }
inline bool isStatusBar(void) const { return m_isStatusBar; }
inline void setAsStatusBar(bool set = true) { m_isStatusBar = set; }
OSTD_PARAM_GETSET(f32, Height, m_height);
OSTD_BOOL_PARAM_GETSET_E_NEG(ButtonText, m_disableButtonText);
OSTD_BOOL_PARAM_GETSET_E(BottomBorder, m_bottomBorder);
private:
void refresh_size(void);
private:
bool m_disableButtonText { true };
std::deque<Button> m_buttons;
bool m_isStatusBar { false };
f32 m_height { 26 };
bool m_bottomBorder { false };
};
}
}

View file

@ -0,0 +1,285 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "WidgetManager.hpp"
#include "Window.hpp"
#include "widgets/Widget.hpp"
#include "../render/BasicRenderer.hpp"
#include "../../ostd/io/Memory.hpp"
namespace ogfx
{
namespace gui
{
WidgetManager::WidgetManager(Window& window, Widget& owner) : m_window(window), m_owner(owner)
{
//
}
bool WidgetManager::hasWidget(Widget& widget)
{
bool found = std::ranges::find(m_widgetList, &widget) != m_widgetList.end();
return found && widget.isValid();
}
bool WidgetManager::addWidget(Widget& widget)
{
if (hasWidget(widget)) return false;
widget.m_parent = &m_owner;
m_widgetList.push_back(&widget);
widget.refresh();
return true;
}
bool WidgetManager::removeWidget(Widget& widget)
{
STDVEC_REMOVE(m_widgetList, &widget);
return true;
}
void WidgetManager::draw(ogfx::BasicRenderer2D& gfx)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
if (w->isManualDrawEnabled()) continue;
gfx.pushClippingRect({ w->getGlobalPosition(), w->getSize() }, true);
w->__draw(gfx);
gfx.popClippingRect();
}
}
void WidgetManager::update(void)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
w->__update();
}
}
void WidgetManager::onThemeApplied(const ostd::Stylesheet& theme)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isThemingEnabled()) continue;
w->__applyTheme(theme, true);
w->reloadTheme();
}
}
void WidgetManager::onMousePressed(const Event& event)
{
for (i32 i = m_widgetList.size() - 1; i >= 0; i--)
{
Widget* w = m_widgetList[i];
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
continue;
event.mouse->mousePressedOnWidget = w;
m_window.getFocusManager().requestFocus(*w);
w->__onMousePressed(event);
m_mousePressedOnWidget = w;
if (event.isHandled() || w->m_stopEvents)
break;
}
}
void WidgetManager::onMouseReleased(const Event& event)
{
if (m_mousePressedOnWidget != nullptr)
{
event.mouse->mousePressedOnWidget = m_mousePressedOnWidget;
m_mousePressedOnWidget->__onMouseReleased(event);
// processDragAndDrop(m_mousePressedOnWidget, event);
}
for (i32 i = m_widgetList.size() - 1; i >= 0; i--)
{
Widget* w = m_widgetList[i];
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
if (w == m_mousePressedOnWidget) continue;
event.mouse->mousePressedOnWidget = m_mousePressedOnWidget;
w->__onMouseReleased(event);
processDragAndDrop(w, event);
if (w->isMouseInside() && w->m_stopEvents)
break;
}
m_mousePressedOnWidget = nullptr;
}
void WidgetManager::onMouseMoved(const Event& event)
{
if (m_mousePressedOnWidget != nullptr &&
m_mousePressedOnWidget->m_pressedButton != MouseEventData::eButton::None &&
!m_mousePressedOnWidget->contains(event.mouse->position_x, event.mouse->position_y, true))
{
m_mousePressedOnWidget->__onMouseDragged(event);
}
for (i32 i = m_widgetList.size() - 1; i >= 0; i--)
{
Widget* w = m_widgetList[i];
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
{
if (w->m_mouseInside)
{
w->m_mouseInside = false;
w->__onMouseExited(event);
}
continue;
}
if (!w->m_mouseInside)
{
w->m_mouseInside = true;
w->__onMouseEntered(event);
}
else
{
if (w->m_pressedButton != MouseEventData::eButton::None)
w->__onMouseDragged(event);
else
w->__onMouseMoved(event);
}
if (event.isHandled() || w->m_stopEvents)
{
bool mouseOut = false;
for (i32 j = i - 1; j >= 0; j--)
{
Widget* ww = m_widgetList[j];
if (ww->m_mouseInside)
{
ww->m_mouseInside = false;
ww->__onMouseExited(event);
}
}
break;
}
}
}
void WidgetManager::onMouseScrolled(const Event& event)
{
for (i32 i = m_widgetList.size() - 1; i >= 0; i--)
{
Widget* w = m_widgetList[i];
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
continue;
w->__onMouseScrolled(event);
if (event.isHandled() || w->m_stopEvents)
break;
}
}
void WidgetManager::onMouseDragged(const Event& event)
{
for (i32 i = m_widgetList.size() - 1; i >= 0; i--)
{
Widget* w = m_widgetList[i];
if (w == nullptr) continue;
if (w->isInvalid()) continue;
if (!w->isVisible()) continue;
// if (!w->contains(event.mouse->position_x, event.mouse->position_y, true))
// continue;
w->__onMouseDragged(event);
if (event.isHandled())
break;
}
}
void WidgetManager::onWindowClosed(const Event& event)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
w->__onWindowClosed(event);
if (event.isHandled())
break;
}
}
void WidgetManager::onWindowResized(const Event& event)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
w->__onWindowResized(event);
if (event.isHandled())
break;
}
}
void WidgetManager::onWindowFocused(const Event& event)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
w->__onWindowFocused(event);
if (event.isHandled())
break;
}
}
void WidgetManager::onWindowFocusLost(const Event& event)
{
for (auto& w : m_widgetList)
{
if (w == nullptr) continue;
if (w->isInvalid()) continue;
w->__onWindowFocusLost(event);
if (event.isHandled())
break;
}
}
void WidgetManager::processDragAndDrop(Widget* widget, const Event& event)
{
if (widget == nullptr) return;
if (!widget->isVisible()) return;
if (!widget->isDragAndDropEnabled()) return;
if (!widget->isMouseInside()) return;
if (Widget::s_dragAndDropData != nullptr)
{
auto& const_cast_event = const_cast<Event&>(event);
const_cast_event.drop.textOrFilePath = "";
const_cast_event.drop.dropType = DropEventData::eDropType::InApp;
const_cast_event.drop.userObject = Widget::s_dragAndDropData;
widget->__onDragAndDrop(const_cast_event);
Widget::clearDragAndDropData();
}
}
}
}

View file

@ -0,0 +1,74 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/string/String.hpp>
namespace ostd
{
class Stylesheet;
}
namespace ogfx
{
class BasicRenderer2D;
namespace gui
{
class Event;
class Widget;
class Window;
class WidgetManager
{
public:
WidgetManager(Window& window, Widget& owner);
bool hasWidget(Widget& widget);
bool addWidget(Widget& widget);
bool removeWidget(Widget& widget);
void draw(ogfx::BasicRenderer2D& gfx);
void update(void);
void onThemeApplied(const ostd::Stylesheet& theme);
void onMousePressed(const Event& event);
void onMouseReleased(const Event& event);
void onMouseMoved(const Event& event);
void onMouseScrolled(const Event& event);
void onMouseDragged(const Event& event);
void onWindowClosed(const Event& event);
void onWindowResized(const Event& event);
void onWindowFocused(const Event& event);
void onWindowFocusLost(const Event& event);
inline i32 widgetCount(void) const { return m_widgetList.size(); }
inline Window& getWindow(void) { return m_window; }
inline const stdvec<Widget*>& getWidgets(void) const { return m_widgetList; }
private:
void processDragAndDrop(Widget* widget, const Event& event);
private:
Window& m_window;
Widget& m_owner;
stdvec<Widget*> m_widgetList;
Widget* m_mousePressedOnWidget { nullptr };
friend class FocusManager;
};
}
}

41
src/ogfx/gui/Widgets.hpp Normal file
View file

@ -0,0 +1,41 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Containers.hpp>
#include <ogfx/gui/widgets/Label.hpp>
#include <ogfx/gui/widgets/CheckBox.hpp>
#include <ogfx/gui/widgets/Scrollbar.hpp>
#include <ogfx/gui/widgets/Button.hpp>
#include <ogfx/gui/widgets/RadioButton.hpp>
#include <ogfx/gui/widgets/ProgressBar.hpp>
#include <ogfx/gui/widgets/Slider.hpp>
#include <ogfx/gui/widgets/ComboBox.hpp>
#include <ogfx/gui/widgets/TreeView.hpp>
#include <ogfx/gui/widgets/Text.hpp>
namespace ogfx
{
namespace gui
{
using ListView = TreeView;
}
}

1072
src/ogfx/gui/Window.cpp Normal file

File diff suppressed because it is too large Load diff

327
src/ogfx/gui/Window.hpp Normal file
View file

@ -0,0 +1,327 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/utils/SDLInclude.hpp>
#include <ostd/utils/Signals.hpp>
#include <ostd/utils/Time.hpp>
#include <ostd/io/IOHandlers.hpp>
#include <ogfx/gui/Events.hpp>
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/RootWidget.hpp>
#include <ogfx/render/BasicRenderer.hpp>
#include <ogfx/gui/WindowOutputHandler.hpp>
#include <ogfx/gui/ContextMenu.hpp>
#include <ogfx/gui/MenuBar.hpp>
#include <ogfx/gui/ToolBar.hpp>
#include <ogfx/gui/FocusManager.hpp>
namespace ogfx
{
class WindowCore : public ostd::BaseObject
{
public: struct FileDialogFilter
{
String description { "" };
stdvec<String> extensionList;
};
public: enum class eCursor : u8
{
Default = 0,
Text,
Wait,
Crosshair,
Progress,
NWSE_Resize,
NESW_Resize,
EW_Resize,
NS_Resize,
Move,
NotAllowed,
Pointer,
NW_Resize,
N_Resize,
NE_Resize,
E_Resize,
SE_Resize,
S_Resize,
SW_Resize,
W_Resize,
Count,
NoCursor = 255
};
public: enum class eTextAlign : u8 {
Default = 0,
Center,
Left
};
public: enum class eWindowState : u8 {
Default = 0,
Maximized,
Minimized
};
public: struct State
{
bool fullscreen { false };
bool borderless { false };
bool resizable { false };
bool minimized { false };
bool maximized { false };
bool alwaysOnTop { false };
};
public: using FileDialogFilterList = stdvec<ogfx::WindowCore::FileDialogFilter>;
public:
inline WindowCore(void) { }
virtual ~WindowCore(void);
inline WindowCore(i32 width, i32 height, const String& title) { initialize(width, height, title); }
void initialize(i32 width, i32 height, const String& title);
void mainLoop(void);
void close(void);
void setSize(i32 width, i32 height);
void setTitle(const String& title);
void setCursor(eCursor cursor);
void setUserScale(f32 scale);
void setPosition(const IPoint& pos);
void setWindowState(eWindowState state);
State getWindowState(void) const;
eCursor getCurosr(void) const;
void enableResizable(bool enable = true);
void setIcon(const String& iconFilePath);
void setBlockingEventsRefreshFPS(u32 fps);
void requestRedraw(void);
void handleSignal(ostd::Signal& signal) override;
stdvec<String> showOpenFileDialog(const FileDialogFilterList& filterList, bool multiselect, const String& defaultPath = "") const;
stdvec<String> showOpenFolderDialog(bool multiselect, const String& defaultPath = "") const;
String showSaveFileDialog(const FileDialogFilterList& filterList, const String& defaultPath = "") const;
inline String showOpenFileDialog(const FileDialogFilterList& filterList) const { auto list = showOpenFileDialog(filterList, false); return (list.size() == 0 ? "" : list[0]); }
inline String showOpenFolderDialog(void) const { auto list = showOpenFolderDialog(false); return (list.size() == 0 ? "" : list[0]); }
inline const ostd::Stylesheet* theme(void) const { return m_guiTheme; }
inline void loadDefaultTHeme(void) { setTheme(DefaultTheme); }
inline ostd::Stylesheet::VariableList& getDefaultStylesheetVariableList(void) { return m_defaultStylesheetVariables; }
inline virtual void setTheme(const ostd::Stylesheet& theme) { }
inline BasicRenderer2D& getGFX(void) { return m_gfx; }
inline bool isInitialized(void) const { return m_initialized; }
inline bool isRunning(void) const { return m_running; }
inline bool isVisible(void) const { return m_visible; }
inline bool isResizeable(void) const { return m_resizeable; }
inline void hide(void) { SDL_HideWindow(m_window); m_visible = false; }
inline void show(void) { SDL_ShowWindow(m_window); m_visible = true; }
inline String getTitle(void) const { return m_title; }
inline i32 getWindowWidth(bool scaled = true) const { return scaled ? cast<f32>(m_windowWidth) / getScaleFactor() : m_windowWidth; }
inline i32 getWindowHeight(bool scaled = true) const { return scaled ? cast<f32>(m_windowHeight) / getScaleFactor() : m_windowHeight; }
inline Color getClearColor(void) const { return m_clearColor; }
inline void setClearColor(const Color& color) { m_clearColor = color; }
inline SDL_Renderer* getSDLRenderer(void) { return m_renderer; }
inline void enableBlockingEvents(bool enable = true) { m_blockingEvents = enable; }
inline bool isBlockingEventsEnabled(void) const { return m_blockingEvents; }
inline ostd::ConsoleOutputHandler& out(void) { return m_out; }
inline GraphicsWindowOutputHandler& wout(void) { return m_wout; }
inline f32 getScaleFactor(void) const { return m_systemScale * m_userScale; }
inline void updateScalleFactor(void) { setUserScale(m_userScale); }
inline Vec2 getMousePosition(void) const { return m_mousePosition; }
inline void startTooltipTimer(const String& text) { m_tooltipText = text; m_tooltipTimer.startCount(ostd::eTimeUnits::Seconds); }
inline void stopTooltipTimer(void) { m_tooltipText = ""; m_tooltipTimer.endCount(); }
inline void restartTooltipTimer(void) { m_tooltipTimer.restart(ostd::eTimeUnits::Seconds); }
inline u64 readTooltipTimer(void) { return m_tooltipTimer.read(); }
inline bool isTooltipShown(void) { return m_tooltipText.new_trim() != "" && m_tooltipTimer.read() >= 2; }
inline String getTooltipText(void) const { return m_tooltipText; }
protected:
MouseEventData get_mouse_state(SDL_Event& event);
virtual void handle_events(void);
virtual void before_render(void);
virtual void after_render(void);
inline virtual void __on_event(SDL_Event& event) { }
inline virtual void __on_window_init(i32 width, i32 height, const String& title) { }
inline virtual void __on_window_destroy(void) { }
inline virtual void __on_window_close(void) { }
inline virtual void __on_signal(ostd::Signal& signal) { }
inline virtual void __on_update(f64 delta) { }
inline virtual void __on_fixed_update(void) { }
inline virtual void __main_loop(void) = 0;
private:
void __handle_event(SDL_Event& event);
void __load_default_stylesheet_variables(void);
void __store_file_dialog_filters(const FileDialogFilterList& filters) const;
protected:
SDL_Window* m_window { nullptr };
SDL_Renderer* m_renderer { nullptr };
BasicRenderer2D m_gfx;
ostd::ConsoleOutputHandler m_out;
GraphicsWindowOutputHandler m_wout;
const ostd::Stylesheet* m_guiTheme { nullptr };
bool m_refreshScreen { true };
private:
Color m_clearColor { 10, 10, 10, 255 };
i32 m_windowWidth { 0 };
i32 m_windowHeight { 0 };
String m_title { "" };
i32 m_blockingEventsDelay { 33 };
Vec2 m_mousePosition { 0, 0 };
ostd::Counter m_tooltipTimer;
String m_tooltipText { "" };
bool m_running { false };
bool m_initialized { false };
bool m_visible { true };
bool m_blockingEvents { false };
bool m_resizeable { true };
bool m_invertHorizontalScroll { true };
f32 m_systemScale { 1.0f };
f32 m_userScale { 1.0f };
SDL_Cursor* m_cursor_Default { nullptr };
SDL_Cursor* m_cursor_Text { nullptr };
SDL_Cursor* m_cursor_Wait { nullptr };
SDL_Cursor* m_cursor_Crosshair { nullptr };
SDL_Cursor* m_cursor_Progress { nullptr };
SDL_Cursor* m_cursor_NWSE_Resize { nullptr };
SDL_Cursor* m_cursor_NESW_Resize { nullptr };
SDL_Cursor* m_cursor_EW_Resize { nullptr };
SDL_Cursor* m_cursor_NS_Resize { nullptr };
SDL_Cursor* m_cursor_Move { nullptr };
SDL_Cursor* m_cursor_NotAllowed { nullptr };
SDL_Cursor* m_cursor_Pointer { nullptr };
SDL_Cursor* m_cursor_NW_Resize { nullptr };
SDL_Cursor* m_cursor_N_Resize { nullptr };
SDL_Cursor* m_cursor_NE_Resize { nullptr };
SDL_Cursor* m_cursor_E_Resize { nullptr };
SDL_Cursor* m_cursor_SE_Resize { nullptr };
SDL_Cursor* m_cursor_S_Resize { nullptr };
SDL_Cursor* m_cursor_SW_Resize { nullptr };
SDL_Cursor* m_cursor_W_Resize { nullptr };
mutable stdvec<String> m_patternStorage;
mutable stdvec<SDL_DialogFileFilter> m_sdlFilters;
public:
inline static constexpr i32 MaxBlockingEventsFPS { 240 };
inline static constexpr i32 DefaultBlockingEventsFPS { 30 };
inline static const IPoint WindowsPositionCenter { -1, -1 };
private:
inline static ostd::Stylesheet DefaultTheme;
stdumap<String, std::pair<String, bool>> m_defaultStylesheetVariables;
};
class GraphicsWindow : public WindowCore
{
public:
inline GraphicsWindow(void) { }
inline GraphicsWindow(i32 width, i32 height, const String& title) { initialize(width, height, title); }
inline virtual void onRender(void) { }
inline virtual void onUpdate(void) { }
inline virtual void onFixedUpdate(f64 frameTime_s) { }
inline virtual void onInitialize(void) { }
inline virtual void onDestroy(void) { }
inline virtual void onClose(void) { }
inline virtual void onSDLEvent(SDL_Event& event) { }
inline virtual void onSignal(ostd::Signal& signal) { }
inline i32 getFPS(void) const { return m_fps; }
protected:
void __on_window_init(i32 width, i32 height, const String& title) override;
void __on_event(SDL_Event& event) override;
void __on_window_destroy(void) override;
void __on_window_close(void) override;
void __main_loop(void) override;
void __on_signal(ostd::Signal& signal) override;
private:
i32 m_fps { 0 };
ostd::StepTimer m_fixedUpdateTImer;
ostd::StepTimer m_fpsUpdateTimer;
ostd::Counter m_fpsUpdateClock;
u64 m_frameTimeAcc { 0 };
i32 m_frameCount { 0 };
};
namespace gui
{
class Window : public WindowCore
{
public:
inline Window(void) { }
inline Window(i32 width, i32 height, const String& title) { initialize(width, height, title); }
void addWidget(Widget& widget, const Vec2& position = { 0, 0 });
void removeWidget(Widget& widget);
void setTheme(const ostd::Stylesheet& theme) override;
inline void showContextMenu(const Vec2& pos) { m_cmenu.show(pos); }
inline void showContextMenu(const ContextMenu::Instance& instance, const Vec2& pos) { setContextMenu(instance); showContextMenu(pos); }
inline void setContextMenu(const ContextMenu::Instance& instance) { m_cmenu.setInstance(instance); }
inline bool isContextMenuVisible(void) const { return m_cmenu.isVisible(); }
inline bool isMenuBarVisible(void) const { return m_menubar.isVisible(); }
inline void hideContextMenu(void) { m_cmenu.hide(); }
inline MenuBar& getMenuBar(void) { return m_menubar; }
inline ToolBar& getToolBar(void) { return m_toolbar; }
inline ToolBar& getStatusBar(void) { return m_statusbar; }
inline void showMenuBar(bool show = true) { m_menubar.show(show); }
inline void showToolBar(bool show = true) { m_toolbar.setVisible(show); }
inline void showStatusBar(bool show = true) { m_statusbar.setVisible(show); }
inline FocusManager& getFocusManager(void) { return m_focusManager; }
inline virtual void onInitialize(void) { }
inline virtual void onDestroy(void) { }
inline virtual void onClose(void) { }
inline virtual void onSDLEvent(SDL_Event& event) { }
inline virtual void onRedraw(BasicRenderer2D& gfx) { }
inline virtual void onUpdate(f64 delta) { }
inline virtual void onFixedUpdate(void) { }
inline virtual void onSignal(ostd::Signal& signal) { }
protected:
void __on_window_init(i32 width, i32 height, const String& title) override;
void __on_event(SDL_Event& event) override;
void __on_window_destroy(void) override;
void __on_window_close(void) override;
void __main_loop(void) override;
void __on_signal(ostd::Signal& signal) override;
void __on_update(f64 delta) override;
void __on_fixed_update(void) override;
void __main_loop_cycle(void);
protected:
RootWidget m_rootWidget { *this };
FocusManager m_focusManager { *this };
ostd::StepTimer m_fixedUpdateTimer;
ostd::StepTimer::TimePoint m_lastFrameTime;
ostd::StepTimer m_mainLoopTimer;
ContextMenu m_cmenu { *this };
MenuBar m_menubar { *this };
ToolBar m_toolbar { *this };
ToolBar m_statusbar { *this, true };
friend class RootWidget;
friend class FocusManager;
};
}
}

View file

@ -0,0 +1,487 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "WindowOutputHandler.hpp"
#include "../render/BasicRenderer.hpp"
#include "../../string/TextStyleParser.hpp"
namespace ogfx
{
using namespace ostd;
GraphicsWindowOutputHandler::GraphicsWindowOutputHandler(void)
{
}
void GraphicsWindowOutputHandler::attachWindow(ogfx::WindowCore& window)
{
if (m_window != nullptr) return;
m_window = &window;
m_renderer.init(window);
m_renderer.loadDefaultMonoFont(18);
}
void GraphicsWindowOutputHandler::setMonospaceFont(const String& filePath)
{
m_renderer.openFont(filePath);
m_renderer.setFontSize(m_fontSize);
__update_char_size();
}
Vec2 GraphicsWindowOutputHandler::getStringSize(const String& str)
{
Vec2 size = { 0.0f, m_charSize.y };
if (str.len() == 0) return size;
size.x = str.len() * m_charSize.x;
return size;
}
bool GraphicsWindowOutputHandler::isReady(void)
{
return m_window != nullptr && m_renderer.hasOpenFont() && m_fontSize > 0;
}
void GraphicsWindowOutputHandler::resetCursorPosition(void)
{
m_curosrPosition = { 0, 0 };
}
void GraphicsWindowOutputHandler::resetColors(void)
{
m_foregroundColor = m_defaultForegroundColor;
m_backgroundColor = m_defaultBackgroundColor;
}
void GraphicsWindowOutputHandler::beginFrame(void)
{
resetColors();
resetCursorPosition();
}
void GraphicsWindowOutputHandler::setConsoleMaxCharacters(const IPoint& size)
{
m_consoleSize = size;
}
void GraphicsWindowOutputHandler::setConsolePosition(const Vec2& pos)
{
m_consolePosition = pos;
}
void GraphicsWindowOutputHandler::setWrapMode(eWrapMode wrapMode)
{
m_wrapMode = wrapMode;
}
void GraphicsWindowOutputHandler::setPadding(const Rectangle& rect)
{
m_padding = rect;
}
void GraphicsWindowOutputHandler::setDefaultBackgorundColor(const Color& color)
{
m_defaultBackgroundColor = color;
}
void GraphicsWindowOutputHandler::setDefaultForegroundColor(const Color& color)
{
m_defaultForegroundColor = color;
}
void GraphicsWindowOutputHandler::setTabWidth(u8 tw)
{
m_tabWidth = tw;
}
void GraphicsWindowOutputHandler::setFontSize(i32 fontSize)
{
m_fontSize = fontSize;
m_renderer.setFontSize(m_fontSize);
__update_char_size();
}
i32 GraphicsWindowOutputHandler::getFontSize(void)
{
return m_fontSize;
}
Vec2 GraphicsWindowOutputHandler::getCharacterSize(i32 fontSize)
{
if (fontSize > 0)
{
auto size = m_renderer.getStringDimensions("A", fontSize);
return { (f32)size.x, (f32)size.y };
}
return m_charSize;
}
Vec2 GraphicsWindowOutputHandler::getConsolePosition(void)
{
return m_consolePosition;
}
GraphicsWindowOutputHandler::eWrapMode GraphicsWindowOutputHandler::getWrapMode(void)
{
return m_wrapMode;
}
Rectangle GraphicsWindowOutputHandler::getPadding(void)
{
return m_padding;
}
Color GraphicsWindowOutputHandler::getDefaultBackgroundColor(void)
{
return m_defaultBackgroundColor;
}
Color GraphicsWindowOutputHandler::getDefaultForegroundColor(void)
{
return m_defaultForegroundColor;
}
u8 GraphicsWindowOutputHandler::getTabWidth(void)
{
return m_tabWidth;
}
Rectangle GraphicsWindowOutputHandler::getConsoleBounds(void)
{
f32 console_w = ((f32)m_consoleSize.x * getCharacterSize().x) + getPadding().x + getPadding().w;
f32 console_h = ((f32)m_consoleSize.y * getCharacterSize().y) + getPadding().y + getPadding().h;
return { getConsolePosition().x - getPadding().x, getConsolePosition().y - getPadding().y, console_w, console_h };
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::bg(const Color& color)
{
m_backgroundColor = color;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::bg(const ConsoleColors::tConsoleColor& color)
{
m_backgroundColor = color.fullColor;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::bg(const String& color)
{
m_backgroundColor = ConsoleColors::getFromName(color).fullColor;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::fg(const Color& color)
{
m_foregroundColor = color;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::fg(const ConsoleColors::tConsoleColor& color)
{
m_foregroundColor = color.fullColor;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::fg(const String& color)
{
m_foregroundColor = ConsoleColors::getFromName(color).fullColor;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::pChar(char c)
{
__print_string(String("").addChar(c));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::pStyled(const String& styled)
{
return pStyled(TextStyleParser::parse(styled, { m_backgroundColor, "", true }, { m_foregroundColor, "", false }));
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::pStyled(const TextStyleParser::tStyledString& styled)
{
if (!styled.validate()) return *this;
Color oldBgCol = m_backgroundColor;
Color oldFgCol = m_foregroundColor;
for (i32 i = 0; i < styled.text.len(); i++)
bg(styled.backgroundColors[i].fullColor).fg(styled.foregroundColors[i].fullColor).pChar(styled.text[i]);
m_backgroundColor = oldBgCol;
m_foregroundColor = oldFgCol;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::pStyled(TextStyleBuilder::IRichStringBase& styled)
{
auto oldBg = styled.getDefaultBackgroundColor();
auto oldFg = styled.getDefaultForegroundColor();
styled.setDefaultBackgroundColor(m_backgroundColor);
styled.setDefaultForegroundColor(m_foregroundColor);
pStyled(styled.getStyledString());
styled.setDefaultBackgroundColor(oldBg);
styled.setDefaultForegroundColor(oldFg);
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::pObject(const BaseObject& bo)
{
__print_string(bo.toString());
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(const String& se)
{
__print_string(se);
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(u8 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(i8 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(u16 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(i16 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(u32 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(i32 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(u64 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(i64 i)
{
__print_string(String("").add(i));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(f32 f, u8 precision)
{
__print_string(String("").add(f, precision));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::p(f64 f, u8 precision)
{
__print_string(String("").add(f, precision));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::nl(void)
{
if (m_curosrPosition.y >= getConsoleSize().y) return *this;
m_curosrPosition.y++;
m_curosrPosition.x = 0;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::tab(void)
{
if (m_curosrPosition.x == 0)
{
__print_string(String("").fixedLength(m_tabWidth, ' ', ""));
return *this;
}
i32 restTab = m_tabWidth - ((i32)m_curosrPosition.x % m_tabWidth);
if (m_curosrPosition.x + restTab > m_consoleSize.x)
restTab = m_consoleSize.x - m_curosrPosition.x;
if (restTab == 0) return *this;
__print_string(String("").fixedLength(restTab, ' ', ""));
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::flush(void)
{
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::clear(void)
{
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::reset(void)
{
resetColors();
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::xy(IPoint position)
{
m_curosrPosition = { (f32)position.x, (f32)position.y };
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::xy(i32 x, i32 y)
{
m_curosrPosition = { (f32)x, (f32)y };
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::x(i32 x)
{
m_curosrPosition.x = (f32)x;
return *this;
}
GraphicsWindowOutputHandler& GraphicsWindowOutputHandler::y(i32 y)
{
m_curosrPosition.y = (f32)y;
return *this;
}
IPoint GraphicsWindowOutputHandler::getCursorPosition(void)
{
return { (i32)std::round(m_curosrPosition.x), (i32)std::round(m_curosrPosition.y) };
}
void GraphicsWindowOutputHandler::getCursorPosition(i32& outX, i32& outY)
{
outX = (i32)std::round(m_curosrPosition.x);
outY = (i32)std::round(m_curosrPosition.y);
}
i32 GraphicsWindowOutputHandler::getCursorX(void)
{
return (i32)std::round(m_curosrPosition.x);
}
i32 GraphicsWindowOutputHandler::getCursorY(void)
{
return (i32)std::round(m_curosrPosition.y);
}
void GraphicsWindowOutputHandler::getConsoleSize(i32& outColumns, i32& outRows)
{
i32 console_rows = std::numeric_limits<i32>::max();
i32 console_cols = std::numeric_limits<i32>::max();
if (m_consoleSize.x > 0) console_cols = m_consoleSize.x;
if (m_consoleSize.y > 0) console_rows = m_consoleSize.y;
outColumns = console_cols;
outRows = console_rows;
}
IPoint GraphicsWindowOutputHandler::getConsoleSize(void)
{
i32 console_rows = std::numeric_limits<i32>::max();
i32 console_cols = std::numeric_limits<i32>::max();
if (m_consoleSize.x > 0) console_cols = m_consoleSize.x;
if (m_consoleSize.y > 0) console_rows = m_consoleSize.y;
return { console_cols, console_rows };
}
void GraphicsWindowOutputHandler::__update_char_size(void)
{
auto size = m_renderer.getStringDimensions("A", m_fontSize);
m_charSize = { (f32)size.x, (f32)size.y };
}
void GraphicsWindowOutputHandler::__print_string(const String& str)
{
if (!isReady()) return;
auto l_endOfConsole = [&](void) -> bool {
return m_curosrPosition.x >= getConsoleSize().x && m_curosrPosition.y >= getConsoleSize().y - 1;
};
auto l_print = [&](const String& string) {
if (l_endOfConsole()) return;
if (string.len() == 0) return;
f32 vertical_margin = 4;
Vec2 pos = m_consolePosition + Vec2 { m_charSize.x * m_curosrPosition.x, m_charSize.y * m_curosrPosition.y };
if (m_backgroundColor.a > 0)
m_renderer.fillRect({ pos.x, pos.y + vertical_margin, m_charSize.x * string.len(), m_charSize.y - vertical_margin }, m_backgroundColor);
if (m_foregroundColor.a > 0)
m_renderer.drawString(string, pos, m_foregroundColor);
m_curosrPosition.x += string.len();
if (m_curosrPosition.x >= getConsoleSize().x)
nl();
};
i32 string_length = str.len();
i32 lineLen = m_curosrPosition.x + string_length;
if (lineLen <= getConsoleSize().x)
{
l_print(str);
return;
}
if (m_wrapMode == eWrapMode::TripleDots)
{
i32 fixedLen = getConsoleSize().x - m_curosrPosition.x;
l_print(str.new_fixedLength(fixedLen, ' ', "..."));
}
else if (m_wrapMode == eWrapMode::NewLine)
{
i32 restOfLine = getConsoleSize().x - m_curosrPosition.x;
String tmp = str.new_substr(0, restOfLine);
l_print(tmp);
tmp = str.new_substr(restOfLine);
i32 nlines = tmp.len() / getConsoleSize().x;
if (nlines == 0)
{
l_print(tmp);
return;
}
for (i32 i = 0; i < nlines; i++)
{
String line = tmp.new_substr(0, getConsoleSize().x);
l_print(line);
tmp.substr(getConsoleSize().x);
}
if (tmp.len() > 0)
l_print(tmp);
}
}
}

View file

@ -0,0 +1,127 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/io/IOHandlers.hpp>
#include <ostd/string/TextStyleParser.hpp>
#include <ogfx/render/BasicRenderer.hpp>
namespace ogfx
{
class WindowCore;
class GraphicsWindowOutputHandler : public ostd::OutputHandlerBase
{
public: enum class eWrapMode { TripleDots, NewLine };
public:
GraphicsWindowOutputHandler(void);
void attachWindow(WindowCore& window);
void setMonospaceFont(const String& filePath);
Vec2 getStringSize(const String& str);
bool isReady(void);
void resetCursorPosition(void);
void resetColors(void);
void beginFrame(void);
void setFontSize(i32 fontSize);
void setConsoleMaxCharacters(const IPoint& size);
void setConsolePosition(const Vec2& pos);
void setWrapMode(eWrapMode wrapMode);
void setPadding(const Rectangle& rect);
void setDefaultForegroundColor(const Color& color);
void setDefaultBackgorundColor(const Color& color);
void setTabWidth(u8 tw);
i32 getFontSize(void);
Vec2 getCharacterSize(i32 fontSize = 0);
Vec2 getConsolePosition(void);
eWrapMode getWrapMode(void);
Rectangle getPadding(void);
Color getDefaultBackgroundColor(void);
Color getDefaultForegroundColor(void);
u8 getTabWidth(void);
Rectangle getConsoleBounds(void);
GraphicsWindowOutputHandler& bg(const Color& color) override;
GraphicsWindowOutputHandler& bg(const ostd::ConsoleColors::tConsoleColor& color) override;
GraphicsWindowOutputHandler& bg(const String& color) override;
GraphicsWindowOutputHandler& fg(const Color& color) override;
GraphicsWindowOutputHandler& fg(const ostd::ConsoleColors::tConsoleColor& color) override;
GraphicsWindowOutputHandler& fg(const String& color) override;
GraphicsWindowOutputHandler& pChar(char c) override;
GraphicsWindowOutputHandler& pStyled(const String& styled) override;
GraphicsWindowOutputHandler& pStyled(const ostd::TextStyleParser::tStyledString& styled) override;
GraphicsWindowOutputHandler& pStyled(ostd::TextStyleBuilder::IRichStringBase& styled) override;
GraphicsWindowOutputHandler& pObject(const ostd::BaseObject& bo) override;
GraphicsWindowOutputHandler& p(const String& se) override;
GraphicsWindowOutputHandler& p(u8 i) override;
GraphicsWindowOutputHandler& p(i8 i) override;
GraphicsWindowOutputHandler& p(u16 i) override;
GraphicsWindowOutputHandler& p(i16 i) override;
GraphicsWindowOutputHandler& p(u32 i) override;
GraphicsWindowOutputHandler& p(i32 i) override;
GraphicsWindowOutputHandler& p(u64 i) override;
GraphicsWindowOutputHandler& p(i64 i) override;
GraphicsWindowOutputHandler& p(f32 f, u8 precision = 0) override;
GraphicsWindowOutputHandler& p(f64 f, u8 precision = 0) override;
GraphicsWindowOutputHandler& nl(void) override;
GraphicsWindowOutputHandler& tab(void) override;
GraphicsWindowOutputHandler& flush(void) override;
GraphicsWindowOutputHandler& clear(void) override;
GraphicsWindowOutputHandler& reset(void) override;
GraphicsWindowOutputHandler& xy(IPoint position) override;
GraphicsWindowOutputHandler& xy(i32 x, i32 y) override;
GraphicsWindowOutputHandler& x(i32 x) override;
GraphicsWindowOutputHandler& y(i32 y) override;
IPoint getCursorPosition(void) override;
void getCursorPosition(i32& outX, i32& outY) override;
i32 getCursorX(void) override;
i32 getCursorY(void) override;
void getConsoleSize(i32& outColumns, i32& outRows) override;
IPoint getConsoleSize(void) override;
private:
void __update_char_size(void);
void __print_string(const String& str);
private:
Color m_backgroundColor;
Color m_foregroundColor;
Color m_defaultBackgroundColor { 0, 0, 0, 0 };
Color m_defaultForegroundColor { 0, 220, 0, 255 };
Vec2 m_curosrPosition;
BasicRenderer2D m_renderer;
WindowCore* m_window { nullptr };
i32 m_fontSize { 20 };
Vec2 m_charSize;
IPoint m_consoleSize { 0, 0 };
bool m_ready { false };
Vec2 m_consolePosition { 0.0f, 0.0f };
eWrapMode m_wrapMode { eWrapMode::TripleDots };
Rectangle m_padding { 10, 10, 10, 10 };
u8 m_tabWidth { 8 };
};
}

View file

@ -0,0 +1,335 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Button.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../../../ostd/io/FileSystem.hpp"
#include "../Window.hpp"
#include <algorithm>
namespace ogfx
{
namespace gui
{
Button& Button::create(const String& text)
{
m_text = text;
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::Button");
disableChildren();
enableBackground(false);
setStylesheetCategoryName("button");
validate();
invalidate_layout();
return *this;
}
// ===================================================================
// Mode handling
// ===================================================================
void Button::setSizeMode(eSizeMode mode)
{
m_sizeMode = mode;
m_modeExplicitlySet = true;
invalidate_layout();
}
eSizeMode Button::effectiveSizeMode(void) const
{
// Auto-detect: if user never set a mode explicitly and our parent
// owns a layout, we run as LayoutManaged. Otherwise honor the
// stored mode (AutoSize by default).
if (!m_modeExplicitlySet && getParent() && getParent()->hasLayout())
return eSizeMode::LayoutManaged;
return m_sizeMode;
}
// ===================================================================
// Lifecycle hooks
// ===================================================================
void Button::onBoundsChanged(const Vec2& /*newSize*/)
{
// Layouts call this after they assign new bounds. Recompute the
// content placement so it stays centered inside the new size.
invalidate_layout();
}
void Button::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
if (event.mouse->button == MouseEventData::eButton::Left && callback_onActionPerformed)
callback_onActionPerformed(event);
}
void Button::onUpdate(void)
{
if (isAnimatedEnabled())
m_anim.update();
}
void Button::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (m_layoutDirty)
recompute_layout();
const Vec2 contentTL = getGlobalContentPosition();
// --- Icon ---
if (isIconEnabled() && m_drawIconSize.x > 0 && m_drawIconSize.y > 0)
{
const Vec2 iconPos = contentTL + m_drawIconOffset;
if (isAnimatedEnabled())
gfx.drawAnimation(m_anim, iconPos, m_drawIconSize, getIconTintColor());
else
gfx.drawImage(m_icon, iconPos, m_drawIconSize, getIconTintColor());
}
// --- Text ---
if (!isIconOnlyEnabled() && m_text.len() > 0)
{
gfx.drawString(
m_text,
contentTL + m_drawTextOffset,
getTextColor(),
getFontSize()
);
}
}
// ===================================================================
// Setters that trigger a recompute
// ===================================================================
void Button::setText(const String& text)
{
m_text = text;
invalidate_layout();
}
void Button::setIcon(const String& filePath)
{
disableIcon();
if (filePath == "") { invalidate_layout(); return; }
if (!ostd::FileSystem::fileExists(filePath)) { invalidate_layout(); return; }
m_icon.destroy();
m_icon.loadFromFile(filePath, getWindow().getGFX());
enableIcon();
invalidate_layout();
}
void Button::applyTheme(const ostd::Stylesheet& theme)
{
enableIcon(getThemeValue<bool>(theme, "showIcon", m_showIcon));
enableIconOnly(getThemeValue<bool>(theme, "iconOnly", m_iconOnly));
enableAnimated(getThemeValue<bool>(theme, "icon.animated", m_animated));
setAnimationData(getThemeValue<AnimationData>(theme, "icon.animation", m_animData));
setIconTintColor(getThemeValue<Color>(theme, "icon.tint", getIconTintColor()));
String filePath = getThemeValue<String>(theme, "icon.path", m_icon.getFilePath());
if (filePath != m_icon.getFilePath())
setIcon(filePath);
setIconSize(getThemeValue<Vec2>(theme, "icon.size", getIconSize()));
setIconSpacing(getThemeValue<f32>(theme, "icon.spacing", m_iconSpacing));
if (isAnimatedEnabled())
{
m_anim.create(m_animData);
m_anim.setSpriteSheet(m_icon);
}
invalidate_layout();
}
// ===================================================================
// Icon size helpers
// ===================================================================
Vec2 Button::icon_floor(void) const
{
// Configurable hint floor: if the user set m_iconSize, use it as the
// minimum icon dimensions. Otherwise fall back to font size square.
const f32 fontEdge = (f32)getFontSize();
if (m_iconSize.x > 0 && m_iconSize.y > 0)
return m_iconSize;
if (m_iconSize.x > 0)
return { m_iconSize.x, m_iconSize.x };
if (m_iconSize.y > 0)
return { m_iconSize.y, m_iconSize.y };
return { fontEdge, fontEdge };
}
Vec2 Button::compute_icon_size(f32 textHeightHint) const
{
if (!isIconEnabled())
return { 0, 0 };
const Vec2 floor = icon_floor();
// Target height: max(text height, floor.y). Width derives from
// the icon's natural aspect ratio if the icon is loaded; otherwise
// match the floor.
f32 targetH = std::max(textHeightHint, floor.y);
if (m_icon.isLoaded())
{
// When the icon is animated, the "natural size" is one frame,
// NOT the whole sprite sheet — the sheet's aspect ratio is
// meaningless (it depends on column/row count). Same logic
// as ImageLabel::compute_draw_geometry.
Vec2 nat;
if (m_animated)
{
const auto frame = m_anim.getFrameRect();
nat = { frame.w, frame.h };
}
else
{
nat = m_icon.getSize();
}
if (nat.x > 0 && nat.y > 0)
{
const f32 aspect = nat.x / nat.y;
return { targetH * aspect, targetH };
}
}
// No natural size known — fall back to whichever is wider.
return { std::max(floor.x, targetH), targetH };
}
// ===================================================================
// Layout recompute
//
// Branches by effective mode:
//
// AutoSize:
// 1. Measure text at native font size.
// 2. Compute icon size: height = max(textH, floorH), aspect preserved.
// 3. Set widget size to fit padding + icon + spacing + text.
// 4. Place icon and text inside the content area.
//
// Manual / LayoutManaged:
// 1. Widget size is whatever it currently is (user / layout decided).
// 2. Measure text at native font size.
// 3. Icon size = m_iconSize hint (or font-size square if unset),
// aspect preserved against icon natural size.
// 4. Center the text+icon group inside the content area. Overflow
// is allowed and clipped at widget level (positions can go
// slightly outside if the user picked sizes that don't fit).
// ===================================================================
void Button::recompute_layout(void)
{
m_layoutDirty = false;
auto& gfx = getWindow().getGFX();
const Rectangle pad = getPadding();
const f32 padW = pad.left() + pad.right();
const f32 padH = pad.top() + pad.bottom();
// 1) Text dimensions (native font size in all modes).
const bool hasText = !isIconOnlyEnabled() && m_text.len() > 0;
const Vec2 textSize = hasText
? gfx.getStringDimensions(m_text, getFontSize())
: Vec2 { 0, 0 };
// 2) Icon dimensions.
const eSizeMode mode = effectiveSizeMode();
Vec2 iconSize { 0, 0 };
if (isIconEnabled())
{
if (mode == eSizeMode::AutoSize)
{
// Floor from m_iconSize hint (or font size); rise to text height.
iconSize = compute_icon_size(textSize.y);
}
else
{
// Manual / LayoutManaged: just use the configured hint, no
// floor-against-text logic. If unset, use font size square.
iconSize = compute_icon_size(0.0f);
// Cap the icon to the content area so padding is always
// respected. Aspect ratio is preserved during the cap.
// AutoSize doesn't need this because it grows the widget
// to accommodate the icon + padding.
const f32 availW = std::max(0.0f, getw() - padW);
const f32 availH = std::max(0.0f, geth() - padH);
if (iconSize.x > 0 && iconSize.y > 0
&& (iconSize.x > availW || iconSize.y > availH))
{
const f32 sx = (iconSize.x > 0) ? availW / iconSize.x : 1.0f;
const f32 sy = (iconSize.y > 0) ? availH / iconSize.y : 1.0f;
const f32 s = std::min(sx, sy);
iconSize = { iconSize.x * s, iconSize.y * s };
}
}
}
const bool hasIcon = iconSize.x > 0 && iconSize.y > 0;
// 3) Native content extents (icon + spacing + text), no padding yet.
f32 contentNativeW = 0;
f32 contentNativeH = 0;
if (hasIcon)
{
contentNativeW += iconSize.x;
contentNativeH = std::max(contentNativeH, iconSize.y);
}
if (hasText)
{
if (hasIcon) contentNativeW += m_iconSpacing;
contentNativeW += textSize.x;
contentNativeH = std::max(contentNativeH, textSize.y);
}
// 4) Decide widget size (AutoSize only) or honor the existing one.
if (mode == eSizeMode::AutoSize)
{
const f32 fallback = (f32)getFontSize();
const f32 wantW = (contentNativeW > 0 ? contentNativeW : fallback) + padW;
const f32 wantH = (contentNativeH > 0 ? contentNativeH : fallback) + padH;
// Only call setSize if the size actually changed — avoids the
// hover-flicker cycle (theme reload -> applyTheme ->
// invalidate_layout -> recompute -> setSize -> hover boundary
// crosses -> ... ).
if (std::abs(getw() - wantW) > 0.5f || std::abs(geth() - wantH) > 0.5f)
setSize({ wantW, wantH });
}
// else: Manual & LayoutManaged keep current widget size.
// 5) Place icon and text inside the content area, centered.
const Vec2 contentSize = getContentBounds().getSize();
const f32 spacing = (hasIcon && hasText) ? m_iconSpacing : 0.0f;
const f32 totalW = (hasIcon ? iconSize.x : 0) + spacing + (hasText ? textSize.x : 0);
const f32 startX = (contentSize.x - totalW) * 0.5f;
m_drawIconSize = iconSize;
if (hasIcon)
m_drawIconOffset = { startX, (contentSize.y - iconSize.y) * 0.5f };
else
m_drawIconOffset = { 0, 0 };
if (hasText)
m_drawTextOffset = {
startX + (hasIcon ? iconSize.x + spacing : 0.0f),
(contentSize.y - textSize.y) * 0.5f
};
else
m_drawTextOffset = { 0, 0 };
}
}
}

View file

@ -0,0 +1,142 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/resources/Image.hpp>
#include <ogfx/utils/Animation.hpp>
namespace ogfx
{
namespace gui
{
// ===========================================================================
// eSizeMode — shared across Button / Label / (and analogous on ImageLabel).
//
// AutoSize: widget computes its own size to fit content. Used when
// there's no layout managing it, and the user wants the
// widget to "just be the right size".
//
// Manual: user controls the widget's size; user also controls the
// content sizes (text/icon). Nothing is computed automatically.
// Content is drawn at its user-specified size, centered, and
// clipped at the widget edge if it doesn't fit.
//
// LayoutManaged: parent's Layout controls the widget's size. Content
// (text/icon) is at its user-specified size, and the widget
// honors whatever bounds the layout assigns.
// This mode is auto-selected when added to a parent that
// has a layout — unless the user explicitly set Manual.
// ===========================================================================
enum class eSizeMode : u8
{
AutoSize,
Manual,
LayoutManaged
};
class Button : public Widget
{
public:
inline Button(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline Button(Window& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); }
Button& create(const String& text);
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onUpdate(void) override;
void onMouseReleased(const Event& event) override;
void onBoundsChanged(const Vec2& newSize) override;
void applyTheme(const ostd::Stylesheet& theme) override;
void setText(const String& text);
void setIcon(const String& filePath);
inline String getText(void) const { return m_text; }
inline Image& getIcon(void) { return m_icon; }
// =========================== SIZE MODE ============================
// Set the widget's size mode explicitly. After calling this, the
// auto-detection (LayoutManaged when parent has a layout) is
// disabled — the user's choice is respected.
void setSizeMode(eSizeMode mode);
inline eSizeMode getSizeMode(void) const { return m_sizeMode; }
// The mode actually used by the layout/draw code. Resolves auto-
// detection: if the user hasn't explicitly set a mode and the
// parent has a layout, this returns LayoutManaged.
eSizeMode effectiveSizeMode(void) const;
// ==================================================================
OSTD_BOOL_PARAM_GETSET_E(IconOnly, m_iconOnly);
OSTD_BOOL_PARAM_GETSET_E(Animated, m_animated);
OSTD_BOOL_PARAM_GETSET_E(Icon, m_showIcon);
OSTD_PARAM_GETSET(ostd::AnimationData, AnimationData, m_animData);
OSTD_PARAM_GETSET(Vec2, IconSize, m_iconSize);
OSTD_PARAM_GETSET(f32, IconSpacing, m_iconSpacing);
OSTD_PARAM_GETSET(Color, IconTintColor, m_iconTint);
private:
inline void invalidate_layout(void) { m_layoutDirty = true; }
// Re-runs the size + content computation. What this does depends
// entirely on effectiveSizeMode() — see the cpp for details.
void recompute_layout(void);
// Returns the icon size to draw, given the current effective text
// height. In AutoSize: max(text-height, hintFloor) with aspect
// preserved. In Manual/LayoutManaged: the user's m_iconSize hint
// (or fontSize fallback). Aspect ratio is always preserved when
// the icon is loaded with a known natural size.
Vec2 compute_icon_size(f32 textHeightHint) const;
// Native floor for the icon: m_iconSize if set, else font size.
// (User-configurable hint as discussed; the icon will not be
// drawn smaller than this in AutoSize mode.)
Vec2 icon_floor(void) const;
private:
String m_text { "" };
Image m_icon;
ostd::AnimationData m_animData;
Animation m_anim;
Vec2 m_iconSize { 0, 0 };
f32 m_iconSpacing { 10 };
Color m_iconTint { Colors::White };
// ===== Mode =====
eSizeMode m_sizeMode { eSizeMode::AutoSize };
bool m_modeExplicitlySet { false };
// ================
// ===== Computed draw state (refreshed by recompute_layout) =====
bool m_layoutDirty { true };
Vec2 m_drawIconSize { 0, 0 };
Vec2 m_drawIconOffset { 0, 0 };
Vec2 m_drawTextOffset { 0, 0 };
// ===============================================================
// ===== Flags =====
bool m_iconOnly { false };
bool m_showIcon { false };
bool m_animated { false };
// =================
};
}
}

View file

@ -0,0 +1,94 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "CheckBox.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
CheckBox& CheckBox::create(const String& text)
{
setText(text);
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::CheckBox");
disableChildren();
enableBackground(false);
setStylesheetCategoryName("checkbox");
validate();
return *this;
}
void CheckBox::applyTheme(const ostd::Stylesheet& theme)
{
setCheckBorderColor(getThemeValue<Color>(theme, "checkBorderColor", Colors::White));
setCheckBoxColor(getThemeValue<Color>(theme, "checkBoxColor", Colors::White));
m_checkBorderRadius = getThemeValue<i32>(theme, "checkBorderRadius", 5);
m_checkBorderWidth = getThemeValue<i32>(theme, "checkBorderWidth", 1);
}
void CheckBox::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (m_textChanged)
__update_size(gfx);
gfx.drawRoundRect({ getGlobalContentPosition(), m_checkSize }, getCheckBorderColor(), m_checkBorderRadius, m_checkBorderWidth);
if (isChecked())
gfx.fillRoundRect({ getGlobalContentPosition() + 5, m_checkSize - 10 }, getCheckBoxColor(), m_checkBorderRadius / 2.0f);
gfx.drawString(getText(), getGlobalContentPosition() + Vec2 { m_checkSize.x + m_spacing, 0 }, getTextColor(), getFontSize());
}
void CheckBox::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
setChecked(!isChecked());
}
void CheckBox::setText(const String& text)
{
m_text = text;
m_textChanged = true;
}
void CheckBox::setChecked(bool checked)
{
if (m_checked == checked)
return;
m_checked = checked;
setThemeQualifier("active", m_checked);
if (callback_onStateChanged)
callback_onStateChanged(*this, m_checked);
}
void CheckBox::__update_size(ogfx::BasicRenderer2D& gfx)
{
auto size = gfx.getStringDimensions(getText(), getFontSize());
m_checkSize = { cast<f32>(size.y), cast<f32>(size.y) };
size.x += m_spacing + m_checkSize.x;
size.x += getPadding().left();
size.x += getPadding().right();
size.y += getPadding().top();
size.y += getPadding().bottom();
setSize({ cast<f32>(size.x), cast<f32>(size.y) });
m_textChanged = false;
}
}
}

View file

@ -0,0 +1,66 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/math/Geometry.hpp>
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class CheckBox : public Widget
{
public:
inline CheckBox(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline CheckBox(Window& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); }
CheckBox& create(const String& text);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseReleased(const Event& event) override;
void setText(const String& text);
void setChecked(bool checked);
inline String getText(void) const { return m_text; }
inline Color getCheckBorderColor(void) const { return m_checkBorderColor; }
inline void setCheckBorderColor(const Color& color) { m_checkBorderColor = color; }
inline Color getCheckBoxColor(void) const { return m_checkBoxColor; }
inline void setCheckBoxColor(const Color& color) { m_checkBoxColor = color; }
inline bool isChecked(void) const { return m_checked; }
inline void setStateChangedCallback(std::function<void(CheckBox&, bool)> callback) { callback_onStateChanged = std::move(callback); }
private:
void __update_size(ogfx::BasicRenderer2D& gfx);
private:
bool m_checked { false };
String m_text { "" };
bool m_textChanged { false };
f32 m_spacing { 10 };
Vec2 m_checkSize { 0, 0 };
i32 m_checkBorderRadius { 5 };
i32 m_checkBorderWidth { 1 };
Color m_checkBorderColor { 255, 255, 255 };
Color m_checkBoxColor { 255, 255, 255 };
std::function<void(CheckBox&, bool)> callback_onStateChanged { nullptr };
};
}
}

View file

@ -0,0 +1,378 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
(license header same as the project)
*/
#include "ColorPicker.hpp"
#include "../../render/BasicRenderer.hpp"
#include <cmath>
#include <algorithm>
namespace ogfx
{
namespace gui
{
// ===================================================================
// Lifecycle
// ===================================================================
ColorPicker& ColorPicker::create(void)
{
setPadding({ 4, 4, 4, 4 });
setTypeName("ogfx::gui::ColorPicker");
setStylesheetCategoryName("colorPicker");
enableBackground();
enableBorder();
disableFocus();
setContentOffset({ 0, 0 });
setSize({ 320, 180 });
validate();
return *this;
}
void ColorPicker::applyTheme(const ostd::Stylesheet& theme)
{
setStripWidth(getThemeValue<f32>(theme, "stripWidth", m_stripWidth));
setRegionSpacing(getThemeValue<f32>(theme, "regionSpacing", m_regionSpacing));
setCursorRadius(getThemeValue<f32>(theme, "cursorRadius", m_cursorRadius));
setInfoColumnWidth(getThemeValue<f32>(theme, "infoColumnWidth", m_infoColumnW));
setCheckerSize(getThemeValue<f32>(theme, "checkerSize", m_checkerSize));
setGradientSteps(getThemeValue<i32>(theme, "gradientSteps", m_gradientSteps));
enableAlphaChannel(getThemeValue<bool>(theme, "showAlpha", m_showAlpha));
enableColorPreview(getThemeValue<bool>(theme, "showPreview", m_showPreview));
m_showPreview = false; // Overridden to disable functionality without removing the code
}
void ColorPicker::onDraw(BasicRenderer2D& gfx)
{
const Rectangle sv = sv_bounds();
const Rectangle hue = hue_bounds();
draw_sv_square(gfx, sv);
draw_hue_strip(gfx, hue);
draw_sv_cursor(gfx, sv);
draw_strip_cursor(gfx, hue, m_hue);
if (m_showAlpha)
{
const Rectangle al = alpha_bounds();
draw_alpha_strip(gfx, al);
draw_strip_cursor(gfx, al, 1.0f - m_alpha);
}
if (m_showPreview)
{
const Rectangle sw = swatch_bounds();
draw_checkerboard(gfx, sw);
gfx.fillRect(sw, getColor());
gfx.drawRect(sw, getBorderColor(), 1);
// The TextEdit child draws itself via the widget tree.
}
}
// ===================================================================
// Public API
// ===================================================================
Color ColorPicker::getColor(void) const
{
f32 r, g, b;
Color::HSVtoRGB(m_hue, m_sat, m_val, r, g, b);
return Color(
static_cast<u8>(std::round(r * 255.0f)),
static_cast<u8>(std::round(g * 255.0f)),
static_cast<u8>(std::round(b * 255.0f)),
static_cast<u8>(std::round(m_alpha * 255.0f))
);
}
void ColorPicker::setColor(const Color& c)
{
const f32 nr = c.r.value / 255.0f;
const f32 ng = c.g.value / 255.0f;
const f32 nb = c.b.value / 255.0f;
f32 h, s, v;
Color::RGBtoHSV(nr, ng, nb, h, s, v);
// Hue preservation: if the incoming color is grayscale, keep the
// current hue. Otherwise the hue cursor would snap to 0 every
// time someone passes in black/gray/white.
if (s > 0.0001f)
m_hue = h;
m_sat = s;
m_val = v;
m_alpha = c.a.value / 255.0f;
fire_changed();
}
void ColorPicker::setHSV(f32 h, f32 s, f32 v)
{
m_hue = std::clamp(h, 0.0f, 1.0f);
m_sat = std::clamp(s, 0.0f, 1.0f);
m_val = std::clamp(v, 0.0f, 1.0f);
fire_changed();
}
void ColorPicker::setAlpha(f32 a)
{
m_alpha = std::clamp(a, 0.0f, 1.0f);
fire_changed();
}
// ===================================================================
// Geometry
// ===================================================================
// The widget's content area is split:
// [ SV square ][ gap ][ hue ][ gap ][ alpha? ][ gap ][ info column ]
//
// SV square takes whatever's left over after the right-hand stuff.
// All bounds are in GLOBAL coordinates so they can be compared
// directly with mouse event positions.
Rectangle ColorPicker::sv_bounds(void) const
{
const Rectangle cb = getGlobalContentBounds();
f32 rightConsumed = m_stripWidth + m_regionSpacing; // hue
if (m_showAlpha) rightConsumed += m_stripWidth + m_regionSpacing;
if (m_showPreview) rightConsumed += m_infoColumnW + m_regionSpacing;
const f32 sqSize = std::max(20.0f, cb.w - rightConsumed);
return { cb.x, cb.y, sqSize, cb.h };
}
Rectangle ColorPicker::hue_bounds(void) const
{
const Rectangle cb = getGlobalContentBounds();
const Rectangle sv = sv_bounds();
return { sv.x + sv.w + m_regionSpacing, cb.y, m_stripWidth, cb.h };
}
Rectangle ColorPicker::alpha_bounds(void) const
{
if (!m_showAlpha) return { 0, 0, 0, 0 };
const Rectangle cb = getGlobalContentBounds();
const Rectangle hb = hue_bounds();
return { hb.x + hb.w + m_regionSpacing, cb.y, m_stripWidth, cb.h };
}
Rectangle ColorPicker::swatch_bounds(void) const
{
if (!m_showPreview) return { 0, 0, 0, 0 };
const Rectangle cb = getGlobalContentBounds();
const Rectangle anchor = m_showAlpha ? alpha_bounds() : hue_bounds();
const f32 x = anchor.x + anchor.w + m_regionSpacing;
const f32 swatchH = 40.0f;
return { x, cb.y, m_infoColumnW, swatchH };
}
// ===================================================================
// Hit testing & mouse
// ===================================================================
ColorPicker::Region ColorPicker::hit_test(const Vec2& m) const
{
if (sv_bounds().contains(m, true)) return Region::SV;
if (hue_bounds().contains(m, true)) return Region::Hue;
if (m_showAlpha && alpha_bounds().contains(m, true)) return Region::Alpha;
return Region::None;
}
void ColorPicker::update_from_mouse(Region region, const Vec2& m)
{
auto norm = [](f32 v, f32 lo, f32 hi) {
return std::clamp((v - lo) / (hi - lo), 0.0f, 1.0f);
};
if (region == Region::SV)
{
const Rectangle b = sv_bounds();
m_sat = norm(m.x, b.x, b.x + b.w);
m_val = 1.0f - norm(m.y, b.y, b.y + b.h);
}
else if (region == Region::Hue)
{
const Rectangle b = hue_bounds();
// Top is hue 0 (red), bottom wraps back near red.
m_hue = norm(m.y, b.y, b.y + b.h);
// Clamp to avoid h == 1.0 causing HSVtoRGB to wrap weirdly.
if (m_hue >= 1.0f) m_hue = 0.9999f;
}
else if (region == Region::Alpha)
{
const Rectangle b = alpha_bounds();
// Top = fully opaque, bottom = fully transparent.
m_alpha = 1.0f - norm(m.y, b.y, b.y + b.h);
}
else
{
return;
}
fire_changed();
}
void ColorPicker::onMousePressed(const Event& event)
{
const Vec2 m { event.mouse->position_x, event.mouse->position_y };
m_activeDrag = hit_test(m);
if (m_activeDrag != Region::None)
{
update_from_mouse(m_activeDrag, m);
event.handle();
}
}
void ColorPicker::onMouseDragged(const Event& event)
{
if (m_activeDrag == Region::None) return;
if (event.mouse->button != MouseEventData::eButton::Left) return;
const Vec2 m { event.mouse->position_x, event.mouse->position_y };
update_from_mouse(m_activeDrag, m);
event.handle();
}
void ColorPicker::onMouseReleased(const Event& /*event*/)
{
m_activeDrag = Region::None;
}
// ===================================================================
// Drawing
// ===================================================================
Color ColorPicker::current_pure_hue(void) const
{
f32 r, g, b;
Color::HSVtoRGB(m_hue, 1.0f, 1.0f, r, g, b);
return Color(
static_cast<u8>(std::round(r * 255.0f)),
static_cast<u8>(std::round(g * 255.0f)),
static_cast<u8>(std::round(b * 255.0f)),
255
);
}
void ColorPicker::draw_sv_square(BasicRenderer2D& gfx, const Rectangle& r)
{
// Strategy: divide the square into N vertical stripes. For each
// stripe i at saturation s_i = i/N, draw a vertical gradient from
// HSV(hue, s_i, 1) (top) down to BLACK (bottom). This is exactly
// the canonical HSV "saturation X, value Y" 2D gradient — no
// shaders needed.
const i32 N = std::max(8, m_gradientSteps);
const f32 stripeW = r.w / static_cast<f32>(N);
for (i32 i = 0; i < N; ++i)
{
const f32 s = (i + 0.5f) / static_cast<f32>(N);
f32 rr, gg, bb;
Color::HSVtoRGB(m_hue, s, 1.0f, rr, gg, bb);
const Color topColor(
static_cast<u8>(std::round(rr * 255.0f)),
static_cast<u8>(std::round(gg * 255.0f)),
static_cast<u8>(std::round(bb * 255.0f)),
255
);
ColorGradient grad({ topColor, Colors::Black }, { 1.0f });
grad.setAngleDeg(ColorGradient::VerticalDeg);
Rectangle stripe {
r.x + i * stripeW,
r.y,
stripeW + 0.5f, // tiny overlap so no seams between stripes
r.h
};
gfx.fillGradientRect(stripe, grad);
}
// Border on top to crisp it up.
gfx.drawRect(r, getBorderColor(), 1);
}
void ColorPicker::draw_hue_strip(BasicRenderer2D& gfx, const Rectangle& r)
{
// Six bands across the hue wheel: red -> yellow -> green -> cyan
// -> blue -> magenta -> red. ColorGradient handles them in one call.
ColorGradient grad(
{
Color(255, 0, 0),
Color(255, 255, 0),
Color( 0, 255, 0),
Color( 0, 255, 255),
Color( 0, 0, 255),
Color(255, 0, 255),
Color(255, 0, 0)
},
{ 1, 1, 1, 1, 1, 1 } // equal weights -> normalized to 1/6 each
);
grad.setAngleDeg(ColorGradient::VerticalDeg);
gfx.fillGradientRect(r, grad);
gfx.drawRect(r, getBorderColor(), 1);
}
void ColorPicker::draw_alpha_strip(BasicRenderer2D& gfx, const Rectangle& r)
{
// Checkerboard background so transparency is visible.
draw_checkerboard(gfx, r);
// Color gradient: pure-color-opaque at top -> same-color-transparent at bottom.
Color top = current_pure_hue();
f32 rr, gg, bb;
Color::HSVtoRGB(m_hue, m_sat, m_val, rr, gg, bb);
top.r = static_cast<u8>(std::round(rr * 255.0f));
top.g = static_cast<u8>(std::round(gg * 255.0f));
top.b = static_cast<u8>(std::round(bb * 255.0f));
top.a = 255;
Color bottom = top;
bottom.a = 0;
ColorGradient grad({ top, bottom }, { 1.0f });
grad.setAngleDeg(ColorGradient::VerticalDeg);
gfx.fillGradientRect(r, grad);
gfx.drawRect(r, getBorderColor(), 1);
}
void ColorPicker::draw_checkerboard(BasicRenderer2D& gfx, const Rectangle& r)
{
const Color light(200, 200, 200);
const Color dark(140, 140, 140);
gfx.fillRect(r, light);
const f32 cs = m_checkerSize;
const i32 cols = static_cast<i32>(std::ceil(r.w / cs));
const i32 rows = static_cast<i32>(std::ceil(r.h / cs));
for (i32 y = 0; y < rows; ++y)
{
for (i32 x = 0; x < cols; ++x)
{
if (((x + y) & 1) == 0) continue;
const f32 cx = r.x + x * cs;
const f32 cy = r.y + y * cs;
const f32 cw = std::min(cs, r.x + r.w - cx);
const f32 ch = std::min(cs, r.y + r.h - cy);
if (cw <= 0 || ch <= 0) continue;
gfx.fillRect({ cx, cy, cw, ch }, dark);
}
}
}
void ColorPicker::draw_sv_cursor(BasicRenderer2D& gfx, const Rectangle& r)
{
const Vec2 c { r.x + m_sat * r.w, r.y + (1.0f - m_val) * r.h };
// Double ring (white outer, black inner) — visible on any background.
gfx.drawCircle(c, m_cursorRadius, Colors::White, 2);
gfx.drawCircle(c, m_cursorRadius - 2.0f, Colors::Black, 1);
}
void ColorPicker::draw_strip_cursor(BasicRenderer2D& gfx, const Rectangle& r, f32 t)
{
const f32 y = r.y + t * r.h;
// Two short horizontal bars flanking the strip, plus a thin line
// across it.
const f32 bar = 4.0f;
gfx.fillRect({ r.x - bar, y - 2.0f, bar, 4.0f }, Colors::White);
gfx.fillRect({ r.x + r.w, y - 2.0f, bar, 4.0f }, Colors::White);
gfx.drawRect({ r.x - bar - 1, y - 3, r.w + (bar + 1) * 2, 6 },
Colors::Black, 1);
}
void ColorPicker::fire_changed(void)
{
if (callback_onColorChanged)
callback_onColorChanged(getColor());
}
}
}

View file

@ -0,0 +1,141 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Text.hpp>
namespace ogfx
{
namespace gui
{
// ===========================================================================
// ColorPicker — inline HSV/alpha picker.
//
// Layout (left to right inside content bounds):
//
// [ SV square ][ hue strip ][ alpha strip ][ swatch + hex field ]
//
// The SV square's hue is driven by the hue strip. The strips are thin
// vertical bars. The swatch shows the current color over a checkerboard
// (so alpha is visible). The hex field is a child TextEdit that round-
// trips with the picker state.
//
// State is stored as HSV+alpha (not RGB). This preserves hue and
// saturation as the user drags value to zero or saturation to zero —
// otherwise the SV/hue cursors would jump every time the color goes
// grayscale.
// ===========================================================================
class ColorPicker : public Widget
{
public:
inline ColorPicker(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
ColorPicker& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onMousePressed(const Event& event) override;
void onMouseDragged(const Event& event) override;
void onMouseReleased(const Event& event) override;
// Round-tripped public API. setColor preserves hue when the
// incoming color is grayscale (s == 0).
Color getColor(void) const;
void setColor(const Color& c);
// Component access for callers that want HSV directly.
inline f32 getHue(void) const { return m_hue; }
inline f32 getSaturation(void) const { return m_sat; }
inline f32 getValue(void) const { return m_val; }
inline f32 getAlpha(void) const { return m_alpha; }
void setHSV(f32 h, f32 s, f32 v);
void setAlpha(f32 a);
inline void setColorChangedCallback(std::function<void(const Color&)> cb)
{ callback_onColorChanged = std::move(cb); }
// Show/hide the alpha strip. Default: true.
OSTD_BOOL_PARAM_GETSET_E(AlphaChannel, m_showAlpha);
// Show/hide the hex input field. Default: true.
OSTD_BOOL_PARAM_GETSET_E(ColorPreview, m_showPreview);
// SV cursor radius (the little ring on the square).
OSTD_PARAM_GETSET(f32, CursorRadius, m_cursorRadius);
// Width of each side strip (hue/alpha).
OSTD_PARAM_GETSET(f32, StripWidth, m_stripWidth);
// Gap between regions.
OSTD_PARAM_GETSET(f32, RegionSpacing, m_regionSpacing);
// Size of the right-hand swatch + hex field column.
OSTD_PARAM_GETSET(f32, InfoColumnWidth, m_infoColumnW);
// Checkerboard cell size used for alpha visualization.
OSTD_PARAM_GETSET(f32, CheckerSize, m_checkerSize);
// Number of stripes used to draw the SV/alpha gradients
// (the renderer's fillGradientRect is 1D, so we hand-draw the
// SV square as N horizontal stripes that each fade their own
// vertical gradient). 32-48 looks smooth and is cheap.
OSTD_PARAM_GETSET(i32, GradientSteps, m_gradientSteps);
private:
enum class Region { None, SV, Hue, Alpha };
// Geometry helpers — all return GLOBAL coordinates so they
// match what the mouse events give us.
Rectangle sv_bounds(void) const;
Rectangle hue_bounds(void) const;
Rectangle alpha_bounds(void) const;
Rectangle swatch_bounds(void) const;
Region hit_test(const Vec2& globalMouse) const;
void update_from_mouse(Region region, const Vec2& globalMouse);
// Drawing helpers.
void draw_sv_square(BasicRenderer2D& gfx, const Rectangle& r);
void draw_hue_strip(BasicRenderer2D& gfx, const Rectangle& r);
void draw_alpha_strip(BasicRenderer2D& gfx, const Rectangle& r);
void draw_checkerboard(BasicRenderer2D& gfx, const Rectangle& r);
void draw_sv_cursor(BasicRenderer2D& gfx, const Rectangle& r);
void draw_strip_cursor(BasicRenderer2D& gfx, const Rectangle& r, f32 t);
// HSV state propagation.
void fire_changed(void);
Color current_pure_hue(void) const; // HSV(h, 1, 1)
private:
f32 m_hue { 0.0f }; // [0, 1]
f32 m_sat { 1.0f }; // [0, 1]
f32 m_val { 1.0f }; // [0, 1]
f32 m_alpha { 1.0f }; // [0, 1]
Region m_activeDrag { Region::None };
bool m_showAlpha { true };
bool m_showPreview { true };
f32 m_cursorRadius { 6.0f };
f32 m_stripWidth { 18.0f };
f32 m_regionSpacing { 8.0f };
f32 m_infoColumnW { 96.0f };
f32 m_checkerSize { 6.0f };
i32 m_gradientSteps { 32 };
std::function<void(const Color&)> callback_onColorChanged { nullptr };
};
}
}

View file

@ -0,0 +1,351 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
(license header same as the project)
*/
#include "ColorPickerSkins.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../Window.hpp"
#include <cmath>
#include <algorithm>
namespace ogfx
{
namespace gui
{
// ===================================================================
// ColorPickerPanel — RGBA slider skin
// ===================================================================
ColorPickerPanel& ColorPickerPanel::create(void)
{
setPadding({ 8, 8, 8, 8 });
setTypeName("ogfx::gui::ColorPickerPanel");
setStylesheetCategoryName("colorPickerPanel");
enableBackground();
enableBorder();
setSize({ 260, 200 });
auto make_slider = [this](Color trackProgress) -> Slider* {
auto* s = new Slider(getWindow(), false, 0.0f, 255.0f, 1.0f);
s->setTrackProgressColor(trackProgress);
s->setValueChangedCallback([this](f32 oldValue, f32 newValue) { on_slider_changed(); });
addWidget(*s);
return s;
};
auto make_label = [this](const String& txt) -> Label* {
auto* l = new Label(getWindow(), txt);
addWidget(*l);
return l;
};
m_rSlider = make_slider(Color(220, 60, 60));
m_gSlider = make_slider(Color(60, 200, 60));
m_bSlider = make_slider(Color(80, 120, 230));
m_aSlider = make_slider(Color(180, 180, 180));
m_rLabel = make_label("R");
m_gLabel = make_label("G");
m_bLabel = make_label("B");
m_aLabel = make_label("A");
m_hexField = new TextEdit(getWindow());
m_hexField->setMaxLength(9);
m_hexField->setCallback(eCallback::ActionPerformed, [this](const Event&) {
Color parsed;
parsed.set(m_hexField->getText());
if (parsed.isValid())
setColor(parsed);
else
sync_sliders_from_color(m_currentColor); // rejected -> restore
});
addWidget(*m_hexField);
layout_children();
sync_sliders_from_color(m_currentColor);
validate();
return *this;
}
void ColorPickerPanel::applyTheme(const ostd::Stylesheet& theme)
{
// Nothing panel-specific yet; child widgets inherit through the
// regular theme propagation.
(void)theme;
}
void ColorPickerPanel::layout_children(void)
{
// Simple manual vertical layout:
// [swatch]
// R: [-----slider-----]
// G: [-----slider-----]
// B: [-----slider-----]
// A: [-----slider-----]
// [ hex field ]
const Rectangle cb = getGlobalContentBounds();
const Vec2 cp = getGlobalContentPosition();
const f32 lblW = 18.0f;
const f32 gap = 6.0f;
const f32 rowH = 22.0f;
const f32 swatchH = 32.0f;
f32 y = swatchH + gap; // swatch occupies the top — drawn in onDraw
auto place_row = [&](Label* lbl, Slider* sl) {
lbl->setPosition({ 0, y + 2 });
lbl->setSize({ lblW, rowH });
sl->setPosition({ lblW + gap, y + rowH * 0.5f - 9.0f });
sl->setSize({ cb.w - lblW - gap, 18.0f });
y += rowH + 4.0f;
};
place_row(m_rLabel, m_rSlider);
place_row(m_gLabel, m_gSlider);
place_row(m_bLabel, m_bSlider);
place_row(m_aLabel, m_aSlider);
y += gap;
m_hexField->setPosition({ 0, y });
m_hexField->setSize({ cb.w, 26.0f });
(void)cp;
}
Color ColorPickerPanel::getColor(void) const
{
return m_currentColor;
}
void ColorPickerPanel::setColor(const Color& c)
{
m_currentColor = c;
sync_sliders_from_color(c);
if (callback_onColorChanged)
callback_onColorChanged(m_currentColor);
}
void ColorPickerPanel::sync_sliders_from_color(const Color& c)
{
m_suppressCallbacks = true;
// Slider has no public setValue (only set_value private + drag/click),
// so we set value through the callback-free path: re-create min/max
// is overkill; instead we keep an internal mirror m_currentColor and
// pull from there in the draw. If your Slider gains a public
// setValue/setValueSilent, swap these in.
//
// For now we rely on the user dragging the sliders to push values
// in, and on the hex field for programmatic input. If you want
// programmatic set to also move slider handles, add a public
// Slider::setValue and call it here.
if (m_hexField)
{
const String hex = c.hexString(true, "#");
m_hexField->setText(hex);
}
m_suppressCallbacks = false;
}
void ColorPickerPanel::on_slider_changed(void)
{
if (m_suppressCallbacks) return;
m_currentColor = Color(
static_cast<u8>(std::round(m_rSlider->getValue())),
static_cast<u8>(std::round(m_gSlider->getValue())),
static_cast<u8>(std::round(m_bSlider->getValue())),
static_cast<u8>(std::round(m_aSlider->getValue()))
);
m_suppressCallbacks = true;
if (m_hexField)
m_hexField->setText(m_currentColor.hexString(true, "#"));
m_suppressCallbacks = false;
if (callback_onColorChanged)
callback_onColorChanged(m_currentColor);
}
void ColorPickerPanel::onDraw(BasicRenderer2D& gfx)
{
// Top swatch with checkerboard.
const Vec2 cp = getGlobalContentPosition();
const Rectangle cb = getGlobalContentBounds();
Rectangle swatch { cp.x, cp.y, cb.w, 32.0f };
// Checkerboard.
const f32 cs = 5.0f;
const i32 cols = static_cast<i32>(std::ceil(swatch.w / cs));
const i32 rows = static_cast<i32>(std::ceil(swatch.h / cs));
gfx.fillRect(swatch, Color(200, 200, 200));
for (i32 yy = 0; yy < rows; ++yy)
for (i32 xx = 0; xx < cols; ++xx)
if ((xx + yy) & 1)
{
const f32 px = swatch.x + xx * cs;
const f32 py = swatch.y + yy * cs;
const f32 pw = std::min(cs, swatch.x + swatch.w - px);
const f32 ph = std::min(cs, swatch.y + swatch.h - py);
gfx.fillRect({ px, py, pw, ph }, Color(140, 140, 140));
}
gfx.fillRect(swatch, m_currentColor);
gfx.drawRect(swatch, getBorderColor(), 1);
}
// ===================================================================
// ColorButton — swatch + popup
// ===================================================================
ColorButton& ColorButton::create(void)
{
setPadding({ 2, 2, 2, 2 });
setTypeName("ogfx::gui::ColorButton");
setStylesheetCategoryName("colorButton");
enableBackground();
enableBorder();
enableFocus();
enableDragAndDrop();
setSize({ 48, 24 });
setCallback(eCallback::DragAndDrop, [&](const ogfx::gui::Event& event) -> void {
auto data = Widget::getDragAndDropData();
if (data && data->getTypeName() == Color().getTypeName())
setColor(*cast<Color*>(data));
});
connectSignal(ostd::BuiltinSignals::MousePressed);
m_contextMenu.onActivate = [this](const ContextMenu::Entry& e) {
if (e.id == MenuId::Copy)
{
// TODO: Replace once I have a clipboard system
SDL_SetClipboardText(getColor().hexString(true, "#"));
}
else if (e.id == MenuId::Paste)
{
// TODO: Replace once I have a clipboard system
const String clipboardText = SDL_GetClipboardText();
Color tmp;
if (!clipboardText.empty() && Color::isValidColorString(clipboardText, &tmp))
setColor(tmp);
}
};
validate();
return *this;
}
void ColorButton::applyTheme(const ostd::Stylesheet& theme)
{
setCheckerSize(getThemeValue<f32>(theme, "checkerSize", m_checkerSize));
}
void ColorButton::setColor(const Color& c)
{
m_color = c;
if (m_popup) m_popup->setColor(c);
if (callback_onColorChanged)
callback_onColorChanged(m_color);
}
void ColorButton::draw_checkerboard(BasicRenderer2D& gfx, const Rectangle& r)
{
gfx.fillRect(r, Color(200, 200, 200));
const f32 cs = m_checkerSize;
const i32 cols = static_cast<i32>(std::ceil(r.w / cs));
const i32 rows = static_cast<i32>(std::ceil(r.h / cs));
for (i32 y = 0; y < rows; ++y)
for (i32 x = 0; x < cols; ++x)
if ((x + y) & 1)
{
const f32 cx = r.x + x * cs;
const f32 cy = r.y + y * cs;
const f32 cw = std::min(cs, r.x + r.w - cx);
const f32 ch = std::min(cs, r.y + r.h - cy);
if (cw > 0 && ch > 0)
gfx.fillRect({ cx, cy, cw, ch }, Color(140, 140, 140));
}
}
void ColorButton::onDraw(BasicRenderer2D& gfx)
{
const Rectangle r = getGlobalContentBounds();
draw_checkerboard(gfx, r);
gfx.fillRect(r, m_color);
gfx.drawRect(r, getBorderColor(), 1);
}
void ColorButton::onMouseReleased(const Event& event)
{
bool mph = m_mousePressedHere;
m_mousePressedHere = false;
if (!isMouseInside()) return;
if (event.mouse->button != MouseEventData::eButton::Left)
{
if (event.mouse->button == MouseEventData::eButton::Right)
getWindow().showContextMenu(m_contextMenu, { event.mouse->position_x, event.mouse->position_y });
return;
}
if (!mph) return;
if (m_popupOpen) close_popup();
else open_popup();
event.handle();
}
void ColorButton::onMousePressed(const Event& event)
{
if (!isMouseInside()) return;
if (event.mouse->button != MouseEventData::eButton::Left)
return;
Widget::setDragAndDropData(m_color);
m_mousePressedHere = true;
event.handle();
}
void ColorButton::open_popup(void)
{
if (m_popup) close_popup();
m_popup = new ColorPicker(getWindow());
m_popup->setRootChild();
// m_popup->setTopMost(true); // TODO: Implement
m_popup->setSize({ 240, 200 });
m_popup->setColor(m_color);
m_popup->setColorChangedCallback([this](const Color& c) {
m_color = c;
if (callback_onColorChanged)
callback_onColorChanged(m_color);
});
// Position just below this button. Flip up if it would overflow
// the bottom of the window.
const Rectangle b = getGlobalBounds();
f32 px = b.x;
f32 py = b.y + b.h + 4.0f;
const f32 winH = static_cast<f32>(getWindow().getWindowHeight());
const f32 winW = static_cast<f32>(getWindow().getWindowWidth());
if (py + m_popup->geth() > winH)
py = b.y - 4.0f - m_popup->geth();
if (px + m_popup->getw() > winW)
px = winW - m_popup->getw() - 4.0f;
if (px < 0) px = 4.0f;
if (py < 0) py = 4.0f;
m_popup->setPosition({ px, py });
getWindow().addWidget(*m_popup);
m_popupOpen = true;
}
void ColorButton::close_popup(void)
{
if (!m_popup) { m_popupOpen = false; return; }
getWindow().removeWidget(*m_popup);
delete m_popup;
m_popup = nullptr;
m_popupOpen = false;
}
void ColorButton::handleSignal(ostd::Signal& signal)
{
if (!m_popupOpen) return;
if (signal.ID != ostd::BuiltinSignals::MousePressed) return;
if (isMouseInside()) return;
if (!m_popup->isMouseInside())
close_popup();
}
}
}

View file

@ -0,0 +1,128 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
(license header same as the project)
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Slider.hpp>
#include <ogfx/gui/widgets/Label.hpp>
#include <ogfx/gui/widgets/Text.hpp>
#include <ogfx/gui/widgets/Button.hpp>
#include <ogfx/gui/widgets/ColorPicker.hpp>
#include <ogfx/gui/ContextMenu.hpp>
namespace ogfx
{
namespace gui
{
// ===========================================================================
// ColorPickerPanel — slider-driven RGBA picker.
//
// An alternate "skin" for ColorPicker: four sliders (R, G, B, A) plus a
// swatch and hex field. Useful when the user wants precise numeric input
// rather than dragging a square. Internally it owns a ColorPicker as the
// authoritative state holder (so the two forms can be cross-bound) but
// you can also use it standalone — calls to setColor/getColor just go
// straight to the underlying picker.
//
// The constructor takes the parent ColorPicker by reference so you can
// share state across multiple skins of the same color. If you want
// standalone behavior, pass an internally-owned picker (see the
// `createStandalone` factory below).
// ===========================================================================
class ColorPickerPanel : public Widget
{
public:
inline ColorPickerPanel(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
ColorPickerPanel& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
Color getColor(void) const;
void setColor(const Color& c);
inline void setColorChangedCallback(std::function<void(const Color&)> cb)
{ callback_onColorChanged = std::move(cb); }
private:
void on_slider_changed(void);
void sync_sliders_from_color(const Color& c);
void layout_children(void);
private:
Slider* m_rSlider { nullptr };
Slider* m_gSlider { nullptr };
Slider* m_bSlider { nullptr };
Slider* m_aSlider { nullptr };
Label* m_rLabel { nullptr };
Label* m_gLabel { nullptr };
Label* m_bLabel { nullptr };
Label* m_aLabel { nullptr };
TextEdit* m_hexField { nullptr };
Color m_currentColor { Colors::White };
bool m_suppressCallbacks { false };
std::function<void(const Color&)> callback_onColorChanged { nullptr };
};
// ===========================================================================
// ColorButton — small clickable swatch that pops up a ColorPicker.
//
// Behaves like a button: shows the current color (over a checkerboard so
// alpha is visible), and clicking it opens a floating ColorPicker just
// below the swatch. The popup is a TopMost root widget added to the
// window; it closes when the user clicks outside it.
// ===========================================================================
class ColorButton : public Widget
{
public:
inline ColorButton(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
ColorButton& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseReleased(const Event& event) override;
void onMousePressed(const Event& event) override;
void handleSignal(ostd::Signal& signal) override;
inline Color getColor(void) const { return m_color; }
void setColor(const Color& c);
inline void setColorChangedCallback(std::function<void(const Color&)> cb)
{ callback_onColorChanged = std::move(cb); }
OSTD_PARAM_GETSET(f32, CheckerSize, m_checkerSize);
private:
void open_popup(void);
void close_popup(void);
void draw_checkerboard(BasicRenderer2D& gfx, const Rectangle& r);
private:
Color m_color { Colors::White };
f32 m_checkerSize { 4.0f };
// The popup is a separate root-level widget owned by us.
// We allocate it on first open and destroy it on close to keep
// the widget tree clean.
ColorPicker* m_popup { nullptr };
bool m_popupOpen { false };
bool m_mousePressedHere { false };
std::function<void(const Color&)> callback_onColorChanged { nullptr };
enum MenuId : i32 { Copy = 1, Paste };
ContextMenu::Instance m_contextMenu {{
{ "Copy Color", MenuId::Copy },
{ "Paste Color", MenuId::Paste },
}, nullptr
};
};
}
}

View file

@ -0,0 +1,180 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ComboBox.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../Window.hpp"
namespace ogfx
{
namespace gui
{
ComboBox& ComboBox::create(void)
{
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::ComboBox");
disableChildren();
disableFocus();
setStylesheetCategoryName("combo");
connectSignal(ostd::BuiltinSignals::MouseReleased);
connectSignal(ostd::BuiltinSignals::MouseMoved);
validate();
return *this;
}
i32 ComboBox::addMenuItem(const String& text)
{
m_menu.entries.push_back({ text, m_nextItemID++ });
if (m_menu.entries.size() == 1)
{
auto& entry = m_menu.entries.back();
m_selectedEntry = entry;
m_selectedEntryText = get_display_text(entry.text);
}
return m_nextItemID - 1;
}
bool ComboBox::removeMenuItem(const String& text)
{
return m_menu.removeByText(text, false);
}
bool ComboBox::removeMenuItem(i32 id)
{
return m_menu.removeById(id, false);
}
void ComboBox::setItemCallback(ContextMenu::Instance::Callback callback)
{
m_menu.onActivate = [this, cb = std::move(callback)] (const ContextMenu::Entry& entry) -> void {
m_selectedEntry = entry;
m_selectedEntryText = get_display_text(entry.text); // Caching because get_display_text is expensive
if (cb) cb(entry);
};
}
void ComboBox::setw(f32 w)
{
Rectangle::setw(w);
m_menu.minWidth = getw();
if (m_selectedEntry.id >= 0)
m_selectedEntryText = get_display_text(m_selectedEntry.text);
}
void ComboBox::applyTheme(const ostd::Stylesheet& theme)
{
setTriangleIndicatorColor(getThemeValue<Color>(theme, "triangleIndicatorColor", getTriangleIndicatorColor()));
setTriangleIndicatorPadding(getThemeValue<f32>(theme, "triangleIndicatorPadding", getTriangleIndicatorPadding()));
}
void ComboBox::onDraw(ogfx::BasicRenderer2D& gfx)
{
gfx.outlinedRect(*this, getBackgroundColor(), getBorderColor(), getBorderWidth());
auto bounds = getGlobalBounds();
gfx.drawCenteredString(m_selectedEntryText, bounds - Rectangle { 0, 0, geth(), 0 }, getTextColor(), getFontSize());
// Triangle Indicator
const f32 triPad = getTriangleIndicatorPadding();
const f32 triSide = geth() - (triPad * 2);
Triangle tri {
bounds.getPosition() + Vec2 { getw() - triPad - triSide, triPad },
bounds.getPosition() + Vec2 { getw() - triPad, triPad },
bounds.getPosition() + Vec2 { getw() - triPad - (triSide * 0.5f), geth() - triPad }
};
gfx.fillTriangle(tri, getTriangleIndicatorColor());
}
void ComboBox::onMouseReleased(const Event& event)
{
if (contains(event.mouse->position_x, event.mouse->position_y))
{
if (m_dropDownShown)
{
setThemeQualifier("active", false);
m_dropDownShown = false;
return;
}
setThemeQualifier("active", true);
m_dropDownShown = true;
Vec2 anchor = getGlobalPosition() + Vec2 { 0, geth() };
getWindow().showContextMenu(m_menu, anchor);
}
}
void ComboBox::handleSignal(ostd::Signal& signal) // This is to get the global events, in order to bypass
// the fact that some components like MenuBar call
// event.handle() at the very beginning of the event chain
{
if (signal.ID == ostd::BuiltinSignals::MouseReleased)
{
auto& med = cast<MouseEventData&>(signal.userData);
if (contains(med.position_x, med.position_y))
return;
if (!m_dropDownShown)
return;
setThemeQualifier("active", false);
reloadTheme();
m_dropDownShown = false;
}
else if (signal.ID == ostd::BuiltinSignals::MouseMoved)
{
auto& med = cast<MouseEventData&>(signal.userData);
if (contains(med.position_x, med.position_y))
return;
setThemeQualifier("hover", false);
}
}
String ComboBox::get_display_text(const String& entry)
{
auto& gfx = getWindow().getGFX();
f32 _w = getw() - geth(); // Reserve space for HxH triangle indicator
// Fast path: full text fits.
if (gfx.getStringDimensions(entry, getFontSize()).x <= _w)
return entry;
// Reserve space for the ellipsis.
f32 ellipsisW = gfx.getStringDimensions("...", getFontSize()).x;
f32 budget = _w - ellipsisW;
if (budget <= 0)
return ""; // Not even room for "..." — degenerate but possible if the combo is tiny
// Binary search for the largest prefix length that fits within `budget`.
// Invariant: any length <= lo fits; any length > hi does not.
size_t lo = 0;
size_t hi = entry.len(); // adjust to your String's length method
while (lo < hi)
{
size_t mid = lo + (hi - lo + 1) / 2; // upper-mid, avoids infinite loop when hi == lo+1
String prefix = entry.new_substr(0, mid);
if (gfx.getStringDimensions(prefix, getFontSize()).x <= budget)
lo = mid; // mid fits, try longer
else
hi = mid - 1; // mid doesn't fit, must be shorter
}
return entry.new_substr(0, lo) + "...";
}
}
}

View file

@ -0,0 +1,63 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/ContextMenu.hpp>
namespace ogfx
{
namespace gui
{
class ComboBox : public Widget
{
public:
inline ComboBox(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
ComboBox& create(void);
i32 addMenuItem(const String& text);
bool removeMenuItem(const String& text);
bool removeMenuItem(i32 id);
void setItemCallback(ContextMenu::Instance::Callback callback);
void setw(f32 w) override;
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseReleased(const Event& event) override;
void handleSignal(ostd::Signal& signal) override;
inline ContextMenu::Entry getSelectedEntry(void) const { return m_selectedEntry; }
OSTD_PARAM_GETSET(Color, TriangleIndicatorColor, m_triangleColor);
OSTD_PARAM_GETSET(f32, TriangleIndicatorPadding, m_trianglePadding);
private:
String get_display_text(const String& entry);
private:
bool m_dropDownShown { false };
ContextMenu::Instance m_menu;
i32 m_nextItemID { 0 };
ContextMenu::Entry m_selectedEntry { "", -1 };
String m_selectedEntryText { "" }; // Truncated text, not always valid, so can't be exposed
Color m_triangleColor { "#008000FF" };
f32 m_trianglePadding { 8.0f };
};
}
}

View file

@ -0,0 +1,372 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Containers.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../Window.hpp"
namespace ogfx
{
namespace gui
{
Panel& Panel::create(void)
{
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::Panel");
disableFocus();
enableStopEvents();
setStylesheetCategoryName("panel");
validate();
return *this;
}
void Panel::applyTheme(const ostd::Stylesheet& theme)
{
setScrollSpeed(getThemeValue<f32>(theme, "scrollSpeed", 0.8f));
setScrollSmoothFactor(getThemeValue<f32>(theme, "scrollSmoothFactor", 0.7f));
m_titleColor = getThemeValue<Color>(theme, "titleColor", Colors::Black);
m_titlebarColor = getThemeValue<Color>(theme, "titlebarColor", Colors::Transparent);
m_titlebarBorderColor = getThemeValue<Color>(theme, "titlebarBorderColor", Colors::Black);
m_titlebarHeight = getThemeValue<f32>(theme, "titlebarHeight", 30);
m_titlebarBorderWidth = getThemeValue<i32>(theme, "titlebarBorderWidth", 1);
m_titlebarFontSize = getThemeValue<i32>(theme, "titlebarFontSize", 26);
m_titleTextAlign = getThemeValue<i32>(theme, "titlebarTextAlign", cast<i32>(Window::eTextAlign::Left));
setTitlebarType(getThemeValue<String>(theme, "titlebarType", TitleBarTypes::None));
}
void Panel::onMousePressed(const Event& event)
{
if (!isDraggable())
return;
if (m_titleBarBounds.contains({ event.mouse->position_x, event.mouse->position_y }, true))
{
m_mousePressed = true;
m_mousePos = { event.mouse->position_x, event.mouse->position_y };
}
}
void Panel::onMouseReleased(const Event& event)
{
m_mousePressed = false;
}
void Panel::onMouseDragged(const Event& event)
{
if (m_mousePressed)
{
Vec2 mpos { event.mouse->position_x, event.mouse->position_y };
addPos(mpos - m_mousePos);
m_mousePos = mpos;
}
}
void Panel::afterDraw(ogfx::BasicRenderer2D& gfx)
{
draw_titlebar(gfx);
drawScrollbars(gfx);
}
void Panel::onWindowResized(const Event& event)
{
updateScrollbarsSize();
}
void Panel::setTitlebarType(const String& type)
{
String t = type.new_toLower().trim();
if (t == TitleBarTypes::None)
{
setContentOffset({ 0, 0 });
updateScrollbarsSize();
m_titlebarType = TitleBarTypes::NoneValue;
}
else if (t == TitleBarTypes::Minimal)
{
setContentOffset({ 0, m_titlebarHeight });
updateScrollbarsSize();
m_titlebarType = TitleBarTypes::MinimalValue;
}
else if (t == TitleBarTypes::Full)
{
setContentOffset({ 0, m_titlebarHeight });
updateScrollbarsSize();
m_titlebarType = TitleBarTypes::FullValue;
}
}
String Panel::getTitlebarType(void) const
{
switch (m_titlebarType)
{
case TitleBarTypes::FullValue: return TitleBarTypes::Full;
case TitleBarTypes::MinimalValue: return TitleBarTypes::Minimal;
case TitleBarTypes::NoneValue: return TitleBarTypes::None;
default: return TitleBarTypes::None;
}
}
void Panel::draw_titlebar(BasicRenderer2D& gfx)
{
f32 br = cast<f32>(getBorderWidth());
br /= 2;
Rectangle titleBarBounds {
getGlobalPosition() + Vec2 { br, br },
Vec2 { getw(), m_titlebarHeight } - Vec2 { br * 2, br * 2 }
};
m_titleBarBounds = titleBarBounds;
switch (m_titlebarType)
{
case TitleBarTypes::FullValue:
{
gfx.outlinedRoundRect(titleBarBounds, m_titlebarColor, m_titlebarColor, { cast<f32>(getBorderRadius()), cast<f32>(getBorderRadius()), 0, 0 }, getBorderWidth());
gfx.drawLine({ getGlobalPosition() + Vec2 { 0, m_titlebarHeight - 2 }, getGlobalPosition() + Vec2 { getGlobalBounds().w, m_titlebarHeight - 2 } }, m_titlebarBorderColor, m_titlebarBorderWidth);
if (m_titleTextAlign == cast<i32>(Window::eTextAlign::Center))
gfx.drawCenteredString(m_title, titleBarBounds, m_titleColor, m_titlebarFontSize);
else
gfx.drawString(m_title, titleBarBounds.getPosition() + Vec2 { 10, 5 }, m_titleColor, m_titlebarFontSize);
break;
}
case TitleBarTypes::MinimalValue:
{
if (m_titleTextAlign == cast<i32>(Window::eTextAlign::Center))
gfx.drawCenteredString(m_title, titleBarBounds, m_titleColor, m_titlebarFontSize);
else
gfx.drawString(m_title, titleBarBounds.getPosition() + Vec2 { 10, 5 }, m_titleColor, m_titlebarFontSize);
break;
}
case TitleBarTypes::NoneValue:
default: break;
}
}
TabPanel& TabPanel::create(void)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::TabPanel");
disableFocus();
enableStopEvents();
setStylesheetCategoryName("tabPanel");
enableBackground();
enableBorder();
setTabBarHeight(m_tabBarHeight);
validate();
return *this;
}
void TabPanel::applyTheme(const ostd::Stylesheet& theme)
{
setTabBarHeight(getThemeValue<f32>(theme, "tabBar.height", m_tabBarHeight));
setTabBarBackgroundColor(getThemeValue<Color>(theme, "tabBar.backgroundColor", m_tabBarBackgroundColor));
setTabBarBorderColor(getThemeValue<Color>(theme, "tabBar.borderColor", m_tabBarBorderColor));
setTabBarBorderColor(getThemeValue<i32>(theme, "tabBar.borderWidth", m_tabBarBorderWidth));
setTabBarSidePadding(getThemeValue<f32>(theme, "tabBar.sidePadding", m_tabSidePadding));
}
void TabPanel::onMousePressed(const Event& event)
{
if (!isMouseInsideTabBar({ event.mouse->position_x, event.mouse->position_y }))
return;
const f32 extra_offset = 5;
i32 index = 0;
for (auto& b : m_tabBoundsList)
{
if (b.contains(event.mouse->position_x, event.mouse->position_y))
{
setCurrentTab(*m_tabs[index]);
Rectangle tabBounds = m_tabBoundsList[index];
f32 tabLocalLeft = tabBounds.x - getGlobalPosition().x;
f32 tabLocalRight = tabLocalLeft + tabBounds.w;
if (tabLocalLeft < 0)
m_tabScrollOffset += tabLocalLeft - extra_offset;
else if (tabLocalRight > getw())
m_tabScrollOffset += (tabLocalRight - getw()) + extra_offset;
break;
}
index++;
}
}
void TabPanel::onMouseScrolled(const Event& event)
{
if (!isMouseInsideTabBar({ event.mouse->position_x, event.mouse->position_y }))
return;
if (m_tabs.size() == 0)
return;
const f32 extra_offset = 5;
m_tabScrollOffset += (event.mouse->scrollAmount.y * -40.0f);
m_tabScrollOffset = std::clamp(m_tabScrollOffset, -extra_offset, m_totalTabWidth - getw() + extra_offset);
}
void TabPanel::onDraw(ogfx::BasicRenderer2D& gfx)
{
gfx.outlinedRect({ getGlobalPosition(), { getw(), getTabBarHeight() } }, getTabBarBackgroundColor(), getTabBarBorderColor(), getTabBarBorderWidth(), false, false, true, false);
draw_tabs(gfx);
}
Panel& TabPanel::addTab(const String& title)
{
m_tabs.push_back(std::make_unique<Panel>(getWindow()));
auto& tab = *m_tabs.back();
tab.setTitle(title);
tab.setTitlebarType(Panel::TitleBarTypes::None);
tab.addThemeOverride("@panel_tab.panel.titlebarType", "none");
tab.addThemeOverride("@panel_tab.panel.showBorder", false);
tab.addThemeOverride("@panel_tab.panel.borderRadius", 0);
tab.setBackgroundColor(getBackgroundColor());
if (m_currentTab == nullptr && m_tabs.size() == 1)
setCurrentTab(tab);
tab.addThemeID("panel_tab");
tab.reloadTheme(true);
return tab;
}
bool TabPanel::removeTab(Panel& tab)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == &tab; });
if (it == m_tabs.end())
return false;
if (m_currentTab == it->get())
prepare_for_current_tab_removal();
m_tabs.erase(it);
return true;
}
bool TabPanel::removeTab(i32 index)
{
if (index < 0 || index >= (i32)m_tabs.size())
return false;
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == m_currentTab; });
if (std::distance(m_tabs.begin(), it) == index)
prepare_for_current_tab_removal();
m_tabs.erase(m_tabs.begin() + index);
return true;
}
bool TabPanel::removeTab(const String& title)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p->getTitle() == title; });
if (it == m_tabs.end())
return false;
if (m_currentTab == it->get())
prepare_for_current_tab_removal();
m_tabs.erase(it);
return true;
}
bool TabPanel::setCurrentTab(Panel& tab)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == &tab; });
if (it == m_tabs.end())
return false;
i32 index = it - m_tabs.begin();
return setCurrentTab(index);
}
bool TabPanel::setCurrentTab(i32 index, bool ignore_same_tab)
{
if (index < 0 || index >= (i32)m_tabs.size())
return false;
auto curr = m_tabs[index].get();
if (!ignore_same_tab && curr == m_currentTab && m_currentTab != nullptr)
return false;
getWindow().getFocusManager().clearFocus();
removeWidget(*m_currentTab);
m_currentTabIndex = index;
m_currentTab = curr;
m_currentTab->setMargin({ 0, 0, 0, 0 });
m_currentTab->setSize(getPureContentBounds().getSize());
m_currentTab->setPosition({ 0, 0 });
m_currentTab->reloadTheme(true);
m_currentTab->resetScroll();
m_currentTab->updateScrollbarsSize();
return addWidget(*m_currentTab);
}
void TabPanel::setTabBarHeight(f32 height)
{
m_tabBarHeight = height;
m_tabBarBorderRadii = { cast<f32>(getBorderRadius()), cast<f32>(getBorderRadius()), 0, 0 };
setContentOffset({ 0, m_tabBarHeight });
}
bool TabPanel::isMouseInsideTabBar(const Vec2& mousePos)
{
Rectangle bounds { getGlobalPosition(), { getw(), getTabBarHeight() } };
return bounds.contains(mousePos, true);
}
void TabPanel::prepare_for_current_tab_removal(void)
{
if (m_currentTab == nullptr)
return;
if (m_tabs.size() < 2)
{
getWindow().getFocusManager().clearFocus();
m_currentTab = nullptr;
return;
}
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == m_currentTab; });
if (it == m_tabs.end())
return; // shouldn't happen but defensive
getWindow().getFocusManager().clearFocus();
if (it != m_tabs.begin())
m_currentTab = (it - 1)->get(); // tab to the left
else
m_currentTab = (it + 1)->get(); // first tab being removed, go right
}
void TabPanel::draw_tabs(ogfx::BasicRenderer2D& gfx)
{
f32 nextTabX = 1 - m_tabScrollOffset;
m_tabBoundsList.clear();
i32 index = -1;
for (const auto& _tab : m_tabs)
{
index++;
if (_tab == nullptr) continue;
const auto& tab = *_tab;
auto titleBounds = gfx.getStringDimensions(tab.getTitle(), getFontSize());
auto glob = getGlobalPosition();
Rectangle tabBounds { glob + Vec2 { nextTabX, 2}, { titleBounds.x + (m_tabSidePadding * 2), m_tabBarHeight } };
if (m_currentTab == _tab.get())
{
gfx.outlinedRect(tabBounds, tab.getBackgroundColor(), getTabBarBorderColor(), getTabBarBorderWidth(), true, true, true, true);
}
else
{
bool draw_right_edge = !(m_currentTabIndex == (index + 1));
gfx.drawRect(tabBounds - Rectangle { 0, 0, 0, 2 }, getTabBarBorderColor(), getTabBarBorderWidth(), false, draw_right_edge, true, false);
}
gfx.drawCenteredString(tab.getTitle(), tabBounds, getTextColor(), getFontSize());
nextTabX += titleBounds.x + (m_tabSidePadding * 2);
m_tabBoundsList.push_back(tabBounds);
}
m_totalTabWidth = nextTabX + m_tabScrollOffset - 1;
}
}
}

View file

@ -0,0 +1,123 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Scrollbar.hpp>
#include <ostd/utils/Time.hpp>
namespace ogfx
{
namespace gui
{
class Panel : public ScrollableWidget
{
public: struct TitleBarTypes
{
inline static const String None { "none" };
inline static const String Full { "full" };
inline static const String Minimal { "minimal" };
inline static constexpr i32 NoneValue = 0;
inline static constexpr i32 FullValue = 1;
inline static constexpr i32 MinimalValue = 2;
};
public:
inline Panel(Window& window) : ScrollableWidget(window) { create(); }
Panel& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
void onMousePressed(const Event& event) override;
void onMouseReleased(const Event& event) override;
void onMouseDragged(const Event& event) override;
void onWindowResized(const Event& event) override;
void setTitlebarType(const String& type);
String getTitlebarType(void) const;
inline String getTitle(void) const { return m_title; }
inline void setTitle(const String& title) { m_title = title; }
inline f32 getTitlebarHeight(void) const { return m_titlebarHeight; }
OSTD_BOOL_PARAM_GETSET_I(Draggable, m_draggable);
private:
void draw_titlebar(BasicRenderer2D& gfx);
private:
String m_title { "Panel" };
Vec2 m_mousePos { 0, 0 };
bool m_mousePressed { false };
bool m_draggable { false };
Rectangle m_titleBarBounds;
Color m_titleColor { Colors::Black };
i32 m_titlebarType = TitleBarTypes::NoneValue;
f32 m_titlebarHeight { 30 };
i32 m_titlebarBorderWidth { 1 };
i32 m_titlebarFontSize { 26 };
Color m_titlebarColor { Colors::Transparent };
Color m_titlebarBorderColor { Colors::Black };
i32 m_titleTextAlign { 0 };
};
class TabPanel : public Widget
{
public:
inline TabPanel(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
TabPanel& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onMousePressed(const Event& event) override;
void onMouseScrolled(const Event& event) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
Panel& addTab(const String& title);
bool removeTab(Panel& tab);
bool removeTab(i32 index);
bool removeTab(const String& title);
bool setCurrentTab(Panel& tab);
bool setCurrentTab(i32 index, bool ignore_same_tab = false);
void setTabBarHeight(f32 height);
bool isMouseInsideTabBar(const Vec2& mousePos);
inline void refreshCurrentTab(void) { setCurrentTab(m_currentTabIndex, true); }
inline f32 getTabBarHeight(void) const { return m_tabBarHeight; }
OSTD_PARAM_GETSET(Color, TabBarBackgroundColor, m_tabBarBackgroundColor);
OSTD_PARAM_GETSET(Color, TabBarBorderColor, m_tabBarBorderColor);
OSTD_PARAM_GETSET(f32, TabBarSidePadding, m_tabSidePadding);
OSTD_PARAM_GETSET(i32, TabBarBorderWidth, m_tabBarBorderWidth);
private:
void prepare_for_current_tab_removal(void);
void draw_tabs(ogfx::BasicRenderer2D& gfx);
private:
stdvec<std::unique_ptr<Panel>> m_tabs;
stdvec<Rectangle> m_tabBoundsList;
Panel* m_currentTab { nullptr };
i32 m_currentTabIndex { -1 };
f32 m_tabScrollOffset { 0.0f };
f32 m_totalTabWidth { 0.0f };
Rectangle m_tabBarBorderRadii { 0, 0, 0, 0 };
f32 m_tabBarHeight { 35 };
Color m_tabBarBackgroundColor { 120, 120, 120 };
Color m_tabBarBorderColor { 170, 170, 170 };
f32 m_tabSidePadding { 20 };
i32 m_tabBarBorderWidth { 2 };
};
}
}

View file

@ -0,0 +1,250 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Label.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../../../ostd/io/FileSystem.hpp"
#include "../Window.hpp"
#include <algorithm>
#include <cmath>
namespace ogfx
{
namespace gui
{
// ===================================================================
// Label
// ===================================================================
Label& Label::create(const String& text)
{
m_text = text;
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::Label");
disableChildren();
disableFocus();
enableBackground(false);
setStylesheetCategoryName("label");
validate();
invalidate_layout();
return *this;
}
void Label::setSizeMode(eSizeMode mode)
{
m_sizeMode = mode;
m_modeExplicitlySet = true;
invalidate_layout();
}
eSizeMode Label::effectiveSizeMode(void) const
{
if (!m_modeExplicitlySet && getParent() && getParent()->hasLayout())
return eSizeMode::LayoutManaged;
return m_sizeMode;
}
void Label::onBoundsChanged(const Vec2& /*newSize*/)
{
invalidate_layout();
}
void Label::setText(const String& text)
{
m_text = text;
invalidate_layout();
}
void Label::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (m_layoutDirty)
recompute_layout();
gfx.drawString(
m_text,
getGlobalContentPosition() + m_drawTextOffset,
getTextColor(),
getFontSize()
);
}
void Label::recompute_layout(void)
{
m_layoutDirty = false;
auto& gfx = getWindow().getGFX();
const Vec2 textSize = gfx.getStringDimensions(m_text, getFontSize());
const Rectangle pad = getPadding();
const eSizeMode mode = effectiveSizeMode();
if (mode == eSizeMode::AutoSize)
{
const f32 wantW = textSize.x + pad.left() + pad.right();
const f32 wantH = textSize.y + pad.top() + pad.bottom();
// Avoid feedback loop on hover/focus theme reloads — only resize
// if the desired size actually changed by a meaningful amount.
if (std::abs(getw() - wantW) > 0.5f || std::abs(geth() - wantH) > 0.5f)
setSize({ wantW, wantH });
// Auto-size means the content fills the content box exactly,
// so the text offset is (0, 0).
m_drawTextOffset = { 0, 0 };
}
else
{
// Manual / LayoutManaged: center text inside the (user/layout-given)
// content area. Overflow is clipped at the widget edge.
const Vec2 contentSize = getContentBounds().getSize();
m_drawTextOffset = {
(contentSize.x - textSize.x) * 0.5f,
(contentSize.y - textSize.y) * 0.5f
};
}
}
// ===================================================================
// ImageLabel
// ===================================================================
ImageLabel& ImageLabel::create(const String& filePath)
{
setImage(filePath);
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::ImageLabel");
disableChildren();
disableFocus();
disableBackground();
disableBorder();
setStylesheetCategoryName("image");
setSize(64, 64);
validate();
return *this;
}
void ImageLabel::applyTheme(const ostd::Stylesheet& theme)
{
enableAnimated(getThemeValue<bool>(theme, "animated", m_animated));
setTintColor(getThemeValue<Color>(theme, "tint", getTintColor()));
setAnimationData(getThemeValue<AnimationData>(theme, "animation", m_animData));
String filePath = getThemeValue<String>(theme, "path", m_image.getFilePath());
if (filePath != m_image.getFilePath())
setImage(filePath);
// NOTE: the old code set widget size from theme here, which is wrong
// once layouts come into play. The theme's "size" affects m_imageSize
// (which is meaningful in Native mode). Widget size stays under
// user/layout control.
setImageSize(getThemeValue<Vec2>(theme, "size", getImageSize()));
if (isAnimatedEnabled())
{
m_anim.create(m_animData);
m_anim.setSpriteSheet(m_image);
}
}
void ImageLabel::setImage(const String& filePath)
{
if (filePath == "") return;
if (!ostd::FileSystem::fileExists(filePath))
return;
m_image.destroy();
m_image.loadFromFile(filePath, getWindow().getGFX());
}
void ImageLabel::onUpdate(void)
{
if (isAnimatedEnabled())
m_anim.update();
}
void ImageLabel::onDraw(ogfx::BasicRenderer2D& gfx)
{
Vec2 drawPos, drawSize;
compute_draw_geometry(drawPos, drawSize);
if (drawSize.x <= 0 || drawSize.y <= 0)
return;
if (isAnimatedEnabled())
gfx.drawAnimation(m_anim, drawPos, drawSize, getTintColor());
else
gfx.drawImage(m_image, drawPos, drawSize, getTintColor());
}
void ImageLabel::compute_draw_geometry(Vec2& outPos, Vec2& outSize) const
{
const Vec2 contentTL = getGlobalContentPosition();
const Vec2 contentSize = getContentBounds().getSize();
// Natural size: when the image is a sprite sheet driven by an
// Animation, one frame is the natural unit, not the whole sheet.
// (Otherwise letterboxing would respect the sheet's aspect ratio,
// which is meaningless — it depends on column/row count.)
Vec2 nat { 0, 0 };
if (m_image.isLoaded())
{
if (m_animated)
{
const auto frame = m_anim.getFrameRect();
nat = { frame.w, frame.h };
}
else
{
nat = m_image.getSize();
}
}
if (m_fit == eImageFit::Fit)
{
if (!m_keepAspect)
{
// Stretch to fill, no aspect preservation.
outPos = contentTL;
outSize = contentSize;
return;
}
// Letterbox: scale uniformly to fit, then center.
if (nat.x <= 0 || nat.y <= 0)
{
// No natural size known — fall back to filling.
outPos = contentTL;
outSize = contentSize;
return;
}
const f32 sx = contentSize.x / nat.x;
const f32 sy = contentSize.y / nat.y;
const f32 s = std::min(sx, sy);
outSize = { nat.x * s, nat.y * s };
outPos = contentTL + Vec2 {
(contentSize.x - outSize.x) * 0.5f,
(contentSize.y - outSize.y) * 0.5f
};
return;
}
// Native: draw at m_imageSize, centered. Overflow is clipped by
// the widget's automatic clipping at the renderer level.
outSize = m_imageSize;
outPos = contentTL + Vec2 {
(contentSize.x - outSize.x) * 0.5f,
(contentSize.y - outSize.y) * 0.5f
};
}
}
}

View file

@ -0,0 +1,143 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Button.hpp> // for eSizeMode
#include <ogfx/resources/Image.hpp>
#include <ogfx/utils/Animation.hpp>
namespace ogfx
{
namespace gui
{
// ===========================================================================
// Label — text-only widget.
//
// Modes (using the shared eSizeMode):
// AutoSize: widget shrinks/grows to fit text + padding.
// Manual: widget keeps user-set size; text drawn inside, clipped
// if it doesn't fit.
// LayoutManaged: parent layout decides size; otherwise behaves like Manual.
// ===========================================================================
class Label : public Widget
{
public:
inline Label(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline Label(Window& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); }
Label& create(const String& text);
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onBoundsChanged(const Vec2& newSize) override;
void setText(const String& text);
inline String getText(void) const { return m_text; }
// Same semantics as Button::setSizeMode / effectiveSizeMode.
void setSizeMode(eSizeMode mode);
inline eSizeMode getSizeMode(void) const { return m_sizeMode; }
eSizeMode effectiveSizeMode(void) const;
private:
inline void invalidate_layout(void) { m_layoutDirty = true; }
void recompute_layout(void);
private:
String m_text { "" };
eSizeMode m_sizeMode { eSizeMode::AutoSize };
bool m_modeExplicitlySet { false };
bool m_layoutDirty { true };
Vec2 m_drawTextOffset { 0, 0 };
};
// ===========================================================================
// ImageLabel — image-only widget.
//
// Two image modes (separate from widget size — the widget's size always
// comes from the user or from the parent layout):
//
// eImageFit::Fit: image scales to fill the widget's content area.
// Optional keepAspectRatio: if true, image is
// scaled uniformly to fit and centered (letterbox);
// if false, image stretches non-uniformly to fill.
//
// eImageFit::Native: image drawn at user-specified m_imageSize,
// centered inside the widget bounds. Crops naturally
// if m_imageSize > widget content size — the widget
// clips at its own bounds anyway.
// ===========================================================================
enum class eImageFit : u8
{
Fit,
Native
};
class ImageLabel : public Widget
{
public:
inline ImageLabel(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline ImageLabel(Window& window, const String& filePath) : Widget({ 0, 0, 0, 0 }, window) { create(filePath); }
ImageLabel& create(const String& filePath);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onUpdate(void) override;
void setImage(const String& filePath);
inline Image& getImage(void) { return m_image; }
// Mode selection. Default is Fit + keepAspectRatio=true.
inline void setImageFit(eImageFit fit) { m_fit = fit; }
inline eImageFit getImageFit(void) const { return m_fit; }
// Only meaningful when fit == Fit. If true, image keeps its
// aspect ratio and is centered with letterbox bars; if false,
// image stretches non-uniformly to fill.
OSTD_BOOL_PARAM_GETSET_E(KeepAspectRatio, m_keepAspect);
// Only meaningful when fit == Native. The size to draw the image at,
// regardless of widget size. The image is drawn centered.
OSTD_PARAM_GETSET(Vec2, ImageSize, m_imageSize);
OSTD_BOOL_PARAM_GETSET_E(Animated, m_animated);
OSTD_PARAM_GETSET(AnimationData, AnimationData, m_animData);
OSTD_PARAM_GETSET(Color, TintColor, m_tintColor);
private:
// Compute (drawPosition, drawSize) for the current frame.
// drawPosition is in global coords. drawSize is the on-screen size.
void compute_draw_geometry(Vec2& outPos, Vec2& outSize) const;
private:
Image m_image;
Animation m_anim;
AnimationData m_animData;
Color m_tintColor { Colors::White };
eImageFit m_fit { eImageFit::Fit };
bool m_keepAspect { true };
Vec2 m_imageSize { 64, 64 }; // user-specified size for Native mode
bool m_animated { false };
};
}
}

View file

@ -0,0 +1,95 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "ProgressBar.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
ProgressBar& ProgressBar::create(void)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::ProgressBar");
disableChildren();
disableFocus();
enableBackground();
enableBorder();
setStylesheetCategoryName("progressbar");
validate();
return *this;
}
void ProgressBar::applyTheme(const ostd::Stylesheet& theme)
{
setProgressColor(getThemeValue<Color>(theme, "progressColor", getProgressColor()));
enableShowDecimal(getThemeValue<bool>(theme, "showDecimal", isShowDecimalEnabled()));
enableShowText(getThemeValue<bool>(theme, "showText", isShowTextEnabled()));
enableProgressGradient(getThemeValue<bool>(theme, "useProgressGradient", isProgressGradientEnabled()));
setProgressGradient(getThemeValue<ColorGradient>(theme, "progressGradient", getProgressGradient()));
}
void ProgressBar::onDraw(ogfx::BasicRenderer2D& gfx)
{
f32 prog = getProgressNormalized();
f32 progressw = (prog * getw());
if (isProgressGradientEnabled())
gfx.fillGradientRect({ getGlobalPosition(), { progressw, geth() } }, m_progressGradient);
else
gfx.fillRect({ getGlobalPosition(), { progressw, geth() } }, getProgressColor());
if (isShowTextEnabled())
{
String text = "";
if (isShowDecimalEnabled())
text.add(prog * 100, 2).add("%");
else
text.add(cast<i32>(std::round(prog * 100))).add("%");
gfx.drawCenteredString(text, getGlobalBounds(), getTextColor(), getFontSize());
}
}
void ProgressBar::setProgressNormalized(f32 normalized_value)
{
normalized_value = std::clamp(normalized_value, 0.0f, 1.0f);
f32 next = m_min + normalized_value * (m_max - m_min);
m_progress.store(next, std::memory_order_relaxed);
}
void ProgressBar::setProgress(f32 value)
{
f32 current = m_progress.load(std::memory_order_relaxed);
f32 next = current;
next = std::clamp(value, m_min, m_max);
m_progress.store(next, std::memory_order_relaxed);
}
f32 ProgressBar::getProgress(void) const
{
return m_progress.load(std::memory_order_relaxed);
}
f32 ProgressBar::getProgressNormalized(void) const
{
f32 s = m_progress.load(std::memory_order_relaxed);
return (s - m_min) / (m_max - m_min);
}
}
}

View file

@ -0,0 +1,61 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <atomic>
namespace ogfx
{
namespace gui
{
class ProgressBar : public Widget
{
public:
inline ProgressBar(Window& window, f32 min = 0.0f, f32 max = 1.0f, f32 start = 0.0f) : Widget({ 0, 0, 0, 0 }, window), m_progress(std::clamp(start, min, max)), m_min(min), m_max(max) { create(); }
ProgressBar& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void setProgressNormalized(f32 normalized_value);
void setProgress(f32 value);
f32 getProgress(void) const;
f32 getProgressNormalized(void) const;
OSTD_PARAM_GETSET(Color, ProgressColor, m_progressColor);
OSTD_BOOL_PARAM_GETSET_E(ShowDecimal, m_showDecimal);
OSTD_BOOL_PARAM_GETSET_E(ShowText, m_showText);
OSTD_BOOL_PARAM_GETSET_E(ProgressGradient, m_useProgressGradient);
OSTD_PARAM_GETSET(ColorGradient, ProgressGradient, m_progressGradient);
private:
Color m_progressColor { 255, 255, 255 };
f32 m_min { 0.0f };
f32 m_max { 1.0f };
std::atomic<f32> m_progress;
bool m_showDecimal { false };
bool m_showText { true };
bool m_useProgressGradient { true };
ColorGradient m_progressGradient {
{ Colors::Crimson.darkened(0.1f), Colors::Crimson.darkened(0.35f) },
{ 1.0f }
};
};
}
}

View file

@ -0,0 +1,144 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RadioButton.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
RadioButton& RadioButton::create(RadioButtonGroup& group, const String& text)
{
m_radioGroup = &group;
setText(text);
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::RadioButton");
disableChildren();
enableBackground(false);
setStylesheetCategoryName("radioButton");
validate();
return *this;
}
void RadioButton::applyTheme(const ostd::Stylesheet& theme)
{
setInnerCircleColor(getThemeValue<Color>(theme, "innerCircleColor", m_innerCircleColor));
setOuterCircleColor(getThemeValue<Color>(theme, "outerCircleColor", m_outerCircleColor));
m_outerCircleBorderWidth = getThemeValue<i32>(theme, "outerCircleBorderWidth", m_outerCircleBorderWidth);
}
void RadioButton::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
if (m_radioGroup == nullptr)
return;
m_radioGroup->set_selected(*this);
}
void RadioButton::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (m_textChanged)
__update_size(gfx);
gfx.drawCircle({ getGlobalContentPosition() + Vec2 { 0, 4 }, m_circleSize }, getOuterCircleColor(), getOuterCircleBorderWidth());
if (isSelected())
gfx.fillCircle({ getGlobalContentPosition() + Vec2 { 3, 7 }, m_circleSize - 6 }, getInnerCircleColor());
gfx.drawString(getText(), getGlobalContentPosition() + Vec2 { m_circleSize.x + m_spacing, 0 }, getTextColor(), getFontSize());
}
void RadioButton::setText(const String& text)
{
m_text = text;
m_textChanged = true;
}
void RadioButton::__update_size(ogfx::BasicRenderer2D& gfx)
{
auto size = gfx.getStringDimensions(getText(), getFontSize());
m_circleSize = { cast<f32>(size.y - 6), cast<f32>(size.y - 6) };
size.x += m_spacing + m_circleSize.x;
size.x += getPadding().left();
size.x += getPadding().right();
size.y += getPadding().top();
size.y += getPadding().bottom();
setSize({ cast<f32>(size.x), cast<f32>(size.y) });
m_textChanged = false;
}
void RadioButton::__set_selected(bool selected)
{
if (m_selected == selected)
return;
m_selected = selected;
setThemeQualifier("active", m_selected);
}
RadioButtonGroup::RadioButtonGroup(void)
{
}
RadioButton& RadioButtonGroup::addButton(Widget& parent, const String& text, const Vec2& position)
{
m_buttons.push_back(std::unique_ptr<RadioButton>(
new RadioButton(parent.getWindow(), *this, text)
));
auto& btn = *m_buttons.back();
parent.addWidget(btn, position);
if (m_buttons.size() == 1)
set_selected(btn);
return btn;
}
void RadioButtonGroup::set_selected(RadioButton& sender)
{
if (is_button_present(sender))
{
if (m_selected != nullptr && m_selected == &sender)
return;
unselect_all();
auto previous = m_selected;
m_selected = &sender;
m_selected->__set_selected(true);
if (previous != nullptr && callback_onSelectionChanged != nullptr)
callback_onSelectionChanged(*previous, sender);
return;
}
}
bool RadioButtonGroup::is_button_present(RadioButton& button)
{
for (auto& btn : m_buttons)
if (btn.get() == &button)
return true;
return false;
}
void RadioButtonGroup::unselect_all(void)
{
for (auto& btn : m_buttons)
btn->__set_selected(false);
}
}
}

View file

@ -0,0 +1,82 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class RadioButtonGroup;
class RadioButton : public Widget
{
public:
void applyTheme(const ostd::Stylesheet& theme) override;
void onMouseReleased(const Event& event) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void setText(const String& text);
inline String getText(void) const { return m_text; }
inline bool isSelected(void) const { return m_selected; }
OSTD_PARAM_GETSET(i32, OuterCircleBorderWidth, m_outerCircleBorderWidth);
OSTD_PARAM_GETSET(Color, InnerCircleColor, m_innerCircleColor);
OSTD_PARAM_GETSET(Color, OuterCircleColor, m_outerCircleColor);
private:
inline RadioButton(Window& window, RadioButtonGroup& group, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(group, text); }
RadioButton& create(RadioButtonGroup& group, const String& text);
void __update_size(ogfx::BasicRenderer2D& gfx);
void __set_selected(bool selected);
private:
RadioButtonGroup* m_radioGroup { nullptr };
Vec2 m_circleSize { 0, 0 };
bool m_selected { false };
String m_text { "" };
bool m_textChanged { false };
f32 m_spacing { 6 };
i32 m_outerCircleBorderWidth { 1 };
Color m_innerCircleColor { 255, 255, 255 };
Color m_outerCircleColor { 255, 255, 255 };
friend class RadioButtonGroup;
};
class RadioButtonGroup
{
public:
RadioButtonGroup(void);
RadioButton& addButton(Widget& parent, const String& text, const Vec2& position = { 0, 0 });
inline void setSelectionChangedCallback(std::function<void(RadioButton& previous, RadioButton& sender)> callback) { callback_onSelectionChanged = std::move(callback); }
private:
void set_selected(RadioButton& sender);
bool is_button_present(RadioButton& button);
void unselect_all(void);
private:
stdvec<std::unique_ptr<RadioButton>> m_buttons;
RadioButton* m_selected { nullptr };
std::function<void(RadioButton& previous, RadioButton& sender)> callback_onSelectionChanged { nullptr };
friend class RadioButton;
};
}
}

View file

@ -0,0 +1,66 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RootWidget.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../Window.hpp"
namespace ogfx
{
namespace gui
{
RootWidget::RootWidget(Window& window) : Widget({ 0, 0, 0, 0 }, window)
{
setRootChild();
disableFocus();
setSize(cast<f32>(window.getWindowWidth()), cast<f32>(window.getWindowHeight()));
setStylesheetCategoryName("window");
setTypeName("ogfx::gui::RootWidget");
}
void RootWidget::onWindowResized(const Event& event)
{
Window& win = getWindow();
f32 offset_y = 0;
if (win.m_menubar.isVisible())
offset_y += win.m_menubar.geth();
if (win.m_toolbar.isVisible())
offset_y += win.m_toolbar.geth();
sety(offset_y);
if (win.m_statusbar.isVisible())
offset_y += win.m_statusbar.geth();
setSize(cast<f32>(event.windowResized->new_width), cast<f32>(event.windowResized->new_height) - offset_y);
}
void RootWidget::applyTheme(const ostd::Stylesheet& theme)
{
setTooltipBackgroundColor(getThemeValue<Color>(theme, "tooltip.backgroundColor", getTooltipBackgroundColor()));
setTooltipBorderColor(getThemeValue<Color>(theme, "tooltip.borderColor", getTooltipBorderColor()));
setTooltipTextColor(getThemeValue<Color>(theme, "tooltip.textColor", getTooltipTextColor()));
setTooltipBorderWidth(getThemeValue<i32>(theme, "tooltip.borderWidth", getTooltipBorderWidth()));
setTooltipFontSize(getThemeValue<i32>(theme, "tooltip.fontSize", getTooltipFontSize()));
}
void RootWidget::onDraw(ogfx::BasicRenderer2D& gfx)
{
gfx.fillRect({ 0, 0, cast<f32>(getWindow().getWindowWidth()), cast<f32>(getWindow().getWindowHeight()) }, getBackgroundColor());
}
}
}

View file

@ -0,0 +1,50 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class RootWidget : public Widget
{
public:
RootWidget(Window& window);
void onWindowResized(const Event& event) override;
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
OSTD_PARAM_GETSET(Color, TooltipBackgroundColor, m_tooltipBackgroundColor);
OSTD_PARAM_GETSET(Color, TooltipBorderColor, m_tooltipBorderColor);
OSTD_PARAM_GETSET(Color, TooltipTextColor, m_tooltipTextColor);
OSTD_PARAM_GETSET(i32, TooltipBorderWidth, m_tooltipBorderWidth);
OSTD_PARAM_GETSET(i32, TooltipFontSize, m_tooltipFontSize);
private:
Color m_tooltipBackgroundColor { "#FFF7D6FF" };
Color m_tooltipBorderColor { 50, 50, 50 };
Color m_tooltipTextColor { 50, 50, 50 };
i32 m_tooltipBorderWidth { 1 };
i32 m_tooltipFontSize { 20 };
};
}
}

View file

@ -0,0 +1,466 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Scrollbar.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
VerticalScrollBar& VerticalScrollBar::create(void)
{
setPadding({ 0, 0, 0, 0 });
setMargin({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::Label");
disableChildren();
disableFocus();
enableBackground(true);
enableBorder(false);
enableTopMost(true);
enableIgnoreScroll(true);
setStylesheetCategoryName("scrollbar");
validate();
return *this;
}
void VerticalScrollBar::applyTheme(const ostd::Stylesheet& theme)
{
w = getThemeValue<f32>(theme, "width", 15);
m_thumbColor = getThemeValue<Color>(theme, "thumb.color", { 120, 120, 120 });
m_thumbBorderRadius = getThemeValue<f32>(theme, "thumb.borderRadius", 16);
m_thumbBorderColor = getThemeValue<Color>(theme, "thumb.borderColor", { 150, 150, 150 });
m_thumbShowBorder = getThemeValue<bool>(theme, "thumb.showBorder", true);
m_trackColor = getThemeValue<Color>(theme, "track.color", { 70, 70, 70 });
}
void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx)
{
if (!getParent()->needsVScroll())
return;
gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii);
gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1);
}
void VerticalScrollBar::onMouseDragged(const Event& event)
{
if (!m_mousePressed)
return;
if (isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y }))
m_mouseDragged = true;
if (m_mouseDragged)
{
auto bounds = getGlobalBounds() - m_correctionOffset;
f32 newThumbY = (event.mouse->position_y - bounds.y) - m_dragGrabOffset;
set_thumb_y(newThumbY);
}
}
void VerticalScrollBar::onMousePressed(const Event& event)
{
m_mousePressed = true;
m_dragGrabOffset = event.mouse->position_y - m_thumbGlobalBounds.y;
}
void VerticalScrollBar::onMouseReleased(const Event& event)
{
m_mousePressed = false;
if (event.mouse->mousePressedOnWidget != this)
return;
if (!m_mouseDragged)
{
auto bounds = getGlobalBounds() - m_correctionOffset;
f32 newThumbY = (event.mouse->position_y - bounds.y);
newThumbY -= m_thumbHeight / 2.0f;
set_thumb_y(newThumbY);
}
m_mouseDragged = false;
}
void VerticalScrollBar::onUpdate(void)
{
update_thumb();
}
bool VerticalScrollBar::isMouseInsideThumb(const Vec2& mouse_pos)
{
return m_thumbGlobalBounds.contains(mouse_pos, true);
}
void VerticalScrollBar::update_thumb(void)
{
x = getParent()->getSize().x - w;
y = 0;
h = getParent()->getGlobalPureContentBounds().h;
auto ext = getParent()->getContentExtents();
auto cont = getParent()->getContentBounds();
auto scrollOfset = getParent()->getScrollOffset();
// Thumb size is proportional to visible/total ratio
f32 visibleRatio = cont.h / ext.h; // e.g. 0.4 means 40% visible
m_thumbHeight = std::max(20.0f, geth() * visibleRatio); // min 20px for usability
// Thumb position is proportional to scroll progress
f32 scrollProgress = -scrollOfset.y / (ext.h - cont.h); // 0 to 1
m_thumbY = gety() + ((geth() - m_thumbHeight) * scrollProgress);
auto bounds = getGlobalBounds() - m_correctionOffset;
m_thumbGlobalBounds = { bounds.x + 2, bounds.y + gety() + m_thumbY, getw() - 4, m_thumbHeight };
}
void VerticalScrollBar::set_thumb_y(f32 thumby)
{
if (!getParent()->needsVScroll())
return;
auto bounds = getGlobalBounds() - m_correctionOffset;
auto ext = getParent()->getContentExtents();
auto cont = getParent()->getContentBounds();
thumby = std::clamp(thumby, 0.0f, h - m_thumbHeight);
m_thumbY = thumby;
f32 scrollProgress = (bounds.y + gety() + m_thumbY - bounds.y) / (bounds.h - m_thumbHeight);
f32 maxScroll = -(ext.h - cont.h);
auto parentScrollOffset = getParent()->getScrollOffset();
parentScrollOffset.y = maxScroll * scrollProgress;
getParent()->setScrollOffset(parentScrollOffset);
}
HorizontalScrollbar& HorizontalScrollbar::create(void)
{
setPadding({ 0, 0, 0, 0 });
setMargin({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::Label");
disableChildren();
disableFocus();
enableBackground(true);
enableBorder(false);
enableTopMost(true);
enableIgnoreScroll(true);
setStylesheetCategoryName("scrollbar");
validate();
return *this;
}
void HorizontalScrollbar::applyTheme(const ostd::Stylesheet& theme)
{
h = getThemeValue<f32>(theme, "width", 15);
m_thumbColor = getThemeValue<Color>(theme, "thumb.color", { 120, 120, 120 });
m_thumbBorderRadius = getThemeValue<f32>(theme, "thumb.borderRadius", 16);
m_thumbBorderColor = getThemeValue<Color>(theme, "thumb.borderColor", { 150, 150, 150 });
m_thumbShowBorder = getThemeValue<bool>(theme, "thumb.showBorder", true);
m_trackColor = getThemeValue<Color>(theme, "track.color", { 70, 70, 70 });
}
void HorizontalScrollbar::afterDraw(ogfx::BasicRenderer2D& gfx)
{
if (!getParent()->needsHScroll())
return;
gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii);
gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1);
}
void HorizontalScrollbar::onMouseDragged(const Event& event)
{
if (!m_mousePressed)
return;
if (isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y }))
m_mouseDragged = true;
if (m_mouseDragged)
{
auto bounds = getGlobalBounds() - m_correctionOffset;
f32 newThumbX = (event.mouse->position_x - bounds.x) - m_dragGrabOffset;
set_thumb_x(newThumbX);
}
}
void HorizontalScrollbar::onMousePressed(const Event& event)
{
m_mousePressed = true;
m_dragGrabOffset = event.mouse->position_x - m_thumbGlobalBounds.x;
}
void HorizontalScrollbar::onMouseReleased(const Event& event)
{
m_mousePressed = false;
if (event.mouse->mousePressedOnWidget != this)
return;
if (!m_mouseDragged)
{
auto bounds = getGlobalBounds() - m_correctionOffset;
f32 newThumbX = (event.mouse->position_x - bounds.x);
newThumbX -= m_thumbWidth / 2.0f;
set_thumb_x(newThumbX);
}
m_mouseDragged = false;
}
void HorizontalScrollbar::onUpdate(void)
{
update_thumb();
}
bool HorizontalScrollbar::isMouseInsideThumb(const Vec2& mouse_pos)
{
return m_thumbGlobalBounds.contains(mouse_pos, true);
}
void HorizontalScrollbar::update_thumb(void)
{
x = 0;
y = getParent()->getSize().y - h;
w = getParent()->getGlobalBounds().w - getParent()->getVScrollbarSize();
auto ext = getParent()->getContentExtents();
auto cont = getParent()->getContentBounds();
auto scrollOfset = getParent()->getScrollOffset();
// Thumb size is proportional to visible/total ratio
f32 visibleRatio = cont.w / ext.w; // e.g. 0.4 means 40% visible
m_thumbWidth = std::max(20.0f, getw() * visibleRatio); // min 20px for usability
// Thumb position is proportional to scroll progress
f32 scrollProgress = -scrollOfset.x / (ext.w - cont.w); // 0 to 1
m_thumbX = getx() + ((getw() - m_thumbWidth) * scrollProgress);
auto bounds = getGlobalBounds() - m_correctionOffset;
m_thumbGlobalBounds = { bounds.x + 2 + m_thumbX, bounds.y + gety(), m_thumbWidth, geth() - 4 };
m_thumbGlobalBounds = { bounds.x + getx() + m_thumbX, bounds.y + 2, m_thumbWidth, geth() - 4 };
}
void HorizontalScrollbar::set_thumb_x(f32 thumbx)
{
if (!getParent()->needsHScroll())
return;
auto bounds = getGlobalBounds() - m_correctionOffset;
auto ext = getParent()->getContentExtents();
auto cont = getParent()->getContentBounds();
thumbx = std::clamp(thumbx, 0.0f, w - m_thumbWidth);
m_thumbX = thumbx;
f32 scrollProgress = (bounds.x + getx() + m_thumbX - bounds.x) / (bounds.w - m_thumbWidth);
f32 maxScroll = -(ext.w - cont.w);
auto parentScrollOffset = getParent()->getScrollOffset();
parentScrollOffset.x = maxScroll * scrollProgress;
getParent()->setScrollOffset(parentScrollOffset);
}
ScrollableWidget& ScrollableWidget::create(void)
{
setTypeName("ogfx::gui::ScrollableWidget");
enableVScroll(true);
enableHScroll(true);
m_vScrollbar.enableManualDraw(true);
addWidget(m_vScrollbar);
m_hScrollbar.setMargin({ 0, 0, 0, 0 });
m_hScrollbar.enableManualDraw(true);
addWidget(m_hScrollbar);
m_smoothScrollTimer.create(60.0f, [&](f64 dt) -> void {
f32 stepX = m_scrollVelocity.x * (1.0f - m_scrollSmoothFactor);
f32 stepY = m_scrollVelocity.y * (1.0f - m_scrollSmoothFactor);
addScrollOffset({ stepX, stepY });
m_scrollVelocity -= { stepX, stepY };
if (std::abs(m_scrollVelocity.x) < 0.5f) m_scrollVelocity.x = 0;
if (std::abs(m_scrollVelocity.y) < 0.5f) m_scrollVelocity.y = 0;
}, true);
m_smoothScrollTimer.setStopCondition([&](void) -> bool {
return m_scrollVelocity.x == 0 && m_scrollVelocity.y == 0;
});
updateScrollbarsSize();
validate();
return *this;
}
void ScrollableWidget::onUpdate(void)
{
m_smoothScrollTimer.update();
if (!needsVScroll() && m_vScrollbarAdded)
{
removeWidget(m_vScrollbar);
m_vScrollbarAdded = false;
}
else if (!m_vScrollbarAdded)
{
addWidget(m_vScrollbar);
m_vScrollbarAdded = true;
}
if (!needsHScroll() && m_hScrollbarAdded)
{
removeWidget(m_hScrollbar);
m_hScrollbarAdded = false;
}
else if (!m_hScrollbarAdded)
{
addWidget(m_hScrollbar);
m_hScrollbarAdded = true;
}
}
void ScrollableWidget::drawScrollbars(ogfx::BasicRenderer2D& gfx)
{
m_vScrollbar.__draw(gfx);
m_hScrollbar.__draw(gfx);
}
void ScrollableWidget::updateScrollbarsSize(void)
{
m_vScrollbar.setMargin({ 0, getPureContentBounds().y, 0, 0 });
}
void ScrollableWidget::resetScroll(bool horizontal, bool vertical, bool propagate)
{
if (vertical)
m_scrollOffset.y = 0;
if (horizontal)
m_scrollOffset.x = 0;
if (propagate)
{
for (auto& w : getChildren().getWidgets())
{
if (w == nullptr) continue;
w->resetScroll(horizontal, vertical, propagate);
}
}
}
bool ScrollableWidget::isMouseInsideVScrollbar(void) const
{
return m_vScrollbar.isMouseInside();
}
bool ScrollableWidget::isMouseInsideHScrollbar(void) const
{
return m_hScrollbar.isMouseInside();
}
void ScrollableWidget::onMouseScrolled(const Event& event)
{
if (isVScrollEnabled())
{
bool mouseInsideHScrollbar = m_hScrollbar.isMouseInside() && needsHScroll();
if (std::abs(event.mouse->scrollAmount.y) > 0 && !mouseInsideHScrollbar)
{
m_scrollVelocity.y += m_scrollSpeed * event.mouse->scrollAmount.y * m_scrollSpeedMultiplier;
if (m_smoothScrollTimer.isStopped())
m_smoothScrollTimer.restart();
event.handle();
}
else if (std::abs(event.mouse->scrollAmount.y) > 0)
{
m_scrollVelocity.x += m_scrollSpeed * event.mouse->scrollAmount.y * m_scrollSpeedMultiplier;
if (m_smoothScrollTimer.isStopped())
m_smoothScrollTimer.restart();
event.handle();
}
}
if (isHScrollEnabled())
{
if (std::abs(event.mouse->scrollAmount.x) > 0)
{
m_scrollVelocity.x += m_scrollSpeed * event.mouse->scrollAmount.x * m_scrollSpeedMultiplier;
if (m_smoothScrollTimer.isStopped())
m_smoothScrollTimer.restart();
event.handle();
}
}
}
void ScrollableWidget::setScrollOffset(const Vec2& offset)
{
auto ext = getContentExtents();
auto cont = getContentBounds();
f32 maxScrollY = -(ext.h - cont.h);
f32 maxScrollX = -(ext.w - cont.w);
m_scrollOffset = offset;
if (m_scrollOffset.y < maxScrollY) m_scrollOffset.y = maxScrollY;
if (m_scrollOffset.y > 0) m_scrollOffset.y = 0;
if (m_scrollOffset.x < maxScrollX) m_scrollOffset.x = maxScrollX;
if (m_scrollOffset.x > 0) m_scrollOffset.x = 0;
}
void ScrollableWidget::addScrollOffset(const Vec2& offset)
{
auto ext = getContentExtents();
auto cont = getContentBounds();
f32 maxScrollY = -(ext.h - cont.h);
f32 maxScrollX = -(ext.w - cont.w);
m_scrollOffset += offset;
if (m_scrollOffset.y < maxScrollY) m_scrollOffset.y = maxScrollY;
if (m_scrollOffset.y > 0) m_scrollOffset.y = 0;
if (m_scrollOffset.x < maxScrollX) m_scrollOffset.x = maxScrollX;
if (m_scrollOffset.x > 0) m_scrollOffset.x = 0;
}
bool ScrollableWidget::needsVScroll(void) const
{
return isVScrollEnabled() && getContentExtents().h > getContentBounds().h;
}
bool ScrollableWidget::needsHScroll(void) const
{
return isHScrollEnabled() && getContentExtents().w > getContentBounds().w;
}
void ScrollableWidget::onWidgetAdded(Widget& child)
{
removeWidget(m_vScrollbar);
removeWidget(m_hScrollbar);
addWidget(m_vScrollbar, { 0, 0 }, true);
addWidget(m_hScrollbar, { 0, 0 }, true);
}
f32 ScrollableWidget::getVScrollbarSize(void) const
{
if (!isVScrollEnabled())
return 0;
if (!needsVScroll())
return 0;
return m_vScrollbar.getw();
}
f32 ScrollableWidget::getHScrollbarSize(void) const
{
if (!isHScrollEnabled())
return 0;
if (!needsHScroll())
return 0;
return m_hScrollbar.geth();
}
}
}

View file

@ -0,0 +1,146 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ostd/utils/Time.hpp>
namespace ogfx
{
namespace gui
{
class VerticalScrollBar : public Widget
{
public:
inline VerticalScrollBar(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
VerticalScrollBar& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseDragged(const Event& event) override;
void onMousePressed(const Event& event) override;
void onMouseReleased(const Event& event) override;
void onUpdate(void) override;
bool isMouseInsideThumb(const Vec2& mouse_pos);
inline void setx(f32 xx) override { }
inline void sety(f32 yy) override { }
inline void setw(f32 ww) override { }
inline void seth(f32 hh) override { }
private:
void update_thumb(void);
void set_thumb_y(f32 thumby);
private:
f32 m_thumbHeight { 0 };
f32 m_thumbY { 0 };
Rectangle m_thumbGlobalBounds { 0, 0, 0, 0 };
bool m_mousePressed { false };
bool m_mouseDragged { false };
f32 m_dragGrabOffset { 0 };
Rectangle m_correctionOffset { 1, 1, 0, 0 };
Rectangle m_trackBorderRadii { 0, 0, 0, 0 };
f32 m_thumbBorderRadius { 16 };
Color m_trackColor { 70, 70, 70 };
Color m_thumbColor { 120, 120, 120 };
Color m_thumbBorderColor { 150, 150, 150 };
bool m_thumbShowBorder { true };
};
class HorizontalScrollbar : public Widget
{
public:
inline HorizontalScrollbar(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
HorizontalScrollbar& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseDragged(const Event& event) override;
void onMousePressed(const Event& event) override;
void onMouseReleased(const Event& event) override;
void onUpdate(void) override;
bool isMouseInsideThumb(const Vec2& mouse_pos);
inline void setx(f32 xx) override { }
inline void sety(f32 yy) override { }
inline void setw(f32 ww) override { }
inline void seth(f32 hh) override { }
private:
void update_thumb(void);
void set_thumb_x(f32 thumbx);
private:
f32 m_thumbWidth { 0 };
f32 m_thumbX { 0 };
Rectangle m_thumbGlobalBounds { 0, 0, 0, 0 };
bool m_mousePressed { false };
bool m_mouseDragged { false };
f32 m_dragGrabOffset { 0 };
Rectangle m_correctionOffset { 0, 0, 0, 0 };
Rectangle m_trackBorderRadii { 0, 0, 0, 0 };
f32 m_thumbBorderRadius { 16 };
Color m_trackColor { 70, 70, 70 };
Color m_thumbColor { 120, 120, 120 };
Color m_thumbBorderColor { 150, 150, 150 };
bool m_thumbShowBorder { true };
};
class ScrollableWidget : public Widget
{
public:
inline ScrollableWidget(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
ScrollableWidget& create(void);
virtual void onUpdate(void) override;
void drawScrollbars(ogfx::BasicRenderer2D& gfx);
void updateScrollbarsSize(void);
void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) override;
bool isMouseInsideVScrollbar(void) const;
bool isMouseInsideHScrollbar(void) const;
inline bool isMouseInsideAnyScrollbar(void) const { return isMouseInsideVScrollbar() || isMouseInsideHScrollbar(); }
virtual void onMouseScrolled(const Event& event) override;
virtual void setScrollOffset(const Vec2& offset) override;
virtual void addScrollOffset(const Vec2& offset) override;
virtual bool needsVScroll(void) const override;
virtual bool needsHScroll(void) const override;
virtual void onWidgetAdded(Widget& child) override;
virtual f32 getVScrollbarSize(void) const override;
virtual f32 getHScrollbarSize(void) const override;
inline Vec2 getScrollOffset(void) const override { return m_scrollOffset; }
inline void setScrollSpeed(f32 speed) { m_scrollSpeed = speed; }
inline f32 getScrollSpeed(void) const { return m_scrollSpeed; }
inline void setScrollSmoothFactor(f32 speed) { m_scrollSmoothFactor = std::clamp(speed, 0.0f, 1.0f); }
inline f32 getScrollSmoothFactor(void) const { return m_scrollSmoothFactor; }
private:
String m_title { "Panel" };
Vec2 m_scrollOffset { 0, 0 };
VerticalScrollBar m_vScrollbar { getWindow() };
HorizontalScrollbar m_hScrollbar { getWindow() };
ostd::StepTimer m_smoothScrollTimer;
f32 m_scrollSpeed { 0.8f };
Vec2 m_scrollVelocity { 0.0f, 0.0f };
f32 m_scrollSmoothFactor { 0.7f };
f32 m_scrollSpeedMultiplier { 15.0f };
bool m_hScrollbarAdded { false };
bool m_vScrollbarAdded { false };
};
}
}

View file

@ -0,0 +1,190 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Slider.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
Slider& Slider::create(bool vertical, f32 min, f32 max, f32 step)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::Slider");
disableChildren();
disableBackground();
disableBorder();
setContentOffset({ 0, 0 });
setStylesheetCategoryName("slider");
setMinValue(min);
setMaxValue(max);
setStep(step);
validate();
return *this;
}
void Slider::applyTheme(const ostd::Stylesheet& theme)
{
setHandleColor(getThemeValue<Color>(theme, "handle.color", getHandleColor()));
setHandleSize(getThemeValue<Vec2>(theme, "handle.size", getHandleSize()));
setTrackColor(getThemeValue<Color>(theme, "track.color", getTrackColor()));
setTrackProgressColor(getThemeValue<Color>(theme, "track.progressColor", getTrackProgressColor()));
setTrackWidth(getThemeValue<f32>(theme, "track.width", getTrackWidth()));
enableDrawTicks(getThemeValue<bool>(theme, "showStepTicks", isDrawTicksEnabled()));
setStepTickHeight(getThemeValue<f32>(theme, "stepTickHeight", getStepTickHeight()));
setStepTickColor(getThemeValue<Color>(theme, "stepTickColor", getStepTickColor()));
}
void Slider::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
if (isMiddleClickShortcutEnabled() && event.mouse->button == MouseEventData::eButton::Middle)
{
set_value(snap_to_step((m_min + m_max) * 0.5f));
return;
}
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
}
void Slider::onMouseDragged(const Event& event)
{
if (!isMouseInside())
return;
if (event.mouse->button == MouseEventData::eButton::Left)
set_value(mouse_position_to_value({ event.mouse->position_x, event.mouse->position_y }));
}
void Slider::onMouseScrolled(const Event& event)
{
if (!isMouseInside())
return;
if (!isScrollWheelShortcutEnabled())
return;
if (getStep() > 0)
{
if (event.mouse->scroll == MouseEventData::eScrollDirection::Down ||
event.mouse->scroll == MouseEventData::eScrollDirection::Right)
set_value(getValue() + (getStep() * -1));
else
set_value(getValue() + getStep());
}
else if (event.mouse->scroll == MouseEventData::eScrollDirection::Down ||
event.mouse->scroll == MouseEventData::eScrollDirection::Up)
set_value(getValue() + (0.1f * event.mouse->scrollAmount.y));
else
set_value(getValue() + (0.1f * event.mouse->scrollAmount.x));
event.handle();
}
void Slider::onDraw(ogfx::BasicRenderer2D& gfx)
{
const auto& bounds = getGlobalContentBounds();
f32 value = m_value;
f32 t = (value - m_min) / (m_max - m_min); // normalized [0, 1]
auto l_centerh = [&](f32 value) -> f32 {
return (geth() / 2.0f) - (value / 2.0f);
};
auto l_centerw = [&](f32 value) -> f32 {
return (getw() / 2.0f) - (value / 2.0f);
};
if (isHorizontal())
{
if (m_step > 0.0f && m_drawTicks)
{
int tickCount = (int)std::round((m_max - m_min) / m_step) + 1;
for (int i = 0; i < tickCount; i++)
{
f32 v = m_min + i * m_step;
v = std::min(v, m_max);
f32 tickT = (v - m_min) / (m_max - m_min);
f32 tickX = bounds.x + bounds.w * tickT;
gfx.fillRect({ tickX - 1, bounds.y + l_centerh(getStepTickHeight()) - getStepTickHeight(), 2, getStepTickHeight() }, getStepTickColor());
}
}
Vec2 correction { -2, 0 };
Vec2 trackPos = bounds.getPosition() + Vec2 { 0, l_centerh(getTrackWidth()) };
gfx.fillRect({ trackPos + correction, bounds.w + (correction.x * -2), getTrackWidth() }, getTrackColor());
gfx.fillRect({ trackPos + correction, (bounds.w + (correction.x * -2)) * t, getTrackWidth() }, getTrackProgressColor());
f32 handleX = bounds.x + bounds.w * t - (getHandleSize().x / 2.0f);
gfx.fillRect({ handleX, bounds.y + l_centerh(getHandleSize().y), getHandleSize() }, getHandleColor());
}
else
{
if (m_step > 0.0f && m_drawTicks)
{
int tickCount = (int)std::round((m_max - m_min) / m_step) + 1;
for (int i = 0; i < tickCount; i++)
{
f32 v = m_min + i * m_step;
v = std::min(v, m_max);
f32 tickT = (v - m_min) / (m_max - m_min);
f32 tickY = bounds.y + bounds.h * (1.0f - tickT);
gfx.fillRect({ bounds.x + l_centerw(getStepTickHeight()) - getStepTickHeight(), tickY - 1, getStepTickHeight(), 2 }, getStepTickColor());
}
}
Vec2 correction { 0, -2 };
Vec2 trackPos = bounds.getPosition() + Vec2 { l_centerw(getTrackWidth()), 0 };
gfx.fillRect({ trackPos + correction, getTrackWidth(), bounds.h + (correction.y * -2) }, getTrackColor());
f32 progressH = (bounds.h + (correction.y * -2)) * t;
gfx.fillRect({ trackPos.x + correction.x, trackPos.y + correction.y + (bounds.h + (correction.y * -2)) - progressH, getTrackWidth(), progressH }, getTrackProgressColor());
Vec2 swappedSize = getHandleSize().swap();
f32 handleY = bounds.y + bounds.h * (1.0f - t) - (swappedSize.y / 2.0f);
gfx.fillRect({ bounds.x + l_centerw(swappedSize.x), handleY, swappedSize }, getHandleColor());
}
}
void Slider::enableVertical(bool enable)
{
if (isVertical() && enable) return;
if (isHorizontal() && !enable) return;
m_vertical = enable;
setSize(getSize().swap());
}
f32 Slider::snap_to_step(f32 val)
{
if (m_step <= 0.0f) return std::clamp(val, m_min, m_max); // full precision
f32 snapped = std::round((val - m_min) / m_step) * m_step + m_min;
return std::clamp(snapped, m_min, m_max);
}
f32 Slider::mouse_position_to_value(Vec2 mousePosition)
{
const auto& bounds = getGlobalBounds();
f32 raw = isHorizontal() ? (mousePosition.x - bounds.x) / bounds.w : 1.0f - (mousePosition.y - bounds.y) / bounds.h;
raw = std::clamp(raw, 0.0f, 1.0f);
return m_min + raw * (m_max - m_min);
}
void Slider::set_value(f32 val)
{
if (m_value == val)
return;
f32 old = m_value;
m_value = snap_to_step(val);
if (callback_onValueChanged)
callback_onValueChanged(old, m_value);
}
}
}

View file

@ -0,0 +1,92 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
namespace ogfx
{
namespace gui
{
class Slider : public Widget
{
public:
inline Slider(Window& window, bool vertical = false, f32 min = 0.0f, f32 max = 1.0f, f32 step = 0.1f) : Widget({ 0, 0, 0, 0 }, window) { create(vertical, min, max, step); }
Slider& create(bool vertical, f32 min, f32 max, f32 step);
void applyTheme(const ostd::Stylesheet& theme) override;
void onMouseReleased(const Event& event) override;
void onMouseDragged(const Event& event) override;
void onMouseScrolled(const Event& event) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
inline f32 getValue(void) const { return m_value; }
inline void setValueChangedCallback(std::function<void(f32 oldValue, f32 newValue)> callback) { callback_onValueChanged = std::move(callback); }
void enableVertical(bool enable = true);
inline bool isVertical(void) const { return m_vertical; }
inline void disableVertical(void) { enableVertical(false); }
inline bool isHorizontal(void) const { return !isVertical(); }
inline void enableHorizontal(bool enable = true) { enableVertical(!enable); }
inline void disableHorizontal(void) { enableHorizontal(false); }
OSTD_BOOL_PARAM_GETSET_E(MiddleClickShortcut, m_middleClickShortcut);
OSTD_BOOL_PARAM_GETSET_E(ScrollWheelShortcut, m_enableScrollWheel);
OSTD_PARAM_GETSET(f32, MinValue, m_min);
OSTD_PARAM_GETSET(f32, MaxValue, m_max);
OSTD_PARAM_GETSET(f32, Step, m_step);
OSTD_PARAM_GETSET(f32, TrackWidth, m_trackWidth);
OSTD_PARAM_GETSET(Color, TrackColor, m_trackColor);
OSTD_PARAM_GETSET(Color, TrackProgressColor, m_trackProgressColor);
OSTD_BOOL_PARAM_GETSET_E(DrawTicks, m_drawTicks);
OSTD_PARAM_GETSET(f32, StepTickHeight, m_tickHeight);
OSTD_PARAM_GETSET(Color, StepTickColor, m_tickColor);
OSTD_PARAM_GETSET(Color, HandleColor, m_handleColor);
OSTD_PARAM_GETSET(Vec2, HandleSize, m_handleSize);
private:
f32 snap_to_step(f32 val);
f32 mouse_position_to_value(Vec2 mousePosition);
void set_value(f32 val);
private:
f32 m_value { 0.0f };
std::function<void(f32 oldValue, f32 newValue)> callback_onValueChanged { nullptr };
f32 m_min { 0.0f };
f32 m_max { 1.0f };
f32 m_step { 0.1f };
bool m_vertical { false };
bool m_middleClickShortcut { true };
bool m_enableScrollWheel { true };
f32 m_trackWidth { 6 };
Color m_trackColor { "#500000FF" };
Color m_trackProgressColor { "#AA0000FF" };
bool m_drawTicks { true };
f32 m_tickHeight { 4 };
Color m_tickColor { Colors::Crimson };
Color m_handleColor { Colors::Crimson };
Vec2 m_handleSize { 10, 18 };
};
}
}

View file

@ -0,0 +1,926 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Text.hpp"
#include "../../utils/Keycodes.hpp"
#include "../../render/BasicRenderer.hpp"
namespace ogfx
{
namespace gui
{
String TextEdit::CharacterFilter::predictResult(const String& utf8, const ostd::TextBuffer& buffer)
{
const String& current = buffer.text();
const u32 selStart = buffer.selectionStart();
const u32 selEnd = buffer.selectionEnd();
String head = current.new_substr(0, cast<i32>(selStart));
String tail = current.new_substr(selEnd, cast<i32>(current.len()));
return head + utf8 + tail;
}
bool TextEdit::IntegerFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer)
{
// Reject anything that isn't a single ASCII byte. Multi-codepoint
// input (IME, emoji, accented chars) is never valid in an integer.
if (utf8.len() != 1) return false;
const char c = utf8[0];
const bool isDigit = (c >= '0' && c <= '9');
const bool isSign = (c == '-');
if (!isDigit && !isSign) return false;
// Predict the post-insert string and validate it as a prefix.
const String result = predictResult(utf8, buffer);
return isValidIntegerPrefix(result);
}
bool TextEdit::IntegerFilter::isValidIntegerPrefix(const String& s) const
{
if (s.empty()) return true;
const ostd::cpp_string& bytes = s.cpp_str();
u32 i = 0;
// Optional leading minus.
if (bytes[0] == '-') {
if (!m_allowNegative) return false;
i = 1;
if (bytes.size() == 1) return true; // Just "-" by itself: valid prefix.
}
// Everything from i onward must be digits. Count them while we're at it.
u32 digitCount = 0;
while (i < bytes.size()) {
if (bytes[i] < '0' || bytes[i] > '9') return false;
digitCount++;
i++;
}
if (m_maxDigits > 0 && digitCount > cast<u32>(m_maxDigits))
return false;
return true;
}
bool TextEdit::HexadecimalFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer)
{
// Reject anything that isn't a single ASCII byte. Multi-codepoint
// input (IME, emoji, accented chars) is never valid in an hex.
if (utf8.len() != 1) return false;
const char c = utf8[0];
if (!isHexDigit(c)) return false;
// Predict the post-insert string and validate it as a prefix.
const String result = predictResult(utf8, buffer);
return isValidHexPrefix(result);
}
bool TextEdit::HexadecimalFilter::isValidHexPrefix(const String& s) const
{
if (s.empty()) return true;
const ostd::cpp_string& bytes = s.cpp_str();
u32 i = 0;
// Everything from i onward must be digits. Count them while we're at it.
u32 digitCount = 0;
while (i < bytes.size()) {
if (!isHexDigit(bytes[i])) return false;
digitCount++;
i++;
}
if (m_maxDigits > 0 && digitCount > cast<u32>(m_maxDigits))
return false;
return true;
}
bool TextEdit::HexadecimalFilter::isHexDigit(char c) const
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
bool TextEdit::DecimalFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer)
{
if (utf8.len() != 1) return false;
const char c = utf8[0];
const bool isDigit = (c >= '0' && c <= '9');
const bool isSign = (c == '-');
const bool isSep = (c == m_separator);
if (!isDigit && !isSign && !isSep) return false;
const String result = predictResult(utf8, buffer);
return isValidDecimalPrefix(result);
}
bool TextEdit::DecimalFilter::isValidDecimalPrefix(const String& s) const
{
if (s.empty()) return true;
const ostd::cpp_string& bytes = s.cpp_str();
u32 i = 0;
if (bytes[0] == '-') {
if (!m_allowNegative) return false;
i = 1;
if (bytes.size() == 1) return true; // Just "-": valid prefix.
}
u32 intDigits = 0;
u32 fracDigits = 0;
bool seenSep = false;
while (i < bytes.size()) {
const char c = bytes[i];
if (c == m_separator) {
if (seenSep) return false; // Two separators: invalid.
if (intDigits == 0) return false; // ".5" without a leading digit:
// we choose to require it for now.
seenSep = true;
}
else if (c >= '0' && c <= '9') {
if (seenSep) fracDigits++;
else intDigits++;
}
else {
return false;
}
i++;
}
if (m_maxIntDigits > 0 && intDigits > cast<u32>(m_maxIntDigits)) return false;
if (m_maxFracDigits > 0 && fracDigits > cast<u32>(m_maxFracDigits)) return false;
return true;
}
bool TextEdit::DateFilter::isValidChar(const String& utf8, const ostd::TextBuffer& buffer)
{
if (utf8.len() != 1) return false;
const String result = predictResult(utf8, buffer);
return isValidDatePrefix(result);
}
void TextEdit::DateFilter::buildPositionMap(void)
{
m_slots.clear();
// Local helper to push a run of digit slots.
auto pushDigits = [&](eSlot s, u32 n) {
for (u32 i = 0; i < n; i++) m_slots.push_back(s);
};
auto pushSep = [&]() { m_slots.push_back(eSlot::Sep); };
switch (m_format) {
case eFormat::DDMMYY:
pushDigits(eSlot::Day, 2); pushSep();
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Year, 2); break;
case eFormat::DDMMYYYY:
pushDigits(eSlot::Day, 2); pushSep();
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Year, 4); break;
case eFormat::MMDDYY:
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Day, 2); pushSep();
pushDigits(eSlot::Year, 2); break;
case eFormat::MMDDYYYY:
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Day, 2); pushSep();
pushDigits(eSlot::Year, 4); break;
case eFormat::YYYYMMDD:
pushDigits(eSlot::Year, 4); pushSep();
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Day, 2); break;
case eFormat::YYMMDD:
pushDigits(eSlot::Year, 2); pushSep();
pushDigits(eSlot::Month, 2); pushSep();
pushDigits(eSlot::Day, 2); break;
}
}
bool TextEdit::DateFilter::isValidDatePrefix(const String& s) const
{
const ostd::cpp_string& bytes = s.cpp_str();
if (bytes.size() > m_slots.size()) return false;
// Track the running values so we can validate ranges as soon as
// a field is fully entered.
u32 dayVal = 0, dayChars = 0;
u32 monthVal = 0, monthChars = 0;
u32 yearVal = 0, yearChars = 0;
for (u32 i = 0; i < bytes.size(); i++) {
const char c = bytes[i];
const eSlot expected = m_slots[i];
if (expected == eSlot::Sep) {
if (c != m_separator) return false;
// Separator means a field just ended — validate it.
// (Only the field that *just ended* needs validation; earlier
// fields were already validated when their separators were typed.)
}
else {
if (c < '0' || c > '9') return false;
const u32 d = cast<u32>(c - '0');
if (expected == eSlot::Day) { dayVal = dayVal * 10 + d; dayChars++; }
if (expected == eSlot::Month) { monthVal = monthVal * 10 + d; monthChars++; }
if (expected == eSlot::Year) { yearVal = yearVal * 10 + d; yearChars++; }
}
// Range validation as soon as a field is fully populated.
if (expected == eSlot::Day && dayChars == 2) {
if (dayVal < 1 || dayVal > 31) return false;
}
if (expected == eSlot::Month && monthChars == 2) {
if (monthVal < 1 || monthVal > 12) return false;
}
// No range check for year — historically valid years span millennia.
// Also reject impossible *partial* values: a 2-digit day field
// can't start with '4'-'9' if the second digit would always make
// it exceed 31. Day field: first digit must be 0-3.
// Month field: first digit must be 0-1.
if (expected == eSlot::Day && dayChars == 1) {
if (dayVal > 3) return false;
}
if (expected == eSlot::Month && monthChars == 1) {
if (monthVal > 1) return false;
}
}
return true;
}
TextEdit& TextEdit::create(void)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::TextEdit");
disableChildren();
setStylesheetCategoryName("textEdit");
enableFocus();
enableBackground();
enableBorder();
enableTextEnteredEvent(true);
setSize({ 200, 30 });
m_cursorBlinkTimer.start();
m_buffer.setOnChanged([this](const ostd::TextBuffer&) {
m_layout.dirty = true;
});
m_buffer.setOnCursorMoved([this](const ostd::TextBuffer&) {
if (m_cursorBlink)
m_cursorBlinkTimer.start();
m_cursorState = true;
});
validate();
return *this;
}
void TextEdit::applyTheme(const ostd::Stylesheet& theme)
{
enableCursorBlink(getThemeValue<bool>(theme, "cursorBlink", isCursorBlinkEnabled()));
setCursorWidth(getThemeValue<f32>(theme, "cursorWidth", getCursorWidth()));
setCursorColor(getThemeValue<Color>(theme, "cursorColor", getCursorColor()));
setSelectionColor(getThemeValue<Color>(theme, "selectionColor", getSelectionColor()));
setMaxLength(getThemeValue<i32>(theme, "macLength", getMaxLength()));
setTextPadding(getThemeValue<f32>(theme, "textPaddingLeft", getTextPadding().left),
getThemeValue<f32>(theme, "textPaddingRight", getTextPadding().right));
String mask = getThemeValue<String>(theme, "characterMask", String(""));
if (mask.len() != 1)
clearCharacterMask();
else
setCharacterMask(mask[0]);
String styleStr = getThemeValue<String>(theme, "cursorStyle", String("line"));
if (styleStr == "block") setCursorStyle(CursorStyle::Block);
else if (styleStr == "box") setCursorStyle(CursorStyle::Box);
else if (styleStr == "underscore") setCursorStyle(CursorStyle::Underscore);
else setCursorStyle(CursorStyle::Line);
}
void TextEdit::onDraw(ogfx::BasicRenderer2D& gfx)
{
const auto bounds = getGlobalBounds();
// -------------------------------------------------------------
// 1. Rebuild the layout cache if the buffer (or font/size) has
// changed since last frame. We mark dirty in the buffer's
// onChanged callback (set up in create()).
// -------------------------------------------------------------
if (m_layout.dirty)
rebuild_layout(gfx);
// -------------------------------------------------------------
// 2. Make sure the cursor is in view. This adjusts m_scrollX as
// needed so the cursor sits inside the widget's content area.
// -------------------------------------------------------------
ensure_cursor_visible(bounds);
// -------------------------------------------------------------
// 3. Compute the text content area inside the widget — the
// rectangle where text is allowed to render. Anything drawn
// outside this rect gets clipped away.
// -------------------------------------------------------------
const Rectangle contentArea {
bounds.x + m_textPadding.left,
bounds.y,
bounds.w - m_textPadding.left - m_textPadding.right,
bounds.h
};
// -------------------------------------------------------------
// 5. Compute the y position for the text baseline area. Vertically
// centered using the cached line height. This is single-line
// behavior; for multiline you'd loop over lines and offset y
// by lineHeight per row.
// -------------------------------------------------------------
const f32 textY = bounds.y + (bounds.h - m_layout.lineHeight) * 0.5f;
const f32 textX = contentArea.x - m_scrollX;
// -------------------------------------------------------------
// 6. Draw the selection highlight FIRST (under the text). This
// way the text stays the regular color and just sits on top
// of a colored band — like every text editor.
// -------------------------------------------------------------
if (m_buffer.hasSelection())
{
const u32 selStart = m_buffer.selectionStart();
const u32 selEnd = m_buffer.selectionEnd();
const f32 x1 = textX + layout_x_for_byte(selStart);
const f32 x2 = textX + layout_x_for_byte(selEnd);
gfx.fillRect(
{ x1, textY, x2 - x1, m_layout.lineHeight },
m_selectionColor
);
}
// -------------------------------------------------------------
// 8. Draw the cursor. Only when focused and the blink is in the
// "on" phase. The cursor renders ON TOP of everything else so
// it's visible inside selections.
// -------------------------------------------------------------
if (isFocused() && (m_cursorState || !m_cursorBlink))
{
const f32 cx = textX + layout_x_for_byte(m_buffer.cursorByteOffset());
auto l_char_width_at_cursor = [this](void) -> f32 {
const u32 byteOff = m_buffer.cursorByteOffset();
const auto& bs = m_layout.byteOffsets;
const auto& xs = m_layout.xPositions;
auto it = std::lower_bound(bs.begin(), bs.end(), byteOff);
if (it != bs.end() && (it + 1) != bs.end()) {
const size_t idx = std::distance(bs.begin(), it);
return xs[idx + 1] - xs[idx];
}
return m_layout.lineHeight * 0.6f; // fallback at end-of-text
};
switch (m_cursorStyle)
{
case CursorStyle::Line:
gfx.fillRect({ cx, textY, m_cursorWidth, m_layout.lineHeight }, m_cursorColor);
break;
case CursorStyle::Block:
{
gfx.fillRect({ cx, textY, l_char_width_at_cursor(), m_layout.lineHeight }, m_cursorColor);
break;
}
case CursorStyle::Box:
{
gfx.drawRect({ cx, textY, l_char_width_at_cursor(), m_layout.lineHeight }, m_cursorColor, m_cursorWidth);
break;
}
case CursorStyle::Underscore:
{
const f32 underH = std::max(2.0f, m_layout.lineHeight * 0.1f);
gfx.fillRect({ cx, textY + m_layout.lineHeight - underH, l_char_width_at_cursor(), m_cursorWidth }, m_cursorColor);
break;
}
}
}
// -------------------------------------------------------------
// 7. Draw the text itself. Single drawString call — the layout
// cache is only used for cursor / selection positioning;
// actual glyph rendering goes through the renderer's existing
// code path (which handles kerning, atlas, etc.).
// -------------------------------------------------------------
if (!m_buffer.empty())
{
if (hasCharacterMask())
gfx.drawString(String::duplicateChar(m_charMask, m_buffer.text().len()), { textX, textY }, getTextColor(), getFontSize());
else
gfx.drawString(m_buffer.text(), { textX, textY }, getTextColor(), getFontSize());
}
}
void TextEdit::onUpdate(void)
{
m_keyRepeatTimer.update();
m_doubleClickTimer.update();
if (m_cursorBlink)
{
m_cursorBlinkTimer.update();
if (m_cursorBlinkTimer.isDone())
{
m_cursorState = !m_cursorState;
m_cursorBlinkTimer.start();
}
}
// Auto-scroll while drag-selecting past the widget edges.
if (m_mouseSelecting)
{
const auto bounds = getGlobalBounds();
const f32 contentLeft = m_textPadding.left;
const f32 contentRight = bounds.w - m_textPadding.right;
// Only act when mouse is outside the content area horizontally.
if (m_lastMouseLocalX < contentLeft || m_lastMouseLocalX > contentRight)
{
// Use the same hit-test as drag, but it'll naturally clamp to
// start/end of buffer when the mouse is way outside. The
// setCursorByteOffset call will trigger ensure_cursor_visible
// on the next draw, which scrolls the view to follow.
const u32 hit = byte_offset_for_pixel_x(m_lastMouseLocalX);
m_buffer.setCursorByteOffset(hit, true);
}
}
}
void TextEdit::onFocusLost(const Event& event)
{
if (m_buffer.hasSelection())
m_buffer.clearSelection();
}
void TextEdit::onTextEntered(const Event& event)
{
auto& data = *event.keyboard;
const String toInsert = clamp_input_to_max_length(data.text);
if (toInsert.empty()) return;
if (!m_filter || m_filter->isValidChar(data.text, m_buffer))
{
if (m_lastChar == toInsert && m_keyRepeatTimer.isCounting())
return;
m_buffer.insertText(toInsert);
m_lastChar = toInsert;
m_keyRepeatTimer.start();
}
}
void TextEdit::onKeyPressed(const Event& event)
{
auto& data = *event.keyboard;
if (m_lastKeyCode == data.keyCode && m_keyRepeatTimer.isCounting())
return;
const bool extend = data.modifiers.anyShift();
const bool word = data.modifiers.primary();
const bool mask = hasCharacterMask();
// ----- Editing operations (chords first, since they're more specific) -----
if (data.isCopy())
{
if (hasCharacterMask() && !isCopyOnMaskEnabled())
return;
const String sel = m_buffer.selectedText();
if (!sel.empty())
SDL_SetClipboardText(sel);
}
else if (data.isCut())
{
if (hasCharacterMask() && !isCopyOnMaskEnabled())
return;
const String sel = m_buffer.selectedText();
if (!sel.empty())
{
SDL_SetClipboardText(sel);
m_buffer.deleteSelection();
}
}
else if (data.isPaste())
{
const String clipboardText = SDL_GetClipboardText();
if (!clipboardText.empty())
{
const String toInsert = clamp_input_to_max_length(clipboardText);
if (!toInsert.empty())
m_buffer.insertText(toInsert);
}
}
else if (data.isSelectAll())
{
m_buffer.selectAll();
}
else if (data.isUndo())
{
if (m_buffer.canUndo())
m_buffer.undo();
}
else if (data.isRedo())
{
if (m_buffer.canRedo())
m_buffer.redo();
}
// ----- Cursor movement -----
else if (data.keyCode == KeyCode::Left)
{
if (mask && word) m_buffer.moveDocumentStart(extend);
else if (!mask && word) m_buffer.moveWordLeft(extend);
else m_buffer.moveLeft(extend);
}
else if (data.keyCode == KeyCode::Right)
{
if (mask && word) m_buffer.moveDocumentEnd(extend);
else if (!mask && word) m_buffer.moveWordRight(extend);
else m_buffer.moveRight(extend);
}
else if (data.keyCode == KeyCode::Home || data.keyCode == KeyCode::Pageup)
{
// PageUp on single-line == Home. When you go multiline, split these.
if (word) m_buffer.moveDocumentStart(extend);
else m_buffer.moveLineStart(extend);
}
else if (data.keyCode == KeyCode::End || data.keyCode == KeyCode::Pagedown)
{
if (word) m_buffer.moveDocumentEnd(extend);
else m_buffer.moveLineEnd(extend);
}
// ----- Deletion -----
else if (data.keyCode == KeyCode::Backspace)
{
if (m_buffer.hasSelection())
{
m_buffer.deleteSelection();
}
else if (mask && word)
{
if (m_buffer.cursorByteOffset() > 0) {
m_buffer.selectRange(0, m_buffer.cursorByteOffset());
m_buffer.deleteSelection();
}
}
else if (!mask && word) m_buffer.backspaceWord();
else m_buffer.backspace();
}
else if (data.keyCode == KeyCode::Delete)
{
if (m_buffer.hasSelection())
{
m_buffer.deleteSelection();
}
else if (mask && word)
{
if (m_buffer.cursorByteOffset() < m_buffer.byteSize()) {
m_buffer.selectRange(m_buffer.cursorByteOffset(), m_buffer.byteSize());
m_buffer.deleteSelection();
}
}
else if (!mask && word) m_buffer.deleteWord();
else m_buffer.deleteForward();
}
// ----- Other -----
else if (data.keyCode == KeyCode::Escape)
{
// Clear selection if there is one; otherwise no-op (the widget could
// give up focus here, but that's an opinionated choice — leave it
// up to the application via callback_onEscape if you want).
if (m_buffer.hasSelection())
m_buffer.clearSelection();
}
else if (data.keyCode == KeyCode::Return)
{
if (callback_onActionPerformed)
callback_onActionPerformed(event);
}
else
{
// Unrecognized key — don't engage the repeat throttle, and don't
// record m_lastKeyCode. Returning here keeps the throttle accurate
// (if the user presses an unhandled key followed by a real one,
// the real one fires immediately rather than being suppressed).
return;
}
// Single bookkeeping point for every accepted branch.
m_keyRepeatTimer.start();
m_lastKeyCode = data.keyCode;
}
void TextEdit::onMousePressed(const Event& event)
{
auto& data = *event.mouse;
const f32 local_x = data.position_x - getGlobalBounds().x;
const u32 hit = byte_offset_for_pixel_x(local_x);
// Double-click detection: a second press within the timer window AND
// close enough to the last press position counts as a double-click.
// The position check matters — without it, two presses far apart in
// a long document would falsely trigger word selection.
constexpr f32 doubleClickRadius = 5.0f;
const bool isDoubleClick =
m_lastClickValid
&& m_doubleClickTimer.isCounting()
&& std::abs(local_x - m_lastClickLocalX) < doubleClickRadius;
if (isDoubleClick)
{
// Word selection. selectWordAt sets both anchor and cursor;
// we mark the drag flag so subsequent drag motion extends the
// selection by word — though we'll keep the simple implementation
// for now where dragging after a double-click extends by character.
if (hasCharacterMask())
m_buffer.selectAll();
else
m_buffer.selectWordAt(hit);
m_mouseSelecting = true;
m_lastClickValid = false; // Don't triple-click into something weird.
}
else
{
m_buffer.setCursorByteOffset(hit);
m_mouseSelecting = true;
m_lastClickLocalX = local_x;
m_lastMouseLocalX = local_x;
m_lastClickValid = true;
m_doubleClickTimer.start();
}
if (m_cursorBlink) m_cursorBlinkTimer.start();
m_cursorState = true;
}
void TextEdit::onMouseReleased(const Event& event)
{
m_mouseSelecting = false;
}
void TextEdit::onMouseDragged(const Event& event)
{
if (!m_mouseSelecting) return;
auto& data = *event.mouse;
const f32 local_x = data.position_x - getGlobalBounds().x;
const u32 hit = byte_offset_for_pixel_x(local_x);
// Extend selection to the dragged-to position. The anchor was set by
// setCursorByteOffset(hit) in onMousePressed and stays put.
m_buffer.setCursorByteOffset(hit, true);
m_lastMouseLocalX = local_x;
if (m_cursorBlink) m_cursorBlinkTimer.start();
m_cursorState = true;
}
void TextEdit::setText(const String& text)
{
if (m_maxLength <= 0) {
m_buffer.setText(text);
return;
}
const u32 inputCps = String::utf8::count_codepoints(text.cpp_str());
if (inputCps <= cast<u32>(m_maxLength)) {
m_buffer.setText(text);
return;
}
m_buffer.setText(String::utf8::truncate(text, cast<u32>(m_maxLength)));
}
void TextEdit::setMaxLength(i32 codepoints)
{
m_maxLength = codepoints;
if (m_maxLength <= 0) return;
const u32 currentCps = m_buffer.codepointCount();
if (currentCps <= cast<u32>(m_maxLength)) return;
const String truncated = String::utf8::truncate(m_buffer.text(), cast<u32>(m_maxLength));
m_buffer.setText(truncated);
}
void TextEdit::rebuild_layout(BasicRenderer2D& gfx)
{
m_layout.byteOffsets.clear();
m_layout.xPositions.clear();
m_layout.totalWidth = 0.0f;
// Empty buffer: a single entry for "x=0 at byte 0" so the cursor
// still has a position to render at.
if (m_buffer.empty())
{
m_layout.lineHeight = gfx.getStringDimensions(" ", getFontSize()).y;
m_layout.byteOffsets.push_back(0);
m_layout.xPositions.push_back(0.0f);
m_layout.dirty = false;
return;
}
const String displayText = hasCharacterMask()
? String::duplicateChar(m_charMask, m_buffer.text().len())
: m_buffer.text();
// One call to the renderer. Returns one Vec2 per codepoint:
// .x = advance (incl. kerning) contributed by that glyph
// .y = glyph height (same for all glyphs at this font/size)
const stdvec<Vec2> perChar =
gfx.getStringDimensionsPerCharacter(displayText, getFontSize());
if (perChar.empty())
{
// Defensive: shouldn't happen with non-empty text, but if the
// font failed to load we still want a valid layout.
m_layout.lineHeight = 0.0f;
m_layout.byteOffsets.push_back(0);
m_layout.xPositions.push_back(0.0f);
m_layout.dirty = false;
return;
}
m_layout.lineHeight = perChar[0].y;
// Walk both the per-character advances AND the buffer's UTF-8 bytes
// in lockstep. Since processString emits exactly one glyph per
// codepoint, the i-th advance corresponds to the codepoint that
// starts at the i-th byte boundary.
const ostd::cpp_string& s = m_buffer.text().cpp_str();
f32 x = 0.0f;
u32 byte_pos = 0;
size_t glyph_idx = 0;
// First entry: position before any glyph.
m_layout.byteOffsets.push_back(0);
m_layout.xPositions.push_back(0.0f);
while (byte_pos < s.size() && glyph_idx < perChar.size())
{
x += perChar[glyph_idx].x;
byte_pos = String::utf8::next_codepoint_start(s, byte_pos);
m_layout.byteOffsets.push_back(byte_pos);
m_layout.xPositions.push_back(x);
glyph_idx++;
}
m_layout.totalWidth = x;
m_layout.dirty = false;
}
f32 TextEdit::layout_x_for_byte(u32 byte_offset) const
{
// Binary search for the entry. Layout invariant: byteOffsets is
// strictly increasing, so std::lower_bound gives us the right index.
const auto& v = m_layout.byteOffsets;
if (v.empty()) return 0.0f;
if (byte_offset >= v.back()) return m_layout.xPositions.back();
auto it = std::lower_bound(v.begin(), v.end(), byte_offset);
const size_t idx = std::distance(v.begin(), it);
return m_layout.xPositions[idx];
}
void TextEdit::ensure_cursor_visible(const Rectangle& bounds)
{
const f32 contentW = bounds.w - m_textPadding.left - m_textPadding.right;
if (contentW <= 0.0f) return;
const f32 cursorX = layout_x_for_byte(m_buffer.cursorByteOffset());
const f32 totalW = m_layout.totalWidth;
// Margin: how far the cursor should stay from the visible edges.
// ~2 character widths feels right; using line height as a proxy works
// well across font sizes since char width scales similarly.
// This single-sourced value also caps itself at 25% of contentW so it
// stays sensible even in very narrow widgets.
const f32 marginRaw = m_layout.lineHeight * 0.6f;
const f32 margin = std::min(marginRaw, contentW * 0.25f);
// Case 1: all the text fits. No scroll needed.
if (totalW <= contentW)
{
m_scrollX = 0.0f;
return;
}
// Case 2: text overflows. Apply margin-aware scrolling.
//
// The visible window in document space is [m_scrollX, m_scrollX + contentW).
// We want cursorX to satisfy:
// m_scrollX + margin <= cursorX <= m_scrollX + contentW - margin
// — except clamped at the document edges.
// If cursor is too close to the left edge of the visible window, scroll
// left so there's `margin` of text visible to its left.
if (cursorX < m_scrollX + margin)
m_scrollX = cursorX - margin;
// If cursor is too close to the right edge, scroll right so there's
// `margin` of room to its right.
else if (cursorX > m_scrollX + contentW - margin)
m_scrollX = cursorX - contentW + margin;
// Clamp scrollX to valid range. The lower bound is 0 (don't show empty
// space to the left of the text). The upper bound is totalW - contentW
// (don't show empty space to the right of the text — i.e., the right
// edge of the text aligns with the right edge of the visible area).
//
// This upper clamp is what produces the "right-anchored when at end"
// behavior automatically: when cursor is at end, the margin formula
// wants to scroll past the end of the text, but we clamp it back so
// the text's right edge sits at the visible area's right edge.
if (m_scrollX < 0.0f)
m_scrollX = 0.0f;
if (m_scrollX > totalW - contentW)
m_scrollX = totalW - contentW;
}
u32 TextEdit::byte_offset_for_pixel_x(f32 widget_local_x) const
{
const f32 doc_x = widget_local_x - m_textPadding.left + m_scrollX;
const auto& xs = m_layout.xPositions;
const auto& bs = m_layout.byteOffsets;
if (xs.empty()) return 0;
if (doc_x <= xs.front()) return bs.front();
if (doc_x >= xs.back()) return bs.back();
auto it = std::upper_bound(xs.begin(), xs.end(), doc_x);
const size_t hi = std::distance(xs.begin(), it);
const size_t lo = hi - 1;
const f32 distLo = doc_x - xs[lo];
const f32 distHi = xs[hi] - doc_x;
return cast<u32>((distLo <= distHi) ? bs[lo] : bs[hi]);
}
String TextEdit::clamp_input_to_max_length(const String& utf8) const
{
if (m_maxLength <= 0) return utf8;
const u32 currentCps = m_buffer.codepointCount();
u32 selCps = 0;
if (m_buffer.hasSelection()) {
const u32 a = m_buffer.selectionStart();
const u32 b = m_buffer.selectionEnd();
const String selected = m_buffer.text().new_substr(a, cast<i32>(b));
selCps = String::utf8::count_codepoints(selected.cpp_str());
}
const u32 insertedCps = String::utf8::count_codepoints(utf8.cpp_str());
const u32 resultCps = currentCps - selCps + insertedCps;
const u32 capCps = cast<u32>(m_maxLength);
if (resultCps <= capCps) return utf8;
// Overflow. Truncate ONLY in the appending case: cursor at end, no selection.
const bool atEnd = (m_buffer.cursorByteOffset() == m_buffer.byteSize());
if (!atEnd || m_buffer.hasSelection()) {
// Mid-buffer insert or replace-selection: reject the whole input.
return String("");
}
// Appending case: truncate to fit.
const u32 availableCps = capCps - currentCps;
return String::utf8::truncate(utf8, availableCps);
}
}
}

View file

@ -0,0 +1,185 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ostd/string/TextBuffer.hpp>
#include <ostd/utils/Time.hpp>
namespace ogfx
{
namespace gui
{
class TextEdit : public Widget
{
public: class CharacterFilter
{
public:
virtual ~CharacterFilter(void) = default;
inline virtual bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) = 0;
protected:
// Helper: build the string that would result from inserting `utf8`
// at the current cursor (replacing any selection). Use this in
// subclasses to ask "is the resulting string a valid prefix?"
static String predictResult(const String& utf8, const ostd::TextBuffer& buffer);
};
public: class IntegerFilter : public CharacterFilter
{
public:
inline IntegerFilter(bool allowNegative = true, i32 maxDigits = -1) : m_allowNegative(allowNegative), m_maxDigits(maxDigits) {}
bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) override;
private:
bool isValidIntegerPrefix(const String& s) const;
private:
bool m_allowNegative;
i32 m_maxDigits;
};
public: class HexadecimalFilter : public CharacterFilter
{
public:
inline HexadecimalFilter(i32 maxDigits = -1) : m_maxDigits(maxDigits) {}
bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) override;
private:
bool isValidHexPrefix(const String& s) const;
bool isHexDigit(char c) const;
private:
i32 m_maxDigits;
};
public: class DecimalFilter : public CharacterFilter
{
public:
inline DecimalFilter(bool allowNegative = true, char separator = '.', i32 maxIntegerDigits = -1, i32 maxFractionDigits = -1) : m_allowNegative(allowNegative), m_separator(separator), m_maxIntDigits(maxIntegerDigits), m_maxFracDigits(maxFractionDigits) {}
bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) override;
private:
bool isValidDecimalPrefix(const String& s) const;
private:
bool m_allowNegative;
char m_separator;
i32 m_maxIntDigits;
i32 m_maxFracDigits;
};
public: class DateFilter : public CharacterFilter
{
public: enum class eFormat{ DDMMYY, DDMMYYYY, MMDDYY, MMDDYYYY, YYYYMMDD, YYMMDD };
private: enum class eSlot { Day, Month, Year, Sep };
public:
DateFilter(eFormat format = eFormat::DDMMYYYY, char separator = '.') : m_format(format), m_separator(separator) { buildPositionMap(); }
bool isValidChar(const String& utf8, const ostd::TextBuffer& buffer) override;
private:
void buildPositionMap(void);
bool isValidDatePrefix(const String& s) const;
private:
stdvec<eSlot> m_slots;
eFormat m_format;
char m_separator;
};
public: struct TextPadding
{
f32 left { 6.0f };
f32 right { 6.0f };
};
public: struct GlyphLayout
{
stdvec<u32> byteOffsets; // size N+1: byte offset before each codepoint, plus end
stdvec<f32> xPositions; // size N+1: pixel x before each codepoint, plus end-x
f32 totalWidth { 0 };
f32 lineHeight { 0 };
bool dirty { true };
};
public: enum class CursorStyle { Line, Block, Box, Underscore };
public:
inline TextEdit(Window& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
TextEdit& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onUpdate(void) override;
void onFocusLost(const Event& event) override;
void onTextEntered(const Event& event) override;
void onKeyPressed(const Event& event) override;
void onMousePressed(const Event& event) override;
void onMouseReleased(const Event& event) override;
void onMouseDragged(const Event& event) override;
inline void useCustomFilter(CharacterFilter* filter) { m_owned_filter.reset(); m_filter = filter; }
inline void useDateFilter(DateFilter::eFormat format = DateFilter::eFormat::DDMMYYYY, char separator = '.') { use_filter(std::make_unique<DateFilter>(format, separator)); }
inline void useIntegerFilter(bool allowNegative = true, i32 maxDigits = -1) { use_filter(std::make_unique<IntegerFilter>(allowNegative, maxDigits)); }
inline void useHexadecimalFilter(i32 maxDigits = -1) { use_filter(std::make_unique<HexadecimalFilter>(maxDigits)); }
inline void useDecimalFilter(bool allowNegative = true, char separator = '.', i32 maxIntegerDigits = -1, i32 maxFractionDigits = -1) { use_filter(std::make_unique<DecimalFilter>(allowNegative, separator, maxIntegerDigits, maxFractionDigits)); }
void setText(const String& text);
inline String getText(void) const { return m_buffer.text(); }
inline String getSelectedText(void) const { return m_buffer.selectedText(); }
void setMaxLength(i32 codepoints);
inline i32 getMaxLength(void) const { return m_maxLength; }
inline void setTextPadding(f32 left, f32 right) { m_textPadding.left = left; m_textPadding.right = right; }
inline TextPadding getTextPadding(void) const { return m_textPadding; }
inline void setCharacterMask(char mask) { m_charMask = mask; }
inline char getCharacterMask(void) const { return m_charMask; }
inline void clearCharacterMask(void) { m_charMask = '\0'; }
inline bool hasCharacterMask(void) const { return std::isprint(m_charMask); }
OSTD_BOOL_PARAM_GETSET_E(CursorBlink, m_cursorBlink);
OSTD_BOOL_PARAM_GETSET_E(CopyOnMask, m_copyOnMask);
OSTD_PARAM_GETSET(f32, CursorWidth, m_cursorWidth);
OSTD_PARAM_GETSET(Color, CursorColor, m_cursorColor);
OSTD_PARAM_GETSET(Color, SelectionColor, m_selectionColor);
OSTD_PARAM_GETSET(CursorStyle, CursorStyle, m_cursorStyle);
private:
void rebuild_layout(BasicRenderer2D& gfx);
void ensure_cursor_visible(const Rectangle& bounds);
f32 layout_x_for_byte(u32 byte_offset) const;
u32 byte_offset_for_pixel_x(f32 widget_local_x) const;
String clamp_input_to_max_length(const String& utf8) const;
inline void use_filter(std::unique_ptr<CharacterFilter> f) { m_owned_filter = std::move(f); m_filter = m_owned_filter.get(); }
private:
ostd::TextBuffer m_buffer { "", true };
ostd::BasicCounter m_cursorBlinkTimer { 30 };
ostd::BasicCounter m_keyRepeatTimer { 1 };
ostd::BasicCounter m_doubleClickTimer { 24 };
std::unique_ptr<CharacterFilter> m_owned_filter; // may be null
CharacterFilter* m_filter { nullptr }; // observer, always used
f32 m_lastClickLocalX { 0.0f };
bool m_lastClickValid { false };
bool m_mouseSelecting { false };
f32 m_lastMouseLocalX { 0.0f };
f32 m_scrollX { 0.0f };
String m_lastChar { "" };
i32 m_lastKeyCode { 0 };
bool m_cursorState { false };
GlyphLayout m_layout;
bool m_copyOnMask { false };
i32 m_maxLength { -1 };
TextPadding m_textPadding;
char m_charMask { '\0' };
bool m_cursorBlink { true };
f32 m_cursorWidth { 2 };
Color m_cursorColor { Colors::Crimson };
Color m_selectionColor { 60, 110, 200, 200 };
CursorStyle m_cursorStyle { CursorStyle::Line };
};
}
}

View file

@ -0,0 +1,638 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "TreeView.hpp"
#include "../../render/BasicRenderer.hpp"
#include "../Window.hpp"
#include "../../../ostd/io/Memory.hpp"
#include <algorithm>
namespace ogfx
{
namespace gui
{
void TreeView::Item::setText(const String& text)
{
if (m_text == text)
return;
m_text = text;
update_dimensions();
if (m_treeView) m_treeView->m_extentsDirty = true;
}
void TreeView::Item::setIcon(const Icon& icon)
{
m_icon = icon;
enableIcon();
}
void TreeView::Item::update_dimensions(void)
{
if (!isValid())
return;
auto pad = m_treeView->getLinePadding();
m_dimensions = m_treeView->getWindow().getGFX().getStringDimensions(m_text, m_treeView->getFontSize());
m_dimensions += Vec2 { pad.x + pad.w, pad.y + pad.h };
}
void TreeView::Item::set_selected(stdvec<Item*>& selectionList)
{
for (auto& sel : selectionList)
sel->m_selected = false;
selectionList.clear();
selectionList.push_back(this);
m_selected = true;
}
void TreeView::Item::add_selected(stdvec<Item*>& selectionList)
{
if (STDVEC_CONTAINS(selectionList, this))
return;
selectionList.push_back(this);
m_selected = true;
}
void TreeView::Item::remove_selected(stdvec<Item*>& selectionList)
{
if (!STDVEC_CONTAINS(selectionList, this))
return;
STDVEC_REMOVE(selectionList, this);
m_selected = false;
}
void TreeView::Item::setExpanded(bool expanded)
{
if (m_expanded == expanded)
return;
m_expanded = expanded;
if (m_treeView) m_treeView->m_extentsDirty = true;
}
bool TreeView::Item::isVisible(void) const
{
// An item is visible if every ancestor is expanded.
const Item* p = m_parentItem;
while (p != nullptr)
{
if (!p->m_expanded) return false;
p = p->m_parentItem;
}
return true;
}
stdvec<String> TreeView::Item::getFullPath(void) const
{
// Walk parent pointers up to the root, collecting text bottom-up,
// then reverse so the result reads root -> ... -> this item.
stdvec<String> path;
path.push_back(m_text);
const Item* p = m_parentItem;
while (p != nullptr)
{
path.push_back(p->m_text);
p = p->m_parentItem;
}
std::reverse(path.begin(), path.end());
return path;
}
TreeView& TreeView::create(void)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::TreeView");
enableStopEvents();
enableBackground();
enableBorder();
setBackgroundColor({ 160, 160, 160 });
setBorderColor({ 50, 50, 50 });
setStylesheetCategoryName("tree");
reloadTheme();
validate();
return *this;
}
void TreeView::applyTheme(const ostd::Stylesheet& theme)
{
enableAlternatingBackground(getThemeValue<bool>(theme, "showAlternatingBackground", isAlternatingBackgroundEnabled()));
setBackgroundColor2(getThemeValue<Color>(theme, "backgroundColor2", getBackgroundColor2()));
setSeparatorLineColor(getThemeValue<Color>(theme, "separatorLineColor", getSeparatorLineColor()));
enableShowSeparatorLine(getThemeValue<bool>(theme, "showSeparatorLine", isShowSeparatorLineEnabled()));
setLinePadding(getThemeValue<Rectangle>(theme, "linePadding", getLinePadding()));
setLineSelectionColor(getThemeValue<Color>(theme, "selectionColor", getLineSelectionColor()));
setLineSelectionTextColor(getThemeValue<Color>(theme, "selectionTextColor", getLineSelectionTextColor()));
setIconSpacing(getThemeValue<f32>(theme, "iconSpacing", getIconSpacing()));
setIconInset(getThemeValue<f32>(theme, "iconInset", getIconInset()));
enableIcons(getThemeValue<bool>(theme, "showIcons", isIconsEnabled()));
setIconTintColor(getThemeValue<Color>(theme, "iconTintColor", getIconTintColor()));
setIndentWidth(getThemeValue<f32>(theme, "indentWidth", getIndentWidth()));
setChevronColor(getThemeValue<Color>(theme, "chevronColor", getChevronColor()));
setChevronInset(getThemeValue<f32>(theme, "chevronInset", getChevronInset()));
setBranchLineColor(getThemeValue<Color>(theme, "branchLineColor", getBranchLineColor()));
enableBranchLines(getThemeValue<bool>(theme, "showBranchLines", isBranchLinesEnabled()));
setBranchLineThickness(getThemeValue<f32>(theme, "branchLineThickness", getBranchLineThickness()));
}
void TreeView::onDraw(ogfx::BasicRenderer2D& gfx)
{
const auto& bounds = getGlobalBounds();
const auto& content = getContentExtents();
const f32 scrollY = -getScrollOffset().y;
const f32 visibleH = getContentBounds().h;
const f32 visibleStart = scrollY;
const f32 visibleEnd = scrollY + visibleH;
Color textColor;
// Walk forward through visible items only, advancing y for each one we draw.
f32 y = 0;
i32 startIdx = 0;
u32 visibleRow = 0; // counts only items whose row is actually drawn
for (auto& itemPtr : m_list)
{
auto& item = *itemPtr;
if (!item.isValid() || !item.isVisible()) { startIdx++; continue; }
f32 itemH = item.getDimensions().y;
if (y + itemH > visibleStart) break;
y += itemH;
visibleRow++;
startIdx++;
}
for (i32 i = startIdx; i < (i32)m_list.size(); i++)
{
auto& item = *m_list[i];
if (!item.isValid()) continue;
if (!item.isVisible()) continue;
f32 itemH = item.getDimensions().y;
if (y > visibleEnd) break;
f32 lineW = content.w;
if (content.w < bounds.w)
lineW = bounds.w;
auto pad = getLinePadding();
Rectangle lineRect = { Vec2 { bounds.x, bounds.y + y + pad.right() } + getScrollOffset(), { lineW, itemH } };
if (item.isSelected())
{
textColor = getLineSelectionTextColor();
gfx.fillRect(lineRect, getLineSelectionColor());
}
else
{
textColor = getTextColor();
if (isAlternatingBackgroundEnabled())
{
bool lineColorToggle = visibleRow % 2 == 0;
gfx.fillRect(lineRect, (lineColorToggle ? getBackgroundColor() : getBackgroundColor2()));
}
}
// Branch lines (drawn under everything else for this row).
if (isBranchLinesEnabled())
render_branch_lines(gfx, item, lineRect);
// The chevron occupies one slot at column == depth.
// Text/icon start one slot further right (column == depth + 1).
const f32 indent = getIndentWidth();
const f32 chevronX = bounds.x + item.getDepth() * indent + getScrollOffset().x;
const f32 contentLeftX = chevronX + indent; // where icon (if any) or text starts
if (item.hasChildren())
{
Rectangle chevronBounds {
chevronX,
bounds.y + y + pad.h + getScrollOffset().y,
indent,
itemH
};
if (m_chevronDrawCallback)
{
gfx.pushClippingRect(chevronBounds, true);
m_chevronDrawCallback(*this, item, chevronBounds, gfx);
gfx.popClippingRect();
}
else
{
render_chevron(gfx, item, chevronBounds);
}
}
f32 textX = contentLeftX - bounds.x - getScrollOffset().x;
if (item.isIconEnabled() && isIconsEnabled())
{
gfx.drawAnimation(item.getIcon(),
Vec2 { contentLeftX + getIconInset(), bounds.y + getIconInset() + y + pad.h + getScrollOffset().y },
{ itemH - (getIconInset() * 2), itemH - (getIconInset() * 2) },
getIconTintColor());
textX += itemH + getIconSpacing();
}
if (isShowSeparatorLineEnabled())
gfx.drawLine({ Vec2 { bounds.x, bounds.y + y + itemH + pad.h } + getScrollOffset(), Vec2 { bounds.x + lineW, bounds.y + y + itemH + pad.h } + getScrollOffset() }, getSeparatorLineColor(), 1);
gfx.drawVCenteredString(item, lineRect + Rectangle { textX, 0, -(textX * 2), 0 }, textColor, getFontSize());
y += itemH;
visibleRow++;
}
}
void TreeView::onUpdate(void)
{
ScrollableWidget::onUpdate();
const f32 scrollY = -getScrollOffset().y;
const f32 visibleH = getContentBounds().h;
const f32 visibleStart = scrollY;
const f32 visibleEnd = scrollY + visibleH;
f32 y = 0;
i32 startIdx = 0;
for (auto& itemPtr : m_list)
{
auto& item = *itemPtr;
if (!item.isValid() || !item.isVisible()) { startIdx++; continue; }
f32 itemH = item.getDimensions().y;
if (y + itemH > visibleStart) break;
y += itemH;
startIdx++;
}
for (i32 i = startIdx; i < (i32)m_list.size(); i++)
{
auto& item = *m_list[i];
if (!item.isValid()) continue;
if (!item.isVisible()) continue;
f32 itemH = item.getDimensions().y;
if (y > visibleEnd) break;
if (item.isIconEnabled())
item.getIcon().update();
y += itemH;
}
}
void TreeView::afterDraw(ogfx::BasicRenderer2D& gfx)
{
drawScrollbars(gfx);
}
void TreeView::onMouseReleased(const Event& event)
{
if (!isMouseInside())
return;
if (event.mouse->button != ogfx::MouseEventData::eButton::Left)
return;
if (isMouseInsideAnyScrollbar())
return;
const Vec2 mousePos { event.mouse->position_x, event.mouse->position_y };
const Vec2 origin = getGlobalBounds().getPosition();
const Vec2 scroll = getScrollOffset();
const f32 localY = mousePos.y - origin.y - scroll.y;
const f32 localX = mousePos.x - origin.x - scroll.x;
const f32 indent = getIndentWidth();
f32 y = 0;
for (auto& itemPtr : m_list)
{
auto& item = *itemPtr;
if (!item.isValid()) continue;
if (!item.isVisible()) continue;
const f32 itemH = item.getDimensions().y;
if (localY >= y && localY < y + itemH)
{
// Chevron hit-test takes priority over selection.
if (item.hasChildren())
{
const f32 chevronXLocal = item.getDepth() * indent;
if (localX >= chevronXLocal && localX < chevronXLocal + indent)
{
item.toggleExpanded();
event.handle();
return;
}
}
bool wasSelected = item.isSelected();
item.set_selected(m_selectedList);
if (!wasSelected && callback_onSelectionChanged)
callback_onSelectionChanged(m_selectedList);
event.handle();
break;
}
y += itemH;
}
}
Rectangle TreeView::getContentExtents(void) const
{
if (!m_extentsDirty)
return m_cachedExtents;
f32 maxX = 0, maxY = 0;
const f32 indent = m_indentWidth;
for (auto& itemPtr : m_list)
{
auto& item = *itemPtr;
if (!item.isValid()) continue;
if (!item.isVisible()) continue;
Vec2 size = item.getDimensions();
// Width of a row is its text/icon width plus the indent + chevron-slot space to its left.
f32 rowW = size.x + (item.getDepth() + 1) * indent;
maxX = std::max(maxX, rowW);
maxY += size.y;
}
m_cachedExtents = { 0, 0, maxX, maxY };
m_extentsDirty = false;
return m_cachedExtents;
}
TreeView::Item& TreeView::getLine(const String& text)
{
for (auto& itemPtr : m_list)
if (itemPtr->getText() == text)
return *itemPtr;
return InvalidItem;
}
TreeView::Item& TreeView::getLine(u32 index)
{
if (index < m_list.size())
return *m_list[index];
return InvalidItem;
}
TreeView::Item& TreeView::addRoot(const String& text)
{
// Flip the previously-last root's flag, then become the new last root.
if (m_lastRoot) m_lastRoot->m_isLastChild = false;
auto item = std::make_unique<Item>(*this);
item->setText(text);
item->m_parentItem = nullptr;
item->m_depth = 0;
item->m_isLastChild = true;
Item* itemPtr = item.get();
m_list.push_back(std::move(item));
m_lastRoot = itemPtr;
if (m_selectedList.empty())
itemPtr->set_selected(m_selectedList);
m_extentsDirty = true;
return *itemPtr;
}
TreeView::Item& TreeView::addRoot(const String& text, const Icon& icon)
{
auto& item = addRoot(text);
item.setIcon(icon);
return item;
}
TreeView::Item& TreeView::addChild(Item& parentItem, const String& text)
{
// Locate the parent's slot in m_list (needed for the insertion offset).
i32 parentIdx = -1;
for (i32 i = 0; i < (i32)m_list.size(); i++)
{
if (m_list[i].get() == &parentItem) { parentIdx = i; break; }
}
if (parentIdx < 0)
return InvalidItem;
// Insert right after the parent's last descendant so display order == deque order.
u32 insertIdx = get_last_descendant_index((u32)parentIdx) + 1;
// The previously-last direct child (if any) loses its last-child flag.
if (parentItem.m_lastChild)
parentItem.m_lastChild->m_isLastChild = false;
auto newItem = std::make_unique<Item>(*this);
newItem->setText(text);
newItem->m_parentItem = &parentItem;
newItem->m_depth = parentItem.m_depth + 1;
newItem->m_isLastChild = true;
Item* newPtr = newItem.get();
// std::deque::insert moves the pointer slots around, but the Item objects
// themselves are heap-allocated and stay put. Pointers stored on other Items
// (m_parentItem, m_lastChild) and in m_selectedList remain valid.
m_list.insert(m_list.begin() + insertIdx, std::move(newItem));
parentItem.m_hasChildren = true;
parentItem.m_lastChild = newPtr;
m_extentsDirty = true;
return *newPtr;
}
TreeView::Item& TreeView::addChild(Item& parentItem, const String& text, const Icon& icon)
{
auto& item = addChild(parentItem, text);
item.setIcon(icon);
return item;
}
u32 TreeView::get_last_descendant_index(u32 itemIndex) const
{
// Walk forward while items are deeper than the anchor; the last such index is the answer.
if (itemIndex >= m_list.size()) return itemIndex;
const u32 anchorDepth = m_list[itemIndex]->m_depth;
u32 last = itemIndex;
for (u32 i = itemIndex + 1; i < m_list.size(); i++)
{
if (m_list[i]->m_depth > anchorDepth)
last = i;
else
break;
}
return last;
}
void TreeView::recompute_has_children_and_last_sibling(Item* parentItem)
{
// Recomputes m_hasChildren on parentItem (or m_lastRoot if null) and
// m_isLastChild flags + cached last-child pointer for the children of that parent.
Item* lastSeen = nullptr;
bool any = false;
for (auto& itPtr : m_list)
{
auto& it = *itPtr;
if (!it.isValid()) continue;
if (it.m_parentItem == parentItem)
{
it.m_isLastChild = false;
lastSeen = &it;
any = true;
}
}
if (lastSeen) lastSeen->m_isLastChild = true;
if (parentItem)
{
parentItem->m_hasChildren = any;
parentItem->m_lastChild = lastSeen;
}
else
{
m_lastRoot = lastSeen;
}
}
bool TreeView::removeLine(const String& text)
{
i32 idx = -1;
for (i32 i = 0; i < (i32)m_list.size(); i++)
{
if (m_list[i]->getText() == text) { idx = i; break; }
}
if (idx < 0) return false;
return removeLine((u32)idx);
}
bool TreeView::removeLine(u32 index)
{
if (!hasLine(index))
return false;
Item* removedParent = m_list[index]->m_parentItem;
// Remove the item plus its entire descendant range.
u32 lastDesc = get_last_descendant_index(index);
// Drop any selection pointers that fall inside the removed range.
for (u32 i = index; i <= lastDesc; i++)
{
Item* p = m_list[i].get();
STDVEC_REMOVE(m_selectedList, p);
}
m_list.erase(m_list.begin() + index, m_list.begin() + lastDesc + 1);
// Fix up parent flags / sibling last-child markers.
recompute_has_children_and_last_sibling(removedParent);
m_extentsDirty = true;
return true;
}
bool TreeView::hasLine(const String& text)
{
for (auto& itemPtr : m_list)
if (itemPtr->getText() == text)
return true;
return false;
}
bool TreeView::hasLine(u32 index)
{
return m_list.size() > index;
}
void TreeView::expandAll(void)
{
for (auto& itemPtr : m_list)
{
if (itemPtr->m_hasChildren && !itemPtr->m_expanded)
itemPtr->m_expanded = true;
}
m_extentsDirty = true;
}
void TreeView::collapseAll(void)
{
for (auto& itemPtr : m_list)
{
if (itemPtr->m_hasChildren && itemPtr->m_expanded)
itemPtr->m_expanded = false;
}
m_extentsDirty = true;
}
void TreeView::render_chevron(ogfx::BasicRenderer2D& gfx, const Item& item, const Rectangle& bounds)
{
// Default chevron: a small triangle. Right-pointing when collapsed, down-pointing when expanded.
const f32 inset = getChevronInset();
const f32 cx = bounds.x + bounds.w * 0.5f;
const f32 cy = bounds.y + bounds.h * 0.5f;
// Half-extents inside the slot.
const f32 hx = std::max(2.0f, bounds.w * 0.5f - inset);
const f32 hy = std::max(2.0f, bounds.h * 0.25f);
Vec2 p0, p1, p2;
if (item.isExpanded())
{
// Down-pointing: ▼
p0 = { cx - hx, cy - hy };
p1 = { cx + hx, cy - hy };
p2 = { cx, cy + hy };
}
else
{
// Right-pointing: ▶
p0 = { cx - hy, cy - hx };
p1 = { cx - hy, cy + hx };
p2 = { cx + hy, cy };
}
gfx.fillTriangle(p0, p1, p2, getChevronColor());
}
void TreeView::render_branch_lines(ogfx::BasicRenderer2D& gfx, const Item& item, const Rectangle& rowRect)
{
const f32 indent = getIndentWidth();
const f32 thick = getBranchLineThickness();
const Color col = getBranchLineColor();
const u32 d = item.getDepth();
if (d == 0) return; // roots have no parent line / no elbow
// Collect ancestor "isLastChild" flags. ancestorIsLast[0] = immediate parent (depth d-1),
// ancestorIsLast[k] = ancestor at depth (d-1-k).
std::vector<bool> ancestorIsLast;
ancestorIsLast.reserve(d);
const Item* p = item.getParentItem();
while (p != nullptr)
{
ancestorIsLast.push_back(p->isLastChild());
p = p->getParentItem();
}
// For each "passing-through" column a in [0, d-1): draw a vertical line spanning the
// full row IF the ancestor at depth (a+1) is NOT the last child of its parent.
// (Column 'a' carries the sibling line for depth a+1; if the depth-a+1 ancestor has
// no later sibling, the line ended above us and shouldn't be drawn here.)
// The column for our own row (a == d-1) is handled by the elbow logic below.
for (u32 a = 0; a + 1 < d; a++)
{
const u32 ancestorDepth = a + 1;
const u32 ancestorIdx = d - 1 - ancestorDepth; // index into ancestorIsLast
if (ancestorIsLast[ancestorIdx]) continue; // no continuing line in this column
const f32 colX = rowRect.x + a * indent + indent * 0.5f;
gfx.drawLine({ Vec2 { colX, rowRect.y }, Vec2 { colX, rowRect.y + rowRect.h } }, col, thick);
}
// Elbow in column d-1 connecting our row to the chevron slot at column d.
const f32 colX = rowRect.x + (d - 1) * indent + indent * 0.5f;
const f32 midY = rowRect.y + rowRect.h * 0.5f;
// If this item has no chevron drawn, extend the horizontal arm all the way to where
// the icon/text starts (the right edge of the chevron slot, i.e. column d+1's left).
// Otherwise stop at the left edge of the chevron slot so the chevron sits clean.
const f32 armEndX = item.hasChildren()
? rowRect.x + d * indent
: rowRect.x + (d + 1) * indent;
// Vertical: top -> middle of the row (always)
gfx.drawLine({ Vec2 { colX, rowRect.y }, Vec2 { colX, midY } }, col, thick);
// Vertical: middle -> bottom (only if there is a sibling below us at this depth)
if (!item.isLastChild())
gfx.drawLine({ Vec2 { colX, midY }, Vec2 { colX, rowRect.y + rowRect.h } }, col, thick);
// Horizontal arm
gfx.drawLine({ Vec2 { colX, midY }, Vec2 { armEndX, midY } }, col, thick);
}
}
}

View file

@ -0,0 +1,168 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/widgets/Widget.hpp>
#include <ogfx/gui/widgets/Scrollbar.hpp>
#include <ogfx/utils/Animation.hpp>
#include <deque>
#include <memory>
namespace ogfx
{
namespace gui
{
class TreeView : public ScrollableWidget
{
public: class Item : public ostd::I_stringeable
{
public:
inline Item(TreeView& parent) { m_treeView = &parent; }
void setText(const String& text);
void setIcon(const Icon& icon);
inline Animation& getIcon(void) { return m_icon; }
inline String toString(void) const override { return m_text; }
inline bool isValid(void) const { return m_treeView != nullptr && m_text.new_trim() != ""; }
inline Vec2 getDimensions(void) const { return m_dimensions; }
inline String getText(void) const { return m_text; }
inline bool isSelected(void) const { return m_selected; }
inline bool isExpanded(void) const { return m_expanded; }
inline bool hasChildren(void) const { return m_hasChildren; }
inline u32 getDepth(void) const { return m_depth; }
inline Item* getParentItem(void) { return m_parentItem; }
inline const Item* getParentItem(void) const { return m_parentItem; }
inline bool isLastChild(void) const { return m_isLastChild; }
void setExpanded(bool expanded);
inline void toggleExpanded(void) { setExpanded(!m_expanded); }
bool isVisible(void) const;
stdvec<String> getFullPath(void) const;
OSTD_BOOL_PARAM_GETSET_E(Icon, m_showIcon);
private:
void update_dimensions(void);
void set_selected(stdvec<Item*>& selectionList);
void add_selected(stdvec<Item*>& selectionList);
void remove_selected(stdvec<Item*>& selectionList);
private:
Vec2 m_dimensions { 0, 0 };
bool m_selected { false };
TreeView* m_treeView { nullptr };
String m_text { "" };
Animation m_icon;
bool m_showIcon { false };
// Tree state
Item* m_parentItem { nullptr };
Item* m_lastChild { nullptr };
u32 m_depth { 0 };
bool m_expanded { true };
bool m_hasChildren { false };
bool m_isLastChild { true };
friend class TreeView;
};
public:
using ChevronDrawCallback = std::function<void(TreeView& sender, const Item& item, const Rectangle& bounds, BasicRenderer2D& gfx)>;
inline TreeView(Window& window) : ScrollableWidget(window) { create(); }
TreeView& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void onUpdate(void) override;
void afterDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseReleased(const Event& event) override;
Rectangle getContentExtents(void) const override;
Item& getLine(const String& text);
Item& getLine(u32 index);
Item& addRoot(const String& text);
Item& addRoot(const String& text, const Icon& icon);
Item& addChild(Item& parentItem, const String& text);
Item& addChild(Item& parentItem, const String& text, const Icon& icon);
bool removeLine(const String& text);
bool removeLine(u32 index);
bool hasLine(const String& text);
bool hasLine(u32 index);
void expandAll(void);
void collapseAll(void);
inline stdvec<Item*>& getSelection(void) { return m_selectedList; }
inline void setSelectionChangedCallback(std::function<void(stdvec<Item*>& selection)> callback) { callback_onSelectionChanged = std::move(callback); }
inline void setChevronDrawCallback(ChevronDrawCallback cb) { m_chevronDrawCallback = std::move(cb); }
OSTD_PARAM_GETSET(Color, BackgroundColor2, m_backgroundColor2);
OSTD_BOOL_PARAM_GETSET_E(AlternatingBackground, m_showAlternatingBackground);
OSTD_PARAM_GETSET(Color, SeparatorLineColor, m_lineColor);
OSTD_BOOL_PARAM_GETSET_E(ShowSeparatorLine, m_showLine);
OSTD_BOOL_PARAM_GETSET_E(Icons, m_showIcons);
OSTD_PARAM_GETSET(Rectangle, LinePadding, m_linePadding);
OSTD_PARAM_GETSET(Color, LineSelectionColor, m_selectionColor);
OSTD_PARAM_GETSET(Color, LineSelectionTextColor, m_selectionTextColor);
OSTD_PARAM_GETSET(Color, IconTintColor, m_iconTintColor);
OSTD_PARAM_GETSET(f32, IconSpacing, m_iconSpacing);
OSTD_PARAM_GETSET(f32, IconInset, m_iconInset);
OSTD_PARAM_GETSET(f32, IndentWidth, m_indentWidth);
OSTD_PARAM_GETSET(Color, ChevronColor, m_chevronColor);
OSTD_PARAM_GETSET(f32, ChevronInset, m_chevronInset);
OSTD_PARAM_GETSET(Color, BranchLineColor, m_branchLineColor);
OSTD_BOOL_PARAM_GETSET_E(BranchLines, m_showBranchLines);
OSTD_PARAM_GETSET(f32, BranchLineThickness, m_branchLineThickness);
private:
void render_chevron(ogfx::BasicRenderer2D& gfx, const Item& item, const Rectangle& bounds);
void render_branch_lines(ogfx::BasicRenderer2D& gfx, const Item& item, const Rectangle& rowRect);
u32 get_last_descendant_index(u32 itemIndex) const;
void recompute_has_children_and_last_sibling(Item* parentItem);
private:
Item InvalidItem { *this };
std::deque<std::unique_ptr<Item>> m_list;
stdvec<Item*> m_selectedList;
std::function<void(stdvec<Item*>& selection)> callback_onSelectionChanged { nullptr };
ChevronDrawCallback m_chevronDrawCallback { nullptr };
mutable Rectangle m_cachedExtents { 0, 0, 0, 0 };
mutable bool m_extentsDirty { true };
Item* m_lastRoot { nullptr };
bool m_showIcons { false };
f32 m_iconSpacing { 0 };
f32 m_iconInset { 4 };
Color m_lineColor { 40, 40, 40 };
bool m_showLine { true };
Rectangle m_linePadding { 5, 5, 20, 0 };
Color m_selectionColor { Colors::Crimson };
Color m_selectionTextColor{ Colors::White };
Color m_iconTintColor { Colors::White };
Color m_backgroundColor2 { Colors::Black };
bool m_showAlternatingBackground { true };
f32 m_indentWidth { 16 };
Color m_chevronColor { Colors::White };
f32 m_chevronInset { 4 };
Color m_branchLineColor { 80, 80, 80 };
bool m_showBranchLines { true };
f32 m_branchLineThickness { 1 };
};
}
}

View file

@ -0,0 +1,540 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Widget.hpp"
#include "gui/Events.hpp"
#include "io/Memory.hpp"
#include "../..//render/BasicRenderer.hpp"
#include "../Window.hpp"
namespace ogfx
{
namespace gui
{
ostd::BaseObject* Widget::s_dragAndDropData { nullptr };
bool Widget::s_hasDragAndDropData { false };
Widget::Widget(const Rectangle& bounds, Window& window) : Rectangle(bounds), m_widgets(window, *this)
{
m_window = &window;
}
Widget::~Widget(void)
{
if (m_window && m_tabIndex >= 0)
m_window->getFocusManager().unregisterTabIndex(*this);
auto& fm = m_window->getFocusManager();
if (fm.getFocused() == this)
fm.clearFocus(); // you'd add this method
}
bool Widget::addWidget(Widget& child, const Vec2& position, bool __skip_callback)
{
if (!m_allowChildren)
return false;
if (position.x != 0 || position.y != 0)
child.setPosition(position);
child.reloadTheme();
bool result = m_widgets.addWidget(child);
if (!result) return false;
if (!__skip_callback)
onWidgetAdded(child);
if (m_layout && !__skip_callback)
relayout();
return result;
}
bool Widget::removeWidget(Widget& child)
{
bool result = m_widgets.removeWidget(child);
if (result && m_layout)
relayout();
return result;
}
Vec2 Widget::getGlobalPosition(void) const
{
Vec2 glob = getPosition();
if (!m_rootChild && m_parent != nullptr)
{
glob += m_parent->getGlobalPosition();
if (!isIgnoreScrollEnabled())
{
glob += m_parent->getPadding().getPosition();
glob += m_parent->getContentOffset();
glob += m_parent->getScrollOffset();
}
}
glob += m_margin.getPosition();
return glob;
}
Vec2 Widget::getGlobalContentPosition(void) const
{
return getGlobalPosition() + getContentBounds().getPosition();
}
Rectangle Widget::getGlobalBounds(void) const
{
return { getGlobalPosition(), getSize() + (m_margin.getSize() * 2) };
}
Rectangle Widget::getContentBounds(void) const
{
auto pad = getPadding();
return { Vec2 { m_contentOffset.x, m_contentOffset.y } + pad.getPosition(), (getSize() - m_contentOffset - (pad.getSize() * 2)) };
}
Rectangle Widget::getPureContentBounds(void) const
{
return { { m_contentOffset.x, m_contentOffset.y }, (getSize() - m_contentOffset) };
}
Rectangle Widget::getGlobalContentBounds(void) const
{
return { getGlobalContentPosition(), getContentBounds().getSize() };
}
Rectangle Widget::getGlobalPureContentBounds(void) const
{
return { getGlobalPosition() + getContentOffset(), getPureContentBounds().getSize() };
}
Rectangle Widget::getContentExtents(void) const
{
// If a layout owns this widget, prefer its measure() — it knows
// the real desired extent (e.g. a vertical box with 50 items
// reports 50*itemHeight even if only 5 are visible).
if (m_layout)
{
Vec2 desired = m_layout->measure(*this);
return { 0, 0, desired.x, desired.y };
}
f32 maxX = 0, maxY = 0;
for (auto* child : m_widgets.getWidgets())
{
if (!child || child->isInvalid()) continue;
if (child->isIgnoreScrollEnabled()) continue;
if (!child->isVisible()) continue;
Vec2 localPos = getPadding().getPosition() + child->getPosition() + child->getMargin().getPosition() + child->getContentBounds().getPosition();
maxX = std::max(maxX, localPos.x + child->getw() + child->getMargin().w);
maxY = std::max(maxY, localPos.y + child->geth() + child->getMargin().h);
}
return { 0, 0, maxX, maxY };
}
bool Widget::contains(Vec2 p, bool includeBounds) const
{
return Rectangle(getGlobalPosition(), getSize()).contains(p, includeBounds);
}
void Widget::enable(bool enable)
{
m_enabled = enable;
setThemeQualifier("disabled", !enable);
}
void Widget::addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value)
{
m_themeOverrides.push_back({ fullKey, value });
}
void Widget::reloadTheme(bool propagate)
{
if (getWindow().theme() == nullptr)
return;
if (!isThemingEnabled())
return;
auto& theme = *getWindow().theme();
applyTheme(theme);
ostd::Stylesheet& const_cast_theme = const_cast<ostd::Stylesheet&>(theme);
struct tBackup {
const ostd::Stylesheet::TypeVariant* ptr;
ostd::Stylesheet::TypeVariant val;
String key;
};
stdvec<tBackup> backup;
for (auto& rule : m_themeOverrides)
{
auto currentValuePtr = theme.getFull(rule.fullKey);
ostd::Stylesheet::TypeVariant currentValue;
if (currentValuePtr)
currentValue = *currentValuePtr;
backup.push_back({ currentValuePtr, currentValue, rule.fullKey });
const_cast_theme.setFull(rule.fullKey, rule.value);
}
__applyTheme(const_cast_theme, propagate);
for (auto&[ptr, val, key] : backup)
{
if (ptr == nullptr)
const_cast_theme.removeRule(key);
else
const_cast_theme.setFull(key, val);
}
}
void Widget::setThemeQualifier(const String& qualifier, bool value)
{
for (auto& [name, state] : m_qualifierList)
{
if (name == qualifier)
{
if (state == value) return;
state = value;
reloadTheme();
return;
}
}
}
bool Widget::getThemeQualifier(const String& qualifier) const
{
for (auto& [name, state] : m_qualifierList)
{
if (name == qualifier)
return state;
}
return false;
}
bool Widget::addThemeID(const String& id)
{
if (STDVEC_CONTAINS(m_themeIDList, id))
return false;
m_themeIDList.push_back(id);
return true;
}
bool Widget::removeThemeID(const String& id)
{
if (!STDVEC_CONTAINS(m_themeIDList, id))
return false;
STDVEC_REMOVE(m_themeIDList, id);
return true;
}
void Widget::setVisible(bool v)
{
m_visible = v;
}
bool Widget::setTabIndex(i32 index)
{
if (getWindow().getFocusManager().registerTabIndex(*this, index))
{
m_tabIndex = index;
return true;
}
return false;
}
void Widget::setLayout(std::unique_ptr<Layout> layout)
{
m_layout = std::move(layout);
if (m_layout)
relayout();
}
void Widget::relayout(void)
{
if (!m_layout) return;
m_layout->arrange(*this);
// After our children's bounds changed, give *their* layouts a chance
// to re-run (a horizontal layout containing a vertical layout, etc.).
for (auto* c : m_widgets.getWidgets())
{
if (!c || c->isInvalid()) continue;
if (c->hasLayout())
c->relayout();
}
}
void Widget::setCallback(eCallback type, EventCallback callback)
{
switch (type)
{
case eCallback::MousePressed: callback_onMousePressed = std::move(callback); break;
case eCallback::MouseReleased: callback_onMouseReleased = std::move(callback); break;
case eCallback::MouseMoved: callback_onMouseMoved = std::move(callback); break;
case eCallback::MouseScrolled: callback_onMouseScrolled = std::move(callback); break;
case eCallback::DragAndDrop: callback_onDragAndDrop = std::move(callback); break;
case eCallback::MouseEntered: callback_onMouseEntered = std::move(callback); break;
case eCallback::MouseExited: callback_onMouseExited = std::move(callback); break;
case eCallback::MouseDragged: callback_onMouseDragged = std::move(callback); break;
case eCallback::KeyPressed: callback_onKeyPressed = std::move(callback); break;
case eCallback::KeyReleased: callback_onKeyReleased = std::move(callback); break;
case eCallback::TextEntered: callback_onTextEntered = std::move(callback); break;
case eCallback::FocusGained: callback_onFocusGained = std::move(callback); break;
case eCallback::FocusLost: callback_onFocusLost = std::move(callback); break;
case eCallback::WindowClosed: callback_onWindowClosed = std::move(callback); break;
case eCallback::WindowResized: callback_onWindowResized = std::move(callback); break;
case eCallback::WindowFocused: callback_onWindowFocused = std::move(callback); break;
case eCallback::WindowFocusLost: callback_onWindowFocusLost = std::move(callback); break;
case eCallback::ActionPerformed: callback_onActionPerformed = std::move(callback); break;
default: break;
}
}
void Widget::__draw(ogfx::BasicRenderer2D& gfx)
{
if (!isVisible())
return;
beforeDraw(gfx);
if (m_useBackgroundGradient)
gfx.fillGradientRect({ getGlobalPosition(), getSize() }, m_backgroundGradient);
else if (m_showBackground)
gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius);
onDraw(gfx);
// gfx.fillRect(getGlobalPureContentBounds(), { 0, 255, 0, 120 });
const bool needsContentClip = !m_rootChild;
if (needsContentClip)
gfx.pushClippingRect(getGlobalPureContentBounds(), true);
if (hasChildren())
m_widgets.draw(gfx);
if (needsContentClip)
gfx.popClippingRect();
if (m_showBorder)
gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth);
afterDraw(gfx);
if (isFocused() && isFocusBorderEnabled())
gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_focusBorderColor, m_borderRadius, m_focusBorderWidth);
}
void Widget::__update(void)
{
onUpdate();
if (hasChildren())
m_widgets.update();
}
void Widget::__onMousePressed(const Event& event)
{
if (event.isHandled() || !isMousePressedEventEnabled()) return;
setThemeQualifier("pressed");
if (hasChildren())
m_widgets.onMousePressed(event);
if (!event.isHandled())
{
if (callback_onMousePressed)
callback_onMousePressed(event);
onMousePressed(event);
m_pressedButton = event.mouse->button;
}
}
void Widget::__onMouseReleased(const Event& event)
{
if (event.isHandled() || !isMouseReleasedEventEnabled()) return;
setThemeQualifier("pressed", false);
m_pressedButton = ogfx::MouseEventData::eButton::None;
if (hasChildren())
m_widgets.onMouseReleased(event);
if (!event.isHandled())
{
if (callback_onMouseReleased)
callback_onMouseReleased(event);
onMouseReleased(event);
}
}
void Widget::__onDragAndDrop(const Event& event)
{
if (callback_onDragAndDrop)
callback_onDragAndDrop(event);
onDragAndDrop(event);
}
void Widget::__onMouseMoved(const Event& event)
{
m_mouseInside = contains(event.mouse->position_x, event.mouse->position_y);
if (event.isHandled() || !isMouseMovedEventEnabled()) return;
if (hasChildren())
m_widgets.onMouseMoved(event);
if (!event.isHandled())
{
if (callback_onMouseMoved)
callback_onMouseMoved(event);
onMouseMoved(event);
}
if (isTooltipEnabled() && isMouseInside())
getWindow().restartTooltipTimer();
}
void Widget::__onMouseScrolled(const Event& event)
{
if (event.isHandled() || !isMouseScrolledEventEnabled()) return;
if (hasChildren())
m_widgets.onMouseScrolled(event);
if (!event.isHandled())
{
if (callback_onMouseScrolled)
callback_onMouseScrolled(event);
onMouseScrolled(event);
}
}
void Widget::__onMouseEntered(const Event& event)
{
if (event.isHandled() || !isMouseEnteredEventEnabled()) return;
setThemeQualifier("hover");
if (callback_onMouseEntered)
callback_onMouseEntered(event);
onMouseEntered(event);
if (isTooltipEnabled())
getWindow().startTooltipTimer(getTooltipText());
}
void Widget::__onMouseExited(const Event& event)
{
if (event.isHandled() || !isMouseExitedEventEnabled()) return;
setThemeQualifier("hover", false);
if (callback_onMouseExited)
callback_onMouseExited(event);
onMouseExited(event);
if (isTooltipEnabled())
getWindow().stopTooltipTimer();
}
void Widget::__onMouseDragged(const Event& event)
{
m_mouseInside = contains(event.mouse->position_x, event.mouse->position_y);
if (event.isHandled() || !isMouseDraggedEventEnabled()) return;
if (hasChildren())
m_widgets.onMouseDragged(event);
if (!event.isHandled())
{
if (callback_onMouseDragged)
callback_onMouseDragged(event);
onMouseDragged(event);
}
if (isTooltipEnabled() && isMouseInside())
getWindow().restartTooltipTimer();
}
void Widget::__onKeyPressed(const Event& event)
{
if (event.isHandled() || !isKeyPressedEventEnabled()) return;
if (callback_onKeyPressed)
callback_onKeyPressed(event);
onKeyPressed(event);
}
void Widget::__onKeyReleased(const Event& event)
{
if (event.isHandled() || !isKeyReleasedEventEnabled()) return;
m_pressedButton = ogfx::MouseEventData::eButton::None;
if (callback_onKeyReleased)
callback_onKeyReleased(event);
onKeyReleased(event);
}
void Widget::__onTextEntered(const Event& event)
{
if (event.isHandled() || !isTextEnteredEventEnabled()) return;
if (callback_onTextEntered)
callback_onTextEntered(event);
onTextEntered(event);
}
void Widget::__onWindowClosed(const Event& event)
{
if (hasChildren())
m_widgets.onWindowClosed(event);
if (!event.isHandled())
{
if (callback_onWindowClosed)
callback_onWindowClosed(event);
onWindowClosed(event);
}
}
void Widget::__onWindowResized(const Event& event)
{
if (hasChildren())
m_widgets.onWindowResized(event);
if (!event.isHandled())
{
if (callback_onWindowResized)
callback_onWindowResized(event);
onWindowResized(event);
}
if (m_layout)
relayout();
}
void Widget::__onWindowFocused(const Event& event)
{
if (hasChildren())
m_widgets.onWindowFocused(event);
if (!event.isHandled())
{
if (callback_onWindowFocused)
callback_onWindowFocused(event);
onWindowFocused(event);
}
}
void Widget::__onWindowFocusLost(const Event& event)
{
if (hasChildren())
m_widgets.onWindowFocusLost(event);
if (!event.isHandled())
{
if (callback_onWindowFocusLost)
callback_onWindowFocusLost(event);
onWindowFocusLost(event);
}
}
void Widget::__applyTheme(const ostd::Stylesheet& theme, bool propagate)
{
if (propagate && hasChildren())
m_widgets.onThemeApplied(theme);
apply_common_theme_values(theme);
applyTheme(theme);
}
void Widget::apply_common_theme_values(const ostd::Stylesheet& theme)
{
setTextColor(getThemeValue<Color>(theme, "textColor", m_textColor));
setBackgroundColor(getThemeValue<Color>(theme, "backgroundColor", m_backgroundColor));
setFontSize(getThemeValue<i32>(theme, "fontSize", m_fontSize));
setBorderRadius(getThemeValue<i32>(theme, "borderRadius", m_borderRadius));
setBorderWidth(getThemeValue<i32>(theme, "borderWidth", m_borderWidth));
enableBorder(getThemeValue<bool>(theme, "showBorder", m_showBorder));
setBorderColor(getThemeValue<Color>(theme, "borderColor", m_borderColor));
enableFocusBorder(getThemeValue<bool>(theme, "showFocusBorder", m_showFocusBorder));
setFocusBorderColor(getThemeValue<Color>(theme, "focusBorderColor", m_focusBorderColor));
setFocusBorderWidth(getThemeValue<i32>(theme, "focusBorderWidth", m_focusBorderWidth));
enableBackground(getThemeValue<bool>(theme, "showBackground", m_showBackground));
setPadding(getThemeValue<Rectangle>(theme, "padding", m_padding));
setMargin(getThemeValue<Rectangle>(theme, "margin", m_margin));
enableBackgroundGradient(getThemeValue<bool>(theme, "useBackgroundGradient", m_useBackgroundGradient));
setBackgroundGradient(getThemeValue<ColorGradient>(theme, "backgroundGradient", m_backgroundGradient));
if (isBackgroundGradientEnabled() && isBackgroundEnabled())
enableBackground(false);
}
}
}

View file

@ -0,0 +1,395 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/gui/Events.hpp>
#include <ogfx/gui/WidgetManager.hpp>
#include <ostd/data/BaseObject.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/io/Stylesheet.hpp>
#include <ostd/utils/Defines.hpp>
#include <ostd/utils/Time.hpp>
#include <ogfx/gui/LayoutHint.hpp>
#include <ogfx/gui/Layout.hpp>
#include <memory>
#include <functional>
namespace ogfx
{
namespace gui
{
class Window;
class Widget : public ostd::BaseObject, public Rectangle
{
private: struct ThemeOverride
{
String fullKey;
ostd::Stylesheet::TypeVariant value;
};
public: enum class eCallback
{
MousePressed = 0,
MouseReleased,
MouseMoved,
MouseScrolled,
DragAndDrop,
MouseEntered,
MouseExited,
MouseDragged,
KeyPressed,
KeyReleased,
TextEntered,
FocusLost,
FocusGained,
WindowClosed,
WindowResized,
WindowFocused,
WindowFocusLost,
ActionPerformed
};
public: using EventCallback = std::function<void(const Event&)>;
public:
// ================================== MAIN =================================
Widget(const Rectangle& bounds, Window& window);
Widget(Widget&&) = default;
Widget& operator=(Widget&&) = delete;
Widget(const Widget&) = delete;
Widget& operator=(const Widget&) = delete;
virtual ~Widget(void);
bool addWidget(Widget& child, const Vec2& position = { 0, 0 }, bool __skip_callback = false);
bool removeWidget(Widget& child);
void enable(bool enable = true);
void addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value);
void reloadTheme(bool propagate = false);
void setThemeQualifier(const String& qualifier, bool value = true);
bool getThemeQualifier(const String& qualifier) const;
bool addThemeID(const String& id);
bool removeThemeID(const String& id);
void setVisible(bool v);
bool setTabIndex(i32 index);
virtual void setCallback(eCallback type, EventCallback callback);
using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override;
template<typename T>
inline T getThemeValue(const ostd::Stylesheet &theme, const String& key, const T& fallback)
{
return theme.get<T>(property(key), fallback, getThemeIDList(), getThemeQualifierList());
}
// ==========================================================================
// ================================== CORE =================================
void __draw(ogfx::BasicRenderer2D& gfx);
void __update(void);
void __onMousePressed(const Event& event);
void __onMouseReleased(const Event& event);
void __onDragAndDrop(const Event& event);
void __onMouseMoved(const Event& event);
void __onMouseScrolled(const Event& event);
void __onMouseEntered(const Event& event);
void __onMouseExited(const Event& event);
void __onMouseDragged(const Event& event);
void __onKeyPressed(const Event& event);
void __onKeyReleased(const Event& event);
void __onTextEntered(const Event& event);
void __onWindowClosed(const Event& event);
void __onWindowResized(const Event& event);
void __onWindowFocused(const Event& event);
void __onWindowFocusLost(const Event& event);
void __applyTheme(const ostd::Stylesheet& theme, bool propagate);
// ==========================================================================
// ============================== LAYOUT ==============================
void setLayout(std::unique_ptr<Layout> layout);
template<typename L, typename... Args>
inline L& setLayout(Args&&... args)
{
static_assert(std::is_base_of_v<Layout, L>, "L must derive from Layout");
auto owned = std::make_unique<L>(std::forward<Args>(args)...);
L* raw = owned.get();
setLayout(std::move(owned));
return *raw;
}
inline Layout* getLayout(void) { return m_layout.get(); }
inline bool hasLayout(void) const { return m_layout != nullptr; }
inline void setw(f32 w) override { Rectangle::setw(w); if (m_layout) relayout(); }
inline void seth(f32 h) override { Rectangle::seth(h); if (m_layout) relayout(); }
// Re-runs the owned Layout (if any) and recursively re-runs layouts
// on children that have their own. Safe to call when no layout is set.
void relayout(void);
inline LayoutHint& layoutHint(void) { return m_layoutHint; }
inline const LayoutHint& layoutHint(void) const { return m_layoutHint; }
// ====================================================================
// =============================== DIMENSIONS ===============================
virtual Vec2 getGlobalPosition(void) const;
virtual Vec2 getGlobalContentPosition(void) const;
virtual Rectangle getGlobalBounds(void) const;
virtual Rectangle getContentBounds(void) const;
virtual Rectangle getPureContentBounds(void) const;
virtual Rectangle getGlobalContentBounds(void) const;
virtual Rectangle getGlobalPureContentBounds(void) const;
virtual Rectangle getContentExtents(void) const;
// ==========================================================================
// ================================= GETSET =================================
inline static void setDragAndDropData(ostd::BaseObject& data) { s_dragAndDropData = &data; s_hasDragAndDropData = true; }
inline static void clearDragAndDropData(void) { s_dragAndDropData = nullptr; }
inline static ostd::BaseObject* getDragAndDropData(void) { return s_dragAndDropData; }
inline i32 getTabIndex(void) const { return m_tabIndex; }
inline Window& getWindow(void) { return *m_window; }
inline Widget* getParent(void) { return m_parent; }
inline const Widget* getParent(void) const { return m_parent; }
inline ogfx::MouseEventData::eButton getPressedMouseButton(void) const { return m_pressedButton; }
inline const stdvec<String>& getThemeIDList(void) const { return m_themeIDList; }
inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; }
inline String getStylesheetCategoryName(void) const { return m_stylesheetCategoryName; }
inline WidgetManager& getChildren(void) { return m_widgets; }
OSTD_PARAM_GETSET(Color, TextColor, m_textColor);
OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor);
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
OSTD_PARAM_GETSET(Color, FocusBorderColor, m_focusBorderColor);
OSTD_PARAM_GETSET(i32, BorderRadius, m_borderRadius);
OSTD_PARAM_GETSET(i32, BorderWidth, m_borderWidth);
OSTD_PARAM_GETSET(i32, FocusBorderWidth, m_focusBorderWidth);
OSTD_PARAM_GETSET(i32, FontSize, m_fontSize);
OSTD_PARAM_GETSET(ostd::ColorGradient, BackgroundGradient, m_backgroundGradient);
OSTD_PARAM_GETSET(Rectangle, Padding, m_padding);
OSTD_PARAM_GETSET(Rectangle, Margin, m_margin);
OSTD_PARAM_GETSET(Vec2, ContentOffset, m_contentOffset);
OSTD_PARAM_GETSET(String, TooltipText, m_tooltipText);
// BOOL PARAMETERS
inline bool isVisible(void) const { return m_visible; }
inline void show(void) { setVisible(true); }
inline void hide(void) { setVisible(false); }
inline bool isChildrenEnabled(void) const { return m_allowChildren; }
inline bool isEnabled(void) const { return m_enabled; }
inline void disable(void) { enable(false); }
inline bool isFocused(void) const { return m_focused; }
inline bool isMouseInside(void) const { return m_mouseInside; }
inline bool hasChildren(void) const { return m_allowChildren && m_widgets.widgetCount() > 0; }
inline bool isRootChild(void) const { return m_rootChild; }
inline void setRootChild(void) { m_rootChild = true; }
inline virtual bool isInvalid(void) const override { return ostd::BaseObject::isInvalid() || (m_parent == nullptr && !m_rootChild); }
OSTD_BOOL_PARAM_GETSET_E(Focus, m_allowFocus);
OSTD_BOOL_PARAM_GETSET_E(StopEvents, m_stopEvents);
OSTD_BOOL_PARAM_GETSET_E(DragAndDrop, m_acceptDragAndDrop);
OSTD_BOOL_PARAM_GETSET_E(Background, m_showBackground);
OSTD_BOOL_PARAM_GETSET_E(Border, m_showBorder);
OSTD_BOOL_PARAM_GETSET_E(FocusBorder, m_showFocusBorder);
OSTD_BOOL_PARAM_GETSET_E(ManualDraw, m_manualDraw);
OSTD_BOOL_PARAM_GETSET_E(TopMost, m_topMost);
OSTD_BOOL_PARAM_GETSET_E(IgnoreScroll, m_ignoreScroll);
OSTD_BOOL_PARAM_GETSET_E(VScroll, m_vScrollEnabled);
OSTD_BOOL_PARAM_GETSET_E(HScroll, m_hScrollEnabled);
OSTD_BOOL_PARAM_GETSET_E(BackgroundGradient, m_useBackgroundGradient);
OSTD_BOOL_PARAM_GETSET_E(Theming, m_enableTheming);
OSTD_BOOL_PARAM_GETSET_E(Tooltip, m_enableTooltip);
// ==========================================================================
// ================================= EVENT ENABLERS =================================
OSTD_BOOL_PARAM_GETSET_E_NEG(MousePressedEvent, m_disableMousePressed);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseReleasedEvent, m_disableMouseReleased);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseMovedEvent, m_disableMouseMoved);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseScrolledEvent, m_disableMouseScrolled);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseEnteredEvent, m_disableMouseEntered);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseExitedEvent, m_disableMouseExited);
OSTD_BOOL_PARAM_GETSET_E_NEG(MouseDraggedEvent, m_disableMouseDragged);
OSTD_BOOL_PARAM_GETSET_E_NEG(KeyPressedEvent, m_disableKeyPressed);
OSTD_BOOL_PARAM_GETSET_E_NEG(KeyReleasedEvent, m_disableKeyReleased);
OSTD_BOOL_PARAM_GETSET_E_NEG(TextEnteredEvent, m_disableTextEntered);
// ==================================================================================
protected:
void apply_common_theme_values(const ostd::Stylesheet& theme);
inline void disableChildren(void) { m_allowChildren = false; }
inline void setStylesheetCategoryName(const String& name) { m_stylesheetCategoryName = name; }
inline String property(const ostd::String& prop) const { return m_stylesheetCategoryName + "." + prop; }
private:
// ======= CORE =======
Window* m_window { nullptr };
Widget* m_parent { nullptr };
WidgetManager m_widgets;
std::unique_ptr<Layout> m_layout;
LayoutHint m_layoutHint;
bool m_rootChild { false };
bool m_focused { false };
i32 m_tabIndex { -1 };
String m_tooltipText { "" };
MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None };
// ====================
// ======= BOOL =======
bool m_allowChildren { true };
bool m_mouseInside { false };
bool m_allowFocus { true };
bool m_enabled { true };
bool m_stopEvents { true };
bool m_clipContents { true };
bool m_acceptDragAndDrop { false };
bool m_visible { true };
bool m_ignoreScroll { false};
bool m_manualDraw { false };
bool m_topMost { false };
bool m_vScrollEnabled { false };
bool m_hScrollEnabled { false };
bool m_enableTheming { true };
bool m_enableTooltip { false };
// ====================
// ======= Event Enablers =======
bool m_disableMousePressed { false };
bool m_disableMouseReleased { false };
bool m_disableMouseMoved { false };
bool m_disableMouseScrolled { false };
bool m_disableMouseEntered { false };
bool m_disableMouseExited { false };
bool m_disableMouseDragged { false };
bool m_disableKeyPressed { false };
bool m_disableKeyReleased { false };
bool m_disableTextEntered { false };
// ==============================
// ====== THEMING =====
String m_stylesheetCategoryName { "" };
stdvec<String> m_themeIDList;
ostd::Stylesheet::QualifierList m_qualifierList {
{ "disabled", false },
{ "pressed", false },
{ "hover", false },
{ "focused", false },
{ "active", false }
};
stdvec<ThemeOverride> m_themeOverrides;
Rectangle m_padding { 0, 0, 0, 0 };
Rectangle m_margin { 0, 0, 0, 0 };
Vec2 m_contentOffset { 0, 0 };
i32 m_borderRadius { 0 };
i32 m_borderWidth { 2 };
Color m_borderColor { 255, 255, 255 };
bool m_showBorder { false };
i32 m_focusBorderWidth { 2 };
Color m_focusBorderColor { 0, 150, 20 };
bool m_showFocusBorder { false };
Color m_backgroundColor { Colors::Transparent };
bool m_showBackground { false };
i32 m_fontSize { 20 };
Color m_textColor { 255, 255, 255 };
bool m_useBackgroundGradient { false };
ColorGradient m_backgroundGradient {
{ Color { 160, 160, 160 }, Color { 120, 120, 120 } },
{ 1.0f }
};
// ====================
// ====== STATIC ======
static ostd::BaseObject* s_dragAndDropData;
static bool s_hasDragAndDropData;
friend class WidgetManager;
friend class FocusManager;
// ====================
// =============================== CALLBACKS/DUMMIES ===============================
protected:
EventCallback callback_onMousePressed { nullptr };
EventCallback callback_onMouseReleased { nullptr };
EventCallback callback_onDragAndDrop { nullptr };
EventCallback callback_onMouseMoved { nullptr };
EventCallback callback_onMouseScrolled { nullptr };
EventCallback callback_onMouseEntered { nullptr };
EventCallback callback_onMouseExited { nullptr };
EventCallback callback_onMouseDragged { nullptr };
EventCallback callback_onKeyPressed { nullptr };
EventCallback callback_onKeyReleased { nullptr };
EventCallback callback_onTextEntered { nullptr };
EventCallback callback_onFocusGained { nullptr };
EventCallback callback_onFocusLost { nullptr };
EventCallback callback_onWindowClosed { nullptr };
EventCallback callback_onWindowResized { nullptr };
EventCallback callback_onWindowFocused { nullptr };
EventCallback callback_onWindowFocusLost { nullptr };
EventCallback callback_onActionPerformed { nullptr };
public:
// ====================== DUMMIES ======================
inline virtual void applyTheme(const ostd::Stylesheet& theme) { }
inline virtual Vec2 getScrollOffset(void) const { return { 0, 0 }; }
inline virtual void setScrollOffset(const Vec2& offset) { }
inline virtual void addScrollOffset(const Vec2& offset) { }
inline virtual bool needsVScroll(void) const { return false; }
inline virtual bool needsHScroll(void) const { return false; }
inline virtual void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) { }
inline virtual f32 getVScrollbarSize(void) const { return 0; }
inline virtual f32 getHScrollbarSize(void) const { return 0; }
inline virtual void refresh(void) { }
// =====================================================
inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { }
inline virtual void afterDraw(ogfx::BasicRenderer2D& gfx) { }
inline virtual void beforeDraw(ogfx::BasicRenderer2D& gfx) { }
inline virtual void onUpdate(void) { }
inline virtual void onWidgetAdded(Widget& child) { }
inline virtual void onMousePressed(const Event& event) { }
inline virtual void onMouseReleased(const Event& event) { }
inline virtual void onMouseMoved(const Event& event) { }
inline virtual void onMouseScrolled(const Event& event) { }
inline virtual void onDragAndDrop(const Event& event) { }
inline virtual void onMouseEntered(const Event& event) { }
inline virtual void onMouseExited(const Event& event) { }
inline virtual void onMouseDragged(const Event& event) { }
inline virtual void onKeyPressed(const Event& event) { }
inline virtual void onKeyReleased(const Event& event) { }
inline virtual void onTextEntered(const Event& event) { }
inline virtual void onFocusGained(const Event& event) { }
inline virtual void onFocusLost(const Event& event) { }
inline virtual void onWindowClosed(const Event& event) { }
inline virtual void onWindowResized(const Event& event) { }
inline virtual void onWindowFocused(const Event& event) { }
inline virtual void onWindowFocusLost(const Event& event) { }
inline virtual void onBoundsChanged(const Vec2& newSize) { }
};
}
}

36
src/ogfx/ogfx.hpp Normal file
View file

@ -0,0 +1,36 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/ostd.hpp>
#include <ogfx/gui/RawTextInput.hpp>
#include <ogfx/gui/Window.hpp>
#include <ogfx/gui/WindowOutputHandler.hpp>
#include <ogfx/gui/Widgets.hpp>
#include <ogfx/render/BasicRenderer.hpp>
#include <ogfx/render/PixelRenderer.hpp>
#include <ogfx/resources/Image.hpp>
#include <ogfx/utils/Animation.hpp>
#include <ogfx/utils/SDLInclude.hpp>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,178 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/utils/Animation.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/data/Color.hpp>
#include <ogfx/resources/Image.hpp>
#include <ostd/io/IOHandlers.hpp>
#include <ogfx/render/FontGlyphAtlas.hpp>
#include <ostd/io/StaticHashMap.hpp>
namespace ogfx
{
class WindowCore;
class BasicRenderer2D
{
public: struct tErrors
{
inline static constexpr i32 NoError = 0;
inline static constexpr i32 FailedToLoad = 1;
inline static constexpr i32 FailedToOpenFontFile = 2;
inline static constexpr i32 Uninitialized = 3;
inline static constexpr i32 InvalidState = 5;
inline static constexpr i32 TTFRenderTextBlendedFail = 6;
inline static constexpr i32 TTFCreateTextureFromSurfaceFail = 7;
inline static constexpr i32 NullFont = 8;
inline static constexpr i32 NoFont = 9;
inline static constexpr i32 FailedToOpenFontByteStrean = 10;
};
private: class SignalHandler : public ostd::BaseObject
{
public:
SignalHandler(BasicRenderer2D& parent);
void handleSignal(ostd::Signal& signal) override;
private:
BasicRenderer2D& m_parent;
};
public:
inline BasicRenderer2D(void) { }
~BasicRenderer2D(void);
i32 init(WindowCore& window);
SDL_Renderer* getSDLRenderer(void) const;
void flushBatch(void);
void endFrame(void);
void pushClippingRect(const Rectangle& rect, bool additive = false);
void popClippingRect(void);
i32 loadDefaultMonoFont(i32 fontSize = 0);
i32 loadDefaultFont(i32 fontSize = 0);
void closeFont(void);
i32 openFont(const String& fontPath, i32 fontSize = 0);
i32 openFont(const ostd::UByte resource_data[], u32 data_size, i32 fontSize = 0);
i32 setFontSize(i32 fontSize);
Vec2 getStringDimensions(const String& message, i32 fontSize = 0, TTF_Font* font = nullptr);
stdvec<Vec2> getStringDimensionsPerCharacter(const String& message, i32 fontSize = 0, TTF_Font* font = nullptr);
inline u32 getDrawCallCount(void) { return m_lastFrameDrawCallCount; }
inline u32 getCacheHitCount(void) { return m_cacheHitCount; }
inline u32 getCacheMissCount(void) { return m_cacheMissCount; }
inline bool hasOpenFont(void) { return m_fontOpen; }
inline TTF_Font* getSDLFont(void) { return m_font; }
inline bool isValid(void) const { return m_initialized && m_fontOpen && (m_font != nullptr || m_fontFromMemory); }
inline i32 geterrorState(void) { return m_errorState; }
inline i32 getFontSize(void) { return m_fontSize; }
inline WindowCore& getWindow(void) { return *m_window; }
inline bool isInitialized(void) { return m_initialized; }
inline FontGlyphAtlas& getFontGlyphAtlas(void) { return m_fontGlyphAtlas; }
void drawImage(const ogfx::Image& image, const Vec2& position, const Vec2& size = { 0, 0 }, const Color& tint = Colors::White, const Rectangle& srcRect = { 0, 0, 0, 0 });
void drawAnimation(const Animation& anim, const Vec2& position, const Vec2& size = { 0, 0 }, const Color& tint = Colors::White);
void fillGradientRect(const Rectangle& rect, ColorGradient& gradient);
void drawString(const String& str, const Vec2& position, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawVCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawHCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawLine(const FLine& line, const Color& color, i32 thickness = 1, bool rounded = true);
void drawRect(const Rectangle& rect, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true);
void drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true);
void drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius, i32 thickness = 1);
void drawRoundRect(const Rectangle& rect, const Color& color, f32 radius, i32 thickness = 1);
void drawRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii, i32 thickness = 1);
void drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii, i32 thickness = 1);
void drawCircle(const Vec2& center, f32 radius, const Color& color, i32 thickness = 1);
void drawCircle(const Rectangle& rect, const Color& color, i32 thickness = 1);
void drawEllipse(const Rectangle& rect, const Color& color, i32 thickness = 1);
void drawTriangle(const Triangle& tri, const Color& color, i32 thickness = 1);
void drawTriangle(const Vec2& A, const Vec2& B, const Vec2& C, const Color& color, i32 thickness = 1);
void fillRect(const Rectangle& rect, const Color& color);
void fillRect(const Vec2& center, const Vec2& size, const Color& color);
void fillRoundRect(const Rectangle& rect, const Color& color, f32 radius);
void fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius);
void fillRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii);
void fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii);
void fillCircle(const Vec2& center, f32 radius, const Color& color);
void fillCircle(const Rectangle& rect, const Color& color);
void fillEllipse(const Rectangle& rect, const Color& color);
void fillTriangle(const Triangle& tri, const Color& color);
void fillTriangle(const Vec2& A, const Vec2& B, const Vec2& C, const Color& color);
void outlinedRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true);
void outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true);
void outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness = 1);
void outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness = 1);
void outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness = 1);
void outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness = 1);
void outlinedCircle(const Vec2& center, f32 radius, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1);
void outlinedCircle(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1);
void outlinedEllipse(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1);
void outlinedTriangle(const Triangle& tri, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1);
void outlinedTriangle(const Vec2& A, const Vec2& B, const Vec2& C, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1);
private:
void init_arrays(void);
void generate_half_circle(const Vec2& center, const Vec2& dir, f32 radius, i32 segments, const Color& color, bool use_null_tex = false);
void generate_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 thickness, f32 startAngle, f32 endAngle, const Color& color, i32 segments, bool use_null_tex = false);
void generate_filled_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 startAngle, const Color& color, i32 segments, bool use_null_tex = false);
void generate_filled_ellipse(const Vec2& center, f32 radiusX, f32 radiusY, const Color& color, i32 segments, bool use_null_tex = false);
void push_polygon(const Vec2* verts, const Vec2* texCoords, u32 vertCount, const u32* inds, u32 indexCount, const Color& color, SDL_Texture* texture, bool use_null_tex = false);
void print_ttf_error(const String& funcName);
inline i32 set_error_state(i32 err) { m_errorState = err; return m_errorState; }
public:
inline static constexpr i32 MaxVertices { 8192 };
inline static constexpr i32 MaxIndices { (i32)(MaxVertices * 1.5f) };
private:
WindowCore* m_window { nullptr };
ostd::ConsoleOutputHandler m_out;
bool m_initialized { false };
stdvec<Rectangle> m_clipStack;
std::array<SDL_Vertex, MaxVertices> m_vertices;
std::array<i32, MaxIndices> m_indices;
i32 m_vertexCount { 0 };
i32 m_indexCount { 0 };
SDL_Texture* m_texture { nullptr };
u32 m_drawCallCount { 0 };
u32 m_lastFrameDrawCallCount { 0 };
bool m_fontOpen { false };
TTF_Font* m_font { nullptr };
i32 m_errorState { tErrors::NoError };
i32 m_fontSize { DefaultFontSize };
bool m_fontFromMemory { false };
FontGlyphAtlas m_fontGlyphAtlas;
SDL_Texture* m_lastUsedGlyphAtlasTex { nullptr };
SignalHandler m_sigHandler { *this };
ostd::StaticHashMap<String, stdvec<Vec2>> m_strDimsCache;
i32 m_cacheHitCount { 0 };
i32 m_cacheMissCount { 0 };
inline static constexpr i32 DefaultFontSize { 16 };
inline static constexpr i32 MaxStringDimsCacheSize { 512 };
};
}

View file

@ -0,0 +1,196 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "FontGlyphAtlas.hpp"
#include "BasicRenderer.hpp"
#include <SDL3/SDL_pixels.h>
#include <SDL3/SDL_render.h>
#include <SDL3/SDL_surface.h>
#include <SDL3_ttf/SDL_ttf.h>
namespace ogfx
{
FontGlyphAtlas FontGlyphAtlas::init(BasicRenderer2D& renderer)
{
m_renderer = &renderer;
m_uvs.reserve(4096);
for (i32 i = 0; i < FontGlyphAtlas::MaxAtlasCount; i++)
m_atlases[i] = nullptr;
m_currentAtlasCount = 0;
create_new_atlas();
return *this;
}
const stdvec<const FontGlyphAtlas::GlyphInfo*> FontGlyphAtlas::processString(const String& str, TTF_Font* font, u32 fontSize)
{
if (m_currentAtlasCount <= 0)
return {};
// Pass 1: ensure all glyphs are rasterized (map may rehash here)
for (auto& c : str)
{
const GlyphInfo* dummy;
if (!rasterize_glyph(String("").addChar(c), font, fontSize, &dummy))
return {};
}
// Pass 2: collect stable pointers (no more insertions, no rehash risk)
stdvec<const GlyphInfo*> glyphs;
glyphs.reserve(str.len());
for (auto& c : str)
{
auto cps = String("").addChar(c).getUTF8Codepoints();
if (cps.size() != 1) return {};
GlyphKey key { cps[0], u64(font), fontSize };
glyphs.push_back(&m_uvs[key]);
}
return glyphs;
}
bool FontGlyphAtlas::rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph)
{
if (!font || m_currentAtlasCount <= 0)
return false;
auto cps = glyphStr.getUTF8Codepoints();
if (cps.size() != 1)
return false;
u32 codepoint = cps[0];
GlyphKey key { codepoint, u64(font), fontSize };
auto it = m_uvs.find(key);
if (it != m_uvs.end())
{
*outGlyph = &(it->second);
return true;
}
SDL_Surface* surf = TTF_RenderText_Blended(font, glyphStr.c_str(), glyphStr.len(), SDL_Color{255, 255, 255, 255});
if (!surf)
return false;
i32 gw = surf->w;
i32 gh = surf->h;
i32 minx, maxx, miny, maxy, advance;
TTF_GetGlyphMetrics(font, codepoint, &minx, &maxx, &miny, &maxy, &advance);
GlyphInfo glyph;
glyph.advance = advance;
glyph.codepoint = codepoint;
SDL_Texture* atlas = m_atlases[m_currentAtlasCount - 1];
if (m_penX + gw > i32(AtlasTextureDimension))
{
m_penX = 0;
m_penY += m_rowHeight;
m_rowHeight = 0;
}
if (m_penY + gh > i32(AtlasTextureDimension))
{
SDL_DestroySurface(surf);
if (!create_new_atlas())
return false;
atlas = m_atlases[m_currentAtlasCount - 1];
m_penX = 0;
m_penY = 0;
m_rowHeight = 0;
}
SDL_Rect dstRect { m_penX, m_penY, gw, gh };
SDL_LockSurface(surf);
SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
SDL_UnlockSurface(surf);
SDL_DestroySurface(surf);
f32 u0 = f32(m_penX) / f32(AtlasTextureDimension);
f32 v0 = f32(m_penY) / f32(AtlasTextureDimension);
f32 u1 = f32(m_penX + gw) / f32(AtlasTextureDimension);
f32 v1 = f32(m_penY + gh) / f32(AtlasTextureDimension);
glyph.atlas = atlas;
glyph.uvs[0] = { u0, v0 };
glyph.uvs[1] = { u1, v0 };
glyph.uvs[2] = { u1, v1 };
glyph.uvs[3] = { u0, v1 };
glyph.size = { cast<f32>(gw), cast<f32>(gh) };
m_uvs[key] = glyph;
*outGlyph = &(m_uvs[key]);
m_penX += gw;
m_rowHeight = std::max(m_rowHeight, gh);
return true;
}
bool FontGlyphAtlas::create_new_atlas(void)
{
if (m_currentAtlasCount >= i32(MaxAtlasCount))
return false;
if (!m_renderer)
return false;
SDL_Renderer* sdlRenderer = m_renderer->getSDLRenderer();
if (!sdlRenderer)
return false;
SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
if (!tex)
return false;
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
{ // Clear the texture to transparent
void* pixels = nullptr;
i32 pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch))
{
memset(pixels, 0x00, pitch * AtlasTextureDimension);
auto u8_pixels = cast<u8*>(pixels);
for (i32 y = 0; y < 4; y++)
{
for (i32 x = 0; x < 4 * 4; x++)
{
u8_pixels[x + (y * pitch)] = 0xFF;
}
}
SDL_UnlockTexture(tex);
}
}
m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++;
m_penX = 4;
m_penY = 0;
m_rowHeight = 0;
return true;
}
}

View file

@ -0,0 +1,89 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/string/String.hpp>
#include <ostd/math/Geometry.hpp>
#include <ogfx/utils/SDLInclude.hpp>
namespace ogfx
{
class BasicRenderer2D;
class FontGlyphAtlas
{
public: struct GlyphInfo
{
Vec2 uvs[4];
SDL_Texture* atlas { nullptr };
Vec2 size;
u32 codepoint { 0 };
i32 advance { 0 };
};
struct GlyphKey
{
u32 codepoint;
u64 fontID;
u32 pixelSize;
bool operator==(const GlyphKey& other) const noexcept
{
return codepoint == other.codepoint &&
fontID == other.fontID &&
pixelSize == other.pixelSize;
}
};
struct GlyphKeyHasher
{
size_t operator()(const GlyphKey& k) const noexcept
{
size_t h = std::hash<u32>()(k.codepoint);
h ^= std::hash<u64>()(k.fontID) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<u32>()(k.pixelSize) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
public:
inline FontGlyphAtlas(void) { }
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer);
const stdvec<const GlyphInfo*> processString(const String& str, TTF_Font* font, u32 fontSize);
inline Vec2 getNullTextureSlotUVs(void) const { return m_nullTexUVs; }
inline SDL_Texture* getAtlas(u32 index) const { return (index >= m_currentAtlasCount ? nullptr : m_atlases[index]); }
private:
bool rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph);
bool create_new_atlas(void);
public:
inline static constexpr u32 AtlasTextureDimension { 8192 };
inline static constexpr u32 MaxAtlasCount { 16 };
private:
std::unordered_map<GlyphKey, GlyphInfo, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
i32 m_currentAtlasCount { 0 };
BasicRenderer2D* m_renderer { nullptr };
i32 m_penX { 0 };
i32 m_penY { 0 };
i32 m_rowHeight { 0 };
Vec2 m_nullTexUVs { 2.0f / cast<f32>(AtlasTextureDimension), 2.0f / cast<f32>(AtlasTextureDimension) };
};
}

View file

@ -0,0 +1,191 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "PixelRenderer.hpp"
#include "../gui/Window.hpp"
#include "../../ostd/io/Memory.hpp"
#include <SDL3/SDL_render.h>
namespace ogfx
{
void PixelRenderer::TextRenderer::initialize(void)
{
for (char c = ' '; c <= '~'; c++)
characterMap[c] = getCharacterIndex(c);
}
bool PixelRenderer::TextRenderer::drawString(const String& str, u32 column, u32 row, u32* screenPixels, i32 screenWidth, i32 screenHeight, u32* fontPixels, const Color& color, const Color& background)
{
String se(str);
if (se == "") return false;
if (row >= CONSOLE_CHARS_V) return false;
if (column >= CONSOLE_CHARS_H) return false;
if (column + str.len() > CONSOLE_CHARS_H) return false;
i32 x = column * FONT_CHAR_W;
i32 y = row * FONT_CHAR_H;
for (auto& c : str)
{
drawCharacter((u8*)screenPixels, screenWidth, screenHeight, (u8*)fontPixels, x, y, c, color, background);
x += FONT_CHAR_W;
}
s_cursor_pos_x = x;
if (s_cursor_pos_x >= CONSOLE_CHARS_H)
s_cursor_pos_x = 0;
return true;
}
i32 PixelRenderer::TextRenderer::getCharacterIndex(char c)
{
using namespace ostd;
i32 charIndex = (i32)c - 32;
IPoint charCoords = CONVERT_1D_2D(charIndex, FONT_H_CHARS);
charCoords.x *= FONT_CHAR_W * 4;
charCoords.y *= FONT_CHAR_H;
charIndex = CONVERT_2D_1D(charCoords.x, charCoords.y, (FONT_H_CHARS * FONT_CHAR_W * 4));
return charIndex;
}
Color PixelRenderer::TextRenderer::applyTint(const Color& baseColor, const Color& tintColor)
{
auto nBase = baseColor.getNormalizedColor();
auto nTint = tintColor.getNormalizedColor();
f32 r = nBase.r * nTint.r;
f32 g = nBase.r * nTint.g;
f32 b = nBase.r * nTint.b;
Color::FloatCol nTinted(r, g, b, 1.0f);
return Color(nTinted);
}
void PixelRenderer::TextRenderer::drawCharacter(u8* screenPixels, i32 screenWidth, i32 screenHeight, u8* fontPixels, i32 x, i32 y, char c, const Color& color, const Color& background)
{
using namespace ostd;
i32 charIndex = characterMap[c];
IPoint charCoords = CONVERT_1D_2D(charIndex, (FONT_CHAR_W * FONT_H_CHARS * 4));
i32 screenx = x * 4, screeny = y;
Color tintedColor;
bool applyBackground = false;
for (i32 y = charCoords.y; y < charCoords.y + (FONT_CHAR_H); y += 1)
{
for (i32 x = charCoords.x; x < charCoords.x + (FONT_CHAR_W * 4); x += 4)
{
i32 index = CONVERT_2D_1D(x, y, (FONT_CHAR_W * FONT_H_CHARS * 4));
i32 screenIndex = CONVERT_2D_1D(screenx, screeny, (screenWidth * 4));
screenx += 4;
if (fontPixels[index] == 0x00 && fontPixels[index + 1] == 0x00 && fontPixels[index + 2] == 0x00)
{
if (background.a == 0)
continue;
applyBackground = true;
}
if (applyBackground)
{
screenPixels[screenIndex + 0] = background.b;
screenPixels[screenIndex + 1] = background.g;
screenPixels[screenIndex + 2] = background.r;
screenPixels[screenIndex + 3] = 255;
applyBackground = false;
continue;
}
tintedColor = applyTint({ fontPixels[index], fontPixels[index + 1], fontPixels[index + 2], 255 }, color);
screenPixels[screenIndex + 0] = tintedColor.b;
screenPixels[screenIndex + 1] = tintedColor.g;
screenPixels[screenIndex + 2] = tintedColor.r;
screenPixels[screenIndex + 3] = fontPixels[index + 3];
}
screeny += 1;
screenx = x * 4;
}
}
PixelRenderer::~PixelRenderer(void)
{
if (isInvalid()) return;
ostd::Memory::destroyArray(m_pixels);
SDL_DestroyTexture(m_texture);
}
void PixelRenderer::initialize(WindowCore& parent)
{
if (isValid()) return; //TODO: Error
if (!parent.isValid() || !parent.isInitialized())
return; //TODO: Error
m_parent = &parent;
m_pixels = ostd::Memory::createArray<u32>(parent.getWindowWidth() * parent.getWindowHeight());
m_texture = SDL_CreateTexture(parent.getSDLRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, parent.getWindowWidth(), parent.getWindowHeight());
m_windowWidth = parent.getWindowWidth();
m_windowHeight = parent.getWindowHeight();
setTypeName("ogfx::PixelRenderer");
enableSignals();
connectSignal(ostd::BuiltinSignals::WindowResized);
validate();
}
void PixelRenderer::handleSignal(ostd::Signal& signal)
{
if (isInvalid()) return;
if (signal.ID == ostd::BuiltinSignals::WindowResized)
{
m_pixels = ostd::Memory::resizeArray<u32>(m_pixels, m_parent->getWindowWidth() * m_parent->getWindowHeight());
SDL_DestroyTexture(m_texture);
m_texture = SDL_CreateTexture(m_parent->getSDLRenderer(), SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, m_parent->getWindowWidth(), m_parent->getWindowHeight());
m_windowWidth = m_parent->getWindowWidth();
m_windowHeight = m_parent->getWindowHeight();
updateBuffer();
displayBuffer();
}
}
void PixelRenderer::updateBuffer(void)
{
if (isInvalid()) return;
SDL_UpdateTexture(m_texture, NULL, m_pixels, m_windowWidth * 4);
}
void PixelRenderer::displayBuffer(void)
{
if (isInvalid()) return;
SDL_FRect rect { 0, 0, cast<f32>(m_windowWidth), cast<f32>(m_windowHeight) };
SDL_RenderTexture(m_parent->getSDLRenderer(), m_texture, NULL, &rect);
}
void PixelRenderer::clear(const Color& color)
{
if (isInvalid()) return;
for (i32 y = 0; y < m_windowHeight; y++)
{
for (u32 x = 0; x < m_windowWidth; x++)
{
i32 index = CONVERT_2D_1D(x, y, m_windowWidth);
m_pixels[index] = color.asInteger(Color::eColorFormat::ARGB);
}
}
}
}

View file

@ -0,0 +1,101 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/io/IOHandlers.hpp>
#include <ostd/string/String.hpp>
#include <ostd/data/Color.hpp>
#include <ogfx/utils/SDLInclude.hpp>
namespace ogfx
{
class WindowCore;
class PixelRenderer : public ostd::BaseObject
{
public: class TextRenderer
{
private:
inline static stdumap<char, i32> characterMap;
public:
static void initialize(void);
static bool drawString(const String& str, u32 column, u32 row, u32* screenPixels, i32 screenWidth, i32 screenHeight, u32* fontPixels, const Color& color = { 255, 255, 255, 255 }, const Color& background = { 255, 255, 255, 0 });
private:
static i32 getCharacterIndex(char c);
static Color applyTint(const Color& baseColor, const Color& tintColor);
static void drawCharacter(u8* screenPixels, i32 screenWidth, i32 screenHeight, u8* fontPixels, i32 x, i32 y, char c, const Color& color = { 255, 255, 255, 255 }, const Color& background = { 255, 255, 255, 0 });
public:
inline static constexpr i32 FONT_CHAR_W = 11;
inline static constexpr i32 FONT_CHAR_H = 26;
inline static constexpr i32 FONT_V_CHARS = 6;
inline static constexpr i32 FONT_H_CHARS = 16;
inline static constexpr i32 CONSOLE_CHARS_H = 90;
inline static constexpr i32 CONSOLE_CHARS_V = 21;
inline static i8 s_cursor_pos_x = 0;
};
public: class Font
{
public:
inline Font(void) { }
inline Font(const String& fontPath)
{
init(fontPath);
}
inline void init(const String& fontPath)
{
ostd::ConsoleOutputHandler out;
m_fontSurface = SDL_LoadBMP(fontPath.c_str());
if (m_fontSurface == NULL)
out.bg(ostd::ConsoleColors::Red).p("Error loading pixel font.").reset().nl();
m_fontPixels = (u32*)m_fontSurface->pixels;
}
inline ~Font(void)
{
SDL_DestroySurface(m_fontSurface);
}
public:
SDL_Surface* m_fontSurface { nullptr };
u32* m_fontPixels { nullptr };
};
public:
inline PixelRenderer(void) { invalidate(); }
~PixelRenderer(void);
void initialize(WindowCore& parent);
void handleSignal(ostd::Signal& signal) override;
void updateBuffer(void);
void displayBuffer(void);
inline u32* getScreenPixels(void) { return m_pixels; }
void clear(const Color& color);
private:
u32* m_pixels { nullptr };
SDL_Texture* m_texture { nullptr };
WindowCore* m_parent { nullptr };
i32 m_windowWidth { 0 };
i32 m_windowHeight { 0 };
};
}

View file

@ -0,0 +1,59 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Image.hpp"
#include "../../io/Logger.hpp"
#include "../render/BasicRenderer.hpp"
#include "../gui/Window.hpp"
namespace ogfx
{
void Image::destroy(void)
{
if (isValid() && isLoaded())
SDL_DestroyTexture(m_sdl_texture);
m_loaded = false;
m_sdl_texture = nullptr;
m_width = 0;
m_height = 0;
m_filePath = "";
invalidate();
}
Image& Image::loadFromFile(const String& filePath, BasicRenderer2D& gfx)
{
if (isLoaded() && isValid())
return *this;
if (!gfx.isInitialized())
return *this; //TODO: Error
m_sdl_texture = IMG_LoadTexture(gfx.getWindow().getSDLRenderer(), filePath.c_str());
if (!m_sdl_texture)
{
OX_ERROR("Failed to load Image: %s", SDL_GetError());
return *this;
}
SDL_GetTextureSize(m_sdl_texture, &m_width, &m_height);
m_filePath = filePath;
m_loaded = true;
setTypeName("ogfx::Image");
validate();
return *this;
}
}

View file

@ -0,0 +1,50 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/BaseObject.hpp>
#include <ogfx/utils/SDLInclude.hpp>
#include <ostd/math/Geometry.hpp>
namespace ogfx
{
class BasicRenderer2D;
class Image : public ostd::BaseObject
{
public:
inline Image(void) { invalidate(); }
inline Image(const String& filePath, BasicRenderer2D& gfx) { loadFromFile(filePath, gfx); }
inline ~Image(void) { destroy(); }
void destroy(void);
Image& loadFromFile(const String& filePath, BasicRenderer2D& gfx);
inline Vec2 getSize(void) const { return { m_width, m_height }; }
inline bool isLoaded(void) const { return m_loaded; }
inline SDL_Texture* getSDLTexture(void) const { return m_sdl_texture; }
inline String getFilePath(void) const { return m_filePath; }
private:
SDL_Texture* m_sdl_texture { nullptr };
String m_filePath { "" };
f32 m_width { 0 };
f32 m_height { 0 };
bool m_loaded { false };
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,34 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/Types.hpp>
namespace ogfx
{
// Ubuntu Mono Regular
extern const u32 ubuntu_mono_regular_ttf_size;
extern const ostd::UByte ubuntu_mono_regular_ttf_data[];
// Ubuntu Regular
extern const u32 ubuntu_regular_ttf_size;
extern const ostd::UByte ubuntu_regular_ttf_data[];
}

View file

@ -0,0 +1,100 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Animation.hpp"
#include "../../ostd/math/Random.hpp"
namespace ogfx
{
Animation& Animation::create(const AnimationData& ad, Image& spriteSheet)
{
m_animData = ad;
m_spriteSheet = &spriteSheet;
m_timer.startCount(ostd::eTimeUnits::Milliseconds);
update_animation();
return *this;
}
void Animation::update(void)
{
f64 dtms = (1.0 / cast<f64>(m_animData.fps)) * 1000.0;
if (m_timer.read() >= dtms)
{
update_animation();
m_timer.restart(ostd::eTimeUnits::Milliseconds);
}
}
void Animation::resetAnimation(void)
{
m_currentFrame = 0;
}
void Animation::update_animation(void)
{
if (!m_animData.still)
{
if (m_animData.random)
{
m_currentFrame = ostd::Random::geti32(0, m_animData.frameCount);
}
else if (m_animData.turnBack)
{
if (!m_back && m_currentFrame >= m_animData.frameCount - 1)
{
m_back = true;
m_currentFrame--;
}
else if (!m_back)
m_currentFrame++;
else if (m_back && m_currentFrame <= 0)
{
m_back = false;
m_currentFrame++;
}
else if (m_back)
m_currentFrame--;
}
else
{
if (m_currentFrame++ >= m_animData.frameCount - 1)
m_currentFrame = 0;
}
}
else if (m_animData.still)
{
if (m_animData.stillFrame >= 0 && m_animData.stillFrame < m_animData.frameCount)
m_currentFrame = m_animData.stillFrame;
else
m_currentFrame = 0;
}
if (m_animData.columns <= 0) return;
i32 col = m_currentFrame % m_animData.columns;
i32 row = (m_animData.rows > 1) ? cast<i32>(m_currentFrame / m_animData.columns) : 0;
i32 x = m_animData.pixelOffsetX
+ ((col + m_animData.columnOffset) * (m_animData.frameWidth + m_animData.spacingX));
i32 y = m_animData.pixelOffsetY
+ ((row + m_animData.rowOffset) * (m_animData.frameHeight + m_animData.spacingY));
m_frameRect = { cast<f32>(x), cast<f32>(y), m_animData.frameWidth, m_animData.frameHeight };
}
}

View file

@ -0,0 +1,107 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ostd/data/AnimationData.hpp>
#include <ostd/math/Geometry.hpp>
#include <ostd/utils/Time.hpp>
#include <ogfx/resources/Image.hpp>
namespace ogfx
{
using AnimationData = ostd::AnimationData;
class Animation
{
public:
inline Animation(void) { }
inline Animation(const AnimationData& ad) { create(ad); };
inline Animation(const AnimationData& ad, Image& spriteSheet) { create(ad, spriteSheet); };
inline Animation& create(const AnimationData& ad) { return create(ad, InvalidImage); }
inline i32 getCurrentFrame(void) const { return m_currentFrame; }
Animation& create(const AnimationData& ad, Image& spriteSheet);
void update(void);
void resetAnimation(void);
inline void setFrameCount(i32 n) { m_animData.frameCount = n; }
inline void setStillFrame(i32 n) { m_animData.stillFrame = n; }
inline void setColumnOffset(i32 o) { m_animData.columnOffset = o; }
inline void setRowOffset(i32 o) { m_animData.rowOffset = o; }
inline void setPixelOffsetX(f32 o) { m_animData.pixelOffsetX = o; }
inline void setPixelOffsetY(f32 o) { m_animData.pixelOffsetY = o; }
inline void setSpacingX(f32 s) { m_animData.spacingX = s; }
inline void setSpacingY(f32 s) { m_animData.spacingY = s; }
inline void setNRows(i32 n) { m_animData.rows = n; }
inline void setNColumns(i32 n) { m_animData.columns = n; }
inline void setFrameWidth(i32 f) { m_animData.frameWidth = f; }
inline void setFrameHeight(i32 f) { m_animData.frameHeight = f; }
inline void enableStill(bool b = true) { m_animData.still = b; }
inline void enableTurnBack(bool b = true) { m_animData.turnBack = b; }
inline void enableRandom(bool b = true) { m_animData.random = b; }
inline i32 getFrameCount(void) { return m_animData.frameCount; }
inline i32 getStillFrame(void) { return m_animData.stillFrame; }
inline f64 getFPS(void) { return m_animData.fps; }
inline i32 getColumnOffset(void) { return m_animData.columnOffset; }
inline i32 getRowOffset(void) { return m_animData.rowOffset; }
inline f32 getPixelOffsetX(void) { return m_animData.pixelOffsetX; }
inline f32 getPixelOffsetY(void) { return m_animData.pixelOffsetY; }
inline f32 getSpacingX(void) { return m_animData.spacingX; }
inline f32 getSpacingY(void) { return m_animData.spacingY; }
inline i32 getNRows(void) { return m_animData.rows; }
inline i32 getNColumns(void) { return m_animData.columns; }
inline i32 getFrameWidth(void) { return m_animData.frameWidth; }
inline i32 getFrameHeight(void) { return m_animData.frameHeight; }
inline bool isStill(void) { return m_animData.still; }
inline bool isTurnBackEnabled(void) { return m_animData.turnBack; }
inline bool isRandomEnabled(void) { return m_animData.random; }
inline void setSpriteSheet(Image& img) { m_spriteSheet = &img; }
inline Rectangle getFrameRect(void) const { return m_frameRect; }
inline const Image& getSpriteSheet(void) const { return (m_spriteSheet != nullptr ? *m_spriteSheet : InvalidImage); }
inline Image& getSpriteSheet(void) { return (m_spriteSheet != nullptr ? *m_spriteSheet : InvalidImage); }
inline bool hasImage(void) const { return m_spriteSheet != nullptr; }
inline const AnimationData& getAnimationData(void) const { return m_animData; }
inline AnimationData& getAnimationData(void) { return m_animData; }
inline void removeSpriteSheet(void) { m_spriteSheet = &InvalidImage; }
private:
void update_animation(void);
private:
inline static Image InvalidImage;
AnimationData m_animData;
ostd::Counter m_timer;
Image* m_spriteSheet { nullptr };
i32 m_currentFrame { 0 };
bool m_back { false };
Rectangle m_frameRect { 0, 0, 0, 0 };
};
struct Icon
{
Image& sheet;
AnimationData info;
Animation asAnim(void) const { return { info, sheet }; }
AnimationData asAnimData(void) const { return info; }
inline operator Animation() const { return asAnim(); }
inline operator AnimationData() const { return asAnimData(); }
};
}

286
src/ogfx/utils/Keycodes.hpp Normal file
View file

@ -0,0 +1,286 @@
/*
OmniaFramework - A collection of useful functionality
Copyright (C) 2026 OmniaX-Dev
This file is part of OmniaFramework.
OmniaFramework is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmniaFramework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <ogfx/utils/SDLInclude.hpp>
namespace ogfx
{
struct KeyCode
{
inline static constexpr u32 Unknown { SDLK_UNKNOWN };
inline static constexpr u32 Return { SDLK_RETURN };
inline static constexpr u32 Escape { SDLK_ESCAPE };
inline static constexpr u32 Backspace { SDLK_BACKSPACE };
inline static constexpr u32 Tab { SDLK_TAB };
inline static constexpr u32 Space { SDLK_SPACE };
inline static constexpr u32 Exclaim { SDLK_EXCLAIM };
inline static constexpr u32 Dblapostrophe { SDLK_DBLAPOSTROPHE };
inline static constexpr u32 Hash { SDLK_HASH };
inline static constexpr u32 Dollar { SDLK_DOLLAR };
inline static constexpr u32 Percent { SDLK_PERCENT };
inline static constexpr u32 Ampersand { SDLK_AMPERSAND };
inline static constexpr u32 Apostrophe { SDLK_APOSTROPHE };
inline static constexpr u32 Leftparen { SDLK_LEFTPAREN };
inline static constexpr u32 Rightparen { SDLK_RIGHTPAREN };
inline static constexpr u32 Asterisk { SDLK_ASTERISK };
inline static constexpr u32 Plus { SDLK_PLUS };
inline static constexpr u32 Comma { SDLK_COMMA };
inline static constexpr u32 Minus { SDLK_MINUS };
inline static constexpr u32 Period { SDLK_PERIOD };
inline static constexpr u32 Slash { SDLK_SLASH };
inline static constexpr u32 Num0 { SDLK_0 };
inline static constexpr u32 Num1 { SDLK_1 };
inline static constexpr u32 Num2 { SDLK_2 };
inline static constexpr u32 Num3 { SDLK_3 };
inline static constexpr u32 Num4 { SDLK_4 };
inline static constexpr u32 Num5 { SDLK_5 };
inline static constexpr u32 Num6 { SDLK_6 };
inline static constexpr u32 Num7 { SDLK_7 };
inline static constexpr u32 Num8 { SDLK_8 };
inline static constexpr u32 Num9 { SDLK_9 };
inline static constexpr u32 Colon { SDLK_COLON };
inline static constexpr u32 Semicolon { SDLK_SEMICOLON };
inline static constexpr u32 Less { SDLK_LESS };
inline static constexpr u32 Equals { SDLK_EQUALS };
inline static constexpr u32 Greater { SDLK_GREATER };
inline static constexpr u32 Question { SDLK_QUESTION };
inline static constexpr u32 At { SDLK_AT };
inline static constexpr u32 LeftBracket { SDLK_LEFTBRACKET };
inline static constexpr u32 Backslash { SDLK_BACKSLASH };
inline static constexpr u32 RightBracket { SDLK_RIGHTBRACKET };
inline static constexpr u32 Caret { SDLK_CARET };
inline static constexpr u32 Underscore { SDLK_UNDERSCORE };
inline static constexpr u32 Grave { SDLK_GRAVE };
inline static constexpr u32 A { SDLK_A };
inline static constexpr u32 B { SDLK_B };
inline static constexpr u32 C { SDLK_C };
inline static constexpr u32 D { SDLK_D };
inline static constexpr u32 E { SDLK_E };
inline static constexpr u32 F { SDLK_F };
inline static constexpr u32 G { SDLK_G };
inline static constexpr u32 H { SDLK_H };
inline static constexpr u32 I { SDLK_I };
inline static constexpr u32 J { SDLK_J };
inline static constexpr u32 K { SDLK_K };
inline static constexpr u32 L { SDLK_L };
inline static constexpr u32 M { SDLK_M };
inline static constexpr u32 N { SDLK_N };
inline static constexpr u32 O { SDLK_O };
inline static constexpr u32 P { SDLK_P };
inline static constexpr u32 Q { SDLK_Q };
inline static constexpr u32 R { SDLK_R };
inline static constexpr u32 S { SDLK_S };
inline static constexpr u32 T { SDLK_T };
inline static constexpr u32 U { SDLK_U };
inline static constexpr u32 V { SDLK_V };
inline static constexpr u32 W { SDLK_W };
inline static constexpr u32 X { SDLK_X };
inline static constexpr u32 Y { SDLK_Y };
inline static constexpr u32 Z { SDLK_Z };
inline static constexpr u32 Leftbrace { SDLK_LEFTBRACE };
inline static constexpr u32 Pipe { SDLK_PIPE };
inline static constexpr u32 Rightbrace { SDLK_RIGHTBRACE };
inline static constexpr u32 Tilde { SDLK_TILDE };
inline static constexpr u32 Delete { SDLK_DELETE };
inline static constexpr u32 Plusminus { SDLK_PLUSMINUS };
inline static constexpr u32 Capslock { SDLK_CAPSLOCK };
inline static constexpr u32 F1 { SDLK_F1 };
inline static constexpr u32 F2 { SDLK_F2 };
inline static constexpr u32 F3 { SDLK_F3 };
inline static constexpr u32 F4 { SDLK_F4 };
inline static constexpr u32 F5 { SDLK_F5 };
inline static constexpr u32 F6 { SDLK_F6 };
inline static constexpr u32 F7 { SDLK_F7 };
inline static constexpr u32 F8 { SDLK_F8 };
inline static constexpr u32 F9 { SDLK_F9 };
inline static constexpr u32 F10 { SDLK_F10 };
inline static constexpr u32 F11 { SDLK_F11 };
inline static constexpr u32 F12 { SDLK_F12 };
inline static constexpr u32 Printscreen { SDLK_PRINTSCREEN };
inline static constexpr u32 Scrolllock { SDLK_SCROLLLOCK };
inline static constexpr u32 Pause { SDLK_PAUSE };
inline static constexpr u32 Insert { SDLK_INSERT };
inline static constexpr u32 Home { SDLK_HOME };
inline static constexpr u32 Pageup { SDLK_PAGEUP };
inline static constexpr u32 End { SDLK_END };
inline static constexpr u32 Pagedown { SDLK_PAGEDOWN };
inline static constexpr u32 Right { SDLK_RIGHT };
inline static constexpr u32 Left { SDLK_LEFT };
inline static constexpr u32 Down { SDLK_DOWN };
inline static constexpr u32 Up { SDLK_UP };
inline static constexpr u32 Numlockclear { SDLK_NUMLOCKCLEAR };
inline static constexpr u32 KpDivide { SDLK_KP_DIVIDE };
inline static constexpr u32 KpMultiply { SDLK_KP_MULTIPLY };
inline static constexpr u32 KpMinus { SDLK_KP_MINUS };
inline static constexpr u32 KpPlus { SDLK_KP_PLUS };
inline static constexpr u32 KpEnter { SDLK_KP_ENTER };
inline static constexpr u32 Kp1 { SDLK_KP_1 };
inline static constexpr u32 Kp2 { SDLK_KP_2 };
inline static constexpr u32 Kp3 { SDLK_KP_3 };
inline static constexpr u32 Kp4 { SDLK_KP_4 };
inline static constexpr u32 Kp5 { SDLK_KP_5 };
inline static constexpr u32 Kp6 { SDLK_KP_6 };
inline static constexpr u32 Kp7 { SDLK_KP_7 };
inline static constexpr u32 Kp8 { SDLK_KP_8 };
inline static constexpr u32 Kp9 { SDLK_KP_9 };
inline static constexpr u32 Kp0 { SDLK_KP_0 };
inline static constexpr u32 KpPeriod { SDLK_KP_PERIOD };
inline static constexpr u32 Application { SDLK_APPLICATION };
inline static constexpr u32 Power { SDLK_POWER };
inline static constexpr u32 KpEquals { SDLK_KP_EQUALS };
inline static constexpr u32 F13 { SDLK_F13 };
inline static constexpr u32 F14 { SDLK_F14 };
inline static constexpr u32 F15 { SDLK_F15 };
inline static constexpr u32 F16 { SDLK_F16 };
inline static constexpr u32 F17 { SDLK_F17 };
inline static constexpr u32 F18 { SDLK_F18 };
inline static constexpr u32 F19 { SDLK_F19 };
inline static constexpr u32 F20 { SDLK_F20 };
inline static constexpr u32 F21 { SDLK_F21 };
inline static constexpr u32 F22 { SDLK_F22 };
inline static constexpr u32 F23 { SDLK_F23 };
inline static constexpr u32 F24 { SDLK_F24 };
inline static constexpr u32 Execute { SDLK_EXECUTE };
inline static constexpr u32 Help { SDLK_HELP };
inline static constexpr u32 Menu { SDLK_MENU };
inline static constexpr u32 Select { SDLK_SELECT };
inline static constexpr u32 Stop { SDLK_STOP };
inline static constexpr u32 Again { SDLK_AGAIN };
inline static constexpr u32 Undo { SDLK_UNDO };
inline static constexpr u32 Cut { SDLK_CUT };
inline static constexpr u32 Copy { SDLK_COPY };
inline static constexpr u32 Paste { SDLK_PASTE };
inline static constexpr u32 Find { SDLK_FIND };
inline static constexpr u32 Mute { SDLK_MUTE };
inline static constexpr u32 Volumeup { SDLK_VOLUMEUP };
inline static constexpr u32 Volumedown { SDLK_VOLUMEDOWN };
inline static constexpr u32 KpComma { SDLK_KP_COMMA };
inline static constexpr u32 KpEqualsas400 { SDLK_KP_EQUALSAS400 };
inline static constexpr u32 Alterase { SDLK_ALTERASE };
inline static constexpr u32 Sysreq { SDLK_SYSREQ };
inline static constexpr u32 Cancel { SDLK_CANCEL };
inline static constexpr u32 Clear { SDLK_CLEAR };
inline static constexpr u32 Prior { SDLK_PRIOR };
inline static constexpr u32 Return2 { SDLK_RETURN2 };
inline static constexpr u32 Separator { SDLK_SEPARATOR };
inline static constexpr u32 Out { SDLK_OUT };
inline static constexpr u32 Oper { SDLK_OPER };
inline static constexpr u32 Clearagain { SDLK_CLEARAGAIN };
inline static constexpr u32 Crsel { SDLK_CRSEL };
inline static constexpr u32 Exsel { SDLK_EXSEL };
inline static constexpr u32 Kp00 { SDLK_KP_00 };
inline static constexpr u32 Kp000 { SDLK_KP_000 };
inline static constexpr u32 Thousandsseparator { SDLK_THOUSANDSSEPARATOR };
inline static constexpr u32 Decimalseparator { SDLK_DECIMALSEPARATOR };
inline static constexpr u32 Currencyunit { SDLK_CURRENCYUNIT };
inline static constexpr u32 Currencysubunit { SDLK_CURRENCYSUBUNIT };
inline static constexpr u32 KpLeftparen { SDLK_KP_LEFTPAREN };
inline static constexpr u32 KpRightparen { SDLK_KP_RIGHTPAREN };
inline static constexpr u32 KpLeftbrace { SDLK_KP_LEFTBRACE };
inline static constexpr u32 KpRightbrace { SDLK_KP_RIGHTBRACE };
inline static constexpr u32 KpTab { SDLK_KP_TAB };
inline static constexpr u32 KpBackspace { SDLK_KP_BACKSPACE };
inline static constexpr u32 KpA { SDLK_KP_A };
inline static constexpr u32 KpB { SDLK_KP_B };
inline static constexpr u32 KpC { SDLK_KP_C };
inline static constexpr u32 KpD { SDLK_KP_D };
inline static constexpr u32 KpE { SDLK_KP_E };
inline static constexpr u32 KpF { SDLK_KP_F };
inline static constexpr u32 KpXor { SDLK_KP_XOR };
inline static constexpr u32 KpPower { SDLK_KP_POWER };
inline static constexpr u32 KpPercent { SDLK_KP_PERCENT };
inline static constexpr u32 KpLess { SDLK_KP_LESS };
inline static constexpr u32 KpGreater { SDLK_KP_GREATER };
inline static constexpr u32 KpAmpersand { SDLK_KP_AMPERSAND };
inline static constexpr u32 KpDblampersand { SDLK_KP_DBLAMPERSAND };
inline static constexpr u32 KpVerticalbar { SDLK_KP_VERTICALBAR };
inline static constexpr u32 KpDblverticalbar { SDLK_KP_DBLVERTICALBAR };
inline static constexpr u32 KpColon { SDLK_KP_COLON };
inline static constexpr u32 KpHash { SDLK_KP_HASH };
inline static constexpr u32 KpSpace { SDLK_KP_SPACE };
inline static constexpr u32 KpAt { SDLK_KP_AT };
inline static constexpr u32 KpExclam { SDLK_KP_EXCLAM };
inline static constexpr u32 KpMemstore { SDLK_KP_MEMSTORE };
inline static constexpr u32 KpMemrecall { SDLK_KP_MEMRECALL };
inline static constexpr u32 KpMemclear { SDLK_KP_MEMCLEAR };
inline static constexpr u32 KpMemadd { SDLK_KP_MEMADD };
inline static constexpr u32 KpMemsubtract { SDLK_KP_MEMSUBTRACT };
inline static constexpr u32 KpMemmultiply { SDLK_KP_MEMMULTIPLY };
inline static constexpr u32 KpMemdivide { SDLK_KP_MEMDIVIDE };
inline static constexpr u32 KpPlusminus { SDLK_KP_PLUSMINUS };
inline static constexpr u32 KpClear { SDLK_KP_CLEAR };
inline static constexpr u32 KpClearentry { SDLK_KP_CLEARENTRY };
inline static constexpr u32 KpBinary { SDLK_KP_BINARY };
inline static constexpr u32 KpOctal { SDLK_KP_OCTAL };
inline static constexpr u32 KpDecimal { SDLK_KP_DECIMAL };
inline static constexpr u32 KpHexadecimal { SDLK_KP_HEXADECIMAL };
inline static constexpr u32 Lctrl { SDLK_LCTRL };
inline static constexpr u32 Lshift { SDLK_LSHIFT };
inline static constexpr u32 Lalt { SDLK_LALT };
inline static constexpr u32 Lgui { SDLK_LGUI };
inline static constexpr u32 Rctrl { SDLK_RCTRL };
inline static constexpr u32 Rshift { SDLK_RSHIFT };
inline static constexpr u32 Ralt { SDLK_RALT };
inline static constexpr u32 Rgui { SDLK_RGUI };
inline static constexpr u32 Mode { SDLK_MODE };
inline static constexpr u32 Sleep { SDLK_SLEEP };
inline static constexpr u32 Wake { SDLK_WAKE };
inline static constexpr u32 ChannelIncrement { SDLK_CHANNEL_INCREMENT };
inline static constexpr u32 ChannelDecrement { SDLK_CHANNEL_DECREMENT };
inline static constexpr u32 MediaPlay { SDLK_MEDIA_PLAY };
inline static constexpr u32 MediaPause { SDLK_MEDIA_PAUSE };
inline static constexpr u32 MediaRecord { SDLK_MEDIA_RECORD };
inline static constexpr u32 MediaFastForward { SDLK_MEDIA_FAST_FORWARD };
inline static constexpr u32 MediaRewind { SDLK_MEDIA_REWIND };
inline static constexpr u32 MediaNextTrack { SDLK_MEDIA_NEXT_TRACK };
inline static constexpr u32 MediaPreviousTrack { SDLK_MEDIA_PREVIOUS_TRACK };
inline static constexpr u32 MediaStop { SDLK_MEDIA_STOP };
inline static constexpr u32 MediaEject { SDLK_MEDIA_EJECT };
inline static constexpr u32 MediaPlayPause { SDLK_MEDIA_PLAY_PAUSE };
inline static constexpr u32 MediaSelect { SDLK_MEDIA_SELECT };
inline static constexpr u32 AcNew { SDLK_AC_NEW };
inline static constexpr u32 AcOpen { SDLK_AC_OPEN };
inline static constexpr u32 AcClose { SDLK_AC_CLOSE };
inline static constexpr u32 AcExit { SDLK_AC_EXIT };
inline static constexpr u32 AcSave { SDLK_AC_SAVE };
inline static constexpr u32 AcPrint { SDLK_AC_PRINT };
inline static constexpr u32 AcProperties { SDLK_AC_PROPERTIES };
inline static constexpr u32 AcSearch { SDLK_AC_SEARCH };
inline static constexpr u32 AcHome { SDLK_AC_HOME };
inline static constexpr u32 AcBack { SDLK_AC_BACK };
inline static constexpr u32 AcForward { SDLK_AC_FORWARD };
inline static constexpr u32 AcStop { SDLK_AC_STOP };
inline static constexpr u32 AcRefresh { SDLK_AC_REFRESH };
inline static constexpr u32 AcBookmarks { SDLK_AC_BOOKMARKS };
inline static constexpr u32 Softleft { SDLK_SOFTLEFT };
inline static constexpr u32 Softright { SDLK_SOFTRIGHT };
inline static constexpr u32 Call { SDLK_CALL };
inline static constexpr u32 Endcall { SDLK_ENDCALL };
inline static constexpr u32 LeftTab { SDLK_LEFT_TAB };
inline static constexpr u32 Level5Shift { SDLK_LEVEL5_SHIFT };
inline static constexpr u32 MultiKeyCompose { SDLK_MULTI_KEY_COMPOSE };
inline static constexpr u32 Lmeta { SDLK_LMETA };
inline static constexpr u32 Rmeta { SDLK_RMETA };
inline static constexpr u32 Lhyper { SDLK_LHYPER };
inline static constexpr u32 Rhyper { SDLK_RHYPER };
};
}

Some files were not shown because too many files have changed in this diff Show more