monomer-1.6.0.0: src/Monomer/Main/Platform.hs
{-|
Module : Monomer.Main.Platform
Copyright : (c) 2018 Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : fjvallarino@gmail.com
Stability : experimental
Portability : non-portable
Helper functions for SDL platform related operations.
-}
{-# LANGUAGE Strict #-}
module Monomer.Main.Platform (
defaultWindowSize,
initSDLWindow,
destroySDLWindow,
getCurrentMousePos,
getDrawableSize,
getWindowSize,
getViewportSize,
getPlatform,
getDisplayDPI
) where
import Control.Exception (finally)
import Control.Monad (when, void, forM_)
import Control.Monad.Extra (whenJust)
import Control.Monad.State
import Data.Maybe
import Data.Text (Text)
import Foreign (alloca, peek)
import Foreign.C (peekCString, withCString)
import Foreign.C.Types
import SDL (($=))
import qualified Codec.Picture as P
import qualified Data.Text as T
import qualified Data.Vector.Storable as V
import qualified Foreign.C.String as STR
import qualified SDL
import qualified SDL.Input.Mouse as Mouse
import qualified SDL.Raw as Raw
import qualified SDL.Raw.Error as SRE
import qualified SDL.Internal.Types as SIT
import qualified SDL.Video.Renderer as SVR
import Monomer.Common
import Monomer.Helper (catchAny, putStrLnErr)
import Monomer.Main.Types
foreign import ccall unsafe "initGlew" glewInit :: IO CInt
foreign import ccall unsafe "initDpiAwareness" initDpiAwareness :: IO CInt
-- | Default window size if not specified.
defaultWindowSize :: (Int, Int)
defaultWindowSize = (800, 600)
-- | Creates and initializes a window using the provided configuration.
initSDLWindow :: AppConfig s e -> IO (SDL.Window, Double, Double, SDL.GLContext)
initSDLWindow config = do
SDL.initialize [SDL.InitVideo]
setDisableCompositorHint disableCompositingFlag
if disableScreensaverFlag
then Raw.disableScreenSaver
else Raw.enableScreenSaver
SDL.HintRenderScaleQuality $= SDL.ScaleLinear
renderQuality <- SDL.get SDL.HintRenderScaleQuality
when (renderQuality /= SDL.ScaleLinear) $
putStrLnErr "Warning: Linear texture filtering not enabled!"
platform <- getPlatform
initDpiAwareness
baseFactor <- case platform of
"Windows" -> getWindowsFactor
"Linux" -> getLinuxFactor
_ -> return 1 -- macOS
let factor
| disableAutoScale = 1
| otherwise = baseFactor
let (winW, winH) = (factor * fromIntegral baseW, factor * fromIntegral baseH)
window <-
SDL.createWindow
"Monomer application"
SDL.defaultWindow {
SDL.windowInitialSize = SDL.V2 (round winW) (round winH),
SDL.windowHighDPI = True,
SDL.windowResizable = windowResizable,
SDL.windowBorder = windowBorder,
SDL.windowGraphicsContext = SDL.OpenGLContext customOpenGL
}
-- Get device pixel rate
Size dw _ <- getDrawableSize window
Size ww _ <- getWindowSize window
let scaleFactor = factor * userScaleFactor
let contentRatio = dw / ww
let (dpr, epr)
| platform `elem` ["Windows", "Linux"] = (scaleFactor, 1 / scaleFactor)
| otherwise = (scaleFactor * contentRatio, 1 / scaleFactor) -- macOS
setWindowIcon window config
whenJust (_apcWindowTitle config) $ \title ->
SDL.windowTitle window $= title
when windowFullscreen $
SDL.setWindowMode window SDL.FullscreenDesktop
when windowMaximized $
SDL.setWindowMode window SDL.Maximized
err <- SRE.getError
err <- STR.peekCString err
putStrLnErr err
ctxRender <- SDL.glCreateContext window
when (platform == "Windows") $
void $ SDL.glCreateContext window
_ <- glewInit
return (window, dpr, epr, ctxRender)
where
customOpenGL = SDL.OpenGLConfig {
SDL.glColorPrecision = SDL.V4 8 8 8 0,
SDL.glDepthPrecision = 24,
SDL.glStencilPrecision = 8,
--SDL.glProfile = SDL.Core SDL.Debug 3 2,
SDL.glProfile = SDL.Core SDL.Normal 3 2,
SDL.glMultisampleSamples = 1
}
disableCompositingFlag = _apcDisableCompositing config == Just True
disableScreensaverFlag = _apcDisableScreensaver config == Just True
disableAutoScale = _apcDisableAutoScale config == Just True
userScaleFactor = fromMaybe 1 (_apcScaleFactor config)
(baseW, baseH) = case _apcWindowState config of
Just (MainWindowNormal size) -> size
_ -> defaultWindowSize
windowResizable = fromMaybe True (_apcWindowResizable config)
windowBorder = fromMaybe True (_apcWindowBorder config)
windowFullscreen = case _apcWindowState config of
Just MainWindowFullScreen -> True
_ -> False
windowMaximized = case _apcWindowState config of
Just MainWindowMaximized -> True
_ -> False
setWindowIcon :: SDL.Window -> AppConfig s e -> IO ()
setWindowIcon (SIT.Window winPtr) config =
forM_ (_apcWindowIcon config) $ \iconPath ->
flip catchAny handleException $ do
iconSurface <- loadImgToSurface (T.unpack iconPath)
let SVR.Surface iconSurfacePtr _ = iconSurface
finally
-- Note: this can use the high-level setWindowIcon once it is available (https://github.com/haskell-game/sdl2/pull/243)
(Raw.setWindowIcon winPtr iconSurfacePtr)
(SVR.freeSurface iconSurface)
where
handleException err = putStrLnErr $
"Failed to set window icon. Does the file exist?\n\t" ++ show err ++ "\n"
-- | Destroys the provided window, shutdowns the video subsystem and SDL.
destroySDLWindow :: SDL.Window -> IO ()
destroySDLWindow window = do
SDL.destroyWindow window
Raw.quitSubSystem Raw.SDL_INIT_VIDEO
SDL.quit
-- | Returns the current mouse position.
getCurrentMousePos :: Double -> IO Point
getCurrentMousePos epr = do
SDL.P (SDL.V2 x y) <- Mouse.getAbsoluteMouseLocation
return $ Point (epr * fromIntegral x) (epr * fromIntegral y)
-- | Returns the drawable size of the provided window. May differ from window
-- size if HDPI is enabled.
getDrawableSize :: SDL.Window -> IO Size
getDrawableSize window = do
SDL.V2 fbWidth fbHeight <- SDL.glGetDrawableSize window
return $ Size (fromIntegral fbWidth) (fromIntegral fbHeight)
-- | Returns the size of the provided window.
getWindowSize :: SDL.Window -> IO Size
getWindowSize window = do
SDL.V2 rw rh <- SDL.get (SDL.windowSize window)
return $ Size (fromIntegral rw) (fromIntegral rh)
{-|
Returns the viewport size. This is the size of the viewport the application will
render to and, depending on the platform, may match window size or not. For
example, on Windows and Linux Wayland this size may be smaller than the window
size because of dpr scaling.
-}
getViewportSize :: SDL.Window -> Double -> IO Size
getViewportSize window dpr = do
SDL.V2 fw fh <- SDL.glGetDrawableSize window
return $ Size (fromIntegral fw / dpr) (fromIntegral fh / dpr)
-- | Returns the name of the host OS.
getPlatform :: IO Text
getPlatform = do
platform <- peekCString =<< Raw.getPlatform
return $ T.pack platform
-- | Returns the diagonal, horizontal and vertical DPI of the main display.
getDisplayDPI :: IO (Double, Double, Double)
getDisplayDPI =
alloca $ \pddpi ->
alloca $ \phdpi ->
alloca $ \pvdpi -> do
Raw.getDisplayDPI 0 pddpi phdpi pvdpi
ddpi <- peek pddpi
hdpi <- peek phdpi
vdpi <- peek pvdpi
return (realToFrac ddpi, realToFrac hdpi, realToFrac vdpi)
-- | Returns the default resize factor for Windows.
getWindowsFactor :: IO Double
getWindowsFactor = max 1 <$> getDisplayDPIFactor
-- | Returns the default resize factor for Linux.
getLinuxFactor :: IO Double
getLinuxFactor = do
dpiFactor <- getDisplayDPIFactor
alloca $ \pmode -> do
Raw.getCurrentDisplayMode 0 pmode
mode <- peek pmode
let width = Raw.displayModeW mode
let detectedDPI
| dpiFactor > 0 = dpiFactor
| width <= 1920 = 1
| otherwise = 2
return detectedDPI
-- | Returns DPI scaling factor using SDL_GetDisplayDPI.
getDisplayDPIFactor :: IO Double
getDisplayDPIFactor = do
(ddpi, hdpi, vdpi) <- getDisplayDPI
return (hdpi / 96)
setDisableCompositorHint :: Bool -> IO ()
setDisableCompositorHint disable =
setBooleanHintSDL "SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR" disable
setBooleanHintSDL :: String -> Bool -> IO ()
setBooleanHintSDL flagName value = void $
withCString flagName $ \cHintNameStr ->
withCString valueStr $ \cValueStr ->
Raw.setHint cHintNameStr cValueStr
where
valueStr = if value then "1" else "0"
readImageRGBA8 :: FilePath -> IO (P.Image P.PixelRGBA8)
readImageRGBA8 path = P.readImage path
>>= either fail (return . P.convertRGBA8)
loadImgToSurface :: FilePath -> IO SDL.Surface
loadImgToSurface path = do
rgba8 <- readImageRGBA8 path
imgData <- V.thaw (P.imageData rgba8)
let width = fromIntegral $ P.imageWidth rgba8
height = fromIntegral $ P.imageHeight rgba8
imgSize = SDL.V2 width height
SDL.createRGBSurfaceFrom imgData imgSize (4 * width) SDL.ABGR8888