126 Commits

Author SHA1 Message Date
b48ef7904b Link against system-cxx-std-lib for GHC 9.4+ (#197)
* Link against system-cxx-std-lib for GHC 9.4+

* Drop OS checks for linking with stdc++ when GHC < 9.4
2024-02-23 14:43:53 +02:00
f6cad45dab Add support for disabled blocks (#196) 2024-02-20 19:20:46 +00:00
49f7bb245e Add support for dispatching raw SDL events to Dear ImGui (#195) 2024-02-20 21:16:37 +02:00
47402c1a93 Update README (#194) 2024-02-04 18:24:06 +00:00
4d1c66e9a1 Add support for SDL2 Renderer backend (#193)
* Add DearImGui.Raw.framerate
* Add DearImGui.withCloseableWindow
* Closes #191: Add support for SDL2 Renderer backend
* Add sdl-renderer flag to protect against older SDL versions that do not have SDL_RenderGeometry
2023-12-15 15:31:04 +02:00
7ec260c359 Bump megaparsec (#190) 2023-09-10 18:55:24 +00:00
bab4d769ea V2.2.0 (#189)
* Upgrade upstream and prepare 2.2.0
* Update vulkan example
2023-09-10 13:24:33 +00:00
eec8b57ce8 Use FIFO for vulkan demo (#188) 2023-09-04 10:10:10 +00:00
d40fa4f6db fix intem->item (#184) 2023-08-09 13:03:43 +03:00
8df98e075c Fix TabItem flags type (#183)
Resolves #175
2023-07-20 16:42:11 +00:00
6dbb455d62 Fix vulkan init wrapper (#180)
`init_info` got dynamic render flag, which is a breaking change with its default value.
Setting it to `false` will fix validation errors coming from misconfiguration.
2023-07-20 16:25:02 +00:00
ddaf41bf88 Upgrade imgui to v1.89.7 2023-07-17 19:13:23 +03:00
8368192370 Allow base-4.19 for ghc-9.6 (#177)
Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
2023-07-02 21:54:03 +00:00
ea3ad959f9 Add getCursorPos (#176) 2023-07-03 00:48:44 +03:00
0cc654f190 Update bound for ghc-9.6 (#174)
* Update bound for ghc-9.6

* Update ci action versions
2023-05-07 17:44:13 +03:00
8697aa3a0a Expose DearImGui.Raw.Context (#172) 2023-01-15 15:27:15 +02:00
802bdb72fe Fix cabal flag for image example (#170) 2022-12-24 12:21:51 +02:00
69a463d98b Fix vulkan versions to a fresh set (#168)
vulkan, vulkan-utils and VMA can be too old/new for each other.
This cuts a fresh, known-to-work set of lower bounds.
2022-12-12 18:33:48 +00:00
9bb66f0113 Fix the text fix and prepare 2.1.3 (#167) 2022-12-12 19:57:54 +02:00
68e30d98ad Fix off-by-one bug in string null termination (#166)
Backport withCString fix and use text version when available
2022-12-12 18:20:39 +03:00
52142bbf7e Add formatPtr to Raw.dragFloat* and Raw.sliderFloat* (#165) 2022-12-05 17:47:21 +03:00
d933248a2c This change fixes the high level API to use the right Raw call (#164) 2022-12-02 17:50:04 +02:00
258777f8c7 Fix sdl flag in cabal.project (#163) 2022-11-30 17:08:06 +00:00
cd99938f97 Prepare v2.1.2 (#162) 2022-11-30 16:32:44 +00:00
48486ee8c2 Add setNextItemOpen (#161)
This change enables starting a new TreeNode open.
2022-11-28 15:56:27 +03:00
a2feb73fa5 Fix the glfw example build condition (#159)
The example needs the opengl2 flag.
2022-11-22 21:13:18 +02:00
051a17a1c5 Add plotLines (#158) 2022-11-20 17:57:15 +02:00
9dac0f9fbe Prepare 2.1.1 (#157) 2022-08-30 21:13:04 +00:00
dab5937eee Fix compilation on MacOS / GHC 8.10.7 (#156)
Fixes errors along the lines of:
dear-imgui  > [ 2 of 17] Compiling DearImGui.GLFW
dear-imgui  > error: unknown type name 'constexpr'

So the "-std=c++11" option is not being passed to the C++ compiler on
MacOS.
The issue seems related to https://github.com/haskell/cabal/issues/6421
2022-08-30 21:57:08 +03:00
7795b3d838 Prepare 2.1.0 (#153)
Breaking change in upstream.
2022-07-25 18:14:21 +00:00
3a5abb2037 Update to 1.88 (#152) 2022-07-25 17:58:22 +00:00
06eb052cc5 added flag_ImDrawIdx (#151)
Co-authored-by: Stefan Dresselhaus <stefan@dresselhaus.cloud>
2022-07-23 15:42:13 +03:00
cf87988336 Prepare 2.0.0 (#148) 2022-05-15 23:37:43 +03:00
3c1d381c14 Replace String arguments with Text (#138)
Shave a few allocations and pointer-chasing due to conversion.
2022-05-15 22:41:10 +03:00
04fe618871 Prepare 1.5.0 (#140) 2022-03-28 13:22:11 +00:00
08d4b423ad Fix GHC-9.2 build (#145) 2022-03-28 13:04:22 +00:00
7d4f3a8b93 Make value and read-only range types distinct (#143)
Fixes #142
2022-03-23 21:22:05 +03:00
bc590d97c5 Tweak tables and add an example (#139)
Previously: #135
2022-03-22 22:36:19 +03:00
e5969f6b35 implementation of ImGui Tables (#135) 2022-03-11 16:48:11 +03:00
f066d03017 added options to selectable (#137) 2022-03-10 15:17:42 +00:00
fc307a4d6e Add remaining popup wrappers (#136)
- BeginPopupContextItem
- BeginPopupContextWindow
- BeginPopupContextVoid

For #132
2022-03-10 11:34:13 +03:00
4517af8123 Add isPopupOpen and wrappers (#134) 2022-03-09 21:08:54 +03:00
b837d583a5 added openPopupOnItemClick (#133) 2022-02-25 17:28:53 +00:00
67e169dc35 Prepare 1.4.0 (#129) 2022-02-13 19:31:45 +03:00
ae3fdb8bc3 Add new GLFW callback from 1.87 (#128) 2022-02-13 16:12:15 +00:00
ccdff36774 Add wchar32 and disable-obsolete flags (#127) 2022-02-13 16:05:49 +00:00
af6ba9e989 Add image support for vulkan backend (#126) 2022-02-13 14:24:08 +00:00
dc11fad07f Update to 1.87 (#125) 2022-02-13 00:26:39 +03:00
265d143261 Prepare 1.3.1 (#123) 2022-01-31 10:56:12 +03:00
0877843619 Update upstream to 1.86 (#122) 2022-01-19 23:36:33 +03:00
a95d95bb65 Prepare 1.3.0 (#120) 2021-12-22 17:40:24 +03:00
23efa7cb02 Fill in changelog for 1.2.1 (#119) 2021-12-22 11:56:55 +00:00
bb94341ad5 Extended font & glyph support (#114)
* Separate font utils, add glyph support (#113)
* Implement font glyph ranges builder
* Implement raw font config binds
* Implement font atlas module
* Rewrite font altas rebuilder in Managed
2021-12-22 13:28:46 +03:00
13e68242a1 Update unordered-containers upper limit (#117) 2021-12-19 18:25:11 +03:00
2469623f2e Fix CI (#118) 2021-12-19 17:32:28 +03:00
3087a99044 Allow megaparsec 9.2 (#112)
Closes #106
2021-10-30 18:57:15 +00:00
f74cd218c5 Bump imgui to 1.85 (#111)
Closes #110
2021-10-30 18:49:39 +00:00
48c8ae0379 Add IO exports (#109) 2021-10-04 18:49:16 +00:00
a2c0c0658e Remove library options preventing hackage upload (#108) 2021-10-04 18:41:07 +00:00
21ce5cabd8 Add ImGuiIO basics (#107) 2021-10-04 17:29:32 +00:00
8db9ddec2f Added compiler flags stanzas. (#105) 2021-09-21 15:14:37 +03:00
4ecf62ac9e Implement ImGuiListClipper (#100)
* Added DearImGui.Raw.ImGuiListClipper
* Added the DearImGui.withListClipper bracket
* Added dependency for `vector`
* Added the test in the Main.hs
2021-09-17 11:09:22 +03:00
8ee82476dc Add raw DrawList bindings (#99) 2021-09-15 08:52:00 +03:00
b4bc36ca89 Update readme example (#103)
Fixes #68
2021-09-14 15:41:38 +03:00
76ce7bb569 Nix upgrades (#102)
Switches the default Nix build to use GHC 8.10.7 and NixOS 21.05
2021-09-14 01:58:39 +00:00
259ffbff48 Fix DearImGui TOC (#101) 2021-09-13 17:05:49 +00:00
97825e49f2 Prepare 1.2.0 (#98) 2021-09-12 19:46:05 +03:00
24345bb8f3 Add font utils (#56)
- clearFontAtlas
- addFontDefault
- addFontFromFileTTF
- addFontFromMemoryTTF (raw only)
- buildFontAtlas

Vulkan example updated to use on f the imgui-distributed ttf files.
2021-09-12 15:41:42 +00:00
e3f7fbfd6f Add imageButton (#97) 2021-09-12 13:20:47 +00:00
88326420b8 Tidy up image example (#96) 2021-09-12 11:56:43 +00:00
f3b85899f2 Added image wrapper (#74)
Raw.image and sdl2/gl example "image"

Wrappers should be backend-specific due to different handling of `userTextureIDPtr`.
2021-09-12 10:35:03 +00:00
c7a694bce8 Add remaining BeginChild arguments as required (#93)
Old behaviour with all default arguments is a special case to run
some action scoped to a different child window.

This now handled by `beginChildContext`/`withChildContext`.
2021-09-12 10:28:48 +00:00
c219f8eb4f Wrap GetCurrentContext and SetCurrentContext (#94) 2021-09-12 10:23:23 +00:00
24519778e6 Add inputTextMultiline, inputTextWithHint (#92)
- Experimental explicit encoding for CStrings.
- Fix potential buffer overruns in inputText.
2021-09-12 11:43:44 +03:00
4bfc7e7099 Add invisibleButton (#91) 2021-09-11 11:01:03 +00:00
efaaa5723a Add Show instances to structs and enums (#90) 2021-09-11 10:40:05 +00:00
be7aa1e9b1 Add functions for getting window position and size (#89)
- getWindowPos
- getWindowSize
- getWindowWidth
- getWindowHeight

Closes #88
2021-09-11 10:09:11 +00:00
08b3139477 Remove seg faults (#87)
* Removed double OpenGl3Shutdown, leading to a segmentation fault in Main.hs.
* Changed nullPtr passing with Maybe to use DearImGui default arguments.
2021-09-11 10:00:08 +00:00
cede825dff Bump haskell.nix (#86) 2021-09-08 16:19:57 +00:00
84a6b8a8fe Allow parser-combinators-1.4 (#85) 2021-09-08 13:10:39 +01:00
cb687b8f01 Prepare 1.1.0 (#83) 2021-09-01 18:05:18 +00:00
8d07a5a42b Add more withXXX wrappers (#82)
- withStyleColor
- withStyleVar
- withIndent
- withItemWidth

Closes #63
2021-09-01 17:23:59 +00:00
d3a0396623 Add GLFW callbacks (#81)
Closes #80
2021-09-01 19:22:55 +03:00
f49e81c739 Fix warnings (#79) 2021-08-30 17:08:23 +00:00
5699f64e95 Bump imgui to 1.84.2 (#78) 2021-08-30 19:57:00 +03:00
9e5b39850e Prepare 1.0.2 (#77) 2021-08-30 19:20:26 +03:00
d7dc999e8b Add withID (#75)
Raw versions are specialized to match overloaded C++ functions.
2021-08-28 15:52:04 +00:00
bde2030c25 Upgrade Haskell.nix and niv (#73) 2021-07-01 12:58:55 +00:00
1706b7e966 Fix changelog header and links (#72) 2021-06-30 22:44:31 +00:00
bfe8453891 Fix missing headers in source dist (#71)
Fixes #50 again
2021-06-30 22:33:00 +00:00
532eebd8ed Prepare Hackage release (#70) 2021-07-01 00:47:23 +03:00
d42eb672a1 Bump imgui to 1.83 (#66)
Fixes reported vulkan error among others.
2021-06-20 19:43:49 +00:00
1d6b7cc97b Relax MonadUnliftIO constraint on vulkanInit (#65) 2021-06-20 19:17:58 +00:00
c4f3a1e0b9 Update all flags to allow setting them manually (#64) 2021-06-20 20:24:22 +03:00
ff267143d0 Bump deps (#62) 2021-06-15 21:52:39 +00:00
dcaad12ca8 Add more drags and sliders (#60)
- DragFloatRange2
- DragInt..4
- DragIntRange2
- DragScalar
- DragScalarN

- SliderAngle
- SliderInt..4
- SliderScalar
- SliderScalarN
- vSliderFloat
- vSliderInt
- vSliderScalar

Scalar sliders expose format and flags arguments.
2021-06-06 19:10:34 +03:00
f584319577 Add more text widgets (#59)
- Text replaced with TextUnformatted
- TextColored
- TextDisabled
- TextWrapped
- LabelText
- BulletText
2021-06-05 09:01:48 +00:00
6ccee5234b Add withFullscreen and related machinery (#55)
- `fullscreenFlags` available for those who want an alternative
  to `withFullscreen` without reinventing too much.
- Raw.begin got `open` and `flags` arguments.
- Added Raw.setNextWindowFullscreen combo block.
2021-06-04 23:18:16 +03:00
73eee5fc9e Upgrade to dear-imgui v1.82 (#57) 2021-05-08 11:58:25 +00:00
5cdce50c3a Add wantCaptureMouse, wantCaptureKeyboard (#54) 2021-05-03 12:57:23 +03:00
8723ac2625 Add withXxx and withXxxOpen wrappers for begin/end pairs (#49)
Adds dependency on unliftio for monad-preserving brackets.

Fixes #32
2021-04-18 13:10:20 +03:00
b921a72960 Update generator for GHC 9.2 (#48) 2021-04-09 17:18:00 +03:00
5634b6f67d Extract raw C bindings (#44)
The original DearImGui interface hasn't changed.
2021-04-05 20:16:09 +03:00
3949882060 Disable build-depends when not building executables (#43)
This change follows up on https://github.com/haskell-game/dear-imgui.hs/pull/41
where it seems like cabal still need the examples dependency even when they are
not buildable, e.g.: `next goal: vulkan-utils (dependency of dear-imgui)` with
cabal-install version 3.2.0.0.
2021-03-12 15:39:24 +00:00
b0337eb084 Update StateVars only when its widget reports a change (#42) 2021-03-12 11:03:54 +00:00
ebd5286e1c Build executables conditionally on features (#41)
* Build executables conditionally on features

* Put away examples under a flag
2021-03-11 22:59:57 +00:00
2eddbdfa04 Recover init and shutdown from withVulkan (#40) 2021-03-11 09:00:30 +00:00
007b3cccb8 Bindings for item widths functions, and text input widget. (#38) 2021-02-21 11:39:17 +00:00
06921defb1 Generator: use mkName instead of newName (#37) 2021-02-09 11:23:23 +00:00
d4aec47f4e Handle remaining enums (#36)
This handles the remaining enum types in the headers that aren't in the enums section.

It also automatically handles adding all the enumerations to the inline-c context types table, and a small improvement to the display of parse error messages.
2021-02-07 23:07:14 +00:00
921aefdd69 Allow building of OpenGL3 component on Windows/Darwin (#35) 2021-02-06 21:19:56 +00:00
9e5dbd755f Add haskell-language-server to the dev shell (#34) 2021-02-06 15:43:26 +00:00
f9412effde Wrap the OpenGL 3 backend (#20) 2021-02-06 14:44:58 +00:00
ac74572121 Add tab bar functions (#30) 2021-02-06 13:26:28 +00:00
860720e7c2 Define types for use in ImGui Context (#31)
Just doing a little cleanup:

* some functions were not exported,
* some functions were missing the initial | for their documentation,
* add types to the ImGui Context instead of coercing to int. This is more robust, in case upstream changes any of the larger enums to be 64 bits instead of 32 for instance
2021-02-06 10:17:37 +00:00
de0e87612c Add setNextWindow functions, pushColor, pushStyle, indent-related functions (#27) 2021-02-05 23:46:48 +00:00
d7686f84e4 Add support for GLFW (#26)
Co-authored-by: Oliver Charles <ollie@ocharles.org.uk>
2021-02-05 21:44:52 +00:00
643d2ea5b7 Add combo to wrap ImGUI::Combo() (#28)
Co-authored-by: Oliver Charles <ollie@ocharles.org.uk>
2021-02-05 21:20:32 +00:00
895f5c1926 Parse enums from headers & generate code (#19) 2021-02-05 20:57:17 +00:00
81582ba6eb Ignore imgui.ini (#29)
This file gets generated by routine testing but we'll never want to commit it.
2021-02-05 20:22:26 +00:00
af49a7b3fb Wrap ImGui::ListBox() (#25) 2021-01-28 23:38:59 +00:00
bb82e87553 Wrap ImGui::TreeNode, TreePush, TreePop (#24) 2021-01-28 23:28:45 +00:00
24903ce76f Implement ImGui::DragFloat{,2,3,4} (#23) 2021-01-28 23:10:58 +00:00
f24a4b78ab Implement ImGui::SliderFloat2,3,4 (#22) 2021-01-28 23:02:04 +00:00
63bb63a32e Wrap ImGui::BeginChild and EndChild (#21) 2021-01-28 22:38:25 +00:00
397cea7bd9 Add Cachix (#14) 2021-01-27 09:08:33 +00:00
50 changed files with 9568 additions and 775 deletions

View File

@ -4,13 +4,19 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v2.4.0
with:
persist-credentials: false
submodules: true
- uses: cachix/install-nix-action@v12
- uses: cachix/install-nix-action@v20
with:
nix_path: nixpkgs=channel:nixos-unstable
- run: nix-build -A hsPkgs.dear-imgui.components.exes --option trusted-public-keys "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" --option substituters "https://hydra.iohk.io https://iohk.cachix.org https://cache.nixos.org/"
- uses: cachix/cachix-action@v12
with:
name: hs-dear-imgui
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix-build --version
- run: nix-build -A hsPkgs.dear-imgui.components.exes

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
/imgui.ini

125
ChangeLog.md Normal file
View File

@ -0,0 +1,125 @@
# Changelog for dear-imgui
## [2.2.0]
- `imgui` updated to [1.89.9].
- Update bounds for ghc-9.6.
- Exposed `DearImGui.Raw.Context`.
- Added `getCursorPos``.
- Fix TabItem flags type.
## [2.1.3]
- Fixed off-by-1 in internal Text wrapper.
- Fixed sliderFloat* Raw calls
- Added `formatPtr` to Raw.dragFloat* and Raw.sliderFloat*
## [2.1.2]
- Fixed glfw example build flags.
- Added `plotLines`.
- Added `setNextItemOpen`.
## [2.1.1]
- Build flag fix for MacOS.
## [2.1.0]
- `imgui` updated to [1.88].
* Breaking: `ImGuiKeyModFlags` renamed to `ImGuiModFlags`.
## [2.0.0]
- `String` arguments replaced with `Text`.
* Upgrading to `text-2` recommended to reap the UTF-8 benefits.
## [1.5.0]
- Added table wrappers.
- Added popup wrappers.
- Added `selectableWith`/`SelectableOptions` to expose optional arguments.
- Fix GHC-9.2 compatibility.
## [1.4.0]
- `imgui` updated to [1.87].
- Added `DearImGui.Vulkan.vulkanAddTexture`.
- Added `DearImGui.GLFW.glfwCursorPosCallback`.
* Apps that don't install backend callbacks, *must* call it themselves.
- Added flags `use-wchar32` (default on) and `disable-obsolete` (default off).
## [1.3.1]
- `imgui` updated to [1.86].
## [1.3.0]
- Added `DearImGui.FontAtlas` and related `DearImGui.Raw.Font.*` bits.
- Removed old font atlas functions from `DearImGui` and `DearImGui.Raw`.
## [1.2.2]
- `imgui` updated to [1.85].
## [1.2.1]
- Added `DearImGui.Raw.DrawList` for drawing primitives.
- Added `DearImGui.Raw.IO` with attribute setters.
- Added `DearImGui.Raw.ListClipper` for efficient list viewports.
## [1.2.0]
- Fixed `nullPtr` in place of default arguments.
- Added functions for getting window position and size.
- Added `invisibleButton`.
- Added `inputTextMultiline` and `inputTextWithHint`.
- Changed `beginChild` and related `withChild*` to use full arguments.
- Added `withChildContext` to run actions inside other child window.
- Added `getCurrentContext`, `setCurrentContext`.
- Added `image` and `imageButton`.
- Added font atlas utilities.
## [1.1.0]
- `imgui` updated to [1.84.2].
- Removed unused Window argument from SDL `newFrame` to match 1.84.
- Added GLFW backend callbacks.
- Added more withXXX wrappers.
## [1.0.2]
- Added `withID` and `ToID(..)` to make composable components possible.
## [1.0.1]
- Fixed missing headers in source dist.
## [1.0.0]
Initial Hackage release based on [1.83].
[1.0.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.0.0
[1.0.1]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.0.1
[1.0.2]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.0.2
[1.1.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.1.0
[1.2.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.2.0
[1.2.1]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.2.1
[1.2.2]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.2.2
[1.3.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.3.0
[1.3.1]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.3.1
[1.4.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.4.0
[1.5.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v1.5.0
[2.0.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.0.0
[2.1.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.1.0
[2.1.1]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.1.1
[2.1.2]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.1.2
[2.1.3]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.1.3
[2.2.0]: https://github.com/haskell-game/dear-imgui.hs/tree/v2.2.0
[1.89.9]: https://github.com/ocornut/imgui/releases/tag/v1.89.9
[1.87]: https://github.com/ocornut/imgui/releases/tag/v1.87
[1.86]: https://github.com/ocornut/imgui/releases/tag/v1.86
[1.85]: https://github.com/ocornut/imgui/releases/tag/v1.85
[1.84.2]: https://github.com/ocornut/imgui/releases/tag/v1.84.2
[1.83]: https://github.com/ocornut/imgui/releases/tag/v1.83

112
Main.hs
View File

@ -5,9 +5,12 @@
module Main (main) where
import Control.Monad
import Data.IORef
import qualified Data.Vector as Vector
import DearImGui
import DearImGui.OpenGL
import DearImGui.OpenGL3
import DearImGui.Internal.Text (pack)
import DearImGui.SDL
import DearImGui.SDL.OpenGL
import Control.Exception
@ -22,23 +25,39 @@ main = do
bracket (glCreateContext w) glDeleteContext \glContext ->
bracket createContext destroyContext \_imguiContext ->
bracket_ (sdl2InitForOpenGL w glContext) sdl2Shutdown $
bracket_ openGL2Init openGL2Shutdown do
bracket_ openGL3Init openGL3Shutdown do
checkVersion
styleColorsLight
checked <- newIORef False
color <- newIORef $ ImVec3 1 0 0
slider <- newIORef 0.42
loop w checked color slider
slider <- newIORef (0.42, 0, 0.314)
r <- newIORef 4
pos <- newIORef $ ImVec2 64 64
size' <- newIORef $ ImVec2 512 512
selected <- newIORef 4
tab1 <- newIORef True
tab2 <- newIORef True
loop w checked color slider r pos size' selected tab1 tab2
openGL2Shutdown
loop :: Window -> IORef Bool -> IORef ImVec3 -> IORef Float -> IO ()
loop w checked color slider = do
quit <- pollEvents
loop
:: Window
-> IORef Bool
-> IORef ImVec3
-> IORef (Float, Float, Float)
-> IORef Int
-> IORef ImVec2
-> IORef ImVec2
-> IORef Int
-> IORef Bool
-> IORef Bool
-> IO ()
loop window checked color slider r pos size' selected tab1Ref tab2Ref = do
shouldQuit <- checkEvents
openGL2NewFrame
sdl2NewFrame w
openGL3NewFrame
sdl2NewFrame
newFrame
-- showDemoWindow
@ -46,9 +65,34 @@ loop w checked color slider = do
-- showAboutWindow
-- showUserGuide
setNextWindowPos pos ImGuiCond_Once Nothing
setNextWindowSize size' ImGuiCond_Once
-- Works, but will make the window contents illegible without doing something more involved.
-- setNextWindowContentSize size'
-- setNextWindowSizeConstraints size' size'
setNextWindowCollapsed False ImGuiCond_Once
setNextWindowBgAlpha 0.42
begin "My Window"
text "Hello!"
beginTabBar "My tab bar" ImGuiTabBarFlags_Reorderable >>= whenTrue do
beginTabItem "Tab 1" tab1Ref ImGuiTabItemFlags_None >>= whenTrue do
text "Tab 1 is currently selected."
endTabItem
beginTabItem "Tab 2" tab2Ref ImGuiTabItemFlags_None >>= whenTrue do
text "Tab 2 is selected now."
endTabItem
reOpen <- tabItemButton "ReopenTabs" ImGuiTabItemFlags_Trailing
when reOpen do
writeIORef tab1Ref True
writeIORef tab2Ref True
endTabBar
listBox "Items" r [ "A", "B", "C" ]
button "Click me" >>= \case
True -> openPopup "Button Popup"
False -> return ()
@ -66,7 +110,7 @@ loop w checked color slider = do
True -> putStrLn "Oh hi Mark"
False -> return ()
sameLine >> arrowButton "Arrow" ImGuiDirUp
sameLine >> arrowButton "Arrow" ImGuiDir_Up
sameLine >> checkbox "Check!" checked >>= \case
True -> readIORef checked >>= print
@ -74,19 +118,49 @@ loop w checked color slider = do
separator
sliderFloat "Slider" slider 0.0 1.0
dragFloat3 "Slider" slider 0.1 0.0 1.0
progressBar 0.314 (Just "Pi")
beginChild "Child" (ImVec2 0 0) True ImGuiWindowFlags_None
beginCombo "Label" "Preview" >>= whenTrue do
selectable "Testing 1"
selectable "Testing 2"
endCombo
combo "Simple" selected [ "1", "2", "3" ]
endChild
text "ListClipper"
withChildOpen "##fixed" (ImVec2 0 200) True ImGuiWindowFlags_None do
let lotsOfItems = Vector.generate 50 (pack . mappend "Item " . show)
withListClipper Nothing lotsOfItems text
text "ListClipper, Haskell-powered"
withChildOpen "##infinite" (ImVec2 0 200) True ImGuiWindowFlags_None do
let infiniteItems = map (pack . mappend "Item " . show) [0 :: Int ..]
withListClipper Nothing infiniteItems text
text "Ethereal ListClipper"
withChildOpen "##ethereal" (ImVec2 0 200) True ImGuiWindowFlags_None do
withListClipper Nothing (ClipRange (0 :: Int) 1000) $
text . pack . mappend "Item " . show
plotHistogram "A histogram" [ 10, 10, 20, 30, 90 ]
colorPicker3 "Test" color
treeNode "Tree Node 1" >>= whenTrue do
treeNode "Tree Node 2" >>= whenTrue do
treePop
treeNode "Tree Node 3" >>= whenTrue do
treePop
treePop
beginMainMenuBar >>= whenTrue do
beginMenu "Hello" >>= whenTrue do
menuItem "Hello"
@ -103,15 +177,17 @@ loop w checked color slider = do
render
glClear GL_COLOR_BUFFER_BIT
openGL2RenderDrawData =<< getDrawData
openGL3RenderDrawData =<< getDrawData
glSwapWindow w
glSwapWindow window
if quit then return () else loop w checked color slider
if shouldQuit
then return ()
else loop window checked color slider r pos size' selected tab1Ref tab2Ref
where
pollEvents = do
checkEvents = do
ev <- pollEventWithImGui
case ev of
@ -121,9 +197,9 @@ loop w checked color slider = do
QuitEvent -> True
_ -> False
(isQuit ||) <$> pollEvents
(isQuit ||) <$> checkEvents
whenTrue :: IO () -> Bool -> IO ()
whenTrue io True = io
whenTrue io False = return ()
whenTrue _io False = return ()

View File

@ -25,90 +25,96 @@ OpenGL:
```
package dear-imgui
flags: +sdl +opengl
flags: +sdl +opengl3
```
With this done, the following module is the "Hello, World!" of ImGui:
``` haskell
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
module Main ( main ) where
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Managed
import DearImGui
import DearImGui.OpenGL
import DearImGui.OpenGL3
import DearImGui.SDL
import DearImGui.SDL.OpenGL
import Graphics.GL
import SDL
import Control.Monad.Managed
import Control.Monad.IO.Class ()
import Control.Monad (when, unless)
import Control.Exception (bracket, bracket_)
main :: IO ()
main = do
-- Initialize SDL
initializeAll
runManaged do
-- Create a window using SDL. As we're using OpenGL, we need to enable OpenGL too.
w <- do
runManaged $ do
-- Create a window using SDL; as we're using OpenGL, we enable OpenGL too
window <- do
let title = "Hello, Dear ImGui!"
let config = defaultWindow { windowGraphicsContext = OpenGLContext defaultOpenGL }
managed $ bracket (createWindow title config) destroyWindow
-- Create an OpenGL context
glContext <- managed $ bracket (glCreateContext w) glDeleteContext
glContext <- managed $ bracket (glCreateContext window) glDeleteContext
-- Create an ImGui context
_ <- managed $ bracket createContext destroyContext
-- Initialize ImGui's SDL2 backend
_ <- managed_ $ bracket_ (sdl2InitForOpenGL w glContext) sdl2Shutdown
managed_ $ bracket_ (sdl2InitForOpenGL window glContext) sdl2Shutdown
-- Initialize ImGui's OpenGL backend
_ <- managed_ $ bracket_ openGL2Init openGL2Shutdown
liftIO $ mainLoop w
managed_ $ bracket_ openGL3Init openGL3Shutdown
liftIO $ mainLoop window
mainLoop :: Window -> IO ()
mainLoop w = do
-- Process the event loop
untilNothingM pollEventWithImGui
mainLoop window = unlessQuit $ do
-- Tell ImGui we're starting a new frame
openGL2NewFrame
sdl2NewFrame w
openGL3NewFrame
sdl2NewFrame
newFrame
-- Build the GUI
bracket_ (begin "Hello, ImGui!") end do
withWindowOpen "Hello, ImGui!" $ do
-- Add a text widget
text "Hello, ImGui!"
-- Add a button widget, and call 'putStrLn' when it's clicked
button "Clickety Click" >>= \case
False -> return ()
True -> putStrLn "Ow!"
button "Clickety Click" >>= \clicked ->
when clicked $ putStrLn "Ow!"
-- Show the ImGui demo window
showDemoWindow
-- Render
glClear GL_COLOR_BUFFER_BIT
render
openGL2RenderDrawData =<< getDrawData
glSwapWindow w
mainLoop w
openGL3RenderDrawData =<< getDrawData
glSwapWindow window
mainLoop window
where
untilNothingM m = m >>= maybe (return ()) (\_ -> untilNothingM m)
-- Process the event loop
unlessQuit action = do
shouldQuit <- gotQuitEvent
unless shouldQuit action
gotQuitEvent = do
ev <- pollEventWithImGui
case ev of
Nothing ->
return False
Just event ->
(isQuit event ||) <$> gotQuitEvent
isQuit event =
eventPayload event == QuitEvent
```
# Hacking

View File

@ -1,3 +1,4 @@
packages: *.cabal
package dear-imgui
flags: +sdl2 +opengl +vulkan
flags: +sdl +glfw +opengl2 +opengl3 +vulkan +examples
ghc-options: -Wall -Wcompat -fno-warn-unused-do-bind

View File

@ -1,15 +1,82 @@
cabal-version: 3.0
name: dear-imgui
version: 1.0.0
build-type: Simple
flag opengl
name: dear-imgui
version: 2.2.0
author: Oliver Charles
maintainer: ollie@ocharles.org.uk, aenor.realm@gmail.com
license: BSD-3-Clause
category: Graphics
synopsis: Haskell bindings for Dear ImGui.
description:
The package supports multiple rendering backends.
Set package flags according to your needs.
build-type: Simple
extra-source-files:
README.md,
ChangeLog.md
extra-source-files:
imgui/*.h,
imgui/backends/*.h,
imgui/backends/*.mm,
imgui/imconfig.h,
imgui/LICENSE.txt
common exe-flags
if flag(debug)
if os(linux)
ghc-options: -Wall -g -rtsopts -dcore-lint -debug
cc-options: -g -O0 -fsanitize=address -fno-omit-frame-pointer
cxx-options: -g -O0 -fsanitize=address -fno-omit-frame-pointer -std=c++11
if os(darwin)
ghc-options: -Wall -g -rtsopts -dcore-lint -debug
cc-options: -g -O0 -fsanitize=address -fno-omit-frame-pointer
cxx-options: -g -O0 -fsanitize=address -fno-omit-frame-pointer -std=c++11
if os(windows)
ghc-options: -Wall -g -rtsopts -dcore-lint -debug
cc-options: -g -O0
cxx-options: -g -O0 -std=c++11
else
if os(linux)
ghc-options: -Wall -O2
cc-options: -O2
cxx-options: -std=c++11 -O2
if os(darwin)
ghc-options: -Wall -O2
cc-options: -O2
if os(windows)
ghc-options: -Wall -O2
cc-options: -O2
source-repository head
type: git
location: https://github.com/haskell-game/dear-imgui.hs
flag debug
description:
Enable OpenGL backend.
Enable debug mode.
default:
False
manual:
True
flag opengl2
description:
Enable OpenGL 2 backend.
default:
False
manual:
True
flag opengl3
description:
Enable OpenGL 3 backend.
default:
True
manual:
False
True
flag vulkan
description:
@ -25,52 +92,140 @@ flag sdl
default:
True
manual:
True
flag sdl-renderer
description:
Enable SDL Renderer backend (requires the SDL_RenderGeometry function available in SDL 2.0.18+).
The sdl configuration flag must also be enabled when using this flag.
default:
False
manual:
True
flag glfw
description:
Enable GLFW backend.
default:
False
manual:
True
flag examples
description:
Build executable examples.
default:
False
manual:
True
flag disable-obsolete
description:
Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
default:
False
manual:
True
flag use-wchar32
description:
Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
default:
True
manual:
True
flag use-ImDrawIdx32
description:
Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
default:
True
manual:
True
common common
build-depends:
base
>= 4.12 && < 4.19
default-language:
Haskell2010
library
exposed-modules:
DearImGui
DearImGui.Context
import: common
hs-source-dirs:
src
default-language:
Haskell2010
ghc-options:
-Wall
exposed-modules:
DearImGui
DearImGui.FontAtlas
DearImGui.Internal.Text
DearImGui.Raw
DearImGui.Raw.DrawList
DearImGui.Raw.Font
DearImGui.Raw.Font.Config
DearImGui.Raw.Font.GlyphRanges
DearImGui.Raw.IO
DearImGui.Raw.ListClipper
DearImGui.Raw.Context
other-modules:
DearImGui.Enums
DearImGui.Structs
cxx-options: -std=c++11
cxx-sources:
imgui/imgui.cpp
imgui/imgui_demo.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
cxx-options:
-std=c++11
extra-libraries:
stdc++
if impl(ghc >= 9.4)
build-depends: system-cxx-std-lib
else
extra-libraries: stdc++
include-dirs:
imgui
build-depends:
base
dear-imgui-generator
, containers
, managed
, inline-c
, inline-c-cpp
, StateVar
, unliftio
, vector
, text
if flag(opengl)
if os(darwin)
ghc-options:
-optcxx-std=c++11
if flag(disable-obsolete)
cxx-options: -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS
if flag(use-wchar32)
cxx-options: -DIMGUI_USE_WCHAR32
cpp-options: -DIMGUI_USE_WCHAR32
if flag(use-ImDrawIdx32)
cxx-options: "-DImDrawIdx=unsigned int"
cpp-options: "-DImDrawIdx=unsigned int"
if flag(opengl2)
exposed-modules:
DearImGui.OpenGL
DearImGui.OpenGL2
cxx-sources:
imgui/backends/imgui_impl_opengl2.cpp
if os(windows)
extra-libraries:
opengl32
else
if os(darwin)
frameworks:
OpenGL
else
extra-libraries:
GL
build-depends:
gl
if flag(opengl3)
exposed-modules:
DearImGui.OpenGL3
cxx-sources:
imgui/backends/imgui_impl_opengl3.cpp
pkgconfig-depends:
glew
if flag(vulkan)
exposed-modules:
@ -99,7 +254,7 @@ library
build-depends:
sdl2
cxx-sources:
imgui/backends/imgui_impl_sdl.cpp
imgui/backends/imgui_impl_sdl2.cpp
if os(windows) || os(darwin)
extra-libraries:
@ -108,7 +263,7 @@ library
pkgconfig-depends:
sdl2
if flag(opengl)
if flag(opengl2) || flag(opengl3)
exposed-modules:
DearImGui.SDL.OpenGL
@ -116,52 +271,153 @@ library
exposed-modules:
DearImGui.SDL.Vulkan
if flag(sdl-renderer)
exposed-modules:
DearImGui.SDL.Renderer
cxx-sources:
imgui/backends/imgui_impl_sdlrenderer2.cpp
if flag(glfw)
exposed-modules:
DearImGui.GLFW
build-depends:
GLFW-b,
bindings-GLFW
cxx-sources:
imgui/backends/imgui_impl_glfw.cpp
if os(linux) || os(darwin)
pkgconfig-depends:
glfw3
if flag(opengl2) || flag(opengl3)
exposed-modules:
DearImGui.GLFW.OpenGL
if flag(vulkan)
exposed-modules:
DearImGui.GLFW.Vulkan
library dear-imgui-generator
import: common
hs-source-dirs: generator
exposed-modules:
DearImGui.Generator
, DearImGui.Generator.Parser
, DearImGui.Generator.Tokeniser
, DearImGui.Generator.Types
build-depends:
template-haskell
>= 2.15 && < 2.21
, containers
^>= 0.6.2.1
, directory
>= 1.3 && < 1.4
, filepath
>= 1.4 && < 1.5
, inline-c
>= 0.9.0.0 && < 0.10
, megaparsec
>= 9.0 && < 9.4
, parser-combinators
>= 1.2.0 && < 1.4
, scientific
>= 0.3.6.2 && < 0.3.8
, text
>= 1.2.4 && < 2.1
, th-lift
>= 0.7 && < 0.9
, transformers
>= 0.5.6 && < 0.7
, unordered-containers
>= 0.2.11 && < 0.3
executable test
import: common, exe-flags
main-is: Main.hs
default-language: Haskell2010
build-depends: base, sdl2, gl, dear-imgui
ghc-options: -Wall
if (!flag(examples) || !flag(sdl) || !flag(opengl2))
buildable: False
else
build-depends: base, sdl2, gl, dear-imgui, vector
executable glfw
import: common, exe-flags
main-is: Main.hs
hs-source-dirs: examples/glfw
default-language: Haskell2010
if (!flag(examples) || !flag(glfw) || !flag(opengl2))
buildable: False
else
build-depends: base, GLFW-b, gl, dear-imgui, managed, text
executable readme
import: common, exe-flags
main-is: Readme.hs
hs-source-dirs: examples
default-language: Haskell2010
build-depends: base, sdl2, gl, dear-imgui, managed
ghc-options: -Wall
build-depends: sdl2, gl, dear-imgui, managed
if (!flag(examples) || !flag(sdl) || !flag(opengl2))
buildable: False
executable fonts
import: common, exe-flags
main-is: Main.hs
hs-source-dirs: examples/fonts
build-depends: sdl2, gl, dear-imgui, managed
if (!flag(examples) || !flag(sdl) || !flag(opengl2))
buildable: False
executable image
import: common, exe-flags
main-is: Image.hs
hs-source-dirs: examples/sdl
build-depends: sdl2, gl, dear-imgui, managed, vector
if (!flag(examples) || !flag(sdl) || !flag(opengl3))
buildable: False
executable sdlrenderer
import: common, exe-flags
main-is: Renderer.hs
hs-source-dirs: examples/sdl
build-depends: sdl2, dear-imgui, managed, text
if (!flag(examples) || !flag(sdl) || !flag(sdl-renderer))
buildable: False
executable vulkan
import: common, exe-flags
main-is: Main.hs
other-modules: Attachments, Backend, Input, Util
hs-source-dirs: examples/vulkan
default-language: Haskell2010
build-depends:
dear-imgui
, base
>= 4.13 && < 4.16
, bytestring
>= 0.10.10.0 && < 0.12
, containers
^>= 0.6.2.1
, logging-effect
^>= 1.3.12
, resourcet
^>= 1.2.4.2
, sdl2
^>= 2.5.3.0
, text-short
^>= 0.1.3
, transformers
^>= 0.5.6.2
, unliftio
>= 0.2.13 && < 0.2.15
, unliftio-core
^>= 0.2.0.1
, vector
^>= 0.12.1.2
, vulkan
^>= 3.9
, vulkan-utils
^>= 0.4.1
ghc-options: -Wall
if (!flag(examples) || !flag(sdl) || !flag(vulkan))
buildable: False
else
build-depends:
dear-imgui
, bytestring
>= 0.10.10.0 && < 0.13
, containers
>= 0.6.2.1 && < 0.7
, logging-effect
>= 1.3.12 && < 1.5
, resourcet
>= 1.2.4.2 && < 1.3
, sdl2
>= 2.5.3.0 && < 2.6
, text
>= 1.2.4 && < 2.1
, transformers
>= 0.5.6 && < 0.7
, unliftio
>= 0.2.13 && < 0.3
, unliftio-core
>= 0.2.0.1 && < 0.3
, vector
>= 0.12.1.2 && < 0.14
, vulkan
>= 3.12
, vulkan-utils
>= 0.5
, VulkanMemoryAllocator
>= 0.7.1
, JuicyPixels

View File

@ -4,12 +4,12 @@ haskellNix ? (import (import ./nix/sources.nix)."haskell.nix" { })
# haskell.nix provides access to the nixpkgs pins which are used by our CI,
# hence you will be more likely to get cache hits when using these.
# But you can also just use your own, e.g. '<nixpkgs>'.
, nixpkgsSrc ? haskellNix.sources.nixpkgs-2009
, nixpkgsSrc ? haskellNix.sources.nixpkgs-2105
# haskell.nix provides some arguments to be passed to nixpkgs, including some
# patches and also the haskell.nix functionality itself as an overlay.
, nixpkgsArgs ? haskellNix.nixpkgsArgs
, compiler-nix-name ? "ghc884"
, compiler-nix-name ? "ghc8107"
}:
let
pkgs = import nixpkgsSrc nixpkgsArgs;
@ -20,4 +20,18 @@ in pkgs.haskell-nix.project {
name = "dear-imgui";
src = ./.;
};
modules = [ {
# This library needs libXext to build, but doesn't explicitly state it in
# its .cabal file.
packages.bindings-GLFW.components.library.libs =
pkgs.lib.mkForce (
pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs.darwin.apple_sdk.frameworks; [ AGL Cocoa OpenGL IOKit Kernel CoreVideo pkgs.darwin.CF ]) ++
pkgs.lib.optionals (!pkgs.stdenv.isDarwin) (with pkgs.xorg; [ libXext libXi libXrandr libXxf86vm libXcursor libXinerama pkgs.libGL ])
);
# Depends on libX11 but doesn't state it in the .cabal file.
packages.GLFW-b.components.library.libs =
with pkgs.xorg;
pkgs.lib.mkForce [ libX11 ];
} ];
}

View File

@ -1,81 +1,87 @@
-- NOTE: If this is file is edited, please also copy and paste it into
-- README.md.
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
module Main ( main ) where
import Control.Exception
import Control.Monad.IO.Class
import Control.Monad.Managed
import DearImGui
import DearImGui.OpenGL
import DearImGui.OpenGL3
import DearImGui.SDL
import DearImGui.SDL.OpenGL
import Graphics.GL
import SDL
import Control.Monad.Managed
import Control.Monad.IO.Class ()
import Control.Monad (when, unless)
import Control.Exception (bracket, bracket_)
main :: IO ()
main = do
-- Initialize SDL
initializeAll
runManaged do
-- Create a window using SDL. As we're using OpenGL, we need to enable OpenGL too.
w <- do
runManaged $ do
-- Create a window using SDL; as we're using OpenGL, we enable OpenGL too
window <- do
let title = "Hello, Dear ImGui!"
let config = defaultWindow { windowGraphicsContext = OpenGLContext defaultOpenGL }
managed $ bracket (createWindow title config) destroyWindow
-- Create an OpenGL context
glContext <- managed $ bracket (glCreateContext w) glDeleteContext
glContext <- managed $ bracket (glCreateContext window) glDeleteContext
-- Create an ImGui context
_ <- managed $ bracket createContext destroyContext
-- Initialize ImGui's SDL2 backend
_ <- managed_ $ bracket_ (sdl2InitForOpenGL w glContext) sdl2Shutdown
managed_ $ bracket_ (sdl2InitForOpenGL window glContext) sdl2Shutdown
-- Initialize ImGui's OpenGL backend
_ <- managed_ $ bracket_ openGL2Init openGL2Shutdown
liftIO $ mainLoop w
managed_ $ bracket_ openGL3Init openGL3Shutdown
liftIO $ mainLoop window
mainLoop :: Window -> IO ()
mainLoop w = do
-- Process the event loop
untilNothingM pollEventWithImGui
mainLoop window = unlessQuit $ do
-- Tell ImGui we're starting a new frame
openGL2NewFrame
sdl2NewFrame w
openGL3NewFrame
sdl2NewFrame
newFrame
-- Build the GUI
bracket_ (begin "Hello, ImGui!") end do
withWindowOpen "Hello, ImGui!" $ do
-- Add a text widget
text "Hello, ImGui!"
-- Add a button widget, and call 'putStrLn' when it's clicked
button "Clickety Click" >>= \case
False -> return ()
True -> putStrLn "Ow!"
button "Clickety Click" >>= \clicked ->
when clicked $ putStrLn "Ow!"
-- Show the ImGui demo window
showDemoWindow
-- Render
glClear GL_COLOR_BUFFER_BIT
render
openGL2RenderDrawData =<< getDrawData
glSwapWindow w
mainLoop w
openGL3RenderDrawData =<< getDrawData
glSwapWindow window
mainLoop window
where
untilNothingM m = m >>= maybe (return ()) (\_ -> untilNothingM m)
-- Process the event loop
unlessQuit action = do
shouldQuit <- gotQuitEvent
unless shouldQuit action
gotQuitEvent = do
ev <- pollEventWithImGui
case ev of
Nothing ->
return False
Just event ->
(isQuit event ||) <$> gotQuitEvent
isQuit event =
eventPayload event == QuitEvent

150
examples/fonts/Main.hs Normal file
View File

@ -0,0 +1,150 @@
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
{-# language RecordWildCards #-}
{-# language NamedFieldPuns #-}
{-# language DeriveTraversable #-}
{- | Font usage example.
Loads two non-standard fonts
This example uses NotoSansJP-Regular.otf from Google Fonts
Licensed under the SIL Open Font License, Version 1.1
https://fonts.google.com/noto/specimen/Noto+Sans+JP
-}
module Main ( main ) where
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Managed
import Data.IORef
import DearImGui
import qualified DearImGui.FontAtlas as FontAtlas
import DearImGui.OpenGL2
import DearImGui.SDL
import DearImGui.SDL.OpenGL
import Graphics.GL
import SDL
-- Rebuild syntax enables us to keep fonts in any
-- traversable type, so let's make our life a little easier.
-- But feel free to use lists or maps.
data FontSet a = FontSet
{ droidFont :: a
, defaultFont :: a
, notoFont :: a
}
deriving (Functor, Foldable, Traversable)
main :: IO ()
main = do
-- Window initialization is similar to another examples.
initializeAll
runManaged do
window <- do
let title = "Hello, Dear ImGui!"
let config = defaultWindow { windowGraphicsContext = OpenGLContext defaultOpenGL }
managed $ bracket (createWindow title config) destroyWindow
glContext <- managed $ bracket (glCreateContext window) glDeleteContext
_ <- managed $ bracket createContext destroyContext
_ <- managed_ $ bracket_ (sdl2InitForOpenGL window glContext) sdl2Shutdown
_ <- managed_ $ bracket_ openGL2Init openGL2Shutdown
-- We use high-level syntax to build font atlas and
-- get handles to use in the main loop.
fontSet <- FontAtlas.rebuild FontSet
{ -- The first mentioned font is loaded first
-- and set as a global default.
droidFont =
FontAtlas.FromTTF
"./imgui/misc/fonts/DroidSans.ttf"
15
Nothing
FontAtlas.Cyrillic
-- You also may use a default hardcoded font for
-- some purposes (i.e. as fallback)
, defaultFont =
FontAtlas.DefaultFont
-- To optimize atlas size, use ranges builder and
-- provide source localization data.
, notoFont =
FontAtlas.FromTTF
"./examples/fonts/NotoSansJP-Regular.otf"
20
Nothing
( FontAtlas.RangesBuilder $ mconcat
[ FontAtlas.addRanges FontAtlas.Latin
, FontAtlas.addText "私をクリックしてください"
, FontAtlas.addText "こんにちは"
]
)
}
liftIO $ do
fontFlag <- newIORef False
mainLoop window do
let FontSet{..} = fontSet
withWindowOpen "Hello, ImGui!" do
-- To use a font for widget text, you may either put it
-- into a 'withFont' block:
withFont defaultFont do
text "Hello, ImGui!"
text "Привет, ImGui!"
-- ...or you can explicitly push and pop a font.
-- Though it's not recommended.
toggled <- readIORef fontFlag
when toggled $
pushFont notoFont
-- Some of those are only present in Noto font range
-- and will render as `?`s.
text "こんにちは, ImGui!"
let buttonText = if toggled then "私をクリックしてください" else "Click Me!"
button buttonText >>= \clicked ->
when clicked $
modifyIORef' fontFlag not
when toggled
popFont
showDemoWindow
mainLoop :: Window -> IO () -> IO ()
mainLoop window frameAction = loop
where
loop = unlessQuit do
openGL2NewFrame
sdl2NewFrame
newFrame
frameAction
glClear GL_COLOR_BUFFER_BIT
render
openGL2RenderDrawData =<< getDrawData
glSwapWindow window
loop
unlessQuit action = do
shouldQuit <- checkEvents
if shouldQuit then pure () else action
checkEvents = do
pollEventWithImGui >>= \case
Nothing ->
return False
Just event ->
(isQuit event ||) <$> checkEvents
isQuit event =
SDL.eventPayload event == SDL.QuitEvent

Binary file not shown.

141
examples/glfw/Main.hs Normal file
View File

@ -0,0 +1,141 @@
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
{-# language RecordWildCards #-}
module Main ( main ) where
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Managed
import Data.Bits ((.|.))
import Data.IORef
import Data.List (sortBy)
import Data.Foldable (traverse_)
import Data.Text (Text, pack)
import DearImGui
import DearImGui.OpenGL2
import DearImGui.GLFW
import DearImGui.GLFW.OpenGL
import Graphics.GL
import Graphics.UI.GLFW (Window)
import qualified Graphics.UI.GLFW as GLFW
main :: IO ()
main = do
initialised <- GLFW.init
unless initialised $ error "GLFW init failed"
runManaged $ do
mwin <- managed $ bracket
(GLFW.createWindow 800 600 "Hello, Dear ImGui!" Nothing Nothing)
(maybe (return ()) GLFW.destroyWindow)
case mwin of
Just win -> do
liftIO $ do
GLFW.makeContextCurrent (Just win)
GLFW.swapInterval 1
-- Create an ImGui context
_ <- managed $ bracket createContext destroyContext
-- Initialize ImGui's GLFW backend
_ <- managed_ $ bracket_ (glfwInitForOpenGL win True) glfwShutdown
-- Initialize ImGui's OpenGL backend
_ <- managed_ $ bracket_ openGL2Init openGL2Shutdown
tableRef <- liftIO $ newIORef
[ (1, "foo")
, (2, "bar")
, (3, "baz")
, (10, "spam")
, (11, "spam")
, (12, "spam")
]
liftIO $ mainLoop win tableRef
Nothing -> do
error "GLFW createWindow failed"
GLFW.terminate
mainLoop :: Window -> IORef [(Integer, Text)] -> IO ()
mainLoop win tableRef = do
-- Process the event loop
GLFW.pollEvents
close <- GLFW.windowShouldClose win
unless close do
-- Tell ImGui we're starting a new frame
openGL2NewFrame
glfwNewFrame
newFrame
-- Build the GUI
bracket_ (begin "Hello, ImGui!") end do
-- Add a text widget
text "Hello, ImGui!"
-- Add a button widget, and call 'putStrLn' when it's clicked
clicking <- button "Clickety Click"
when clicking $
putStrLn "Ow!"
itemContextPopup do
text "pop!"
button "ok" >>= \clicked ->
when clicked $
closeCurrentPopup
newLine
mkTable tableRef
-- Render
glClear GL_COLOR_BUFFER_BIT
render
openGL2RenderDrawData =<< getDrawData
GLFW.swapBuffers win
mainLoop win tableRef
mkTable :: IORef [(Integer, Text)] -> IO ()
mkTable tableRef =
withTableOpen sortable "MyTable" 3 $ do
tableSetupColumn "Hello"
tableSetupColumnWith defTableColumnOptions "World"
withSortableTable \isDirty sortSpecs ->
when (isDirty && not (null sortSpecs)) do
-- XXX: do your sorting & cache it. Dont sort every frame.
putStrLn "So dirty!"
print sortSpecs
modifyIORef' tableRef . sortBy $
foldMap mkCompare sortSpecs
tableHeadersRow
readIORef tableRef >>=
traverse_ \(ix, title) -> do
tableNextRow
tableNextColumn $ text (pack $ show ix)
tableNextColumn $ text title
tableNextColumn $ void (button "")
where
mkCompare TableSortingSpecs{..} a b =
let
dir = if tableSortingReverse then flip else id
in
case tableSortingColumn of
0 -> dir compare (fst a) (fst b)
1 -> dir compare (snd a) (snd b)
_ -> EQ
sortable = defTableOptions
{ tableFlags =
ImGuiTableFlags_Sortable .|.
ImGuiTableFlags_SortMulti
}

195
examples/sdl/Image.hs Normal file
View File

@ -0,0 +1,195 @@
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
{- | Drawing an DearImGui image using OpenGL textures.
https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
-}
module Main ( main ) where
import Control.Exception
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Managed (managed, managed_, runManaged)
import DearImGui
import qualified DearImGui.Raw as Raw
import qualified DearImGui.Raw.DrawList as DrawList
import DearImGui.OpenGL3
import DearImGui.SDL
import DearImGui.SDL.OpenGL
import Graphics.GL
import qualified SDL as SDL
-- For the texture creation
import Foreign
import qualified Data.Vector.Storable as VS
data Texture = Texture
{ textureID :: GLuint
, textureWidth :: GLsizei
, textureHeight :: GLsizei
}
deriving (Show)
textureSize :: Texture -> ImVec2
textureSize texture =
ImVec2
(fromIntegral $ textureWidth texture)
(fromIntegral $ textureHeight texture)
-- | Create a texture pointer in GL memory.
create2DTexture :: Int -> Int -> IO Texture
create2DTexture width height =
alloca \ptr -> do
glGenTextures 1 ptr
tID <- peek ptr
return Texture
{ textureID = tID
, textureWidth = fromIntegral width
, textureHeight = fromIntegral height
}
bindTexture :: Texture -> Ptr GLubyte -> IO ()
bindTexture texture dataPtr = do
glEnable GL_TEXTURE_2D
glBindTexture GL_TEXTURE_2D (textureID texture)
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT
glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT
glTexImage2D
GL_TEXTURE_2D
0
GL_RGB
(textureWidth texture)
(textureHeight texture)
0
GL_RGB
GL_UNSIGNED_BYTE
(castPtr dataPtr)
fill :: Texture -> (GLubyte, GLubyte, GLubyte) -> VS.Vector GLubyte
fill texture (r, g, b) =
VS.generate
(3 * width * height)
(\i ->
case i `mod` 3 of
0 -> r
1 -> g
2 -> b
_ -> error "assert: 3-byte pitch"
)
where
width = fromIntegral (textureWidth texture)
height = fromIntegral (textureHeight texture)
main :: IO ()
main = do
-- Initialize SDL
SDL.initializeAll
runManaged do
-- Create a window using SDL. As we're using OpenGL, we need to enable OpenGL too.
window <- do
let title = "Hello, Dear ImGui!"
let config = SDL.defaultWindow { SDL.windowGraphicsContext = SDL.OpenGLContext SDL.defaultOpenGL, SDL.windowResizable = True }
managed $ bracket (SDL.createWindow title config) SDL.destroyWindow
-- Create an OpenGL context
glContext <- managed $ bracket (SDL.glCreateContext window) SDL.glDeleteContext
-- Create an ImGui context
_dearContext <- managed $ bracket createContext destroyContext
-- Initialize ImGui's SDL2 backend
managed_ $ bracket_ (sdl2InitForOpenGL window glContext) sdl2Shutdown
-- Initialize ImGui's OpenGL backend
managed_ $ bracket_ openGL3Init do
putStrLn "ImguiOpenGL shut down"
openGL3Shutdown
liftIO do
blueish <- create2DTexture 320 240
VS.unsafeWith (fill blueish (0x00, 0x7F, 0xFF)) $
bindTexture blueish
pinkish <- create2DTexture 240 320
VS.unsafeWith (fill pinkish (0xFF, 0x00, 0x7F)) $
bindTexture pinkish
err <- glGetError
putStrLn $ "Error-code: " ++ show err
print (blueish, pinkish)
mainLoop window (blueish, pinkish) False
mainLoop :: SDL.Window -> (Texture, Texture) -> Bool -> IO ()
mainLoop window textures flag = unlessQuit do
-- Tell ImGui we're starting a new frame
openGL3NewFrame
sdl2NewFrame
newFrame
let texture = if flag then fst textures else snd textures
-- Drawing images require some backend-specific code.
-- Meanwhile, we have to deal with raw bindings.
let openGLtextureID = intPtrToPtr $ fromIntegral $ textureID texture
-- Build the GUI
clicked <- withWindow "Image example" \open ->
if open then do
text "That's an image, click it"
newLine
-- Using imageButton
Foreign.with (textureSize texture) \sizePtr ->
Foreign.with (ImVec2 0 0) \uv0Ptr ->
Foreign.with (ImVec2 1 1) \uv1Ptr ->
Foreign.with (ImVec4 1 1 1 1) \tintColPtr ->
Foreign.with (ImVec4 1 1 1 1) \bgColPtr ->
Raw.imageButton openGLtextureID sizePtr uv0Ptr uv1Ptr (-1) bgColPtr tintColPtr
else
pure False
-- Using DrawList
bg <- getBackgroundDrawList
Foreign.with (ImVec2 100 100) \pMin ->
Foreign.with (ImVec2 200 200) \pMax ->
Foreign.with (ImVec2 0.25 0.25) \uvMin ->
Foreign.with (ImVec2 0.75 0.75) \uvMax ->
DrawList.addImageRounded
bg
openGLtextureID
pMin pMax uvMin uvMax
(Raw.imCol32 0 255 0 0xFF) -- Extract green channel
32 ImDrawFlags_RoundCornersBottom
-- Render
glClear GL_COLOR_BUFFER_BIT
DearImGui.render
DearImGui.getDrawData >>= openGL3RenderDrawData
SDL.glSwapWindow window
mainLoop window textures (flag /= clicked)
where
unlessQuit action = do
shouldQuit <- checkEvents
if shouldQuit then pure () else action
checkEvents = do
pollEventWithImGui >>= \case
Nothing ->
return False
Just event ->
(isQuit event ||) <$> checkEvents
isQuit event =
SDL.eventPayload event == SDL.QuitEvent

146
examples/sdl/Renderer.hs Normal file
View File

@ -0,0 +1,146 @@
{-# language BlockArguments #-}
{-# language LambdaCase #-}
{-# language OverloadedStrings #-}
-- | Port of [example_sdl2_sdlrenderer2](https://github.com/ocornut/imgui/blob/54c1bdecebf3c9bb9259c07c5f5666bb4bd5c3ea/examples/example_sdl2_sdlrenderer2/main.cpp).
--
-- Minor differences:
-- - No changing of the clear color via @ImGui::ColorEdit3@ as a Haskell binding
-- doesn't yet exist for this function.
-- - No high DPI renderer scaling as this seems to be in flux [upstream](https://github.com/ocornut/imgui/issues/6065)
module Main ( main ) where
import Control.Exception (bracket, bracket_)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Managed (managed, managed_, runManaged)
import Data.IORef (IORef, newIORef)
import Data.Text (pack)
import DearImGui
import DearImGui.SDL (pollEventWithImGui, sdl2NewFrame, sdl2Shutdown)
import DearImGui.SDL.Renderer
( sdl2InitForSDLRenderer, sdlRendererInit, sdlRendererNewFrame, sdlRendererRenderDrawData
, sdlRendererShutdown
)
import SDL (V4(V4), ($=), ($~), get)
import Text.Printf (printf)
import qualified SDL
main :: IO ()
main = do
-- Initialize SDL2
SDL.initializeAll
runManaged do
-- Create a window using SDL2
window <- do
let title = "ImGui + SDL2 Renderer"
let config = SDL.defaultWindow
{ SDL.windowInitialSize = SDL.V2 1280 720
, SDL.windowResizable = True
, SDL.windowPosition = SDL.Centered
}
managed $ bracket (SDL.createWindow title config) SDL.destroyWindow
-- Create an SDL2 renderer
renderer <- managed do
bracket
(SDL.createRenderer window (-1) SDL.defaultRenderer)
SDL.destroyRenderer
-- Create an ImGui context
_ <- managed $ bracket createContext destroyContext
-- Initialize ImGui's SDL2 backend
_ <- managed_ do
bracket_ (sdl2InitForSDLRenderer window renderer) sdl2Shutdown
-- Initialize ImGui's SDL2 renderer backend
_ <- managed_ $ bracket_ (sdlRendererInit renderer) sdlRendererShutdown
liftIO $ mainLoop renderer
mainLoop :: SDL.Renderer -> IO ()
mainLoop renderer = do
refs <- newRefs
go refs
where
go refs = unlessQuit do
-- Tell ImGui we're starting a new frame
sdlRendererNewFrame
sdl2NewFrame
newFrame
-- Show the ImGui demo window
get (refsShowDemoWindow refs) >>= \case
False -> pure ()
True -> showDemoWindow
withWindowOpen "Hello, world!" do
text "This is some useful text."
_ <- checkbox "Demo Window" $ refsShowDemoWindow refs
_ <- checkbox "Another Window" $ refsShowAnotherWindow refs
_ <- sliderFloat "float" (refsFloat refs) 0 1
button "Button" >>= \case
False -> pure ()
True -> refsCounter refs $~ succ
sameLine
counter <- get $ refsCounter refs
text $ "counter = " <> pack (show counter)
fr <- framerate
text
$ pack
$ printf "Application average %.3f ms/frame (%.1f FPS)" (1000 / fr) fr
get (refsShowAnotherWindow refs) >>= \case
False -> pure ()
True ->
withCloseableWindow "Another Window" (refsShowAnotherWindow refs) do
text "Hello from another window!"
button "Close Me" >>= \case
False -> pure ()
True -> refsShowAnotherWindow refs $= False
-- Render
SDL.rendererDrawColor renderer $= V4 0 0 0 255
SDL.clear renderer
render
sdlRendererRenderDrawData =<< getDrawData
SDL.present renderer
go refs
-- Process the event loop
unlessQuit action = do
shouldQuit <- checkEvents
if shouldQuit then pure () else action
checkEvents = do
pollEventWithImGui >>= \case
Nothing ->
return False
Just event ->
(isQuit event ||) <$> checkEvents
isQuit event =
SDL.eventPayload event == SDL.QuitEvent
data Refs = Refs
{ refsShowDemoWindow :: IORef Bool
, refsShowAnotherWindow :: IORef Bool
, refsFloat :: IORef Float
, refsCounter :: IORef Int
}
newRefs :: IO Refs
newRefs =
Refs
<$> newIORef True
<*> newIORef False
<*> newIORef 0
<*> newIORef 0

View File

@ -44,7 +44,7 @@ import Data.Traversable
import Data.Word
( Word32 )
import Foreign.C.String
( CString )
( peekCString )
import Foreign.C.Types
( CInt )
import Foreign.Ptr
@ -53,8 +53,6 @@ import Foreign.Ptr
-- bytestring
import Data.ByteString
( ByteString )
import qualified Data.ByteString.Short as ShortByteString
( packCString )
-- containers
import qualified Data.Map.Strict as Map
@ -77,11 +75,13 @@ import qualified SDL
import qualified SDL.Raw
import qualified SDL.Video.Vulkan
-- text-short
import Data.Text.Short
( ShortText )
import qualified Data.Text.Short as ShortText
( intercalate, pack, fromShortByteString, toByteString, unpack )
-- text
import Data.Text
( Text )
import qualified Data.Text as Text
( intercalate, pack, unpack )
import Data.Text.Encoding
( encodeUtf8 )
-- transformers
import Control.Monad.IO.Class
@ -118,7 +118,7 @@ import Attachments
--------------------------------------------------------------------------------
type LogMessage = WithSeverity ShortText
type LogMessage = WithSeverity Text
class ( MonadUnliftIO m, MonadResource m, MonadLog LogMessage m ) => MonadVulkan m
instance ( MonadUnliftIO m, MonadResource m, MonadLog LogMessage m ) => MonadVulkan m
@ -127,9 +127,9 @@ instance ( MonadUnliftIO m, MonadResource m, MonadLog LogMessage m ) => MonadVul
logHandler :: MonadIO m => LogMessage -> m ()
logHandler ( WithSeverity sev mess )
= liftIO . putStrLn . ShortText.unpack $ showSeverity sev <> " " <> mess
= liftIO . putStrLn . Text.unpack $ showSeverity sev <> " " <> mess
showSeverity :: Severity -> ShortText
showSeverity :: Severity -> Text
showSeverity Emergency = "! PANIC !"
showSeverity Alert = "! ALERT !"
showSeverity Critical = "! CRIT !"
@ -190,9 +190,9 @@ initialiseVulkanContext instanceType appName ( VulkanRequirements { instanceRequ
device <- logDebug "Creating logical device" *>
Vulkan.Utils.createDeviceFromRequirements swapchainDeviceRequirements [] physicalDevice deviceCreateInfo
queue <- Vulkan.getDeviceQueue device ( fromIntegral queueFamily ) 0
pure ( VulkanContext { .. } )
vulkanInstanceInfo
@ -206,7 +206,7 @@ vulkanInstanceInfo appName = do
let
validationLayer :: Maybe ValidationLayerName
validationLayer
= coerce
= coerce
. foldMap
( ( Vulkan.layerName :: Vulkan.LayerProperties -> ByteString )
>>> \case
@ -244,7 +244,7 @@ vulkanInstanceInfo appName = do
case validationLayer of
Nothing -> logInfo "Validation layer unavailable. Is the Vulkan SDK installed?"
Just _ -> logInfo ( "Enabled validation layers " <> ShortText.pack ( show enabledLayers ) )
Just _ -> logInfo ( "Enabled validation layers " <> Text.pack ( show enabledLayers ) )
pure createInfo
@ -305,26 +305,23 @@ initialiseWindow ( WindowInfo { height, width, windowName, mouseMode } ) = do
void ( SDL.setMouseLocationMode mouseMode )
window <- logDebug "Creating SDL window" *> createWindow width height windowName
neededExtensions <- logDebug "Loading needed extensions" *> SDL.Video.Vulkan.vkGetInstanceExtensions window
extensionNames <- traverse ( liftIO . peekCString ) neededExtensions
logInfo $ "Needed instance extensions are: " <> ShortText.intercalate ", " extensionNames
pure ( window, map ShortText.toByteString extensionNames )
peekCString :: CString -> IO ShortText
peekCString = fmap ( fromMaybe "???" . ShortText.fromShortByteString ) . ShortByteString.packCString
extensionNames <- traverse ( liftIO . fmap fromString . peekCString ) neededExtensions
logInfo $ "Needed instance extensions are: " <> Text.intercalate ", " extensionNames
pure ( window, map encodeUtf8 extensionNames )
data WindowInfo
= WindowInfo
{ width :: CInt
, height :: CInt
, windowName :: ShortText
, windowName :: Text
, mouseMode :: SDL.LocationMode
}
createWindow :: MonadVulkan m => CInt -> CInt -> ShortText -> m SDL.Window
createWindow :: MonadVulkan m => CInt -> CInt -> Text -> m SDL.Window
createWindow x y title =
snd <$> ResourceT.allocate
( SDL.createWindow
( fromString ( ShortText.unpack title ) )
( fromString ( Text.unpack title ) )
SDL.defaultWindow
{ SDL.windowGraphicsContext = SDL.VulkanContext
, SDL.windowInitialSize = SDL.V2 x y
@ -374,11 +371,10 @@ chooseSwapchainFormat
case sortOn ( Down . score ) ( Boxed.Vector.toList surfaceFormats ) of
[] -> error "No formats found."
( best : _ )
| Vulkan.FORMAT_UNDEFINED <- ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) best
-> pure preferredFormat
| otherwise
-> pure best
Vulkan.SurfaceFormatKHR{format=Vulkan.FORMAT_UNDEFINED} : _rest ->
pure preferredFormat
best : _rest
-> pure best
where
match :: Eq a => a -> a -> Int
@ -405,21 +401,14 @@ createSwapchain physicalDevice device surface surfaceFormat imageUsage imageCoun
surfaceCapabilities <- Vulkan.getPhysicalDeviceSurfaceCapabilitiesKHR physicalDevice ( Vulkan.SurfaceKHR surface )
( _, presentModes ) <- Vulkan.getPhysicalDeviceSurfacePresentModesKHR physicalDevice ( Vulkan.SurfaceKHR surface )
let
presentMode :: Vulkan.PresentModeKHR
presentMode
| Vulkan.PRESENT_MODE_MAILBOX_KHR `elem` presentModes
= Vulkan.PRESENT_MODE_MAILBOX_KHR
| otherwise
= Vulkan.PRESENT_MODE_FIFO_KHR
presentMode =
Vulkan.PRESENT_MODE_FIFO_KHR -- run at presentation rate
-- Vulkan.PRESENT_MODE_MAILBOX_KHR -- max-FPS alternative for benchmarks, input lag debugging, etc.
currentExtent :: Vulkan.Extent2D
currentExtent = ( Vulkan.currentExtent :: Vulkan.SurfaceCapabilitiesKHR -> Vulkan.Extent2D ) surfaceCapabilities
currentTransform :: Vulkan.SurfaceTransformFlagBitsKHR
currentTransform = ( Vulkan.currentTransform :: Vulkan.SurfaceCapabilitiesKHR -> Vulkan.SurfaceTransformFlagBitsKHR ) surfaceCapabilities
Vulkan.SurfaceCapabilitiesKHR{currentExtent, currentTransform} = surfaceCapabilities
Vulkan.SurfaceFormatKHR{format=fmt, colorSpace=csp} = surfaceFormat
swapchainCreateInfo :: Vulkan.SwapchainCreateInfoKHR '[]
swapchainCreateInfo =
@ -428,8 +417,8 @@ createSwapchain physicalDevice device surface surfaceFormat imageUsage imageCoun
, Vulkan.flags = Vulkan.zero
, Vulkan.surface = Vulkan.SurfaceKHR surface
, Vulkan.minImageCount = imageCount
, Vulkan.imageFormat = ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) surfaceFormat
, Vulkan.imageColorSpace = ( Vulkan.colorSpace :: Vulkan.SurfaceFormatKHR -> Vulkan.ColorSpaceKHR ) surfaceFormat
, Vulkan.imageFormat = fmt
, Vulkan.imageColorSpace = csp
, Vulkan.imageExtent = currentExtent
, Vulkan.imageArrayLayers = 1
, Vulkan.imageUsage = imageUsage
@ -494,7 +483,7 @@ simpleRenderPass dev attachments = Vulkan.withRenderPass dev createInfo Nothing
{ Vulkan.next = ()
, Vulkan.flags = Vulkan.zero
, Vulkan.attachments = Boxed.Vector.fromList attachmentDescriptions
, Vulkan.subpasses = Boxed.Vector.singleton subpass
, Vulkan.subpasses = Boxed.Vector.singleton subpass
, Vulkan.dependencies = Boxed.Vector.fromList [ dependency1, dependency2 ]
}
@ -591,7 +580,7 @@ createFramebuffer
-> Vulkan.Extent2D
-> f Vulkan.ImageView
-> m ( ResourceT.ReleaseKey, Vulkan.Framebuffer )
createFramebuffer dev renderPass extent attachments = Vulkan.withFramebuffer dev createInfo Nothing ResourceT.allocate
createFramebuffer dev renderPass Vulkan.Extent2D{width, height} attachments = Vulkan.withFramebuffer dev createInfo Nothing ResourceT.allocate
where
createInfo :: Vulkan.FramebufferCreateInfo '[]
createInfo =
@ -600,8 +589,8 @@ createFramebuffer dev renderPass extent attachments = Vulkan.withFramebuffer dev
, Vulkan.flags = Vulkan.zero
, Vulkan.renderPass = renderPass
, Vulkan.attachments = Boxed.Vector.fromList . toList $ attachments
, Vulkan.width = ( Vulkan.width :: Vulkan.Extent2D -> Word32 ) extent
, Vulkan.height = ( Vulkan.height :: Vulkan.Extent2D -> Word32 ) extent
, Vulkan.width = width
, Vulkan.height = height
, Vulkan.layers = 1
}

View File

@ -24,7 +24,9 @@ import Control.Arrow
import Control.Exception
( throw )
import Control.Monad
( unless, void )
( unless, void, when )
import Data.Bits
( (.|.) )
import Data.Foldable
( traverse_ )
import Data.String
@ -62,11 +64,13 @@ import qualified Data.Vector as Boxed
( Vector )
import qualified Data.Vector as Boxed.Vector
( (!), head, singleton, unzip )
import qualified Data.Vector.Storable as Storable.Vector
-- vulkan
import qualified Vulkan
import qualified Vulkan.Exception as Vulkan
import qualified Vulkan.Zero as Vulkan
import qualified Vulkan.Exception as Vulkan
import qualified Vulkan.Zero as Vulkan
import qualified VulkanMemoryAllocator as VMA
-- dear-imgui
import Attachments
@ -76,6 +80,13 @@ import qualified DearImGui as ImGui
import qualified DearImGui.Vulkan as ImGui.Vulkan
import qualified DearImGui.SDL as ImGui.SDL
import qualified DearImGui.SDL.Vulkan as ImGui.SDL.Vulkan
import Util (vmaVulkanFunctions)
import Foreign (Ptr, castPtr, copyBytes, with, withForeignPtr, wordPtrToPtr)
import qualified DearImGui.Raw as ImGui.Raw
import UnliftIO (MonadUnliftIO)
import qualified Vulkan.CStruct.Extends as Vulkan
import qualified Codec.Picture as Picture
--------------------------------------------------------------------------------
@ -83,6 +94,39 @@ type Handler = LogMessage -> ResourceT IO ()
deriving via ( ReaderT Handler (ResourceT IO) )
instance MonadResource ( LoggingT LogMessage (ResourceT IO) )
gui :: MonadUnliftIO m => (ImGui.Raw.ImVec2, Ptr ()) -> m ImGui.DrawData
gui texture = do
-- Prepare frame
ImGui.Vulkan.vulkanNewFrame
ImGui.SDL.sdl2NewFrame
ImGui.newFrame
-- Run your windows
ImGui.showDemoWindow
ImGui.withWindowOpen "Vulkan demo" do
clicked <- liftIO do
with (fst texture) \sizePtr ->
with (ImGui.Raw.ImVec2 0 0) \uv0Ptr ->
with (ImGui.Raw.ImVec2 1 1) \uv1Ptr ->
with (ImGui.Raw.ImVec4 1 1 1 1) \tintColPtr ->
with (ImGui.Raw.ImVec4 1 1 1 1) \bgColPtr ->
ImGui.Raw.imageButton
(snd texture)
sizePtr
uv0Ptr
uv1Ptr
(-1)
bgColPtr
tintColPtr
when clicked $
ImGui.text "clicky click!"
-- Process ImGui state into draw commands
ImGui.render
ImGui.getDrawData
main :: IO ()
main = runResourceT . ( `runLoggingT` logHandler ) $ app @( LoggingT LogMessage ( ResourceT IO ) )
@ -157,9 +201,7 @@ app = do
surfaceCapabilities <- Vulkan.getPhysicalDeviceSurfaceCapabilitiesKHR physicalDevice ( Vulkan.SurfaceKHR surface )
let
minImageCount, maxImageCount, imageCount :: Word32
minImageCount = ( Vulkan.minImageCount :: Vulkan.SurfaceCapabilitiesKHR -> Word32 ) surfaceCapabilities
maxImageCount = ( Vulkan.maxImageCount :: Vulkan.SurfaceCapabilitiesKHR -> Word32 ) surfaceCapabilities
Vulkan.SurfaceCapabilitiesKHR{minImageCount, maxImageCount} = surfaceCapabilities
imageCount
| maxImageCount == 0 = minImageCount + 1
| otherwise = ( minImageCount + 1 ) `min` maxImageCount
@ -169,31 +211,30 @@ app = do
swapchainResources :: Maybe SwapchainResources -> m ( m (), SwapchainResources )
swapchainResources mbOldResources = do
( surfaceFormat, imGuiRenderPass ) <- case mbOldResources of
( colFmt, surfaceFormat, imGuiRenderPass ) <- case mbOldResources of
Nothing -> do
logDebug "Choosing swapchain format & color space"
surfaceFormat <- chooseSwapchainFormat preferredFormat physicalDevice surface
let
colFmt :: Vulkan.Format
colFmt = ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) surfaceFormat
let Vulkan.SurfaceFormatKHR{format=colFmt} = surfaceFormat
logDebug "Creating Dear ImGui render pass"
( _, imGuiRenderPass ) <-
simpleRenderPass device
( noAttachments
{ colorAttachments = Boxed.Vector.singleton $ presentableColorAttachmentDescription colFmt }
)
pure ( surfaceFormat, imGuiRenderPass )
Just oldResources -> pure ( surfaceFormat oldResources, imGuiRenderPass oldResources )
let
colFmt :: Vulkan.Format
colFmt = ( Vulkan.format :: Vulkan.SurfaceFormatKHR -> Vulkan.Format ) surfaceFormat
pure ( colFmt, surfaceFormat, imGuiRenderPass )
Just oldResources -> do
let surFmt = surfaceFormat oldResources
let Vulkan.SurfaceFormatKHR{format=colFmt} = surFmt
pure ( colFmt, surFmt, imGuiRenderPass oldResources )
logDebug "Creating swapchain"
( swapchainKey, swapchain, swapchainExtent ) <-
createSwapchain
physicalDevice device
surface surfaceFormat
physicalDevice
device
surface
surfaceFormat
surfaceUsage
imageCount
( swapchain <$> mbOldResources )
@ -261,6 +302,80 @@ app = do
logDebug "Allocating command buffers"
commandBuffers <- snd <$> allocatePrimaryCommandBuffers device commandPool imageCount
logDebug "Allocating VMA"
(_key, vma) <- VMA.withAllocator
Vulkan.zero
{ VMA.instance' = Vulkan.instanceHandle instance'
, VMA.device = Vulkan.deviceHandle device
, VMA.physicalDevice = Vulkan.physicalDeviceHandle physicalDevice
, VMA.vulkanFunctions = Just $ vmaVulkanFunctions device instance'
}
ResourceT.allocate
logDebug "Loading image data"
picture <- liftIO (Picture.readImage "Example.png") >>= either error (pure . Picture.convertRGBA8)
logDebug "Allocating image"
let textureWidth = Picture.imageWidth picture
let textureHeight = Picture.imageHeight picture
(_key, (image, _imageAllocation, _imageAllocationInfo)) <- VMA.withImage
vma
( Vulkan.zero
{ Vulkan.imageType = Vulkan.IMAGE_TYPE_2D
, Vulkan.mipLevels = 1
, Vulkan.arrayLayers = 1
, Vulkan.format = Vulkan.FORMAT_R8G8B8A8_SRGB
, Vulkan.extent = Vulkan.Extent3D (fromIntegral textureWidth) (fromIntegral textureHeight) 1
, Vulkan.tiling = Vulkan.IMAGE_TILING_OPTIMAL
, Vulkan.initialLayout = Vulkan.IMAGE_LAYOUT_UNDEFINED
, Vulkan.usage = Vulkan.IMAGE_USAGE_SAMPLED_BIT .|. Vulkan.IMAGE_USAGE_TRANSFER_DST_BIT
, Vulkan.sharingMode = Vulkan.SHARING_MODE_EXCLUSIVE
, Vulkan.samples = Vulkan.SAMPLE_COUNT_1_BIT
}
)
( Vulkan.zero
{ VMA.flags = Vulkan.zero
, VMA.usage = VMA.MEMORY_USAGE_GPU_ONLY
, VMA.requiredFlags = Vulkan.MEMORY_PROPERTY_DEVICE_LOCAL_BIT
}
)
ResourceT.allocate
let (pictureF, pictureSize) = Storable.Vector.unsafeToForeignPtr0 (Picture.imageData picture)
let stageBufferCI = Vulkan.zero
{ Vulkan.size = fromIntegral pictureSize
, Vulkan.usage = Vulkan.BUFFER_USAGE_TRANSFER_SRC_BIT
, Vulkan.sharingMode = Vulkan.SHARING_MODE_EXCLUSIVE
}
let stageAllocationCI = Vulkan.zero
{ VMA.flags = VMA.ALLOCATION_CREATE_MAPPED_BIT
, VMA.usage = VMA.MEMORY_USAGE_CPU_TO_GPU
, VMA.requiredFlags = Vulkan.MEMORY_PROPERTY_HOST_VISIBLE_BIT
}
(stageKey, (stage, stageAllocation, stageAllocationInfo)) <- VMA.withBuffer
vma
stageBufferCI
stageAllocationCI
ResourceT.allocate
liftIO $ withForeignPtr pictureF \srcPtr ->
copyBytes (VMA.mappedData stageAllocationInfo) (castPtr srcPtr) pictureSize
VMA.flushAllocation vma stageAllocation 0 Vulkan.WHOLE_SIZE
logDebug "Allocating sampler"
(_key, sampler) <- Vulkan.withSampler device Vulkan.zero Nothing ResourceT.allocate
logDebug "Allocating image view"
(_key, imageView) <- createImageView
device
image
Vulkan.IMAGE_VIEW_TYPE_2D
Vulkan.FORMAT_R8G8B8A8_SRGB
Vulkan.IMAGE_ASPECT_COLOR_BIT
-------------------------------------------
-- Initialise Dear ImGui.
@ -294,23 +409,96 @@ app = do
logDebug "Creating fence"
( fenceKey, fence ) <- createFence device
logDebug "Allocating one-shot command buffer"
( fontUploadCommandBufferKey, fontUploadCommandBuffer ) <-
( oneshotCommandBufferKey, oneshotCommandBuffer ) <-
second Boxed.Vector.head <$>
allocatePrimaryCommandBuffers device commandPool 1
logDebug "Recording one-shot commands"
beginCommandBuffer fontUploadCommandBuffer
_ <- ImGui.Vulkan.vulkanCreateFontsTexture fontUploadCommandBuffer
endCommandBuffer fontUploadCommandBuffer
beginCommandBuffer oneshotCommandBuffer
_ <- ImGui.Vulkan.vulkanCreateFontsTexture oneshotCommandBuffer
logDebug "Uploading texture"
let textureSubresource = Vulkan.ImageSubresourceRange
{ Vulkan.aspectMask = Vulkan.IMAGE_ASPECT_COLOR_BIT
, Vulkan.baseMipLevel = 0
, Vulkan.levelCount = 1
, Vulkan.baseArrayLayer = 0
, Vulkan.layerCount = 1
}
let uploadBarrier = Vulkan.zero
{ Vulkan.srcAccessMask = Vulkan.zero
, Vulkan.dstAccessMask = Vulkan.ACCESS_TRANSFER_WRITE_BIT
, Vulkan.oldLayout = Vulkan.IMAGE_LAYOUT_UNDEFINED
, Vulkan.newLayout = Vulkan.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
, Vulkan.srcQueueFamilyIndex = Vulkan.QUEUE_FAMILY_IGNORED
, Vulkan.dstQueueFamilyIndex = Vulkan.QUEUE_FAMILY_IGNORED
, Vulkan.image = image
, Vulkan.subresourceRange = textureSubresource
} :: Vulkan.ImageMemoryBarrier '[]
Vulkan.cmdPipelineBarrier
oneshotCommandBuffer
Vulkan.PIPELINE_STAGE_TOP_OF_PIPE_BIT
Vulkan.PIPELINE_STAGE_TRANSFER_BIT
Vulkan.zero
mempty
mempty
(Boxed.Vector.singleton $ Vulkan.SomeStruct uploadBarrier)
Vulkan.cmdCopyBufferToImage oneshotCommandBuffer stage image Vulkan.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL $
Boxed.Vector.singleton Vulkan.BufferImageCopy
{ Vulkan.bufferOffset = 0
, Vulkan.bufferRowLength = Vulkan.zero
, Vulkan.bufferImageHeight = Vulkan.zero
, Vulkan.imageSubresource = Vulkan.ImageSubresourceLayers
{ aspectMask = Vulkan.IMAGE_ASPECT_COLOR_BIT
, mipLevel = 0
, baseArrayLayer = 0
, layerCount = 1
}
, Vulkan.imageOffset = Vulkan.zero
, Vulkan.imageExtent = Vulkan.Extent3D
{ width = fromIntegral textureWidth
, height = fromIntegral textureHeight
, depth = 1
}
}
logDebug "Transitioning texture"
let transitionBarrier = Vulkan.zero
{ Vulkan.srcAccessMask = Vulkan.ACCESS_TRANSFER_WRITE_BIT
, Vulkan.dstAccessMask = Vulkan.ACCESS_SHADER_READ_BIT
, Vulkan.oldLayout = Vulkan.IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
, Vulkan.newLayout = Vulkan.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
, Vulkan.srcQueueFamilyIndex = Vulkan.QUEUE_FAMILY_IGNORED
, Vulkan.dstQueueFamilyIndex = Vulkan.QUEUE_FAMILY_IGNORED
, Vulkan.image = image
, Vulkan.subresourceRange = textureSubresource
} :: Vulkan.ImageMemoryBarrier '[]
Vulkan.cmdPipelineBarrier
oneshotCommandBuffer
Vulkan.PIPELINE_STAGE_TRANSFER_BIT
Vulkan.PIPELINE_STAGE_FRAGMENT_SHADER_BIT
Vulkan.zero
mempty
mempty
(Boxed.Vector.singleton $ Vulkan.SomeStruct transitionBarrier)
endCommandBuffer oneshotCommandBuffer
logDebug "Submitting one-shot commands"
submitCommandBuffer queue fontUploadCommandBuffer [] [] ( Just fence )
submitCommandBuffer queue oneshotCommandBuffer [] [] ( Just fence )
waitForFences device ( WaitAll [ fence ] )
logDebug "Finished uploading font objects"
logDebug "Cleaning up one-shot commands"
ImGui.Vulkan.vulkanDestroyFontUploadObjects
traverse_ ResourceT.release [ fenceKey, fontUploadCommandBufferKey ]
traverse_ ResourceT.release [ fenceKey, oneshotCommandBufferKey, stageKey ]
logDebug "Adding imgui texture"
Vulkan.DescriptorSet ds <- ImGui.Vulkan.vulkanAddTexture sampler imageView Vulkan.IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
let textureSize = ImGui.Raw.ImVec2 (fromIntegral textureWidth) (fromIntegral textureHeight)
let texture = (textureSize, wordPtrToPtr $ fromIntegral ds)
let
mainLoop :: AppState m -> m ()
@ -341,12 +529,6 @@ app = do
pure ( True, False )
else
handleJust vulkanException ( pure . reloadQuit ) do
ImGui.Vulkan.vulkanNewFrame
ImGui.SDL.sdl2NewFrame window
ImGui.newFrame
ImGui.showDemoWindow
ImGui.render
drawData <- ImGui.getDrawData
let
commandBuffer :: Vulkan.CommandBuffer
commandBuffer = commandBuffers Boxed.Vector.! fromIntegral nextImageIndex
@ -355,7 +537,10 @@ app = do
Vulkan.resetCommandBuffer commandBuffer Vulkan.zero
beginCommandBuffer commandBuffer
cmdBeginRenderPass commandBuffer imGuiRenderPass framebuffer clearValues swapchainExtent
drawData <- gui texture
ImGui.Vulkan.vulkanRenderDrawData drawData commandBuffer Nothing
cmdEndRenderPass commandBuffer
endCommandBuffer commandBuffer
submitCommandBuffer
@ -370,7 +555,7 @@ app = do
freeOldResources
let
freeOldResources :: m ()
freeOldResources = pure ()
freeOldResources = pure ()
unless quit $ mainLoop ( AppState {..} )
let

View File

@ -1,4 +1,7 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Util where
@ -12,6 +15,10 @@ import Data.Functor.Identity
( Identity(..) )
import Data.Traversable
( for )
#if MIN_VERSION_VulkanMemoryAllocator(0,8,0)
import Foreign
( castFunPtr )
#endif
-- transformers
import Control.Monad.Trans.State.Strict
@ -19,6 +26,16 @@ import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Writer.Strict
( runWriter, tell )
-- vulkan
import qualified Vulkan
#if MIN_VERSION_VulkanMemoryAllocator(0,8,0)
import qualified Vulkan.Dynamic as VkDynamic
#endif
import Vulkan.Zero (zero)
-- VulkanMemoryAllocator
import qualified VulkanMemoryAllocator as VMA
---------------------------------------------------------------
iunzipWith
@ -38,3 +55,19 @@ ifor i0 upd ta f = (`evalState` i0) . getCompose $ result
where
result :: Compose (State i) f (t b)
result = for ta \ a -> ( coerce ( \ i -> ( f i a, upd i ) ) )
vmaVulkanFunctions
:: Vulkan.Device
-> Vulkan.Instance
-> VMA.VulkanFunctions
#if MIN_VERSION_VulkanMemoryAllocator(0,8,0)
vmaVulkanFunctions Vulkan.Device{deviceCmds} Vulkan.Instance{instanceCmds} =
zero
{ VMA.vkGetInstanceProcAddr =
castFunPtr $ VkDynamic.pVkGetInstanceProcAddr instanceCmds
, VMA.vkGetDeviceProcAddr =
castFunPtr $ VkDynamic.pVkGetDeviceProcAddr deviceCmds
}
#else
vmaVulkanFunctions _device _instance = zero
#endif

View File

@ -0,0 +1,191 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
module DearImGui.Generator
( declareEnumerations, enumerationsTypesTable )
where
-- base
import Control.Arrow
( second )
import Data.Bits
( Bits )
import Data.Foldable
( toList )
import qualified Data.List.NonEmpty as NonEmpty
( head )
import Data.String
( fromString )
import Data.Traversable
( for )
import Foreign.Storable
( Storable )
#if MIN_VERSION_template_haskell(2,18,0)
import Data.Coerce
( coerce )
#endif
-- containers
import Data.Map.Strict
( Map )
import qualified Data.Map.Strict as Map
( fromList )
-- directory
import System.Directory
( canonicalizePath )
-- filepath
import System.FilePath
( takeDirectory )
-- inline-c
import qualified Language.C.Types as InlineC
( TypeSpecifier(TypeName) )
-- megaparsec
import qualified Text.Megaparsec as Megaparsec
-- template-haskell
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
-- text
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
( readFile )
-- dear-imgui-generator
import qualified DearImGui.Generator.Parser as Parser
( headers )
import DearImGui.Generator.Tokeniser
( Tok, tokenise )
import DearImGui.Generator.Types
( Comment(..), Enumeration(..), Headers(..)
, generateNames
)
--------------------------------------------------------------------------------
-- Obtaining parsed header data.
headers :: Headers ( TH.Name, TH.Name )
headers = $( do
currentPath <- TH.loc_filename <$> TH.location
basicHeaders <- TH.runIO do
headersPath <- canonicalizePath ( takeDirectory currentPath <> "/../../imgui/imgui.h" )
headersSource <- Text.readFile headersPath
tokens <- case tokenise headersSource of
Left err -> error ( "Couldn't tokenise Dear ImGui headers:\n\n" <> show err )
Right toks -> pure toks
case Megaparsec.parse Parser.headers "" tokens of
Left err -> do
let
errorPos :: Int
errorPos = Megaparsec.errorOffset . NonEmpty.head $ Megaparsec.bundleErrors err
prev, rest :: [ Tok ]
( prev, rest ) = second ( take 15 ) . splitAt 5 . drop ( errorPos - 5 ) $ tokens
error $
"Couldn't parse Dear ImGui headers:\n\n" <>
( unlines ( map Megaparsec.parseErrorPretty . toList $ Megaparsec.bundleErrors err ) ) <> "\n" <>
( unlines ( map show prev ) <> "\n\n" <> unlines ( map show rest ) )
Right res -> pure res
TH.lift $ generateNames basicHeaders
)
--------------------------------------------------------------------------------
-- Generating TH splices.
enumerationsTypesTable :: Map InlineC.TypeSpecifier ( TH.Q TH.Type )
enumerationsTypesTable = Map.fromList . map mkTypePair $ enums headers
where
mkTypePair :: Enumeration ( TH.Name, TH.Name ) -> ( InlineC.TypeSpecifier, TH.Q TH.Type )
mkTypePair ( Enumeration { enumName, enumTypeName } ) =
( InlineC.TypeName $ fromString ( Text.unpack enumName )
, TH.conT ( fst $ enumTypeName )
)
declareEnumerations :: TH.Name -> TH.Name -> TH.Q [ TH.Dec ]
declareEnumerations finiteEnumName countName = do
concat <$> mapM ( declareEnumeration finiteEnumName countName ) ( enums headers )
declareEnumeration :: TH.Name -> TH.Name -> Enumeration ( TH.Name, TH.Name ) -> TH.Q [ TH.Dec ]
declareEnumeration finiteEnumName countName ( Enumeration {..} ) = do
let
tyName, conName :: TH.Name
( tyName, conName ) = enumTypeName
isFlagEnum :: Bool
isFlagEnum = "Flags" `Text.isInfixOf` enumName
newtypeCon :: TH.Q TH.Con
newtypeCon =
TH.normalC conName
[ TH.bangType
( TH.bang TH.noSourceUnpackedness TH.noSourceStrictness )
( TH.conT underlyingType )
]
classes :: [ TH.Q TH.Type ]
classes
| isFlagEnum
= map TH.conT [ ''Eq, ''Ord, ''Show, ''Storable, ''Bits ]
| otherwise
= map TH.conT [ ''Eq, ''Ord, ''Show, ''Storable ]
derivClause :: TH.Q TH.DerivClause
derivClause = TH.derivClause ( Just TH.NewtypeStrategy ) classes
newtypeDecl <-
#if MIN_VERSION_template_haskell(2,18,0)
( if null docs
then TH.newtypeD
else
\ ctx name bndrs kd con derivs ->
TH.newtypeD_doc ctx name ( fmap pure bndrs ) ( fmap pure kd ) ( con, Nothing, [] ) derivs
( Just . Text.unpack . Text.unlines . coerce $ docs )
)
#else
TH.newtypeD
#endif
( pure [] ) tyName [] Nothing newtypeCon [ derivClause ]
mbAddFiniteEnumInst <-
if hasExplicitCount
then do
finiteEnumInst <-
TH.instanceD ( pure [] ) ( TH.appT ( TH.conT finiteEnumName ) ( TH.conT tyName ) )
[ TH.tySynInstD ( TH.TySynEqn Nothing
<$> TH.appT ( TH.conT countName ) ( TH.conT tyName )
<*> TH.litT ( TH.numTyLit enumSize )
)
]
pure ( finiteEnumInst : )
else pure id
synonyms <- for patterns \ ( patternName, patternValue, CommentText _patDoc ) -> do
let
patNameStr :: String
patNameStr = Text.unpack patternName
patName <- TH.newName patNameStr
patSynSig <- TH.patSynSigD patName ( TH.conT tyName )
pat <-
#if MIN_VERSION_template_haskell(2,18,0)
( if Text.null _patDoc
then TH.patSynD
else
\ nm args dir pat ->
TH.patSynD_doc nm args dir pat
( Just $ Text.unpack _patDoc ) []
)
#else
TH.patSynD
#endif
patName ( TH.prefixPatSyn [] ) TH.implBidir
( TH.conP conName [ TH.litP $ TH.integerL patternValue ] )
pure ( patSynSig, pat )
pure ( newtypeDecl : mbAddFiniteEnumInst ( unpairs synonyms ) )
unpairs :: [ ( a, a ) ] -> [ a ]
unpairs [] = []
unpairs ( ( x, y ) : as ) = x : y : unpairs as

View File

@ -0,0 +1,464 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module DearImGui.Generator.Parser
( CustomParseError(..)
, headers
)
where
-- base
import Control.Applicative
( (<|>), many, optional, some )
import Control.Monad
( void )
import Data.Bits
( Bits(shiftL) )
import Data.Char
( isSpace, toLower )
import Data.Either
( partitionEithers )
import Data.Functor
( ($>) )
import Data.Int
( Int64 )
import Data.Maybe
( catMaybes, fromMaybe )
import Foreign.C.Types
( CInt )
-- template-haskell
import qualified Language.Haskell.TH as TH
( Name )
-- megaparsec
import Text.Megaparsec
( MonadParsec(..), ShowErrorComponent(..)
, (<?>), anySingle, customFailure, single
)
-- parser-combinators
import Control.Applicative.Combinators
( manyTill, option, sepBy1, skipManyTill )
-- scientific
import Data.Scientific
( floatingOrInteger, toBoundedInteger )
-- text
import Data.Text
( Text )
import qualified Data.Text as Text
( all, any, breakOn, drop, dropWhile, dropWhileEnd
, length, stripPrefix, unlines, unpack, pack
)
-- transformers
import Control.Monad.Trans.State.Strict
( StateT(..)
, get, modify'
)
-- unordered-containers
import Data.HashMap.Strict
( HashMap )
import qualified Data.HashMap.Strict as HashMap
( fromList, insert, lookup )
-- dear-imgui-generator
import DearImGui.Generator.Tokeniser
( Tok(..) )
import DearImGui.Generator.Types
( Comment(..), Enumeration(..), Headers(..) )
import qualified Text.Show as Text
--------------------------------------------------------------------------------
-- Parse error type.
data CustomParseError
= Couldn'tLookupEnumValues
{ enumName :: !Text
, problems :: ![Text]
}
| MissingForwardDeclaration
{ enumName :: !Text
, library :: HashMap Text ( TH.Name, Comment )
}
| UnexpectedSection
{ sectionName :: !Text
, problem :: ![Text]
}
deriving stock ( Show, Eq, Ord )
instance ShowErrorComponent CustomParseError where
showErrorComponent ( Couldn'tLookupEnumValues { enumName, problems } ) = Text.unpack $
"Couldn't lookup the following values in enum " <> enumName <> ":\n"
<> Text.unlines ( map ( " - " <> ) problems )
showErrorComponent ( MissingForwardDeclaration { enumName, library } ) = Text.unpack $
"Missing forward declaration for enum named " <> enumName <> "\n"
<> "In Library: " <> Text.pack ( Text.show library)
showErrorComponent ( UnexpectedSection { sectionName, problem } ) = Text.unpack $
"Unexpected section name.\n\
\Expected: " <> sectionName <> "\n\
\ Actual: " <> Text.unlines ( map ( " " <> ) problem )
--------------------------------------------------------------------------------
-- Parsing headers.
headers :: MonadParsec CustomParseError [Tok] m => m ( Headers () )
headers = do
_ <- skipManyTill anySingle ( namedSection "Header mess" )
_ <- skipManyTill anySingle ( namedSection "Forward declarations" )
( _structNames, enumNamesAndTypes ) <- forwardDeclarations
_ <- skipManyTill anySingle ( namedSection "Dear ImGui end-user API functions" )
_ <- skipManyTill anySingle ( namedSection "Flags & Enumerations" )
( _defines, basicEnums ) <- partitionEithers <$>
manyTill
( ( Left <$> try ignoreDefine )
<|> ( Left <$> try cppConditional )
<|> ( Right <$> enumeration enumNamesAndTypes )
)
( namedSection "Helpers: Memory allocations macros, ImVector<>" )
_ <- skipManyTill anySingle ( namedSection "ImGuiStyle" )
_ <- skipManyTill anySingle ( namedSection "ImGuiIO" )
_ <- skipManyTill anySingle ( namedSection "Misc data structures" )
_ <- skipManyTill anySingle ( namedSection "Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)" )
_ <- skipManyTill anySingle ( namedSection "Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData)" )
skipManyTill anySingle ( try . lookAhead $ many comment *> keyword "enum" )
drawingEnums <- many ( enumeration enumNamesAndTypes )
_ <- skipManyTill anySingle ( namedSection "Font API (ImFontConfig, ImFontGlyph, ImFontAtlasFlags, ImFontAtlas, ImFontGlyphRangesBuilder, ImFont)" )
skipManyTill anySingle ( try . lookAhead $ many comment *> keyword "enum" )
fontEnums <- many ( enumeration enumNamesAndTypes )
_ <- skipManyTill anySingle ( namedSection "Viewports" )
_ <- skipManyTill anySingle ( namedSection "Platform Dependent Interfaces" ) -- XXX: since 1.87
_ <- skipManyTill anySingle ( namedSection "Obsolete functions and types" )
let
enums :: [ Enumeration () ]
enums = basicEnums <> drawingEnums <> fontEnums
pure ( Headers { enums } )
--------------------------------------------------------------------------------
-- Parsing forward declarations.
forwardDeclarations
:: MonadParsec CustomParseError [Tok] m
=> m ( HashMap Text Comment, HashMap Text ( TH.Name, Comment ) )
forwardDeclarations = do
_ <- many comment
structs <- many do
keyword "struct"
structName <- identifier
reservedSymbol ';'
doc <- comment
pure ( structName, doc )
_ <- many comment
enums <- many do
keyword "enum"
enumName <- identifier
symbol ":"
ty <- cTypeName
reservedSymbol ';'
doc <- commentText <$> comment
pure ( enumName, ( ty, CommentText <$> Text.drop 2 . snd $ Text.breakOn "//" doc ) )
_ <- many comment
typedefs <- many do
keyword "typedef"
ty <- cTypeName
enumName <- identifier
reservedSymbol ';'
doc <- commentText <$> comment
_ <- many comment
pure ( enumName, ( ty, CommentText <$> Text.drop 2 . snd $ Text.breakOn "//" doc ) )
-- Stopping after simple structs and enums for now.
pure ( HashMap.fromList structs, HashMap.fromList (enums <> typedefs) )
cTypeName :: MonadParsec e [Tok] m => m TH.Name
cTypeName = keyword "int" $> ''CInt
--------------------------------------------------------------------------------
-- Parsing enumerations.
data EnumState = EnumState
{ enumValues :: HashMap Text Integer
, currEnumTag :: Integer
, enumSize :: Integer
, hasExplicitCount :: Bool
}
enumeration :: MonadParsec CustomParseError [Tok] m => HashMap Text ( TH.Name, Comment ) -> m ( Enumeration () )
enumeration enumNamesAndTypes = do
inlineDocs <- try do
inlineDocs <- many comment
keyword "enum"
pure inlineDocs
fullEnumName <- identifier
_ <- try $ (symbol ":" >> cTypeName >> pure ()) <|> pure ()
let
enumName :: Text
enumName = Text.dropWhileEnd ( == '_' ) fullEnumName
enumTypeName :: ()
enumTypeName = ()
( underlyingType, forwardDoc ) <- case HashMap.lookup enumName enumNamesAndTypes of
Just res -> pure res
Nothing -> customFailure ( MissingForwardDeclaration { enumName, library=enumNamesAndTypes } )
let
docs :: [Comment]
docs = forwardDoc : CommentText "" : inlineDocs
reservedSymbol '{'
( patterns, EnumState { enumSize, hasExplicitCount } ) <-
( `runStateT` EnumState { enumValues = mempty, currEnumTag = 0, enumSize = 0, hasExplicitCount = False } ) $
catMaybes
<$> many
( some ignoredPatternContent $> Nothing
<|> enumerationPattern fullEnumName
)
reservedSymbol '}'
reservedSymbol ';'
pure ( Enumeration { .. } )
ignoredPatternContent :: MonadParsec e [Tok] m => m ()
ignoredPatternContent = void ( try comment ) <|> cppConditional
enumerationPattern
:: MonadParsec CustomParseError [ Tok ] m
=> Text
-> StateT EnumState m ( Maybe ( Text, Integer, Comment ) )
enumerationPattern enumName = do
mbPatNameVal <- patternNameAndValue enumName
_ <- optional $ reservedSymbol ','
comm <- fromMaybe ( CommentText "" ) <$> optional comment
pure $
case mbPatNameVal of
Nothing -> Nothing
Just ( patName, patValue ) -> Just ( patName, patValue, comm )
patternNameAndValue
:: forall m
. MonadParsec CustomParseError [ Tok ] m
=> Text
-> StateT EnumState m ( Maybe ( Text, Integer ) )
patternNameAndValue enumName =
try do
sz <- count
modify' ( \ ( EnumState {..} ) -> EnumState { enumSize = sz, hasExplicitCount = True, .. } )
pure Nothing
<|> do
pat@( _, val ) <- value
modify' ( \ ( EnumState {..} ) -> EnumState { enumSize = enumSize + 1, currEnumTag = val + 1, .. } )
pure ( Just pat )
where
count :: StateT EnumState m Integer
count = do
let idName = enumName <> "COUNT"
_ <- single ( Identifier idName )
mbVal <- optional do
_ <- reservedSymbol '='
EnumState{enumValues} <- get
integerExpression enumValues
countVal <- case mbVal of
Nothing -> currEnumTag <$> get
Just ct -> pure ct
modify' ( \ st -> st { enumValues = HashMap.insert idName countVal ( enumValues st ) } )
pure countVal
value :: StateT EnumState m ( Text, Integer )
value = do
name <- identifier
val <- patternRHS
modify' ( \ st -> st { enumValues = HashMap.insert name val ( enumValues st ) } )
pure ( name, val )
patternRHS :: StateT EnumState m Integer
patternRHS =
( do
reservedSymbol '='
EnumState{enumValues} <- get
try disjunction <|> try (integerExpression enumValues)
)
<|> ( currEnumTag <$> get )
disjunction :: StateT EnumState m Integer
disjunction = do
initial <- identifier <* symbol "|"
( rest :: [Text] ) <- identifier `sepBy1` symbol "|"
let summands = initial : rest
valsMap <- enumValues <$> get
let
res :: Either [ Text ] Integer
res = foldr
( \ summand errsOrVal -> case HashMap.lookup summand valsMap of
Nothing -> case errsOrVal of { Right _ -> Left [ summand ]; Left errs -> Left ( summand : errs ) }
Just v -> case errsOrVal of { Right v' -> Right ( v + v' ); Left errs -> Left errs }
)
( Right 0 )
summands
case res of
Left problems -> customFailure ( Couldn'tLookupEnumValues { enumName, problems } )
Right v -> pure v
--------------------------------------------------------------------------------
-- Simple token parsers.
comment :: MonadParsec e [ Tok ] m => m Comment
comment = CommentText <$>
token ( \ case { Comment comm -> Just comm; _ -> Nothing } ) mempty
<?> "comment"
keyword :: MonadParsec e [ Tok ] m => Text -> m ()
keyword kw = token ( \ case { Keyword kw' | kw == kw' -> Just (); _ -> Nothing } ) mempty
<?> ( Text.unpack kw <> " (keyword)" )
identifier :: MonadParsec e [ Tok ] m => m Text
identifier = token ( \ case { Identifier i -> Just i; _ -> Nothing } ) mempty
<?> "identifier"
{-
prefixedIdentifier :: MonadParsec e [ Tok ] m => Text -> m Text
prefixedIdentifier prefix =
token
( \ case
{ Identifier i -> Text.dropWhile ( == '_' ) <$> Text.stripPrefix prefix i
; _ -> Nothing
}
) mempty
-}
reservedSymbol :: MonadParsec e [ Tok ] m => Char -> m ()
reservedSymbol s = token ( \ case { ReservedSymbol s' | s == s' -> Just (); _ -> Nothing } ) mempty
<?> ( [s] <> " (reserved symbol)" )
symbol :: MonadParsec e [ Tok ] m => Text -> m ()
symbol s = token ( \ case { Symbolic s' | s == s' -> Just (); _ -> Nothing } ) mempty
<?> ( Text.unpack s <> " (symbol)" )
integerExpression :: MonadParsec e [ Tok ] m => HashMap Text Integer -> m Integer
integerExpression enums = try integerPower <|> try integerAdd <|> try integerSub <|> integer
where
integerPower :: MonadParsec e [ Tok ] m => m Integer
integerPower = do
a <- integer
_ <- symbol "<<"
i <- integer
pure ( a `shiftL` fromIntegral i )
integerAdd :: MonadParsec e [ Tok ] m => m Integer
integerAdd = do
a <- integer
_ <- symbol "+"
i <- integer
pure ( a + i )
integerSub :: MonadParsec e [ Tok ] m => m Integer
integerSub = do
a <- integer
_ <- symbol "-"
i <- integer
pure ( a - i )
integer :: forall e m. MonadParsec e [ Tok ] m => m Integer
integer =
option id mkSign <*>
token
( \case
Number i suff
| Just _ <- toBoundedInteger @Int64 i
, Right i' <- floatingOrInteger @Float @Integer i
, not ( Text.any ( (== 'f' ) . toLower ) suff )
->
Just i'
Identifier name ->
HashMap.lookup name enums
_ ->
Nothing
)
mempty
<?> "integer"
where
mkSign :: m ( Integer -> Integer )
mkSign = ( symbol "+" $> id ) <|> ( symbol "-" $> negate )
section :: MonadParsec e [ Tok ] m => m [Text]
section =
do
sectionText <- try do
separator
token
( \ case
{ Comment txt -> fmap ( Text.dropWhile isSpace )
. Text.stripPrefix "[SECTION]"
. Text.dropWhile isSpace
$ txt
; _ -> Nothing
}
) mempty
rest <- endOfSectionHeader
pure ( sectionText : filter ( not . Text.all ( \ c -> c == '-' || isSpace c ) ) rest )
<?> "section"
separator :: MonadParsec e [ Tok ] m => m ()
separator = token
( \ case
{ Comment hyphens | Text.length hyphens > 10 && Text.all ( == '-') hyphens -> Just ()
; _ -> Nothing
}
) mempty
<?> "separator"
endOfSectionHeader :: MonadParsec e [ Tok ] m => m [Text]
endOfSectionHeader = try ( (:) <$> ( commentText <$> comment ) <*> endOfSectionHeader )
<|> ( separator $> [] )
namedSection :: MonadParsec CustomParseError [ Tok ] m => Text -> m ()
namedSection sectionName =
do
sectionTexts <- section
case sectionTexts of
sectionText : _
| Just _ <- Text.stripPrefix sectionName sectionText
-> pure ()
_ -> customFailure ( UnexpectedSection { sectionName, problem = sectionTexts } )
<?> ( "section named " <> Text.unpack sectionName )
cppDirective :: MonadParsec e [Tok] m => ( Text -> Maybe a ) -> m a
cppDirective f = token ( \case { BeginCPP a -> f a; _ -> Nothing } ) mempty
cppConditional :: MonadParsec e [Tok] m => m ()
cppConditional = do
void $ cppDirective ( \case { "ifdef" -> Just True; "ifndef" -> Just False; _ -> Nothing } )
-- assumes no nesting
void $ skipManyTill anySingle ( cppDirective ( \case { "endif" -> Just (); _ -> Nothing } ) )
void $ skipManyTill anySingle ( single EndCPPLine )
ignoreDefine :: MonadParsec e [Tok] m => m ()
ignoreDefine = do
void $ many comment
void $ cppDirective ( \case { "define" -> Just (); _ -> Nothing } )
void $ skipManyTill anySingle ( single EndCPPLine )

View File

@ -0,0 +1,197 @@
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module DearImGui.Generator.Tokeniser where
-- base
import Control.Arrow
( first, second )
import Control.Applicative
( (<|>), some )
import Data.Char
( isAlpha, isAlphaNum, isDigit, isPunctuation, isSpace, isSymbol, toLower )
import Data.Function
( (&) )
import Data.Functor
( ($>) )
import Data.Monoid
( Sum(..) )
-- megaparsec
import Text.Megaparsec
( MonadParsec, VisualStream(..)
, chunk, parseMaybe, satisfy, try
)
import Text.Megaparsec.Char.Lexer
( hexadecimal, scientific )
-- parser-combinators
import Control.Monad.Combinators
( optional )
-- scientific
import Data.Scientific
( Scientific )
-- text
import Data.Text
( Text )
import qualified Data.Text as Text
( break, breakOn, cons, drop, dropWhile
, head, last, length
, pack, snoc, span, strip, tail, take
, uncons, unpack
)
-- unordered-containers
import Data.HashSet
( HashSet )
import qualified Data.HashSet as HashSet
( fromList, member )
--------------------------------------------------------------------------------
data TokeniserError
= Couldn'tParseNumber { problem :: !Text }
| UnhandledCase { unhandled :: !( Char, Text ) }
deriving stock ( Eq, Ord, Show )
data Tok
= Keyword !Text
| ReservedSymbol !Char
| Symbolic !Text
| Identifier !Text
| Comment !Text
| Char !Char
| String !Text
| Number !Scientific !Text
| BeginCPP !Text
| EndCPPLine
deriving stock ( Show, Eq, Ord )
showToken :: Tok -> String
showToken = \case
Keyword t -> Text.unpack t
ReservedSymbol c -> [c]
Symbolic t -> Text.unpack t
Identifier t -> Text.unpack t
Comment t -> Text.unpack t
Char c -> [c]
String t -> Text.unpack t
Number s t -> show s <> Text.unpack t
BeginCPP t -> "#" <> Text.unpack t
EndCPPLine -> "EndCppLine"
tokenLength :: Tok -> Int
tokenLength = \case
Keyword t -> Text.length t
ReservedSymbol _ -> 1
Symbolic t -> Text.length t
Identifier t -> Text.length t
Comment t -> Text.length t
Char _ -> 1
String t -> Text.length t
Number s t -> length ( show s ) + Text.length t
BeginCPP t -> 1 + Text.length t
EndCPPLine -> length ( "EndCPPLine" :: String )
instance VisualStream [Tok] where
showTokens _ = foldMap showToken
tokensLength _ = getSum . foldMap ( Sum . tokenLength )
keywords :: HashSet Text
keywords = HashSet.fromList
[ "auto", "break", "case", "char", "const", "continue", "default", "do", "double"
, "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long"
, "register", "restrict", "return", "short", "signed", "sizeof", "static", "struct"
, "switch", "typedef", "union", "unsigned", "void", "volatile", "while"
]
reservedSymbols :: HashSet Char
reservedSymbols = HashSet.fromList [ '(', ')', '{', '}', ',', ';', '=', '#' ]
tokenise :: Text -> Either TokeniserError [ Tok ]
tokenise ( Text.uncons -> Just ( c, cs ) )
| isSpace c
= tokenise ( Text.dropWhile isSpace cs )
| isAlpha c || c == '_'
, let
this, rest :: Text
( this, rest ) = first ( c `Text.cons` ) $ Text.span ( \ x -> isAlphaNum x || x == '_' ) cs
= if this `HashSet.member` keywords
then ( Keyword this : ) <$> tokenise rest
else ( Identifier this : ) <$> tokenise rest
| isDigit c
, let
this, rest :: Text
( this, rest ) = continuePastExponent $ first ( c `Text.cons` ) $ Text.span ( \ x -> isAlphaNum x || x == '.' ) cs
= case parseMaybe @() parseNumber this of
Just numTok -> ( numTok : ) <$> tokenise rest
Nothing -> Left ( Couldn'tParseNumber { problem = this } )
| c == '\''
, Just ( '\'', rest ) <- Text.uncons ( Text.drop 1 cs )
= ( Char ( Text.head cs ) : ) <$> tokenise rest
| c == '\"'
, let
this, rest :: Text
( this, rest ) = second Text.tail $ Text.break ( == '"') cs
= ( String this : ) <$> tokenise rest
| c == '#'
, let
directive, line, rest :: Text
( directive, ( line, rest ) )
= cs
& Text.break ( isSpace )
& second ( Text.break ( `elem` [ '\n', '\r' ] ) )
= do
lineTokens <- tokenise line
restTokens <- tokenise rest
pure ( ( BeginCPP directive : lineTokens ) <> ( EndCPPLine : restTokens ) )
| c `HashSet.member` reservedSymbols
= ( ReservedSymbol c : ) <$> tokenise cs
| c == '/'
= case Text.take 1 cs of
"/" ->
let
comm, rest :: Text
( comm, rest ) = first Text.strip $ Text.break ( `elem` [ '\n', '\r' ] ) ( Text.drop 1 cs )
in ( Comment comm : ) <$> tokenise rest
"*" ->
let
comm, rest :: Text
( comm, rest ) = Text.breakOn "*/" ( Text.drop 1 cs )
in ( Comment comm : ) <$> tokenise rest
_ ->
let
this, rest :: Text
( this, rest ) = first ( c `Text.cons` ) $ Text.span ( \ x -> x /= '_' && ( isSymbol x || isPunctuation x ) ) cs
in ( Symbolic this : ) <$> tokenise rest
| isSymbol c || isPunctuation c
, let
this, rest :: Text
( this, rest ) = first ( c `Text.cons` ) $ Text.span ( \ x -> x /= '_' && ( isSymbol x || isPunctuation x ) ) cs
= ( Symbolic this : ) <$> tokenise rest
| otherwise
= Left $ UnhandledCase { unhandled = ( c, cs ) }
tokenise _ = Right []
continuePastExponent :: ( Text, Text ) -> ( Text, Text )
continuePastExponent ( this, rest )
| toLower ( Text.last this ) `elem` [ 'e', 'p' ]
, Just ( r, rs ) <- Text.uncons rest
, r `elem` [ '+', '-' ]
, ( this', rest' ) <- Text.span isAlphaNum rs
= ( this `Text.snoc` r <> this', rest' )
| otherwise
= ( this, rest )
parseNumber :: MonadParsec e Text m => m Tok
parseNumber = try ( chunk "0.f" $> Number 0 "f" ) <|> do
value <- try ( chunk "0x" *> hexadecimal ) <|> scientific
mbSuffix <- fmap ( maybe "" Text.pack ) . optional . some $ satisfy ( \ s -> toLower s `elem` ( "uflz" :: String ) )
pure ( Number value mbSuffix )

View File

@ -0,0 +1,65 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskellQuotes #-}
module DearImGui.Generator.Types where
-- base
import Data.Functor
( (<&>) )
-- template-haskell
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Syntax as TH
-- text
import Data.Text
( Text )
import qualified Data.Text as Text
( unpack )
-- th-lift
import Language.Haskell.TH.Lift
() -- 'Lift' instance for Name
--------------------------------------------------------------------------------
newtype Comment = CommentText { commentText :: Text }
deriving stock ( Show, TH.Lift )
deriving newtype ( Eq, Ord )
data Enumeration typeName
= Enumeration
{ docs :: ![Comment]
, enumName :: !Text
, enumTypeName :: !typeName
, enumSize :: !Integer
, underlyingType :: !TH.Name
, hasExplicitCount :: !Bool
, patterns :: [ ( Text, Integer, Comment ) ]
}
deriving stock ( Show, TH.Lift )
data Headers typeName
= Headers
{ enums :: [ Enumeration typeName ] }
deriving stock ( Show, TH.Lift )
generateNames :: Headers () -> Headers ( TH.Name, TH.Name )
generateNames ( Headers { enums = basicEnums } ) = Headers { enums = namedEnums }
where
namedEnums :: [ Enumeration ( TH.Name, TH.Name ) ]
namedEnums = basicEnums <&> \ enum@( Enumeration { enumName } ) ->
let
enumNameStr :: String
enumNameStr = Text.unpack enumName
tyName, conName :: TH.Name
tyName = TH.mkName enumNameStr
conName = TH.mkName enumNameStr
in
enum { enumTypeName = ( tyName, conName ) }

2
imgui

Submodule imgui updated: 58075c4414...c6e0284ac5

View File

@ -5,10 +5,10 @@
"homepage": "https://input-output-hk.github.io/haskell.nix",
"owner": "input-output-hk",
"repo": "haskell.nix",
"rev": "ef4aef4ce2060dc1a41b2690df1f54f986e0f9ab",
"sha256": "0537fbjh4mcnywa33h4hl135kw7i8c0j8qndyzv5i82j7mc8wjvs",
"rev": "970c84ad19e84d4ae42075cfe283022394f6effa",
"sha256": "01afbcas324n7j2bpfib7b4fazg5y6k7b74803c0i9ayrs6sgav6",
"type": "tarball",
"url": "https://github.com/input-output-hk/haskell.nix/archive/ef4aef4ce2060dc1a41b2690df1f54f986e0f9ab.tar.gz",
"url": "https://github.com/input-output-hk/haskell.nix/archive/970c84ad19e84d4ae42075cfe283022394f6effa.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"niv": {
@ -17,10 +17,10 @@
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "3cd7914b2c4cff48927e11c216dadfab7d903fe5",
"sha256": "1agq4nvbhrylf2s77kb4xhh9k7xcwdwggq764k4jgsbs70py8cw3",
"rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
"sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/3cd7914b2c4cff48927e11c216dadfab7d903fe5.tar.gz",
"url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {

View File

@ -6,52 +6,63 @@ let
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: spec:
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; }
else
pkgs.fetchurl { inherit (spec) url sha256; };
fetch_file = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
else
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: spec:
if spec.builtin or true then
builtins_fetchTarball { inherit (spec) url sha256; }
else
pkgs.fetchzip { inherit (spec) url sha256; };
fetch_tarball = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = spec:
builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
fetch_git = name: spec:
let
ref =
if spec ? ref then spec.ref else
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
in
builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
fetch_builtin-tarball = spec:
builtins.trace
''
WARNING:
The niv type "builtin-tarball" will soon be deprecated. You should
instead use `builtin = true`.
fetch_local = spec: spec.path;
$ niv modify <package> -a type=tarball -a builtin=true
''
builtins_fetchTarball { inherit (spec) url sha256; };
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = spec:
builtins.trace
''
WARNING:
The niv type "builtin-url" will soon be deprecated. You should
instead use `builtin = true`.
$ niv modify <package> -a type=file -a builtin=true
''
(builtins_fetchurl { inherit (spec) url sha256; });
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
(
concatMapStrings (s: if builtins.isList s then "-" else s)
(
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
)
);
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources:
mkPkgs = sources: system:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
@ -71,14 +82,27 @@ let
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs spec
else if spec.type == "tarball" then fetch_tarball pkgs spec
else if spec.type == "git" then fetch_git spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec
else if spec.type == "builtin-url" then fetch_builtin-url spec
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else
# this turns the string into an actual Nix path (for both absolute and
# relative paths)
if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}";
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
@ -87,23 +111,37 @@ let
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else {};
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, sha256 }@attrs:
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball { inherit url; }
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, sha256 }@attrs:
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl { inherit url; }
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchurl attrs;
@ -115,14 +153,15 @@ let
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = fetch config.pkgs name spec; }
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? ./sources.json
, sources ? builtins.fromJSON (builtins.readFile sourcesFile)
, pkgs ? mkPkgs sources
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
@ -130,5 +169,6 @@ let
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

View File

@ -9,7 +9,7 @@ in
# You might want some extra tools in the shell (optional).
# Some common tools can be added with the `tools` argument
tools = { cabal = "3.2.0.0"; };
tools = { cabal = "3.2.0.0"; haskell-language-server = "latest"; };
# Prevents cabal from choosing alternate plans, so that
# *all* dependencies are provided by Nix.

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +0,0 @@
{-# language DuplicateRecordFields #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language TemplateHaskell #-}
module DearImGui.Context where
import Language.C.Types
import Language.C.Inline.Context
import qualified Data.Map.Strict as Map
import Foreign
data ImVec3 = ImVec3 { x, y, z :: {-# unpack #-} !Float }
instance Storable ImVec3 where
sizeOf ~ImVec3{x, y, z} = sizeOf x + sizeOf y + sizeOf z
alignment _ = 0
poke ptr ImVec3{ x, y, z } = do
poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z
peek ptr = do
x <- peek (castPtr ptr )
y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
return ImVec3{ x, y, z }
data ImVec4 = ImVec4 { x, y, z, w :: {-# unpack #-} !Float }
instance Storable ImVec4 where
sizeOf ~ImVec4{x, y, z, w} = sizeOf x + sizeOf y + sizeOf z + sizeOf w
alignment _ = 0
poke ptr ImVec4{ x, y, z, w } = do
poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z
poke (castPtr ptr `plusPtr` (sizeOf x * 3)) w
peek ptr = do
x <- peek (castPtr ptr )
y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
w <- peek (castPtr ptr `plusPtr` (sizeOf x * 3))
return ImVec4{ x, y, z, w }
data ImGuiContext
imguiContext :: Context
imguiContext = mempty
{ ctxTypesTable = Map.fromList
[ ( TypeName "ImVec3", [t| ImVec3 |] )
, ( TypeName "ImVec4", [t| ImVec4 |] )
, ( TypeName "ImGuiContext", [t| ImGuiContext |])
]
}

34
src/DearImGui/Enums.hs Normal file
View File

@ -0,0 +1,34 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module DearImGui.Enums where
-- base
import GHC.Exts
( proxy# )
import GHC.TypeNats
( Nat, KnownNat, natVal' )
import Numeric.Natural
( Natural )
-- dear-imgui-generator
import DearImGui.Generator
( declareEnumerations )
--------------------------------------------------------------------------------
class KnownNat ( Count a ) => FiniteEnum a where
type Count a :: Nat
count :: Natural
count = natVal' @( Count a ) proxy#
declareEnumerations ''FiniteEnum ''Count

503
src/DearImGui/FontAtlas.hs Normal file
View File

@ -0,0 +1,503 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGui.FontAtlas
Font atlas builder, accompanied with lower-level functions.
@
import qualified DearImGui.FontAtlas as FontAtlas
prepareAtlas =
FontAtlas.rebuild
[ FontAtlas.FileTTF "comic-sans-mono.ttf" 13 csOptions csRanges
, FontAtlas.Default
]
where
csOptions = mconcat
[ FontAtlas.fontNo 1
, FontAtlas.glyphOffset (0, -1)
]
csRanges = RangeBuilder $ mconcat
[ FontAtlas.addText "Hello world"
, FontRanges.addChar 'Ꙑ'
, FontRanges.addRanges FontRanges.Korean
]
@
-}
module DearImGui.FontAtlas
( -- * Main types
Raw.Font(..)
, FontSource(..)
-- * Building atlas
, rebuild
-- ** Configuring sources
, ConfigSetup(..)
, fontDataOwnedByAtlas
, fontNo
, sizePixels
, oversampleH
, oversampleV
, pixelSnapH
, glyphExtraSpacing
, glyphOffset
, glyphRanges
, glyphMinAdvanceX
, glyphMaxAdvanceX
, mergeMode
, fontBuilderFlags
, rasterizerMultiply
, ellipsisChar
-- ** Configuring ranges
, Ranges(..)
, RangesBuilderSetup(..)
, addChar
, addText
, addRanges
, addRangesRaw
, pattern Latin
, pattern Korean
, pattern Japanese
, pattern ChineseFull
, pattern ChineseSimplifiedCommon
, pattern Cyrillic
, pattern Thai
, pattern Vietnamese
-- * Lower level types and functions
-- , Raw.FontConfig(..)
-- , Raw.GlyphRanges(..)
, build
, clear
, setupFont
, setupRanges
, withRanges
, withConfig
, addFontFromFileTTF
, addFontFromFileTTF_
)
where
-- base
import Data.Bool (bool)
import Data.Maybe (fromMaybe)
import Foreign
import Foreign.C
-- transformers
import Control.Monad.IO.Class
( MonadIO, liftIO )
-- managed
import Control.Monad.Managed
( MonadManaged, managed )
import qualified Control.Monad.Managed as Managed
-- unlift
import UnliftIO (MonadUnliftIO)
import UnliftIO.Exception (bracket)
-- dear-imgui
import DearImGui.Raw.Font (Font(..))
import qualified DearImGui.Raw.Font as Raw
import DearImGui.Raw.Font.Config (FontConfig(..))
import qualified DearImGui.Raw.Font.Config as FontConfig
import DearImGui.Raw.Font.GlyphRanges (GlyphRanges(..), GlyphRangesBuilder(..))
import qualified DearImGui.Raw.Font.GlyphRanges as GlyphRanges
import DearImGui.Internal.Text (Text)
import qualified DearImGui.Internal.Text as Text
import DearImGui.Structs (ImVec2(..), ImWchar)
-- | Font setup data
data FontSource
= DefaultFont
| FromTTF FilePath Float (Maybe ConfigSetup) Ranges
-- TODO: FromMemory
-- | Font config monoid interface to be used in 'FontSource'.
--
-- @
-- mergeMode True <> fontNo 1
-- @
newtype ConfigSetup = ConfigSetup
{ applyToConfig :: FontConfig -> IO ()
}
instance Semigroup ConfigSetup where
ConfigSetup f <> ConfigSetup g =
ConfigSetup \fc -> f fc >> g fc
instance Monoid ConfigSetup where
mempty = ConfigSetup (const mempty)
-- | Glyph ranges settings, from presets to builder configuration.
data Ranges
= RangesRaw GlyphRanges
| RangesBuiltin GlyphRanges.Builtin
| RangesBuilder RangesBuilderSetup
-- | Basic Latin, Extended Latin
pattern Latin :: Ranges
pattern Latin = RangesBuiltin GlyphRanges.Latin
-- | Default + Korean characters
pattern Korean :: Ranges
pattern Korean = RangesBuiltin GlyphRanges.Korean
-- | Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs
pattern Japanese :: Ranges
pattern Japanese = RangesBuiltin GlyphRanges.Japanese
-- | Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs
pattern ChineseFull :: Ranges
pattern ChineseFull = RangesBuiltin GlyphRanges.ChineseFull
-- | Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese
pattern ChineseSimplifiedCommon :: Ranges
pattern ChineseSimplifiedCommon = RangesBuiltin GlyphRanges.ChineseSimplifiedCommon
-- | Default + about 400 Cyrillic characters
pattern Cyrillic :: Ranges
pattern Cyrillic = RangesBuiltin GlyphRanges.Cyrillic
-- | Default + Thai characters
pattern Thai :: Ranges
pattern Thai = RangesBuiltin GlyphRanges.Thai
-- | Default + Vietnamese characters
pattern Vietnamese :: Ranges
pattern Vietnamese = RangesBuiltin GlyphRanges.Vietnamese
-- | Ranges builder monoid interface to be executed through 'buildRanges'.
--
-- @
-- addRanges FontRanges.DefaultRanges <> addText "Привет"
-- @
newtype RangesBuilderSetup = RangesBuilderSetup
{ applyToBuilder :: GlyphRangesBuilder -> IO ()
}
instance Semigroup RangesBuilderSetup where
RangesBuilderSetup f <> RangesBuilderSetup g =
RangesBuilderSetup \fc -> f fc >> g fc
instance Monoid RangesBuilderSetup where
mempty = RangesBuilderSetup (const mempty)
-- | Rebuild font atlas with provided configuration
-- and return corresponding structure of font handles
-- to be used with 'withFont'.
--
-- Accepts any 'Traversable' instance, so you are free to use
-- lists, maps or custom structures.
rebuild :: (MonadIO m, Traversable t) => t FontSource -> m (t Font)
rebuild sources = liftIO $ Managed.with action pure
where
action = do
clear
fonts <- traverse setupFont sources
build
return fonts
-- | Reset font atlas, clearing internal data
--
-- Alias for 'Raw.clearFontAtlas'
clear :: (MonadIO m) => m ()
clear = Raw.clearFontAtlas
-- | Build font atlas
--
-- Alias for 'Raw.buildFontAtlas'
build :: (MonadIO m) => m ()
build = Raw.buildFontAtlas
-- | Load a font from TTF file.
--
-- Specify font path and atlas glyph size.
--
-- Use 'Raw.addFontDefault' if you want to retain built-in font too.
--
-- Call 'build' after adding all the fonts,
-- particularly if you're loading them from memory or use custom glyphs.
-- Or stick to `rebuild` function.
--
-- Call backend-specific `CreateFontsTexture` before using 'newFrame'.
addFontFromFileTTF :: MonadIO m
=> FilePath -- ^ Font file path
-> Float -- ^ Font size in pixels
-> Maybe FontConfig -- ^ Configuration data
-> Maybe GlyphRanges -- ^ Glyph ranges to use
-> m (Maybe Font) -- ^ Returns font handle, if added successfully
addFontFromFileTTF font size config ranges = liftIO do
res@(Font ptr) <- withCString font \fontPtr ->
Raw.addFontFromFileTTF
fontPtr
(CFloat size)
(fromMaybe (FontConfig nullPtr) config)
(fromMaybe (GlyphRanges nullPtr) ranges)
pure $
if castPtr ptr == nullPtr
then Nothing
else Just res
-- FIXME: turn off asserts, so it would work
addFontFromFileTTF_ :: MonadIO m
=> FilePath -- ^ Font file path
-> Float -- ^ Font size in pixels
-> m (Maybe Raw.Font) -- ^ Returns font handle, if added successfully
addFontFromFileTTF_ font size =
addFontFromFileTTF font size Nothing Nothing
-- | Load a font with provided configuration, return its handle
-- and defer range builder and config destructors, if needed.
setupFont :: (MonadManaged m) => FontSource -> m Font
setupFont = \case
DefaultFont ->
Raw.addFontDefault
FromTTF path size configSetup ranges -> do
glyphRanges' <- setupRanges ranges
config <- managed (withConfig configSetup)
mFont <- addFontFromFileTTF path size config glyphRanges'
case mFont of
Nothing ->
liftIO . fail $ "Couldn't load font from " <> path
Just font ->
pure font
-- | Configure glyph ranges with provided configuration, return a handle
-- and defer builder destructors, if needed.
setupRanges :: (MonadManaged m) => Ranges -> m (Maybe GlyphRanges)
setupRanges = \case
RangesRaw ranges ->
pure $ Just ranges
RangesBuiltin builtin ->
pure $ GlyphRanges.builtinSetup builtin
RangesBuilder settings -> do
built <- managed $ withRanges settings
pure $ Just built
-- | Perform glyph ranges build based on provided configuration,
-- and execute a computation with built glyph ranges.
withRanges :: (MonadUnliftIO m) => RangesBuilderSetup -> (GlyphRanges -> m a) -> m a
withRanges (RangesBuilderSetup setup) fn =
bracket acquire release execute
where
acquire = do
builder <- GlyphRanges.new
liftIO $ setup builder
rangesVec <- GlyphRanges.buildRangesVector builder
return (rangesVec, builder)
release (rangesVec, builder) = do
GlyphRanges.destroyRangesVector rangesVec
GlyphRanges.destroy builder
execute (rangesVec, _) =
fn (GlyphRanges.fromRangesVector rangesVec)
-- | Configure font config with provided setup,
-- and execute a computation with built object.
-- return its handle and list of resource destructors.
withConfig :: (MonadUnliftIO m) => Maybe ConfigSetup -> (Maybe FontConfig -> m a) -> m a
withConfig mSetup action =
case mSetup of
Nothing ->
action Nothing
Just (ConfigSetup setup) ->
bracket acquire (FontConfig.destroy) (action . Just)
where
acquire = do
config <- FontConfig.new
liftIO $ setup config
return config
-- | Single Unicode character
addChar :: ImWchar -> RangesBuilderSetup
addChar char =
RangesBuilderSetup \builder ->
GlyphRanges.addChar builder char
-- | UTF-8 string
addText :: Text -> RangesBuilderSetup
addText str =
RangesBuilderSetup \builder ->
Text.withCString str (GlyphRanges.addText builder)
-- | Existing ranges (as is)
addRangesRaw :: GlyphRanges -> RangesBuilderSetup
addRangesRaw ranges =
RangesBuilderSetup \builder ->
GlyphRanges.addRanges builder ranges
-- | Existing ranges (through settings interface)
addRanges :: Ranges -> RangesBuilderSetup
addRanges = \case
RangesRaw ranges ->
addRangesRaw ranges
RangesBuilder settings ->
settings
RangesBuiltin builtin ->
addRangesRaw (GlyphRanges.getBuiltin builtin)
-- | TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself).
--
-- By default, it is @true@
fontDataOwnedByAtlas :: Bool -> ConfigSetup
fontDataOwnedByAtlas value =
ConfigSetup \fc ->
FontConfig.setFontDataOwnedByAtlas fc (bool 0 1 value)
-- | Index of font within TTF/OTF file.
--
-- By default, it is @0@
fontNo :: Int -> ConfigSetup
fontNo value =
ConfigSetup \fc ->
FontConfig.setFontNo fc (fromIntegral value)
-- | Size in pixels for rasterizer
--
-- More or less maps to the resulting font height.
--
-- Implicitly set by @addFont...@ functions.
sizePixels :: Float -> ConfigSetup
sizePixels value =
ConfigSetup \fc ->
FontConfig.setSizePixels fc (CFloat value)
-- | Rasterize at higher quality for sub-pixel positioning.
--
-- Note: the difference between 2 and 3 is minimal so you can reduce this to 2 to save memory.
-- Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.
--
-- By default, it is @3@
oversampleH :: Int -> ConfigSetup
oversampleH value =
ConfigSetup \fc ->
FontConfig.setOversampleH fc (fromIntegral value)
-- | Rasterize at higher quality for sub-pixel positioning.
--
-- This is not really useful as we don't use sub-pixel positions on the Y axis.
--
-- By default, it is @1@
oversampleV :: Int -> ConfigSetup
oversampleV value =
ConfigSetup \fc ->
FontConfig.setOversampleV fc (fromIntegral value)
-- | Align every glyph to pixel boundary.
--
-- Useful if you are merging a non-pixel aligned font with the default font.
-- If enabled, you can set OversampleH/V to 1.
--
-- By default, it is @false@
pixelSnapH :: Bool -> ConfigSetup
pixelSnapH value =
ConfigSetup \fc ->
FontConfig.setPixelSnapH fc (bool 0 1 value)
-- | Extra spacing (in pixels) between glyphs.
--
-- Only X axis is supported for now.
--
-- By default, it is @0, 0@
glyphExtraSpacing :: (Float, Float) -> ConfigSetup
glyphExtraSpacing (x, y) =
ConfigSetup \fc ->
Foreign.with (ImVec2 x y) (FontConfig.setGlyphExtraSpacing fc)
-- | Offset all glyphs from this font input.
--
-- By default, it is @0, 0@
glyphOffset :: (Float, Float) -> ConfigSetup
glyphOffset (x, y) =
ConfigSetup \fc ->
Foreign.with (ImVec2 x y) (FontConfig.setGlyphOffset fc)
-- | Pointer to a user-provided list of Unicode range.
--
-- 2 values per range, inclusive. Zero-terminated list.
--
-- THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE.
--
-- By default, it is @NULL@
glyphRanges :: GlyphRanges -> ConfigSetup
glyphRanges value =
ConfigSetup \fc ->
FontConfig.setGlyphRanges fc value
-- | Minimum AdvanceX for glyphs.
--
-- Set Min to align font icons, set both Min/Max to enforce mono-space font.
--
-- By default, it is @0@
glyphMinAdvanceX :: Float -> ConfigSetup
glyphMinAdvanceX value =
ConfigSetup \fc ->
FontConfig.setGlyphMinAdvanceX fc (CFloat value)
-- | Maximum AdvanceX for glyphs.
--
-- By default, it is @FLT_MAX@.
glyphMaxAdvanceX :: Float -> ConfigSetup
glyphMaxAdvanceX value =
ConfigSetup \fc ->
FontConfig.setGlyphMaxAdvanceX fc (CFloat value)
-- | Merge into previous ImFont, so you can combine multiple inputs font into one ImFont.
--
-- e.g. ASCII font + icons + Japanese glyphs.
-- You may want to use @GlyphOffset.y@ when merging font of different heights.
--
-- By default, it is @false@
mergeMode :: Bool -> ConfigSetup
mergeMode value =
ConfigSetup \fc ->
FontConfig.setMergeMode fc (bool 0 1 value)
-- | Settings for custom font GlyphRanges.
--
-- THIS IS BUILDER IMPLEMENTATION DEPENDENT.
--
-- By default, it is @0@. Leave it so if unsure.
fontBuilderFlags :: Int -> ConfigSetup
fontBuilderFlags value =
ConfigSetup \fc ->
FontConfig.setFontBuilderFlags fc (fromIntegral value)
-- | Brighten (>1.0f) or darken (<1.0f) font output.
--
-- Brightening small fonts may be a good workaround to make them more readable.
--
-- By default, it is @1.0f@.
rasterizerMultiply :: Float -> ConfigSetup
rasterizerMultiply value =
ConfigSetup \fc ->
FontConfig.setRasterizerMultiply fc (CFloat value)
-- | Explicitly specify unicode codepoint of ellipsis character.
--
-- When fonts are being merged first specified ellipsis will be used.
--
-- By default, it is @-1@
ellipsisChar :: ImWchar -> ConfigSetup
ellipsisChar value =
ConfigSetup \fc ->
FontConfig.setEllipsisChar fc value

203
src/DearImGui/GLFW.hs Normal file
View File

@ -0,0 +1,203 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGui.GLFW
GLFW specific functions backend for Dear ImGui.
Modules for initialising a backend with GLFW can be found under the corresponding backend,
e.g. "DearImGui.GLFW.OpenGL".
-}
module DearImGui.GLFW (
-- ** GLFW
glfwNewFrame
, glfwShutdown
-- $callbacks
, glfwWindowFocusCallback
, glfwCursorEnterCallback
, glfwCursorPosCallback
, glfwMouseButtonCallback
, glfwScrollCallback
, glfwKeyCallback
, glfwCharCallback
, glfwMonitorCallback
)
where
-- base
import Foreign
( Ptr, castPtr )
import Foreign.C.Types
( CInt, CDouble, CUInt )
import Unsafe.Coerce (unsafeCoerce)
-- bindings-GLFW
import Bindings.GLFW
( C'GLFWmonitor, C'GLFWwindow )
-- GLFW-b
import Graphics.UI.GLFW
( Monitor, Window )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
-- transformers
import Control.Monad.IO.Class
( MonadIO, liftIO )
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "backends/imgui_impl_glfw.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplGlfw_NewFrame@.
glfwNewFrame :: MonadIO m => m ()
glfwNewFrame = liftIO do
[C.exp| void { ImGui_ImplGlfw_NewFrame(); } |]
-- $callbacks
-- == GLFW callbacks
-- * When calling Init with @install_callbacks=true@:
-- GLFW callbacks will be installed for you.
-- They will call user's previously installed callbacks, if any.
-- * When calling Init with @install_callbacks=false@:
-- GLFW callbacks won't be installed.
-- You will need to call those function yourself from your own GLFW callbacks.
-- | Wraps @ImGui_ImplGlfw_Shutdown@.
glfwShutdown :: MonadIO m => m ()
glfwShutdown = liftIO do
[C.exp| void { ImGui_ImplGlfw_Shutdown(); } |]
glfwWindowFocusCallback :: MonadIO m => Window -> CInt -> m ()
glfwWindowFocusCallback window focused = liftIO do
[C.exp| void {
ImGui_ImplGlfw_WindowFocusCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(int focused)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwCursorEnterCallback :: MonadIO m => Window -> CInt -> m ()
glfwCursorEnterCallback window entered = liftIO do
[C.exp| void {
ImGui_ImplGlfw_CursorEnterCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(int entered)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwCursorPosCallback :: MonadIO m => Window -> CDouble -> CDouble -> m ()
glfwCursorPosCallback window x y = liftIO do
[C.exp| void {
ImGui_ImplGlfw_CursorPosCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(double x),
$(double y)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwMouseButtonCallback :: MonadIO m => Window -> CInt -> CInt -> CInt -> m ()
glfwMouseButtonCallback window button action mods = liftIO do
[C.exp| void {
ImGui_ImplGlfw_MouseButtonCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(int button),
$(int action),
$(int mods)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwScrollCallback :: MonadIO m => Window -> CDouble -> CDouble -> m ()
glfwScrollCallback window xoffset yoffset = liftIO do
[C.exp| void {
ImGui_ImplGlfw_ScrollCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(double xoffset),
$(double yoffset)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwKeyCallback :: MonadIO m => Window -> CInt -> CInt -> CInt -> CInt -> m ()
glfwKeyCallback window key scancode action mods = liftIO do
[C.exp| void {
ImGui_ImplGlfw_KeyCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(int key),
$(int scancode),
$(int action),
$(int mods)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwCharCallback :: MonadIO m => Window -> CUInt -> m ()
glfwCharCallback window c = liftIO do
[C.exp| void {
ImGui_ImplGlfw_CharCallback(
static_cast<GLFWwindow *>(
$(void * windowPtr)
),
$(unsigned int c)
);
} |]
where
windowPtr = castPtr $ unWindow window
glfwMonitorCallback :: MonadIO m => Monitor -> CInt -> m ()
glfwMonitorCallback monitor event = liftIO do
[C.exp| void {
ImGui_ImplGlfw_MonitorCallback(
static_cast<GLFWmonitor *>(
$(void * monitorPtr)
),
$(int event)
);
} |]
where
monitorPtr = castPtr $ unMonitor monitor
-- | Strip the unpublished newtype wrapper.
unWindow :: Window -> Ptr C'GLFWwindow
unWindow = unsafeCoerce
-- | Strip the unpublished newtype wrapper.
unMonitor :: Monitor -> Ptr C'GLFWmonitor
unMonitor = unsafeCoerce

View File

@ -0,0 +1,61 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGUI.GLFW.OpenGL
Initialising the OpenGL backend for Dear ImGui using GLFW3.
-}
module DearImGui.GLFW.OpenGL
( glfwInitForOpenGL )
where
-- base
import Data.Bool
( bool )
import Foreign.C.Types
( CBool )
import Foreign.Ptr
( Ptr )
import Unsafe.Coerce
( unsafeCoerce )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
-- GLFW
import Graphics.UI.GLFW
( Window )
-- transformers
import Control.Monad.IO.Class
( MonadIO, liftIO )
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "backends/imgui_impl_opengl2.h"
C.include "backends/imgui_impl_glfw.h"
C.include "GLFW/glfw3.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplGlfw_InitForOpenGL@.
glfwInitForOpenGL :: MonadIO m => Window -> Bool -> m Bool
glfwInitForOpenGL window installCallbacks = liftIO do
( 0 /= ) <$> [C.exp| bool { ImGui_ImplGlfw_InitForOpenGL((GLFWwindow*)$(void* windowPtr), $(bool cInstallCallbacks)) } |]
where
windowPtr :: Ptr ()
windowPtr = unsafeCoerce window
cInstallCallbacks :: CBool
cInstallCallbacks = bool 0 1 installCallbacks

View File

@ -0,0 +1,60 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGui.GLFW.Vulkan
Initialising the Vulkan backend for Dear ImGui using GLFW3.
-}
module DearImGui.GLFW.Vulkan
( glfwInitForVulkan )
where
-- base
import Data.Bool
( bool )
import Foreign.C.Types
( CBool )
import Foreign.Ptr
( Ptr )
import Unsafe.Coerce
( unsafeCoerce )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
-- GLFW
import Graphics.UI.GLFW
( Window )
-- transformers
import Control.Monad.IO.Class ( MonadIO, liftIO )
C.context Cpp.cppCtx
C.include "imgui.h"
C.include "backends/imgui_impl_vulkan.h"
C.include "backends/imgui_impl_glfw.h"
C.include "GLFW/glfw3.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplGlfw_InitForVulkan@.
glfwInitForVulkan :: MonadIO m => Window -> Bool -> m Bool
glfwInitForVulkan window installCallbacks = liftIO do
( 0 /= ) <$> [C.exp| bool { ImGui_ImplGlfw_InitForVulkan((GLFWwindow*)$(void* windowPtr), $(bool cInstallCallbacks)) } |]
where
windowPtr :: Ptr ()
windowPtr = unsafeCoerce window
cInstallCallbacks :: CBool
cInstallCallbacks = bool 0 1 installCallbacks

View File

@ -0,0 +1,76 @@
{-# LANGUAGE CPP #-}
module DearImGui.Internal.Text
( withCString
, withCStringOrNull
, withCStringLen
, withCStringEnd
, peekCString
, Text
, pack
, unpack
) where
-- base
import Foreign (nullPtr, plusPtr)
import Foreign.C.String (CString)
import qualified GHC.Foreign as Foreign
import System.IO (utf8)
-- text
import Data.Text (Text, pack, unpack)
import Data.Text.Foreign (withCStringLen)
-- unliftio-core
import UnliftIO (MonadUnliftIO, UnliftIO(..), withUnliftIO)
#if MIN_VERSION_text(2,0,1)
-- XXX: just wrap the provided combinator
import qualified Data.Text.Foreign as Text
withCString :: MonadUnliftIO m => Text -> (CString -> m a) -> m a
withCString text action =
withUnliftIO $ \(UnliftIO unlift) ->
Text.withCString text $ \buf ->
unlift $ action buf
#elif MIN_VERSION_text(2,0,0)
-- XXX: the text is UTF-8, alas no withCString is available
import Data.Text.Foreign (lengthWord8, unsafeCopyToPtr)
import Data.Word (Word8)
import Foreign (allocaBytes, castPtr, pokeByteOff)
withCString :: MonadUnliftIO m => Text -> (CString -> m a) -> m a
withCString t@(Text _arr _off len) action =
withUnliftIO $ \(UnliftIO unlift) ->
allocaBytes (len + 1) $ \buf -> do
unsafeCopyToPtr t buf
pokeByteOff buf len (0 :: Word8)
unlift $ action (castPtr buf)
#else
-- XXX: the text is UTF-16, let GHC do it
withCString :: MonadUnliftIO m => Text -> (CString -> m a) -> m a
withCString t action = do
withUnliftIO $ \(UnliftIO unlift) ->
Foreign.withCString utf8 (unpack t) $ \textPtr ->
unlift $ action textPtr
#endif
peekCString :: CString -> IO Text
peekCString = fmap pack . Foreign.peekCString utf8
withCStringOrNull :: Maybe Text -> (CString -> IO a) -> IO a
withCStringOrNull Nothing k = k nullPtr
withCStringOrNull (Just s) k = withCString s k
withCStringEnd :: MonadUnliftIO m => Text -> (CString -> CString -> m a) -> m a
withCStringEnd t action =
withUnliftIO $ \(UnliftIO unlift) ->
withCStringLen t $ \(textPtr, size) ->
unlift $ action textPtr (textPtr `plusPtr` size)

View File

@ -9,10 +9,10 @@
{-|
Module: DearImGui.OpenGL
OpenGL backend for Dear ImGui.
OpenGL 2 backend for Dear ImGui.
-}
module DearImGui.OpenGL
module DearImGui.OpenGL2
( openGL2Init
, openGL2Shutdown
, openGL2NewFrame

69
src/DearImGui/OpenGL3.hs Normal file
View File

@ -0,0 +1,69 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGui.OpenGL
OpenGL 3 backend for Dear ImGui.
-}
module DearImGui.OpenGL3
( openGL3Init
, openGL3Shutdown
, openGL3NewFrame
, openGL3RenderDrawData
)
where
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
-- transformers
import Control.Monad.IO.Class
( MonadIO, liftIO )
-- DearImGui
import DearImGui
( DrawData(..) )
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "GL/glew.h"
C.include "backends/imgui_impl_opengl3.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplOpenGL3_Init@.
openGL3Init :: MonadIO m => m Bool
openGL3Init = liftIO $
( 0 /= ) <$> [C.block| bool {
glewInit();
return ImGui_ImplOpenGL3_Init();
} |]
-- | Wraps @ImGui_ImplOpenGL3_Shutdown@.
openGL3Shutdown :: MonadIO m => m ()
openGL3Shutdown = liftIO do
[C.exp| void { ImGui_ImplOpenGL3_Shutdown(); } |]
-- | Wraps @ImGui_ImplOpenGL3_NewFrame@.
openGL3NewFrame :: MonadIO m => m ()
openGL3NewFrame = liftIO do
[C.exp| void { ImGui_ImplOpenGL3_NewFrame(); } |]
-- | Wraps @ImGui_ImplOpenGL3_RenderDrawData@.
openGL3RenderDrawData :: MonadIO m => DrawData -> m ()
openGL3RenderDrawData (DrawData ptr) = liftIO do
[C.exp| void { ImGui_ImplOpenGL3_RenderDrawData((ImDrawData*) $( void* ptr )) } |]

1850
src/DearImGui/Raw.hs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
{-# language DerivingStrategies #-}
{-# language DuplicateRecordFields #-}
{-# language GeneralizedNewtypeDeriving #-}
{-# language NamedFieldPuns #-}
{-# language OverloadedStrings #-}
{-# language PatternSynonyms #-}
{-# language TemplateHaskell #-}
module DearImGui.Raw.Context where
-- containers
import qualified Data.Map.Strict as Map
-- inline-c
import Language.C.Inline.Context
( Context(..) )
import Language.C.Types
( pattern TypeName )
-- dear-imgui
import DearImGui.Structs
-- dear-imgui-generator
import DearImGui.Generator
( enumerationsTypesTable )
--------------------------------------------------------------------------------
imguiContext :: Context
imguiContext = mempty
{ ctxTypesTable = enumerationsTypesTable <>
Map.fromList
[ ( TypeName "ImVec2", [t| ImVec2 |] )
, ( TypeName "ImVec3", [t| ImVec3 |] )
, ( TypeName "ImVec4", [t| ImVec4 |] )
, ( TypeName "ImU32", [t| ImU32 |] )
, ( TypeName "ImGuiID", [t| ImGuiID |] )
, ( TypeName "ImWchar", [t| ImWchar |] )
, ( TypeName "ImDrawList", [t| ImDrawList |] )
, ( TypeName "ImGuiContext", [t| ImGuiContext |] )
, ( TypeName "ImFont", [t| ImFont |] )
, ( TypeName "ImFontConfig", [t| ImFontConfig |] )
, ( TypeName "ImFontGlyphRangesBuilder", [t| ImFontGlyphRangesBuilder |] )
, ( TypeName "ImGuiListClipper", [t| ImGuiListClipper |] )
, ( TypeName "ImGuiTableSortSpecs", [t| ImGuiTableSortSpecs |] )
]
}

View File

@ -0,0 +1,742 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-| Draw command list
This is the low-level list of polygons that ImGui functions are filling.
At the end of the frame, all command lists are passed to your @ImGuiIO::RenderDrawListFn@ function for rendering.
Each dear imgui window contains its own ImDrawList.
You can use 'getWindowDrawList' to access the current window draw list and draw custom primitives.
You can interleave normal ImGui calls and adding primitives to the current draw list.
In single viewport mode, top-left is == @GetMainViewport()->Pos@ (generally @0,0@),
bottom-right is == @GetMainViewport()->Pos+Size@ (generally io.DisplaySize).
You are totally free to apply whatever transformation matrix to want to the data
(depending on the use of the transformation you may want to apply it to ClipRect as well!).
__Important__: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui functions),
if you use this API a lot consider coarse culling your drawn objects.
-}
module DearImGui.Raw.DrawList
( DrawList(..)
, new
, destroy
-- * Primitives
-- $primitives
, addLine
, addRect
, addRectFilled
, addRectFilledMultiColor
, addQuad
, addQuadFilled
, addTriangle
, addTriangleFilled
, addCircle
, addCircleFilled
, addNgon
, addNgonFilled
, addText_
, addText
, addPolyLine
, addConvexPolyFilled
, addBezierCubic
, addBezierQuadratic
-- ** Image primitives
-- $image
, addImage
, addImageQuad
, addImageRounded
-- * Stateful path API
-- $stateful
, pathClear
, pathLineTo
, pathLineToMergeDuplicate
, pathFillConvex
, pathStroke
, pathArcTo
, pathArcToFast
, pathBezierCubicCurveTo
, pathBezierQuadraticCurveTo
, pathRect
-- * Advanced
-- , addCallback
, addDrawCmd
, cloneOutput
-- * Internal state
, pushClipRect
, pushClipRectFullScreen
, popClipRect
, getClipRectMin
, getClipRectMax
, pushTextureID
, popTextureID
)
where
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign hiding (new)
import Foreign.C
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
import DearImGui.Enums
import DearImGui.Structs
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
-- | A single draw command list.
-- Generally one per window, conceptually you may see this as a dynamic "mesh" builder.
newtype DrawList = DrawList (Ptr ImDrawList)
new :: MonadIO m => m DrawList
new = liftIO do
DrawList <$> [C.block|
ImDrawList* {
return IM_NEW(ImDrawList(GetDrawListSharedData()));
}
|]
destroy :: MonadIO m => DrawList -> m ()
destroy (DrawList drawList) = liftIO do
[C.block|
void {
IM_DELETE($(ImDrawList* drawList));
}
|]
pushClipRect :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> CBool -> m ()
pushClipRect (DrawList drawList) clip_rect_min clip_rect_max intersect_with_current_clip_rect = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PushClipRect(
*$(ImVec2* clip_rect_min),
*$(ImVec2* clip_rect_max),
$(bool intersect_with_current_clip_rect)
);
}
|]
pushClipRectFullScreen :: MonadIO m => DrawList -> m ()
pushClipRectFullScreen (DrawList drawList) = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PushClipRectFullScreen();
}
|]
popClipRect :: MonadIO m => DrawList -> m ()
popClipRect (DrawList drawList) = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PopClipRect();
}
|]
getClipRectMin :: MonadIO m => DrawList -> m ImVec2
getClipRectMin (DrawList drawList) = liftIO do
C.withPtr_ \ptr ->
[C.block|
void {
*$(ImVec2 * ptr) = $(ImDrawList* drawList)->GetClipRectMin();
}
|]
getClipRectMax :: MonadIO m => DrawList -> m ImVec2
getClipRectMax (DrawList drawList) = liftIO do
C.withPtr_ \ptr ->
[C.block|
void {
*$(ImVec2 * ptr) = $(ImDrawList* drawList)->GetClipRectMax();
}
|]
pushTextureID :: MonadIO m => DrawList -> Ptr () -> m ()
pushTextureID (DrawList drawList) userTextureIDPtr = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PushTextureID(
$(void* userTextureIDPtr)
);
}
|]
popTextureID :: MonadIO m => DrawList -> m ()
popTextureID (DrawList drawList) = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PopTextureID();
}
|]
{- $primitives
- For rectangular primitives, @p_min@ and @p_max@ represent the upper-left and lower-right corners.
- For circle primitives, use @num_segments == 0@ to automatically calculate tessellation (preferred).
In older versions (until Dear ImGui 1.77) the 'addCircle' functions defaulted to num_segments == 12.
In future versions we will use textures to provide cheaper and higher-quality circles.
Use 'addNgon' and 'addNgonFilled' functions if you need to guaranteed a specific number of sides.
-}
addLine :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> CFloat -> m ()
addLine (DrawList drawList) p1 p2 col thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddLine(
*$(ImVec2* p1),
*$(ImVec2* p2),
$(ImU32 col),
$(float thickness)
);
}
|]
addRect :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> CFloat -> ImDrawFlags -> CFloat -> m ()
addRect (DrawList drawList) p_min p_max col rounding flags thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddRect(
*$(ImVec2* p_min),
*$(ImVec2* p_max),
$(ImU32 col),
$(float rounding),
$(ImDrawFlags flags),
$(float thickness)
);
}
|]
addRectFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> CFloat -> ImDrawFlags -> m ()
addRectFilled (DrawList drawList) p_min p_max col rounding flags = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddRectFilled(
*$(ImVec2* p_min),
*$(ImVec2* p_max),
$(ImU32 col),
$(float rounding),
$(ImDrawFlags flags)
);
}
|]
addRectFilledMultiColor :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> ImU32 -> ImU32 -> ImU32 -> m ()
addRectFilledMultiColor (DrawList drawList) p_min p_max col_upr_left col_upr_right col_bot_right col_bot_left = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddRectFilledMultiColor(
*$(ImVec2* p_min),
*$(ImVec2* p_max),
$(ImU32 col_upr_left),
$(ImU32 col_upr_right),
$(ImU32 col_bot_right),
$(ImU32 col_bot_left)
);
}
|]
addQuad :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> CFloat -> m ()
addQuad (DrawList drawList) p1 p2 p3 p4 col thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddQuad(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
*$(ImVec2* p4),
$(ImU32 col),
$(float thickness)
);
}
|]
addQuadFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> m ()
addQuadFilled (DrawList drawList) p1 p2 p3 p4 col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddQuadFilled(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
*$(ImVec2* p4),
$(ImU32 col)
);
}
|]
addTriangle :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> CFloat -> m ()
addTriangle (DrawList drawList) p1 p2 p3 col thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddTriangle(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
$(ImU32 col),
$(float thickness)
);
}
|]
addTriangleFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> ImU32 -> m ()
addTriangleFilled (DrawList drawList) p1 p2 p3 col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddTriangleFilled(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
$(ImU32 col)
);
}
|]
addCircle :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> ImU32 -> CInt -> CFloat -> m ()
addCircle (DrawList drawList) center radius col num_segments thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddCircle(
*$(ImVec2* center),
$(float radius),
$(ImU32 col),
$(int num_segments),
$(float thickness)
);
}
|]
addCircleFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> ImU32 -> CInt -> m ()
addCircleFilled (DrawList drawList) center radius col num_segments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddCircleFilled(
*$(ImVec2* center),
$(float radius),
$(ImU32 col),
$(int num_segments)
);
}
|]
addNgon :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> ImU32 -> CInt -> CFloat -> m ()
addNgon (DrawList drawList) center radius col num_segments thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddNgon(
*$(ImVec2* center),
$(float radius),
$(ImU32 col),
$(int num_segments),
$(float thickness)
);
}
|]
addNgonFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> ImU32 -> CInt -> m ()
addNgonFilled (DrawList drawList) center radius col num_segments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddNgonFilled(
*$(ImVec2* center),
$(float radius),
$(ImU32 col),
$(int num_segments)
);
}
|]
addText_ :: MonadIO m => DrawList -> Ptr ImVec2 -> ImU32 -> CString -> CString -> m ()
addText_ (DrawList drawList) pos col text_begin text_end = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddText(
*$(ImVec2* pos),
$(ImU32 col),
$(char* text_begin),
$(char* text_end)
);
}
|]
addText :: MonadIO m => DrawList -> Ptr ImFont -> CFloat -> Ptr ImVec2 -> ImU32 -> CString -> CString -> CFloat -> Ptr ImVec4 -> m ()
addText (DrawList drawList) fontPtr font_size pos col text_begin text_end wrap_width cpu_fine_clip_rect = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddText(
$(ImFont* fontPtr),
$(float font_size),
*$(ImVec2* pos),
$(ImU32 col),
$(char* text_begin),
$(char* text_end),
$(float wrap_width),
$(ImVec4* cpu_fine_clip_rect)
);
}
|]
addPolyLine :: MonadIO m => DrawList -> Ptr ImVec2 -> CInt -> ImU32 -> ImDrawFlags -> CFloat -> m ()
addPolyLine (DrawList drawList) points num_points col flags thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddPolyline(
$(ImVec2* points),
$(int num_points),
$(ImU32 col),
$(ImDrawFlags flags),
$(float thickness)
);
}
|]
addConvexPolyFilled :: MonadIO m => DrawList -> Ptr ImVec2 -> CInt -> ImU32 -> m ()
addConvexPolyFilled (DrawList drawList) points num_points col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddConvexPolyFilled(
$(ImVec2* points),
$(int num_points),
$(ImU32 col)
);
}
|]
addBezierCubic
:: MonadIO m
=> DrawList
-> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -- Positions (control points)
-> ImU32
-> CFloat
-> CInt
-> m ()
addBezierCubic (DrawList drawList) p1 p2 p3 p4 col thickness numSegments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddBezierCubic(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
*$(ImVec2* p4),
$(ImU32 col),
$(float thickness),
$(int numSegments)
);
}
|]
addBezierQuadratic
:: MonadIO m
=> DrawList
-> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -- Positions (control points)
-> ImU32
-> CFloat
-> CInt
-> m ()
addBezierQuadratic (DrawList drawList) p1 p2 p3 col thickness numSegments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddBezierQuadratic(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
$(ImU32 col),
$(float thickness),
$(int numSegments)
);
}
|]
{- $image
* Read FAQ to understand what @ImTextureID@ is.
* @p_min@ and @p_max@ represent the upper-left and lower-right corners of the rectangle.
* @uv_min@ and @uv_max@ represent the normalized texture coordinates to use for those corners.
Using @(0,0)->(1,1)@ texture coordinates will generally display the entire texture.
-}
addImage
:: MonadIO m
=> DrawList
-> Ptr ()
-> Ptr ImVec2 -> Ptr ImVec2 -- Positions
-> Ptr ImVec2 -> Ptr ImVec2 -- UVs
-> ImU32
-> m ()
addImage (DrawList drawList) userTextureIDPtr p_min p_max uv_min uv_max col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddImage(
$(void* userTextureIDPtr),
*$(ImVec2* p_min),
*$(ImVec2* p_max),
*$(ImVec2* uv_min),
*$(ImVec2* uv_max),
$(ImU32 col)
);
}
|]
addImageQuad
:: MonadIO m
=> DrawList
-> Ptr ()
-> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -- Positions
-> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -> Ptr ImVec2 -- UVs
-> ImU32
-> m ()
addImageQuad (DrawList drawList) userTextureIDPtr p1 p2 p3 p4 uv1 uv2 uv3 uv4 col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddImageQuad(
$(void* userTextureIDPtr),
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
*$(ImVec2* p4),
*$(ImVec2* uv1),
*$(ImVec2* uv2),
*$(ImVec2* uv3),
*$(ImVec2* uv4),
$(ImU32 col)
);
}
|]
addImageRounded
:: MonadIO m
=> DrawList
-> Ptr ()
-> Ptr ImVec2 -> Ptr ImVec2 -- Positions
-> Ptr ImVec2 -> Ptr ImVec2 -- UVs
-> ImU32
-> CFloat
-> ImDrawFlags
-> m ()
addImageRounded (DrawList drawList) userTextureIDPtr p_min p_max uv_min uv_max col rounding flags = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddImageRounded(
$(void* userTextureIDPtr),
*$(ImVec2* p_min),
*$(ImVec2* p_max),
*$(ImVec2* uv_min),
*$(ImVec2* uv_max),
$(ImU32 col),
$(float rounding),
$(ImDrawFlags flags)
);
}
|]
{- $stateful
Add points then finish with 'pathFillConvex' or 'pathStroke'.
-}
pathClear :: MonadIO m => DrawList -> m ()
pathClear (DrawList drawList) = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathClear();
}
|]
pathLineTo :: MonadIO m => DrawList -> Ptr ImVec2 -> m ()
pathLineTo (DrawList drawList) pos = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathLineTo(
*$(ImVec2* pos)
);
}
|]
pathLineToMergeDuplicate :: MonadIO m => DrawList -> Ptr ImVec2 -> m ()
pathLineToMergeDuplicate (DrawList drawList) pos = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathLineToMergeDuplicate(
*$(ImVec2* pos)
);
}
|]
-- | Note: Anti-aliased filling requires points to be in clockwise order.
pathFillConvex :: MonadIO m => DrawList -> ImU32 -> m ()
pathFillConvex (DrawList drawList) col = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathFillConvex(
$(ImU32 col)
);
}
|]
pathStroke :: MonadIO m => DrawList -> ImU32 -> ImDrawFlags -> CFloat -> m ()
pathStroke (DrawList drawList) col flags thickness = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathStroke(
$(ImU32 col),
$(ImDrawFlags flags),
$(float thickness)
);
}
|]
pathArcTo :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> CFloat -> CFloat -> CInt -> m ()
pathArcTo (DrawList drawList) center radius a_min a_max num_segments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathArcTo(
*$(ImVec2* center),
$(float radius),
$(float a_min),
$(float a_max),
$(int num_segments)
);
}
|]
-- | Use precomputed angles for a 12 steps circle.
pathArcToFast :: MonadIO m => DrawList -> Ptr ImVec2 -> CFloat -> CInt -> CInt -> m ()
pathArcToFast (DrawList drawList) center radius a_min_of_12 a_max_of_12 = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathArcToFast(
*$(ImVec2* center),
$(float radius),
$(int a_min_of_12),
$(int a_max_of_12)
);
}
|]
pathBezierCubicCurveTo
:: MonadIO m
=> DrawList
-> Ptr ImVec2
-> Ptr ImVec2
-> Ptr ImVec2
-> CInt
-> m ()
pathBezierCubicCurveTo (DrawList drawList) p1 p2 p3 num_segments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathBezierCubicCurveTo(
*$(ImVec2* p1),
*$(ImVec2* p2),
*$(ImVec2* p3),
$(int num_segments)
);
}
|]
pathBezierQuadraticCurveTo
:: MonadIO m
=> DrawList
-> Ptr ImVec2
-> Ptr ImVec2
-> CInt
-> m ()
pathBezierQuadraticCurveTo (DrawList drawList) p1 p2 num_segments = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathBezierQuadraticCurveTo(
*$(ImVec2* p1),
*$(ImVec2* p2),
$(int num_segments)
);
}
|]
pathRect :: MonadIO m => DrawList -> Ptr ImVec2 -> Ptr ImVec2 -> CFloat -> ImDrawFlags -> m ()
pathRect (DrawList drawList) rect_min rect_max rounding flags = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->PathRect(
*$(ImVec2* rect_min),
*$(ImVec2* rect_max),
$(float rounding),
$(ImDrawFlags flags)
);
}
|]
-- | This is useful if you need to forcefully create a new draw call (to allow for dependent rendering / blending).
-- Otherwise primitives are merged into the same draw-call as much as possible.
addDrawCmd :: MonadIO m => DrawList -> m ()
addDrawCmd (DrawList drawList) = liftIO do
[C.block|
void {
$(ImDrawList* drawList)->AddDrawCmd();
}
|]
-- | Create a clone of the CmdBuffer/IdxBuffer/VtxBuffer.
cloneOutput :: MonadIO m => DrawList -> m DrawList
cloneOutput (DrawList drawList) = liftIO do
DrawList <$> [C.block|
ImDrawList* {
return $(ImDrawList* drawList)->CloneOutput();
}
|]

141
src/DearImGui/Raw/Font.hs Normal file
View File

@ -0,0 +1,141 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-| Fonts
It includes default atlas management, font configuration and glyph ranges.
-}
module DearImGui.Raw.Font
( -- * Types
Font(..)
, GlyphRanges(..)
-- * Adding fonts
, addFontDefault
, addFontFromFileTTF
, addFontFromMemoryTTF
-- * Using fonts
, pushFont
, popFont
-- * Atlas management
, clearFontAtlas
, buildFontAtlas
)
where
-- base
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign ( Ptr, castPtr )
import Foreign.C
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
import DearImGui.Structs
import DearImGui.Raw.Font.Config
( FontConfig(..) )
import DearImGui.Raw.Font.GlyphRanges
( GlyphRanges(..) )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
-- | Font runtime data handle
--
-- Wraps @ImFont*@.
newtype Font = Font (Ptr ImFont)
-- | Add the default font (@ProggyClean.ttf@, 13 px) to the atlas.
addFontDefault :: MonadIO m
=> m Font -- ^ Returns font handle for future usage
addFontDefault = liftIO do
Font <$> [C.block|
ImFont* {
return GetIO().Fonts->AddFontDefault();
}
|]
-- | Add a custom OTF/TTF font from a file.
addFontFromFileTTF :: MonadIO m
=> CString -- ^ Font file path
-> CFloat -- ^ Font size in pixels
-> FontConfig -- ^ Configuration data
-> GlyphRanges -- ^ Glyph ranges to use
-> m Font -- ^ Returns font handle for future usage
addFontFromFileTTF filenamePtr sizePixels (FontConfig fontConfig) (GlyphRanges glyphRanges) = liftIO do
Font <$> [C.block|
ImFont* {
return GetIO().Fonts->AddFontFromFileTTF(
$(char* filenamePtr),
$(float sizePixels),
$(ImFontConfig* fontConfig),
$(ImWchar* glyphRanges));
}
|]
-- | Transfer a buffer with TTF data to font atlas builder.
addFontFromMemoryTTF :: MonadIO m => CStringLen -> CFloat -> FontConfig -> GlyphRanges -> m Font
addFontFromMemoryTTF (castPtr -> fontDataPtr, fromIntegral -> fontSize) sizePixels (FontConfig fontConfig) (GlyphRanges glyphRanges) = liftIO do
Font <$> [C.block|
ImFont* {
return GetIO().Fonts->AddFontFromMemoryTTF(
$(void* fontDataPtr),
$(int fontSize),
$(float sizePixels),
$(ImFontConfig* fontConfig),
$(ImWchar* glyphRanges)
);
}
|]
-- | Pushes a font into the parameters stack,
-- so ImGui would render following text using it.
pushFont :: MonadIO m => Font -> m ()
pushFont (Font font) = liftIO do
[C.exp| void { PushFont($(ImFont* font)); } |]
-- | Pops a font pushed into the parameters stack
--
-- Should be called only after a corresponding 'pushFont' call.
popFont :: MonadIO m => m ()
popFont = liftIO do
[C.exp| void { PopFont(); } |]
-- | Explicitly build pixels data for the atlas.
buildFontAtlas :: MonadIO m => m ()
buildFontAtlas = liftIO do
[C.block|
void {
GetIO().Fonts->Build();
}
|]
-- | Clear all font atlas input and output data
clearFontAtlas :: MonadIO m => m ()
clearFontAtlas = liftIO do
[C.block|
void {
GetIO().Fonts->Clear();
}
|]

View File

@ -0,0 +1,256 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-| Font configuration
IO functions to modify font config values.
-}
module DearImGui.Raw.Font.Config
( FontConfig(..)
, new
, destroy
-- * Changing settings
, setFontDataOwnedByAtlas
, setFontNo
, setSizePixels
, setOversampleH
, setOversampleV
, setPixelSnapH
, setGlyphExtraSpacing
, setGlyphOffset
, setGlyphRanges
, setGlyphMinAdvanceX
, setGlyphMaxAdvanceX
, setMergeMode
, setFontBuilderFlags
, setRasterizerMultiply
, setEllipsisChar
)
where
-- base
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign ( Ptr )
import Foreign.C
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
import DearImGui.Structs
import DearImGui.Raw.Font.GlyphRanges
( GlyphRanges(..) )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
-- | Font configuration data handle
--
-- Wraps @ImFontConfig*@.
newtype FontConfig = FontConfig (Ptr ImFontConfig)
-- | Create an instance of config
new :: MonadIO m => m FontConfig
new = liftIO do
FontConfig <$> [C.block|
ImFontConfig* {
return IM_NEW(ImFontConfig);
}
|]
-- | Destroy an instance of config
--
-- Should be used __after__ font atlas building.
destroy :: MonadIO m => FontConfig -> m ()
destroy (FontConfig config) = liftIO do
[C.block|
void {
IM_DELETE($(ImFontConfig* config));
}
|]
-- | TTF/OTF data ownership taken by the container ImFontAtlas (will delete memory itself).
--
-- By default, it is @true@
setFontDataOwnedByAtlas :: MonadIO m => FontConfig -> CBool -> m ()
setFontDataOwnedByAtlas (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->FontDataOwnedByAtlas = $(bool value);
}
|]
-- | Index of font within TTF/OTF file
--
-- By default, it is @0@
setFontNo :: MonadIO m => FontConfig -> CInt -> m ()
setFontNo (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->FontNo = $(int value);
}
|]
-- | Size in pixels for rasterizer (more or less maps to the resulting font height).
--
-- Implicitly set by @addFont...@ functions.
setSizePixels :: MonadIO m => FontConfig -> CFloat -> m ()
setSizePixels (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->SizePixels = $(float value);
}
|]
-- | Rasterize at higher quality for sub-pixel positioning. Note the difference between 2 and 3 is minimal so you can reduce this to 2 to save memory. Read https://github.com/nothings/stb/blob/master/tests/oversample/README.md for details.
--
-- By default, it is @3@
setOversampleH :: MonadIO m => FontConfig -> CInt -> m ()
setOversampleH (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->OversampleH = $(int value);
}
|]
-- | Rasterize at higher quality for sub-pixel positioning. This is not really useful as we don't use sub-pixel positions on the Y axis.
--
-- By default, it is @1@
setOversampleV :: MonadIO m => FontConfig -> CInt -> m ()
setOversampleV (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->OversampleV = $(int value);
}
|]
-- | Align every glyph to pixel boundary. Useful e.g. if you are merging a non-pixel aligned font with the default font. If enabled, you can set OversampleH/V to 1.
--
-- By default, it is @false@
setPixelSnapH :: MonadIO m => FontConfig -> CBool -> m ()
setPixelSnapH (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->PixelSnapH = $(bool value);
}
|]
-- | Extra spacing (in pixels) between glyphs. Only X axis is supported for now.
--
-- By default, it is @0, 0@
setGlyphExtraSpacing :: MonadIO m => FontConfig -> Ptr ImVec2 -> m ()
setGlyphExtraSpacing (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->GlyphExtraSpacing = *$(ImVec2* value);
}
|]
-- | Offset all glyphs from this font input.
--
-- By default, it is @0, 0@
setGlyphOffset :: MonadIO m => FontConfig -> Ptr ImVec2 -> m ()
setGlyphOffset (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->GlyphOffset = *$(ImVec2* value);
}
|]
-- | Pointer to a user-provided list of Unicode range (2 value per range, values are inclusive, zero-terminated list). THE ARRAY DATA NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE.
--
-- By default, it is @NULL@
setGlyphRanges :: MonadIO m => FontConfig -> GlyphRanges -> m ()
setGlyphRanges (FontConfig fc) (GlyphRanges value) = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->GlyphRanges = $(ImWchar* value);
}
|]
-- | Minimum AdvanceX for glyphs, set Min to align font icons, set both Min/Max to enforce mono-space font
--
-- By default, it is @0@
setGlyphMinAdvanceX :: MonadIO m => FontConfig -> CFloat -> m ()
setGlyphMinAdvanceX (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->GlyphMinAdvanceX = $(float value);
}
|]
-- | Maximum AdvanceX for glyphs
--
-- By default, it is @FLT_MAX@
setGlyphMaxAdvanceX :: MonadIO m => FontConfig -> CFloat -> m ()
setGlyphMaxAdvanceX (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->GlyphMaxAdvanceX = $(float value);
}
|]
-- | Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights.
--
-- By default, it is @false@
setMergeMode :: MonadIO m => FontConfig -> CBool -> m ()
setMergeMode (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->MergeMode = $(bool value);
}
|]
-- | Settings for custom font builder.
-- THIS IS BUILDER IMPLEMENTATION DEPENDENT.
--
-- By default, it is @0@. Leave it so if unsure.
setFontBuilderFlags :: MonadIO m => FontConfig -> CUInt -> m ()
setFontBuilderFlags (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->FontBuilderFlags = $(unsigned int value);
}
|]
-- | Brighten (>1.0f) or darken (<1.0f) font output.
-- Brightening small fonts may be a good workaround to make them more readable.
--
-- By default, it is @1.0f@
setRasterizerMultiply :: MonadIO m => FontConfig -> CFloat -> m ()
setRasterizerMultiply (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->RasterizerMultiply = $(float value);
}
|]
-- | Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used.
--
-- By default, it is @-1@
setEllipsisChar :: MonadIO m => FontConfig -> ImWchar -> m ()
setEllipsisChar (FontConfig fc) value = liftIO do
[C.block|
void {
$(ImFontConfig* fc)->EllipsisChar = $(ImWchar value);
}
|]

View File

@ -0,0 +1,295 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-| Font glyph ranges builder
Helper to build glyph ranges from text/string data.
Feed your application strings/characters to it then call 'buildRanges'.
Low-level example of usage:
@
-- import ImGui.Fonts
-- import ImGui.Raw.GlyphRangesBuilder as GRB
builder <- GRB.new
GRB.addRanges builder getGlyphRangesDefault
liftIO $ withCString "Привет" $ GRB.addText builder
rangesVec <- GRB.buildRanges builder
let ranges = GRB.fromRangesVector rangesVec
addFontFromFileTTF'
"./imgui/misc/fonts/DroidSans.ttf" 12
Nothing
(Just ranges)
-- it is strictly necessary to explicitly build the atlas
buildFontAtlas
-- resource destruction comes only after the building
GRB.destroyRangesVector rangesVec
GRB.destroy builder
@
-}
module DearImGui.Raw.Font.GlyphRanges
( GlyphRanges(..)
-- * Built-in ranges
, Builtin(..)
, getBuiltin
, builtinSetup
-- * Preparing a builder
, GlyphRangesBuilder(..)
, new
, destroy
, addChar
, addText
, addRanges
-- * Extracting data
, GlyphRangesVector(..)
, buildRangesVector
, fromRangesVector
, destroyRangesVector
)
where
-- base
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign ( Ptr )
import Foreign.C
import System.IO.Unsafe (unsafePerformIO)
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
import DearImGui.Structs
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
-- | Glyph ranges handle
--
-- Wraps @ImWchar*@.
newtype GlyphRanges = GlyphRanges (Ptr ImWchar)
-- | Builtin glyph ranges tags.
data Builtin
= Latin
| Korean
| Japanese
| ChineseFull
| ChineseSimplifiedCommon
| Cyrillic
| Thai
| Vietnamese
deriving (Eq, Ord, Show, Enum, Bounded)
-- | Get builtin glyph ranges from a tag.
getBuiltin :: Builtin -> GlyphRanges
getBuiltin = \case
Latin -> getGlyphRangesDefault
Korean -> getGlyphRangesKorean
Japanese -> getGlyphRangesJapanese
ChineseFull -> getGlyphRangesChineseFull
ChineseSimplifiedCommon -> getGlyphRangesChineseSimplifiedCommon
Cyrillic -> getGlyphRangesCyrillic
Thai -> getGlyphRangesThai
Vietnamese -> getGlyphRangesVietnamese
-- | Special case of @getBuiltin@, but for font source setup.
builtinSetup :: Builtin -> Maybe GlyphRanges
builtinSetup = \case
Latin -> Nothing
others -> Just (getBuiltin others)
-- | Basic Latin, Extended Latin
getGlyphRangesDefault :: GlyphRanges
getGlyphRangesDefault = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesDefault();
}
|]
-- | Default + Korean characters
getGlyphRangesKorean :: GlyphRanges
getGlyphRangesKorean = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesKorean();
}
|]
-- | Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs
getGlyphRangesJapanese :: GlyphRanges
getGlyphRangesJapanese = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesJapanese();
}
|]
-- | Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs
getGlyphRangesChineseFull :: GlyphRanges
getGlyphRangesChineseFull = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesChineseFull();
}
|]
-- | Default + Half-Width + Japanese Hiragana/Katakana + set of 2500 CJK Unified Ideographs for common simplified Chinese
getGlyphRangesChineseSimplifiedCommon :: GlyphRanges
getGlyphRangesChineseSimplifiedCommon = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon();
}
|]
-- | Default + about 400 Cyrillic characters
getGlyphRangesCyrillic :: GlyphRanges
getGlyphRangesCyrillic = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesCyrillic();
}
|]
-- | Default + Thai characters
getGlyphRangesThai :: GlyphRanges
getGlyphRangesThai = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesThai();
}
|]
-- | Default + Vietnamese characters
getGlyphRangesVietnamese :: GlyphRanges
getGlyphRangesVietnamese = unsafePerformIO do
GlyphRanges <$> [C.block|
const ImWchar* {
return GetIO().Fonts->GetGlyphRangesVietnamese();
}
|]
-- | Glyph ranges builder handle
--
-- Wraps @ImFontGlyphRangesBuilder*@.
newtype GlyphRangesBuilder = GlyphRangesBuilder (Ptr ImFontGlyphRangesBuilder)
-- | Glyph ranges vector handle to keep builder output
--
-- Wraps @ImVector<ImWchar>*@.
newtype GlyphRangesVector = GlyphRangesVector (Ptr ())
-- | Create an instance of builder
new :: MonadIO m => m GlyphRangesBuilder
new = liftIO do
GlyphRangesBuilder <$> [C.block|
ImFontGlyphRangesBuilder* {
return IM_NEW(ImFontGlyphRangesBuilder);
}
|]
-- | Destroy an instance of builder
--
-- Should be used __after__ font atlas building.
destroy :: MonadIO m => GlyphRangesBuilder -> m ()
destroy (GlyphRangesBuilder builder) = liftIO do
[C.block|
void {
IM_DELETE($(ImFontGlyphRangesBuilder* builder));
}
|]
-- | Add character
addChar :: MonadIO m => GlyphRangesBuilder -> ImWchar -> m ()
addChar (GlyphRangesBuilder builder) wChar = liftIO do
[C.block|
void {
$(ImFontGlyphRangesBuilder* builder)->AddChar($(ImWchar wChar));
}
|]
-- | Add string (each character of the UTF-8 string are added)
addText :: MonadIO m => GlyphRangesBuilder -> CString -> m ()
addText (GlyphRangesBuilder builder) string = liftIO do
[C.block|
void {
$(ImFontGlyphRangesBuilder* builder)->AddText($(char* string));
}
|]
-- FIXME: the function uses 'const char* text_end = NULL' parameter,
-- which is pointer for the line ending. It is low level, though it
-- could be utilized for string length parameter.
-- | Add ranges, e.g. 'addRanges builder getGlyphRangesDefault'
-- to force add all of ASCII/Latin+Ext
addRanges :: MonadIO m => GlyphRangesBuilder -> GlyphRanges -> m()
addRanges (GlyphRangesBuilder builder) (GlyphRanges ranges) = liftIO do
[C.block|
void {
$(ImFontGlyphRangesBuilder* builder)->AddRanges($(ImWchar* ranges));
}
|]
-- | Build new ranges and create ranges vector instance,
-- containing them
buildRangesVector :: MonadIO m => GlyphRangesBuilder -> m (GlyphRangesVector)
buildRangesVector (GlyphRangesBuilder builder) = liftIO do
GlyphRangesVector <$> [C.block|
void* {
ImVector<ImWchar>* ranges = IM_NEW(ImVector<ImWchar>);
$(ImFontGlyphRangesBuilder* builder)->BuildRanges(ranges);
return ranges;
}
|]
-- | Extract glyph ranges from a vector
--
-- Should be used __before__ vector destruction.
fromRangesVector :: GlyphRangesVector -> GlyphRanges
fromRangesVector (GlyphRangesVector vecPtr) = unsafePerformIO do
GlyphRanges <$> [C.block|
ImWchar* {
return ((ImVector<ImWchar>*) $(void* vecPtr))->Data;
}
|]
-- | Destroy a ranges vector instance
--
-- Should be used __after__ font atlas building.
destroyRangesVector :: MonadIO m => GlyphRangesVector -> m ()
destroyRangesVector (GlyphRangesVector vecPtr) = liftIO do
[C.block|
void {
IM_DELETE(((ImVector<ImWchar>*) $(void* vecPtr)));
}
|]

134
src/DearImGui/Raw/IO.hs Normal file
View File

@ -0,0 +1,134 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-| Main configuration and I/O between your application and ImGui
-}
module DearImGui.Raw.IO
( setIniFilename
, setLogFilename
, setMouseDoubleClickMaxDist
, setMouseDoubleClickTime
, setMouseDragThreshold
, setKeyRepeatDelay
, setKeyRepeatRate
, setUserData
) where
-- TODO: add exports
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign
( Ptr )
import Foreign.C
( CFloat(..)
, CString
)
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
-- import DearImGui.Enums
-- import DearImGui.Structs
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
setIniFilename :: MonadIO m => CString -> m ()
setIniFilename ptr = liftIO do
[C.block|
void {
GetIO().IniFilename = $(char * ptr);
}
|]
setLogFilename :: MonadIO m => CString -> m ()
setLogFilename ptr = liftIO do
[C.block|
void {
GetIO().LogFilename = $(char * ptr);
}
|]
setMouseDoubleClickTime :: MonadIO m => CFloat -> m ()
setMouseDoubleClickTime seconds = liftIO do
[C.block|
void {
GetIO().MouseDoubleClickTime = $(float seconds);
}
|]
setMouseDoubleClickMaxDist :: MonadIO m => CFloat -> m ()
setMouseDoubleClickMaxDist pixels = liftIO do
[C.block|
void {
GetIO().MouseDoubleClickMaxDist = $(float pixels);
}
|]
setMouseDragThreshold :: MonadIO m => CFloat -> m ()
setMouseDragThreshold pixels = liftIO do
[C.block|
void {
GetIO().MouseDragThreshold = $(float pixels);
}
|]
setKeyRepeatDelay :: MonadIO m => CFloat -> m ()
setKeyRepeatDelay seconds = liftIO do
[C.block|
void {
GetIO().KeyRepeatDelay = $(float seconds);
}
|]
setKeyRepeatRate :: MonadIO m => CFloat -> m ()
setKeyRepeatRate pixels = liftIO do
[C.block|
void {
GetIO().KeyRepeatRate = $(float pixels);
}
|]
setUserData :: MonadIO m => Ptr () -> m ()
setUserData ptr = liftIO do
[C.block|
void {
GetIO().UserData = $(void* ptr);
}
|]
{- TODO:
bool WantTextInput; // Mobile/console: when set, you may display an on-screen keyboard. This is set by Dear ImGui when it wants textual keyboard input to happen (e.g. when a InputText widget is active).
bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled.
bool WantSaveIniSettings; // When manual .ini load/save is active (io.IniFilename == NULL), this will be set to notify your application that you can call SaveIniSettingsToMemory() and save yourself. Important: clear io.WantSaveIniSettings yourself after saving!
bool NavActive; // Keyboard/Gamepad navigation is currently allowed (will handle ImGuiKey_NavXXX events) = a window is focused and it doesn't use the ImGuiWindowFlags_NoNavInputs flag.
bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events).
int MetricsRenderVertices; // Vertices output during last call to Render()
int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3
int MetricsRenderWindows; // Number of visible windows
int MetricsActiveWindows; // Number of active windows
int MetricsActiveAllocations; // Number of active allocations, updated by MemAlloc/MemFree based on current context. May be off if you have multiple imgui contexts.
ImVec2 MouseDelta;
-}

View File

@ -0,0 +1,149 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-| Helper: Manually clip large list of items.
If you are submitting lots of evenly spaced items and you have a random access to the list,
you can perform coarse clipping based on visibility to save yourself from processing those items at all.
The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped.
Dear ImGui already clips items based on their bounds but it needs to measure text size to do so,
whereas manual coarse clipping before submission makes this cost and your own data fetching/submission cost almost null.
Usage:
@
clipper <- ListClipper.new
ListClipper.begin clipper 1000 -- We have 1000 elements, evenly spaced.
whileTrue (ListClipper.step clipper) $
start <- ListClipper.displayStart clipper
end <- ListClipper.displayEnd clipper
for_ [start .. end] \ix ->
ImGui.text $ "line number " <> show ix
@
Generally what happens is:
* Clipper lets you process the first element (DisplayStart = 0, DisplayEnd = 1) regardless of it being visible or not.
* User code submit one element.
* Clipper can measure the height of the first element
* Clipper calculate the actual range of elements to display based on the current clipping rectangle,
position the cursor before the first visible element.
* User code submit visible elements.
-}
module DearImGui.Raw.ListClipper
( ListClipper
, new
, delete
, begin
, displayStart
, displayEnd
, step
)
where
import Control.Monad.IO.Class
( MonadIO, liftIO )
import Foreign hiding (new)
import Foreign.C
import System.IO.Unsafe (unsafePerformIO)
-- dear-imgui
import DearImGui.Raw.Context
( imguiContext )
import DearImGui.Structs
( ImGuiListClipper )
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
C.context (Cpp.cppCtx <> C.bsCtx <> imguiContext)
C.include "imgui.h"
Cpp.using "namespace ImGui"
-- | @ImGuiListClipper@ object handle.
type ListClipper = Ptr ImGuiListClipper
-- | Create a new 'ListClipper' instance.
new :: (MonadIO m) => m ListClipper
new = liftIO do
[C.block|
ImGuiListClipper* {
return IM_NEW(ImGuiListClipper);
}
|]
-- | Destroy 'ListClipper' instance.
delete :: (MonadIO m) => ListClipper -> m ()
delete clipper = liftIO do
[C.block|
void {
IM_DELETE($(ImGuiListClipper* clipper));
}
|]
-- | ListClipper setup
--
-- @items_count@: Use 'maxBound' if you don't know how many items you have
-- (in which case the cursor won't be advanced in the final step).
--
-- @items_height@: Use -1.0f to be calculated automatically on first step.
-- Otherwise pass in the distance between your items, typically
-- 'getTextLineHeightWithSpacing' or 'getFrameHeightWithSpacing'.
--
-- Wraps @ListClipper::Begin()@.
begin :: (MonadIO m) => ListClipper -> CInt -> CFloat -> m ()
begin clipper items_count items_height = liftIO do
[C.block|
void {
$(ImGuiListClipper* clipper)->Begin($(int items_count), $(float items_height));
}
|]
-- | An accessor for @ListClipper::Begin@
displayStart :: ListClipper -> CInt
displayStart clipper = unsafePerformIO do
[C.exp|
int {
$(ImGuiListClipper* clipper)->DisplayStart
}
|]
-- | An accessor for @ListClipper::DisplayStart@
displayEnd :: ListClipper -> CInt
displayEnd clipper = unsafePerformIO
[C.exp|
int {
$(ImGuiListClipper* clipper)->DisplayEnd
}
|]
-- | Call until it returns 'False'.
--
-- The 'displayStart'/'displayEnd' fields will be set and you can process/draw those items.
--
-- Wraps @ListClipper::Step()@.
step :: (MonadIO m) => ListClipper -> m Bool
step clipper = liftIO do
(0 /=) <$> [C.block|
bool {
return $(ImGuiListClipper* clipper)->Step();
}
|]

View File

@ -21,12 +21,14 @@ module DearImGui.SDL (
, sdl2Shutdown
, pollEventWithImGui
, pollEventsWithImGui
-- *** Raw
, dispatchRawEvent
)
where
-- base
import Control.Monad
( when )
( void, when )
import Foreign.Marshal.Alloc
( alloca )
import Foreign.Ptr
@ -40,9 +42,9 @@ import qualified Language.C.Inline.Cpp as Cpp
-- sdl2
import SDL
import SDL.Internal.Types
import SDL.Raw.Enum as Raw
import qualified SDL.Raw.Event as Raw
import qualified SDL.Raw.Types as Raw
-- transformers
import Control.Monad.IO.Class
@ -51,15 +53,15 @@ import Control.Monad.IO.Class
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "backends/imgui_impl_sdl.h"
C.include "backends/imgui_impl_sdl2.h"
C.include "SDL.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplSDL2_NewFrame@.
sdl2NewFrame :: MonadIO m => Window -> m ()
sdl2NewFrame (Window windowPtr) = liftIO do
[C.exp| void { ImGui_ImplSDL2_NewFrame((SDL_Window*)($(void* windowPtr))); } |]
sdl2NewFrame :: MonadIO m => m ()
sdl2NewFrame = liftIO do
[C.exp| void { ImGui_ImplSDL2_NewFrame(); } |]
-- | Wraps @ImGui_ImplSDL2_Shutdown@.
@ -78,11 +80,24 @@ pollEventWithImGui = liftIO do
nEvents <- Raw.peepEvents evPtr 1 Raw.SDL_PEEKEVENT Raw.SDL_FIRSTEVENT Raw.SDL_LASTEVENT
when (nEvents > 0) do
let evPtr' = castPtr evPtr :: Ptr ()
[C.exp| void { ImGui_ImplSDL2_ProcessEvent((SDL_Event*) $(void* evPtr')) } |]
void $ dispatchRawEvent evPtr
pollEvent
-- | Dispatch a raw 'Raw.Event' value to Dear ImGui.
--
-- You may want this function instead of 'pollEventWithImGui' if you do not use
-- @sdl2@'s higher-level 'Event' type (e.g. your application has its own polling
-- mechanism).
--
-- __It is your application's responsibility to both manage the input__
-- __pointer's memory and to fill the memory location with a raw 'Raw.Event'__
-- __value.__
dispatchRawEvent :: MonadIO m => Ptr Raw.Event -> m Bool
dispatchRawEvent evPtr = liftIO do
let evPtr' = castPtr evPtr :: Ptr ()
(0 /=) <$> [C.exp| bool { ImGui_ImplSDL2_ProcessEvent((const SDL_Event*) $(void* evPtr')) } |]
-- | Like the SDL2 'pollEvents' function, while also dispatching the events to
-- Dear ImGui. See 'pollEventWithImGui'.
pollEventsWithImGui :: MonadIO m => m [Event]

View File

@ -42,7 +42,7 @@ import Control.Monad.IO.Class
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "backends/imgui_impl_opengl2.h"
C.include "backends/imgui_impl_sdl.h"
C.include "backends/imgui_impl_sdl2.h"
C.include "SDL.h"
C.include "SDL_opengl.h"
Cpp.using "namespace ImGui"

View File

@ -0,0 +1,74 @@
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
Module: DearImGUI.SDL.Renderer
Initialising the SDL2 renderer backend for Dear ImGui.
-}
module DearImGui.SDL.Renderer
( sdl2InitForSDLRenderer
, sdlRendererInit
, sdlRendererShutdown
, sdlRendererNewFrame
, sdlRendererRenderDrawData
)
where
-- inline-c
import qualified Language.C.Inline as C
-- inline-c-cpp
import qualified Language.C.Inline.Cpp as Cpp
-- sdl2
import SDL.Internal.Types
( Renderer(..), Window(..) )
-- transformers
import Control.Monad.IO.Class
( MonadIO, liftIO )
-- DearImGui
import DearImGui
( DrawData(..) )
C.context (Cpp.cppCtx <> C.bsCtx)
C.include "imgui.h"
C.include "backends/imgui_impl_sdlrenderer2.h"
C.include "backends/imgui_impl_sdl2.h"
C.include "SDL.h"
Cpp.using "namespace ImGui"
-- | Wraps @ImGui_ImplSDL2_InitForSDLRenderer@.
sdl2InitForSDLRenderer :: MonadIO m => Window -> Renderer -> m Bool
sdl2InitForSDLRenderer (Window windowPtr) (Renderer renderPtr) = liftIO do
(0 /=) <$> [C.exp| bool { ImGui_ImplSDL2_InitForSDLRenderer((SDL_Window*)$(void* windowPtr), (SDL_Renderer*)$(void* renderPtr)) } |]
-- | Wraps @ImGui_ImplSDLRenderer2_Init@.
sdlRendererInit :: MonadIO m => Renderer -> m Bool
sdlRendererInit (Renderer renderPtr) = liftIO do
(0 /=) <$> [C.exp| bool { ImGui_ImplSDLRenderer2_Init((SDL_Renderer*)$(void* renderPtr)) } |]
-- | Wraps @ImGui_ImplSDLRenderer2_Shutdown@.
sdlRendererShutdown :: MonadIO m => m ()
sdlRendererShutdown = liftIO do
[C.exp| void { ImGui_ImplSDLRenderer2_Shutdown(); } |]
-- | Wraps @ImGui_ImplSDLRenderer2_NewFrame@.
sdlRendererNewFrame :: MonadIO m => m ()
sdlRendererNewFrame = liftIO do
[C.exp| void { ImGui_ImplSDLRenderer2_NewFrame(); } |]
-- | Wraps @ImGui_ImplSDLRenderer2_RenderDrawData@.
sdlRendererRenderDrawData :: MonadIO m => DrawData -> m ()
sdlRendererRenderDrawData (DrawData ptr) = liftIO do
[C.exp| void { ImGui_ImplSDLRenderer2_RenderDrawData((ImDrawData*) $( void* ptr )) } |]

View File

@ -33,7 +33,7 @@ import Control.Monad.IO.Class ( MonadIO, liftIO )
C.context Cpp.cppCtx
C.include "imgui.h"
C.include "backends/imgui_impl_vulkan.h"
C.include "backends/imgui_impl_sdl.h"
C.include "backends/imgui_impl_sdl2.h"
C.include "SDL.h"
C.include "SDL_vulkan.h"
Cpp.using "namespace ImGui"

214
src/DearImGui/Structs.hs Normal file
View File

@ -0,0 +1,214 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE CPP #-}
module DearImGui.Structs where
-- base
import Data.Word
( Word32
#ifndef IMGUI_USE_WCHAR32
, Word16
#endif
)
import Foreign
( Storable(..), castPtr, plusPtr, Ptr, Int16, nullPtr )
import Foreign.C
( CInt, CBool )
import DearImGui.Enums
import Data.Bits ((.&.))
--------------------------------------------------------------------------------
data ImVec2 = ImVec2 { x, y :: {-# unpack #-} !Float }
deriving (Show)
instance Storable ImVec2 where
sizeOf ~ImVec2{x, y} = sizeOf x + sizeOf y
alignment _ = 0
poke ptr ImVec2{ x, y } = do
poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
peek ptr = do
x <- peek (castPtr ptr )
y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
return ImVec2{ x, y }
data ImVec3 = ImVec3 { x, y, z :: {-# unpack #-} !Float }
deriving (Show)
instance Storable ImVec3 where
sizeOf ~ImVec3{x, y, z} = sizeOf x + sizeOf y + sizeOf z
alignment _ = 0
poke ptr ImVec3{ x, y, z } = do
poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z
peek ptr = do
x <- peek (castPtr ptr )
y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
return ImVec3{ x, y, z }
data ImVec4 = ImVec4 { x, y, z, w :: {-# unpack #-} !Float }
deriving (Show)
instance Storable ImVec4 where
sizeOf ~ImVec4{x, y, z, w} = sizeOf x + sizeOf y + sizeOf z + sizeOf w
alignment _ = 0
poke ptr ImVec4{ x, y, z, w } = do
poke (castPtr ptr `plusPtr` (sizeOf x * 0)) x
poke (castPtr ptr `plusPtr` (sizeOf x * 1)) y
poke (castPtr ptr `plusPtr` (sizeOf x * 2)) z
poke (castPtr ptr `plusPtr` (sizeOf x * 3)) w
peek ptr = do
x <- peek (castPtr ptr )
y <- peek (castPtr ptr `plusPtr` (sizeOf x * 1))
z <- peek (castPtr ptr `plusPtr` (sizeOf x * 2))
w <- peek (castPtr ptr `plusPtr` (sizeOf x * 3))
return ImVec4{ x, y, z, w }
--------------------------------------------------------------------------------
-- | DearImGui context handle.
data ImGuiContext
-- | Individual font handle.
data ImFont
-- | Font configuration handle.
data ImFontConfig
-- | Glyph ranges builder handle.
data ImFontGlyphRangesBuilder
-- | Opaque DrawList handle.
data ImDrawList
-- | 'DearImGui.Raw.ListClipper.ListClipper' pointer tag.
data ImGuiListClipper
-- | A unique ID used by widgets (typically the result of hashing a stack of string)
-- unsigned Integer (same as ImU32)
type ImGuiID = ImU32
-- | 32-bit unsigned integer (often used to store packed colors).
type ImU32 = Word32
type ImS16 = Int16
-- | Single wide character (used mostly in glyph management)
#ifdef IMGUI_USE_WCHAR32
type ImWchar = Word32
#else
type ImWchar = Word16
#endif
--------------------------------------------------------------------------------
-- | Sorting specifications for a table (often handling sort specs for a single column, occasionally more)
-- Obtained by calling TableGetSortSpecs().
-- When @SpecsDirty == true@ you can sort your data. It will be true with sorting specs have changed since last call, or the first time.
-- Make sure to set @SpecsDirty = false@ after sorting, else you may wastefully sort your data every frame!
data ImGuiTableSortSpecs = ImGuiTableSortSpecs
{ specs :: Ptr ImGuiTableColumnSortSpecs
, specsCount :: CInt
, specsDirty :: CBool
} deriving (Show, Eq)
instance Storable ImGuiTableSortSpecs where
sizeOf _ =
sizeOf (undefined :: Ptr ImGuiTableColumnSortSpecs) +
sizeOf (undefined :: CInt) +
sizeOf (undefined :: CBool)
alignment _ =
alignment nullPtr
poke ptr ImGuiTableSortSpecs{..} = do
let specsPtr = castPtr ptr
poke specsPtr specs
let specsCountPtr = castPtr $ specsPtr `plusPtr` sizeOf specs
poke specsCountPtr specsCount
let specsDirtyPtr = castPtr $ specsCountPtr `plusPtr` sizeOf specsCount
poke specsDirtyPtr specsDirty
peek ptr = do
let specsPtr = castPtr ptr
specs <- peek specsPtr
let specsCountPtr = castPtr $ specsPtr `plusPtr` sizeOf specs
specsCount <- peek specsCountPtr
let specsDirtyPtr = castPtr $ specsCountPtr `plusPtr` sizeOf specsCount
specsDirty <- peek specsDirtyPtr
pure ImGuiTableSortSpecs{..}
-- | Sorting specification for one column of a table
data ImGuiTableColumnSortSpecs = ImGuiTableColumnSortSpecs
{ columnUserID :: ImGuiID -- ^ User id of the column (if specified by a TableSetupColumn() call)
, columnIndex :: ImS16 -- ^ Index of the column
, sortOrder :: ImS16 -- ^ Index within parent ImGuiTableSortSpecs (always stored in order starting from 0, tables sorted on a single criteria will always have a 0 here)
, sortDirection :: ImGuiSortDirection -- ^ 'ImGuiSortDirection_Ascending' or 'ImGuiSortDirection_Descending'
} deriving (Show, Eq)
instance Storable ImGuiTableColumnSortSpecs where
sizeOf _ = 12
alignment _ = 4
poke ptr ImGuiTableColumnSortSpecs{..} = do
let columnUserIDPtr = castPtr ptr
poke columnUserIDPtr columnUserID
let columnIndexPtr = castPtr $ columnUserIDPtr `plusPtr` sizeOf columnUserID
poke columnIndexPtr columnIndex
let sortOrderPtr = castPtr $ columnIndexPtr `plusPtr` sizeOf columnIndex
poke sortOrderPtr sortOrder
let sortDirectionPtr = castPtr $ sortOrderPtr `plusPtr` sizeOf sortOrder
poke sortDirectionPtr sortDirection
peek ptr = do
let columnUserIDPtr = castPtr ptr
columnUserID <- peek columnUserIDPtr
let columnIndexPtr = castPtr $ columnUserIDPtr `plusPtr` sizeOf columnUserID
columnIndex <- peek columnIndexPtr
let sortOrderPtr = castPtr $ columnIndexPtr `plusPtr` sizeOf columnIndex
sortOrder <- peek sortOrderPtr
let sortDirectionPtr = castPtr $ sortOrderPtr `plusPtr` sizeOf sortOrder
sortDirection' <- peek sortDirectionPtr :: IO CInt
-- XXX: Specs struct uses trimmed field: @SortDirection : 8@
let sortDirection = case sortDirection' .&. 0xFF of
0 ->
ImGuiSortDirection_None
1 ->
ImGuiSortDirection_Ascending
2 ->
ImGuiSortDirection_Descending
_ ->
error $ "Unexpected value for ImGuiSortDirection: " <> show sortDirection
pure ImGuiTableColumnSortSpecs{..}

View File

@ -12,11 +12,15 @@ Vulkan backend for Dear ImGui.
module DearImGui.Vulkan
( InitInfo(..)
, withVulkan
, vulkanInit
, vulkanShutdown
, vulkanNewFrame
, vulkanRenderDrawData
, vulkanCreateFontsTexture
, vulkanDestroyFontUploadObjects
, vulkanSetMinImageCount
, vulkanAddTexture
)
where
@ -28,9 +32,9 @@ import Data.Word
import Foreign.Marshal.Alloc
( alloca )
import Foreign.Ptr
( Ptr, freeHaskellFunPtr, nullPtr )
( FunPtr, Ptr, freeHaskellFunPtr, nullPtr )
import Foreign.Storable
( Storable(poke) )
( poke )
-- inline-c
import qualified Language.C.Inline as C
@ -83,7 +87,18 @@ data InitInfo =
-- | Wraps @ImGui_ImplVulkan_Init@ and @ImGui_ImplVulkan_Shutdown@.
withVulkan :: MonadUnliftIO m => InitInfo -> Vulkan.RenderPass -> ( Bool -> m a ) -> m a
withVulkan ( InitInfo {..} ) renderPass action = do
withVulkan initInfo renderPass action =
bracket
( vulkanInit initInfo renderPass )
vulkanShutdown
( \ ( _, initResult ) -> action initResult )
-- | Wraps @ImGui_ImplVulkan_Init@.
--
-- Use 'vulkanShutdown' to clean up on shutdown.
-- Prefer using 'withVulkan' when possible, as it automatically handles cleanup.
vulkanInit :: MonadIO m => InitInfo -> Vulkan.RenderPass -> m (FunPtr (Vulkan.Result -> IO ()), Bool)
vulkanInit ( InitInfo {..} ) renderPass = do
let
instancePtr :: Ptr Vulkan.Instance_T
instancePtr = Vulkan.instanceHandle instance'
@ -97,38 +112,41 @@ withVulkan ( InitInfo {..} ) renderPass action = do
withCallbacks f = case mbAllocator of
Nothing -> f nullPtr
Just callbacks -> alloca ( \ ptr -> poke ptr callbacks *> f ptr )
bracket
( liftIO do
checkResultFunPtr <- $( C.mkFunPtr [t| Vulkan.Result -> IO () |] ) checkResult
initResult <- withCallbacks \ callbacksPtr ->
[C.block| bool {
ImGui_ImplVulkan_InitInfo initInfo;
VkInstance instance = { $( VkInstance_T* instancePtr ) };
initInfo.Instance = instance;
VkPhysicalDevice physicalDevice = { $( VkPhysicalDevice_T* physicalDevicePtr ) };
initInfo.PhysicalDevice = physicalDevice;
VkDevice device = { $( VkDevice_T* devicePtr ) };
initInfo.Device = device;
initInfo.QueueFamily = $(uint32_t queueFamily);
VkQueue queue = { $( VkQueue_T* queuePtr ) };
initInfo.Queue = queue;
initInfo.PipelineCache = $(VkPipelineCache pipelineCache);
initInfo.DescriptorPool = $(VkDescriptorPool descriptorPool);
initInfo.Subpass = $(uint32_t subpass);
initInfo.MinImageCount = $(uint32_t minImageCount);
initInfo.ImageCount = $(uint32_t imageCount);
initInfo.MSAASamples = $(VkSampleCountFlagBits msaaSamples);
initInfo.Allocator = $(VkAllocationCallbacks* callbacksPtr);
initInfo.CheckVkResultFn = $( void (*checkResultFunPtr)(VkResult) );
return ImGui_ImplVulkan_Init(&initInfo, $(VkRenderPass renderPass) );
}|]
pure ( checkResultFunPtr, initResult /= 0 )
)
( \ ( checkResultFunPtr, _ ) -> liftIO do
[C.exp| void { ImGui_ImplVulkan_Shutdown(); } |]
freeHaskellFunPtr checkResultFunPtr
)
( \ ( _, initResult ) -> action initResult )
liftIO do
checkResultFunPtr <- $( C.mkFunPtr [t| Vulkan.Result -> IO () |] ) checkResult
initResult <- withCallbacks \ callbacksPtr ->
[C.block| bool {
ImGui_ImplVulkan_InitInfo initInfo;
VkInstance instance = { $( VkInstance_T* instancePtr ) };
initInfo.Instance = instance;
VkPhysicalDevice physicalDevice = { $( VkPhysicalDevice_T* physicalDevicePtr ) };
initInfo.PhysicalDevice = physicalDevice;
VkDevice device = { $( VkDevice_T* devicePtr ) };
initInfo.Device = device;
initInfo.QueueFamily = $(uint32_t queueFamily);
VkQueue queue = { $( VkQueue_T* queuePtr ) };
initInfo.Queue = queue;
initInfo.PipelineCache = $(VkPipelineCache pipelineCache);
initInfo.DescriptorPool = $(VkDescriptorPool descriptorPool);
initInfo.Subpass = $(uint32_t subpass);
initInfo.MinImageCount = $(uint32_t minImageCount);
initInfo.ImageCount = $(uint32_t imageCount);
initInfo.MSAASamples = $(VkSampleCountFlagBits msaaSamples);
initInfo.Allocator = $(VkAllocationCallbacks* callbacksPtr);
initInfo.CheckVkResultFn = $( void (*checkResultFunPtr)(VkResult) );
initInfo.UseDynamicRendering = false;
// TODO: initInfo.ColorAttachmentFormat
return ImGui_ImplVulkan_Init(&initInfo, $(VkRenderPass renderPass) );
}|]
pure ( checkResultFunPtr, initResult /= 0 )
-- | Wraps @ImGui_ImplVulkan_Shutdown@.
--
-- Counterpart to 'vulkanInit', for clean-up.
vulkanShutdown :: MonadIO m => (FunPtr a, b) -> m ()
vulkanShutdown ( checkResultFunPtr, _ ) = liftIO do
[C.exp| void { ImGui_ImplVulkan_Shutdown(); } |]
freeHaskellFunPtr checkResultFunPtr
-- | Wraps @ImGui_ImplVulkan_NewFrame@.
vulkanNewFrame :: MonadIO m => m ()
@ -170,3 +188,16 @@ vulkanDestroyFontUploadObjects = liftIO do
vulkanSetMinImageCount :: MonadIO m => Word32 -> m ()
vulkanSetMinImageCount minImageCount = liftIO do
[C.exp| void { ImGui_ImplVulkan_SetMinImageCount($(uint32_t minImageCount)); } |]
-- | Wraps @ImGui_ImplVulkan_AddTexture@.
vulkanAddTexture :: MonadIO m => Vulkan.Sampler -> Vulkan.ImageView -> Vulkan.ImageLayout -> m Vulkan.DescriptorSet
vulkanAddTexture sampler imageView imageLayout = liftIO do
[C.block|
VkDescriptorSet {
return ImGui_ImplVulkan_AddTexture(
$(VkSampler sampler),
$(VkImageView imageView),
$(VkImageLayout imageLayout)
);
}
|]

View File

@ -31,6 +31,10 @@ vulkanTypesTable = Map.fromList
, ( C.TypeName "VkRenderPass" , [t| Vulkan.RenderPass |] )
, ( C.TypeName "VkResult" , [t| Vulkan.Result |] )
, ( C.TypeName "VkSampleCountFlagBits", [t| Vulkan.SampleCountFlagBits |] )
, ( C.TypeName "VkSampler" , [t| Vulkan.Sampler |] )
, ( C.TypeName "VkImageView" , [t| Vulkan.ImageView |] )
, ( C.TypeName "VkImageLayout" , [t| Vulkan.ImageLayout |] )
, ( C.TypeName "VkDescriptorSet" , [t| Vulkan.DescriptorSet |] )
]
vulkanCtx :: C.Context