GLUT-2.7.0.0: src/Graphics/UI/GLUT/GameMode.hs
--------------------------------------------------------------------------------
-- |
-- Module : Graphics.UI.GLUT.GameMode
-- Copyright : (c) Sven Panne 2002-2013
-- License : BSD3
--
-- Maintainer : Sven Panne <svenpanne@gmail.com>
-- Stability : stable
-- Portability : portable
--
-- In addition to the functionality offered by
-- 'Graphics.UI.GLUT.Window.fullScreen', GLUT offers an sub-API to change the
-- screen resolution, color depth, and refresh rate of the display for a single
-- full screen window. This mode of operation is called /game mode/, and is
-- restricted in various ways: No pop-up menus are allowed for this full screen
-- window, no other (sub-)windows can be created, and all other applications are
-- hidden.
--
-- /X Implementation Notes:/ Note that game mode is not fully supported in the
-- original GLUT for X, it is essentially the same as using
-- 'Graphics.UI.GLUT.Window.fullScreen'. The GLUT clone freeglut
-- (see <http://freeglut.sourceforge.net/>) does not have this restriction.
--
--------------------------------------------------------------------------------
module Graphics.UI.GLUT.GameMode (
GameModeCapability(..), GameModeCapabilityDescription(..),
gameModeCapabilities, enterGameMode, leaveGameMode,
BitsPerPlane, RefreshRate, GameModeInfo(..), gameModeInfo,
gameModeActive
) where
import Control.Monad.IO.Class ( MonadIO(..) )
import Data.List ( intersperse )
import Data.StateVar ( GettableStateVar, makeGettableStateVar
, SettableStateVar, makeSettableStateVar )
import Foreign.C.String ( withCString )
import Graphics.Rendering.OpenGL ( Size(..) )
import Graphics.Rendering.OpenGL.Raw.Types ( GLenum )
import Graphics.UI.GLUT.Raw
import Graphics.UI.GLUT.Types
--------------------------------------------------------------------------------
-- | Capabilities for 'gameModeCapabilities'
data GameModeCapability
= GameModeWidth -- ^ Width of the screen resolution in pixels
| GameModeHeight -- ^ Height of the screen resolution in pixels
| GameModeBitsPerPlane -- ^ Color depth of the screen in bits
| GameModeRefreshRate -- ^ Refresh rate in Hertz
| GameModeNum -- ^ Match the Nth frame buffer configuration
-- compatible with the given capabilities
-- (numbering starts at 1)
deriving ( Eq, Ord, Show )
gameModeCapabilityToString :: GameModeCapability -> String
gameModeCapabilityToString x = case x of
GameModeWidth -> "width"
GameModeHeight -> "height"
GameModeBitsPerPlane -> "bpp"
GameModeRefreshRate -> "hertz"
GameModeNum -> "num"
-- | A single capability description for 'gameModeCapabilities'.
data GameModeCapabilityDescription = Where' GameModeCapability Relation Int
deriving ( Eq, Ord, Show )
gameModeCapabilityDescriptionToString :: GameModeCapabilityDescription -> String
gameModeCapabilityDescriptionToString (Where' c r i) =
gameModeCapabilityToString c ++ relationToString r ++ show i
--------------------------------------------------------------------------------
-- | Controls the /game mode/ to be used when 'enterGameMode' is called. It is
-- described by a list of zero or more capability descriptions, which are
-- translated into a set of criteria used to select the appropriate screen
-- configuration. The criteria are matched in strict left to right order of
-- precdence. That is, the first specified criterion (leftmost) takes precedence
-- over the later criteria for non-exact criteria
-- ('Graphics.UI.GLUT.Initialization.IsGreaterThan',
-- 'Graphics.UI.GLUT.Initialization.IsLessThan', etc.). Exact criteria
-- ('Graphics.UI.GLUT.Initialization.IsEqualTo',
-- 'Graphics.UI.GLUT.Initialization.IsNotEqualTo') must match exactly so
-- precedence is not relevant.
--
-- To determine which configuration will actually be tried by 'enterGameMode'
-- (if any), use 'gameModeInfo'.
--
-- Note that even for game mode the current values of
-- 'Graphics.UI.GLUT.Initialization.initialDisplayMode'or
-- 'Graphics.UI.GLUT.Initialization.initialDisplayCapabilities' will
-- determine which buffers are available, if double buffering is used or not,
-- etc.
gameModeCapabilities :: SettableStateVar [GameModeCapabilityDescription]
gameModeCapabilities = makeSettableStateVar $ \ds ->
withCString (descriptionsToString ds) glutGameModeString
-- freeglut currently handles only simple game mode descriptions like "WxH:B@R",
-- so we try hard to use this format instead of the more general format allowed
-- by the "real" GLUT.
descriptionsToString :: [GameModeCapabilityDescription] -> String
descriptionsToString ds =
let ws = [ x | Where' GameModeWidth IsEqualTo x <- ds ]
hs = [ x | Where' GameModeHeight IsEqualTo x <- ds ]
bs = [ x | Where' GameModeBitsPerPlane IsEqualTo x <- ds ]
rs = [ x | Where' GameModeRefreshRate IsEqualTo x <- ds ]
allSimple = (length ws + length hs + length bs + length rs) == (length ds)
dimensionsOK = (null ws) == (null hs)
in if allSimple && dimensionsOK
then simpleCapStr ws hs bs rs
else generalCapStr ds
simpleCapStr :: [Int] -> [Int] -> [Int] -> [Int] -> String
simpleCapStr ws hs bs rs =
showCap "" ws ++ showCap "x" hs ++ showCap ":" bs ++ showCap "@" rs
where showCap _ [] = ""
showCap prefix (x:_) = prefix ++ show x
generalCapStr :: [GameModeCapabilityDescription] -> String
generalCapStr =
concat . intersperse " " . map gameModeCapabilityDescriptionToString
--------------------------------------------------------------------------------
-- | Enter /game mode/, trying to change resolution, refresh rate, etc., as
-- specified by the current value of 'gameModeCapabilities'. An identifier for
-- the game mode window and a flag, indicating if the display mode actually
-- changed, are returned. The game mode window is made the /current window/.
--
-- Re-entering /game mode/ is allowed, the previous game mode window gets
-- destroyed by this, and a new one is created.
enterGameMode :: MonadIO m => m (Window, Bool)
enterGameMode = do
w <- glutEnterGameMode
c <- getBool glut_GAME_MODE_DISPLAY_CHANGED
return (Window w, c)
--------------------------------------------------------------------------------
-- | Leave /game mode/, restoring the old display mode and destroying the game
-- mode window.
leaveGameMode :: MonadIO m => m ()
leaveGameMode = glutLeaveGameMode
--------------------------------------------------------------------------------
-- | The color depth of the screen, measured in bits (e.g. 8, 16, 24, 32, ...)
type BitsPerPlane = Int
-- | The refresh rate of the screen, measured in Hertz (e.g. 60, 75, 100, ...)
type RefreshRate = Int
data GameModeInfo = GameModeInfo Size BitsPerPlane RefreshRate
deriving ( Eq, Ord, Show )
--------------------------------------------------------------------------------
-- | Return 'Just' the mode which would be tried by the next call to
-- 'enterGameMode'. Returns 'Nothing' if the mode requested by the current value
-- of 'gameModeCapabilities' is not possible, in which case 'enterGameMode'
-- would simply create a full screen window using the current mode.
gameModeInfo :: GettableStateVar (Maybe GameModeInfo)
gameModeInfo = makeGettableStateVar $ do
possible <- getBool glut_GAME_MODE_POSSIBLE
if possible
then do
w <- glutGameModeGet glut_GAME_MODE_WIDTH
h <- glutGameModeGet glut_GAME_MODE_HEIGHT
let size = Size (fromIntegral w) (fromIntegral h)
b <- glutGameModeGet glut_GAME_MODE_PIXEL_DEPTH
r <- glutGameModeGet glut_GAME_MODE_REFRESH_RATE
return $ Just $ GameModeInfo size (fromIntegral b) (fromIntegral r)
else return Nothing
getBool :: MonadIO m => GLenum -> m Bool
getBool x = do
val <- glutGameModeGet x
return $ val /= 0
--------------------------------------------------------------------------------
-- | Contains 'True' when the /game mode/ is active, 'False' otherwise.
gameModeActive :: GettableStateVar Bool
gameModeActive = makeGettableStateVar $ getBool glut_GAME_MODE_ACTIVE