Skip to content

Commit 4d1c66e

Browse files
authored
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
1 parent 7ec260c commit 4d1c66e

File tree

6 files changed

+272
-3
lines changed

6 files changed

+272
-3
lines changed

dear-imgui.cabal

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ flag sdl
9494
manual:
9595
True
9696

97+
flag sdl-renderer
98+
description:
99+
Enable SDL Renderer backend (requires the SDL_RenderGeometry function available in SDL 2.0.18+).
100+
The sdl configuration flag must also be enabled when using this flag.
101+
default:
102+
False
103+
manual:
104+
True
105+
97106
flag glfw
98107
description:
99108
Enable GLFW backend.
@@ -260,6 +269,12 @@ library
260269
exposed-modules:
261270
DearImGui.SDL.Vulkan
262271

272+
if flag(sdl-renderer)
273+
exposed-modules:
274+
DearImGui.SDL.Renderer
275+
cxx-sources:
276+
imgui/backends/imgui_impl_sdlrenderer2.cpp
277+
263278
if flag(glfw)
264279
exposed-modules:
265280
DearImGui.GLFW
@@ -358,6 +373,14 @@ executable image
358373
if (!flag(examples) || !flag(sdl) || !flag(opengl3))
359374
buildable: False
360375

376+
executable sdlrenderer
377+
import: common, exe-flags
378+
main-is: Renderer.hs
379+
hs-source-dirs: examples/sdl
380+
build-depends: sdl2, dear-imgui, managed, text
381+
if (!flag(examples) || !flag(sdl) || !flag(sdl-renderer))
382+
buildable: False
383+
361384
executable vulkan
362385
import: common, exe-flags
363386
main-is: Main.hs

examples/sdl/Renderer.hs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
{-# language BlockArguments #-}
2+
{-# language LambdaCase #-}
3+
{-# language OverloadedStrings #-}
4+
5+
-- | Port of [example_sdl2_sdlrenderer2](https://github.com/ocornut/imgui/blob/54c1bdecebf3c9bb9259c07c5f5666bb4bd5c3ea/examples/example_sdl2_sdlrenderer2/main.cpp).
6+
--
7+
-- Minor differences:
8+
-- - No changing of the clear color via @ImGui::ColorEdit3@ as a Haskell binding
9+
-- doesn't yet exist for this function.
10+
-- - No high DPI renderer scaling as this seems to be in flux [upstream](https://github.com/ocornut/imgui/issues/6065)
11+
12+
module Main ( main ) where
13+
14+
import Control.Exception (bracket, bracket_)
15+
import Control.Monad.IO.Class (MonadIO(liftIO))
16+
import Control.Monad.Managed (managed, managed_, runManaged)
17+
import Data.IORef (IORef, newIORef)
18+
import Data.Text (pack)
19+
import DearImGui
20+
import DearImGui.SDL (pollEventWithImGui, sdl2NewFrame, sdl2Shutdown)
21+
import DearImGui.SDL.Renderer
22+
( sdl2InitForSDLRenderer, sdlRendererInit, sdlRendererNewFrame, sdlRendererRenderDrawData
23+
, sdlRendererShutdown
24+
)
25+
import SDL (V4(V4), ($=), ($~), get)
26+
import Text.Printf (printf)
27+
import qualified SDL
28+
29+
30+
main :: IO ()
31+
main = do
32+
-- Initialize SDL2
33+
SDL.initializeAll
34+
35+
runManaged do
36+
-- Create a window using SDL2
37+
window <- do
38+
let title = "ImGui + SDL2 Renderer"
39+
let config = SDL.defaultWindow
40+
{ SDL.windowInitialSize = SDL.V2 1280 720
41+
, SDL.windowResizable = True
42+
, SDL.windowPosition = SDL.Centered
43+
}
44+
managed $ bracket (SDL.createWindow title config) SDL.destroyWindow
45+
46+
-- Create an SDL2 renderer
47+
renderer <- managed do
48+
bracket
49+
(SDL.createRenderer window (-1) SDL.defaultRenderer)
50+
SDL.destroyRenderer
51+
52+
-- Create an ImGui context
53+
_ <- managed $ bracket createContext destroyContext
54+
55+
-- Initialize ImGui's SDL2 backend
56+
_ <- managed_ do
57+
bracket_ (sdl2InitForSDLRenderer window renderer) sdl2Shutdown
58+
59+
-- Initialize ImGui's SDL2 renderer backend
60+
_ <- managed_ $ bracket_ (sdlRendererInit renderer) sdlRendererShutdown
61+
62+
liftIO $ mainLoop renderer
63+
64+
65+
mainLoop :: SDL.Renderer -> IO ()
66+
mainLoop renderer = do
67+
refs <- newRefs
68+
go refs
69+
where
70+
go refs = unlessQuit do
71+
-- Tell ImGui we're starting a new frame
72+
sdlRendererNewFrame
73+
sdl2NewFrame
74+
newFrame
75+
76+
-- Show the ImGui demo window
77+
get (refsShowDemoWindow refs) >>= \case
78+
False -> pure ()
79+
True -> showDemoWindow
80+
81+
withWindowOpen "Hello, world!" do
82+
text "This is some useful text."
83+
_ <- checkbox "Demo Window" $ refsShowDemoWindow refs
84+
_ <- checkbox "Another Window" $ refsShowAnotherWindow refs
85+
_ <- sliderFloat "float" (refsFloat refs) 0 1
86+
87+
button "Button" >>= \case
88+
False -> pure ()
89+
True -> refsCounter refs $~ succ
90+
sameLine
91+
counter <- get $ refsCounter refs
92+
text $ "counter = " <> pack (show counter)
93+
94+
fr <- framerate
95+
text
96+
$ pack
97+
$ printf "Application average %.3f ms/frame (%.1f FPS)" (1000 / fr) fr
98+
99+
get (refsShowAnotherWindow refs) >>= \case
100+
False -> pure ()
101+
True ->
102+
withCloseableWindow "Another Window" (refsShowAnotherWindow refs) do
103+
text "Hello from another window!"
104+
button "Close Me" >>= \case
105+
False -> pure ()
106+
True -> refsShowAnotherWindow refs $= False
107+
108+
-- Render
109+
SDL.rendererDrawColor renderer $= V4 0 0 0 255
110+
SDL.clear renderer
111+
render
112+
sdlRendererRenderDrawData =<< getDrawData
113+
SDL.present renderer
114+
115+
go refs
116+
117+
-- Process the event loop
118+
unlessQuit action = do
119+
shouldQuit <- checkEvents
120+
if shouldQuit then pure () else action
121+
122+
checkEvents = do
123+
pollEventWithImGui >>= \case
124+
Nothing ->
125+
return False
126+
Just event ->
127+
(isQuit event ||) <$> checkEvents
128+
129+
isQuit event =
130+
SDL.eventPayload event == SDL.QuitEvent
131+
132+
133+
data Refs = Refs
134+
{ refsShowDemoWindow :: IORef Bool
135+
, refsShowAnotherWindow :: IORef Bool
136+
, refsFloat :: IORef Float
137+
, refsCounter :: IORef Int
138+
}
139+
140+
newRefs :: IO Refs
141+
newRefs =
142+
Refs
143+
<$> newIORef True
144+
<*> newIORef False
145+
<*> newIORef 0
146+
<*> newIORef 0

src/DearImGui.hs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ module DearImGui
4545
-- * Windows
4646
, withWindow
4747
, withWindowOpen
48+
, withCloseableWindow
4849
, withFullscreen
4950
, fullscreenFlags
5051

@@ -335,6 +336,7 @@ module DearImGui
335336
, Raw.getBackgroundDrawList
336337
, Raw.getForegroundDrawList
337338
, Raw.imCol32
339+
, Raw.framerate
338340

339341
-- * Types
340342
, module DearImGui.Enums
@@ -415,6 +417,26 @@ withWindowOpen :: MonadUnliftIO m => Text -> m () -> m ()
415417
withWindowOpen name action =
416418
withWindow name (`when` action)
417419

420+
-- | Append items to a closeable window unless it is collapsed or fully clipped.
421+
--
422+
-- You may append multiple times to the same window during the same frame
423+
-- by calling 'withWindowOpen' in multiple places.
424+
--
425+
-- The 'Bool' state variable will be set to 'False' when the window's close
426+
-- button is pressed.
427+
withCloseableWindow :: (HasSetter ref Bool, MonadUnliftIO m) => Text -> ref -> m () -> m ()
428+
withCloseableWindow name ref action = bracket open close (`when` action)
429+
where
430+
open = liftIO do
431+
with 1 \boolPtr -> do
432+
Text.withCString name \namePtr -> do
433+
isVisible <- Raw.begin namePtr (Just boolPtr) Nothing
434+
isOpen <- peek boolPtr
435+
when (isOpen == 0) $ ref $=! False
436+
pure isVisible
437+
438+
close = liftIO . const Raw.end
439+
418440
-- | Append items to a fullscreen window.
419441
--
420442
-- The action runs inside a window that is set to behave as a backdrop.

src/DearImGui/Raw.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ module DearImGui.Raw
249249
, getBackgroundDrawList
250250
, getForegroundDrawList
251251
, imCol32
252+
, framerate
252253

253254
-- * Types
254255
, module DearImGui.Enums
@@ -1779,6 +1780,12 @@ wantCaptureKeyboard :: MonadIO m => m Bool
17791780
wantCaptureKeyboard = liftIO do
17801781
(0 /=) <$> [C.exp| bool { GetIO().WantCaptureKeyboard } |]
17811782

1783+
-- | Estimate of application framerate (rolling average over 60 frames), in
1784+
-- frame per second. Solely for convenience.
1785+
framerate :: MonadIO m => m Float
1786+
framerate = liftIO do
1787+
realToFrac <$> [C.exp| float { GetIO().Framerate } |]
1788+
17821789
-- | This draw list will be the first rendering one.
17831790
--
17841791
-- Useful to quickly draw shapes/text behind dear imgui contents.

src/DearImGui/Raw/IO.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,11 @@ setUserData ptr = liftIO do
120120

121121
{- TODO:
122122
123-
bool WantCaptureMouse; // Set when Dear ImGui will use mouse inputs, in this case do not dispatch them to your main game/application (either way, always pass on mouse inputs to imgui). (e.g. unclicked mouse is hovering over an imgui window, widget is active, mouse was clicked over an imgui window, etc.).
124-
bool WantCaptureKeyboard; // Set when Dear ImGui will use keyboard inputs, in this case do not dispatch them to your main game/application (either way, always pass keyboard inputs to imgui). (e.g. InputText active, or an imgui window is focused and navigation is enabled, etc.).
125123
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).
126124
bool WantSetMousePos; // MousePos has been altered, backend should reposition mouse on next frame. Rarely used! Set only when ImGuiConfigFlags_NavEnableSetMousePos flag is enabled.
127125
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!
128126
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.
129127
bool NavVisible; // Keyboard/Gamepad navigation is visible and allowed (will handle ImGuiKey_NavXXX events).
130-
float Framerate; // Rough estimate of application framerate, in frame per second. Solely for convenience. Rolling average estimation based on io.DeltaTime over 120 frames.
131128
int MetricsRenderVertices; // Vertices output during last call to Render()
132129
int MetricsRenderIndices; // Indices output during last call to Render() = number of triangles * 3
133130
int MetricsRenderWindows; // Number of visible windows

src/DearImGui/SDL/Renderer.hs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{-# LANGUAGE BlockArguments #-}
2+
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE NamedFieldPuns #-}
4+
{-# LANGUAGE OverloadedStrings #-}
5+
{-# LANGUAGE PatternSynonyms #-}
6+
{-# LANGUAGE QuasiQuotes #-}
7+
{-# LANGUAGE TemplateHaskell #-}
8+
9+
{-|
10+
Module: DearImGUI.SDL.Renderer
11+
12+
Initialising the SDL2 renderer backend for Dear ImGui.
13+
-}
14+
15+
module DearImGui.SDL.Renderer
16+
( sdl2InitForSDLRenderer
17+
, sdlRendererInit
18+
, sdlRendererShutdown
19+
, sdlRendererNewFrame
20+
, sdlRendererRenderDrawData
21+
)
22+
where
23+
24+
-- inline-c
25+
import qualified Language.C.Inline as C
26+
27+
-- inline-c-cpp
28+
import qualified Language.C.Inline.Cpp as Cpp
29+
30+
-- sdl2
31+
import SDL.Internal.Types
32+
( Renderer(..), Window(..) )
33+
34+
-- transformers
35+
import Control.Monad.IO.Class
36+
( MonadIO, liftIO )
37+
38+
-- DearImGui
39+
import DearImGui
40+
( DrawData(..) )
41+
42+
43+
C.context (Cpp.cppCtx <> C.bsCtx)
44+
C.include "imgui.h"
45+
C.include "backends/imgui_impl_sdlrenderer2.h"
46+
C.include "backends/imgui_impl_sdl2.h"
47+
C.include "SDL.h"
48+
Cpp.using "namespace ImGui"
49+
50+
51+
-- | Wraps @ImGui_ImplSDL2_InitForSDLRenderer@.
52+
sdl2InitForSDLRenderer :: MonadIO m => Window -> Renderer -> m Bool
53+
sdl2InitForSDLRenderer (Window windowPtr) (Renderer renderPtr) = liftIO do
54+
(0 /=) <$> [C.exp| bool { ImGui_ImplSDL2_InitForSDLRenderer((SDL_Window*)$(void* windowPtr), (SDL_Renderer*)$(void* renderPtr)) } |]
55+
56+
-- | Wraps @ImGui_ImplSDLRenderer2_Init@.
57+
sdlRendererInit :: MonadIO m => Renderer -> m Bool
58+
sdlRendererInit (Renderer renderPtr) = liftIO do
59+
(0 /=) <$> [C.exp| bool { ImGui_ImplSDLRenderer2_Init((SDL_Renderer*)$(void* renderPtr)) } |]
60+
61+
-- | Wraps @ImGui_ImplSDLRenderer2_Shutdown@.
62+
sdlRendererShutdown :: MonadIO m => m ()
63+
sdlRendererShutdown = liftIO do
64+
[C.exp| void { ImGui_ImplSDLRenderer2_Shutdown(); } |]
65+
66+
-- | Wraps @ImGui_ImplSDLRenderer2_NewFrame@.
67+
sdlRendererNewFrame :: MonadIO m => m ()
68+
sdlRendererNewFrame = liftIO do
69+
[C.exp| void { ImGui_ImplSDLRenderer2_NewFrame(); } |]
70+
71+
-- | Wraps @ImGui_ImplSDLRenderer2_RenderDrawData@.
72+
sdlRendererRenderDrawData :: MonadIO m => DrawData -> m ()
73+
sdlRendererRenderDrawData (DrawData ptr) = liftIO do
74+
[C.exp| void { ImGui_ImplSDLRenderer2_RenderDrawData((ImDrawData*) $( void* ptr )) } |]

0 commit comments

Comments
 (0)