dear-imgui-3.0.0: examples/sdl/Image.hs
{-# 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.Enums.ImDrawFlags as ImDrawFlags
import qualified DearImGui.Raw.ImDrawList as ImDrawList
import qualified DearImGui.Impl.OpenGL3 as ImplGL3
import qualified DearImGui.Impl.SDL2 as ImplSDL2
import Graphics.GL
import qualified SDL as SDL
-- For the texture creation
import Data.Fixed (mod')
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)
type RGB = (GLubyte, GLubyte, GLubyte)
fill :: Texture -> (Double -> Double -> RGB) -> VS.Vector GLubyte
fill texture pixel =
VS.fromListN (3 * width * height)
[ channel
| py <- [0 .. height - 1]
, px <- [0 .. width - 1]
, let (r, g, b) = pixel (fromIntegral px / fromIntegral width) (fromIntegral py / fromIntegral height)
, channel <- [r, g, b]
]
where
width = fromIntegral (textureWidth texture)
height = fromIntegral (textureHeight texture)
plasma :: Double -> Double -> RGB
plasma u v = (sinePalette 0, sinePalette 2, sinePalette 4)
where
sinePalette phase = round (128 + 127 * sin (interference + phase))
interference =
sin (10 * u)
+ sin (8 * v + 1)
+ sin (6 * (u + v))
+ sin (20 * sqrt ((u - 0.5) ^ (2 :: Int) + (v - 0.5) ^ (2 :: Int)))
colourWheel :: Double -> Double -> Double -> RGB
colourWheel aspect u v
| radius <= 1 = hsvToRgb hue radius 1
| even (floor (u * 12) + floor (v * 16) :: Int) = (0x30, 0x30, 0x30)
| otherwise = (0x50, 0x50, 0x50)
where
dx = (u - 0.5) * 2
dy = (v - 0.5) * 2 / aspect
radius = sqrt (dx * dx + dy * dy)
hue = atan2 dy dx / (2 * pi) + 0.5
hsvToRgb :: Double -> Double -> Double -> RGB
hsvToRgb h s v = (byte (r + m), byte (g + m), byte (b + m))
where
chroma = v * s
sector = h * 6
secondary = chroma * (1 - abs (sector `mod'` 2 - 1))
m = v - chroma
byte = round . (* 255)
(r, g, b) = case floor sector :: Int of
0 -> (chroma, secondary, 0)
1 -> (secondary, chroma, 0)
2 -> (0, chroma, secondary)
3 -> (0, secondary, chroma)
4 -> (secondary, 0, chroma)
_ -> (chroma, 0, secondary)
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_ $ ImplSDL2.withInitForOpenGL window glContext
-- Initialize ImGui's OpenGL backend
managed_ $ ImplGL3.withInit Nothing
liftIO do
plasmaTexture <- create2DTexture 320 240
VS.unsafeWith (fill plasmaTexture plasma) $
bindTexture plasmaTexture
wheelTexture <- create2DTexture 240 320
VS.unsafeWith (fill wheelTexture (colourWheel (240 / 320))) $
bindTexture wheelTexture
err <- glGetError
putStrLn $ "Error-code: " ++ show err
print (plasmaTexture, wheelTexture)
mainLoop window (plasmaTexture, wheelTexture) False
mainLoop :: SDL.Window -> (Texture, Texture) -> Bool -> IO ()
mainLoop window textures flag = unlessQuit do
-- Tell ImGui we're starting a new frame
ImplGL3.newFrame
ImplSDL2.newFrame
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 = 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
imageButton
"##btn"
(textureRefFromID openGLtextureID)
(textureSize texture)
(ImVec2 0 0)
(ImVec2 1 1)
(ImVec4 1 1 1 1)
(ImVec4 1 1 1 1)
else
pure False
-- Using DrawList
bg <- getBackgroundDrawList
ImDrawList.addImageRounded
bg
(textureRefFromID openGLtextureID)
(ImVec2 100 100) (ImVec2 200 200)
(ImVec2 0.25 0.25) (ImVec2 0.75 0.75)
(imCol32 0 255 0 0xFF) -- Extract green channel
32 ImDrawFlags.RoundBottom
-- Render
glClear GL_COLOR_BUFFER_BIT
DearImGui.render
DearImGui.getDrawData >>= ImplGL3.renderDrawData
SDL.glSwapWindow window
mainLoop window textures (flag /= clicked)
where
unlessQuit action = do
shouldQuit <- checkEvents
if shouldQuit then pure () else action
checkEvents = do
ImplSDL2.pollEvent >>= \case
Nothing ->
return False
Just event ->
(isQuit event ||) <$> checkEvents
isQuit event =
SDL.eventPayload event == SDL.QuitEvent