--------------------------------------------------------------------------------
-- |
-- Module      :  Graphics.UI.GLUT.Overlay
-- Copyright   :  (c) Sven Panne 2002-2018
-- License     :  BSD3
--
-- Maintainer  :  Sven Panne <svenpanne@gmail.com>
-- Stability   :  stable
-- Portability :  portable
--
-- When  overlay hardware is available, GLUT provides a set of routines for
-- establishing, using, and removing an overlay for GLUT windows. When an
-- overlay is established, a separate OpenGL context is also established. A
-- window\'s overlay OpenGL state is kept distinct from the normal planes\'
-- OpenGL state.
--
--------------------------------------------------------------------------------

module Graphics.UI.GLUT.Overlay (
   -- * Overlay creation and destruction
   hasOverlay, overlayPossible,

   -- * Showing and hiding an overlay
   overlayVisible,

   -- * Changing the /layer in use/
   Layer(..), layerInUse,

   -- * Re-displaying
   postOverlayRedisplay
) where

import Control.Monad.IO.Class ( MonadIO(..) )
import Data.StateVar ( GettableStateVar, makeGettableStateVar
                     , SettableStateVar, makeSettableStateVar
                     , StateVar, makeStateVar )
import Graphics.Rendering.OpenGL ( GLenum )

import Graphics.UI.GLUT.QueryUtils
import Graphics.UI.GLUT.Raw
import Graphics.UI.GLUT.Types

--------------------------------------------------------------------------------

-- | Controls the overlay for the /current window/. The requested display mode
-- for the overlay is determined by the /initial display mode/.
-- 'overlayPossible' can be used to determine if an overlay is possible for the
-- /current window/ with the current /initial display mode/. Do not attempt to
-- establish an overlay when one is not possible; GLUT will terminate the
-- program.
--
-- When 'hasOverlay' is set to 'True' when an overlay already exists, the
-- existing overlay is first removed, and then a new overlay is established. The
-- state of the old overlay\'s OpenGL context is discarded. Implicitly, the
-- window\'s /layer in use/ changes to the overlay immediately after the overlay
-- is established.
--
-- The initial display state of an overlay is shown, however the overlay is only
-- actually shown if the overlay\'s window is shown.
--
-- Setting 'hasOverlay' to 'False' is safe even if no overlay is currently
-- established, nothing happens in this case. Implicitly, the window\'s /layer
-- in use/ changes to the normal plane immediately once the overlay is removed.
--
-- If the program intends to re-establish the overlay later, it is typically
-- faster and less resource intensive to use 'overlayVisible' to simply change
-- the display status of the overlay.
--
-- /X Implementation Notes:/ GLUT for X uses the @SERVER_OVERLAY_VISUALS@
-- convention to determine if overlay visuals are available. While the
-- convention allows for opaque overlays (no transparency) and overlays with the
-- transparency specified as a bitmask, GLUT overlay management only provides
-- access to transparent pixel overlays.
--
-- Until RGBA overlays are better understood, GLUT only supports color index
-- overlays.

hasOverlay :: StateVar Bool
hasOverlay = makeStateVar getHasOverlay setHasOverlay

setHasOverlay :: Bool -> IO ()
setHasOverlay False = glutRemoveOverlay
setHasOverlay True  = glutEstablishOverlay

getHasOverlay :: IO Bool
getHasOverlay = layerGet (/= 0) glut_HAS_OVERLAY

--------------------------------------------------------------------------------

-- | Contains 'True' if an overlay could be established for the /current window/
-- given the current /initial display mode/. If it contains 'False', setting
-- 'hasOverlay' will fail with a fatal error.

overlayPossible :: GettableStateVar Bool
overlayPossible = makeGettableStateVar $ layerGet (/= 0) glut_OVERLAY_POSSIBLE

--------------------------------------------------------------------------------

-- | Controls the visibility of the overlay of the /current window/.
--
-- The effect of showing or hiding an overlay takes place immediately. Note that
-- setting 'overlayVisible' to 'True' will not actually display the overlay
-- unless the window is also shown (and even a shown window may be obscured by
-- other windows, thereby obscuring the overlay). It is typically faster and
-- less resource intensive to use the routines below to control the display
-- status of an overlay as opposed to removing and re-establishing the overlay.

overlayVisible :: SettableStateVar Bool
overlayVisible =
   makeSettableStateVar $ \flag ->
      if flag then glutShowOverlay else glutHideOverlay

--------------------------------------------------------------------------------

-- | The /layer in use/.
data Layer
   = Normal   -- ^ The normal plane.
   | Overlay  -- ^ The overlay.
   deriving ( Eq, Ord, Show )

marshalLayer :: Layer -> GLenum
marshalLayer x = case x of
   Normal -> glut_NORMAL
   Overlay -> glut_OVERLAY

unmarshalLayer :: GLenum -> Layer
unmarshalLayer x
   | x == glut_NORMAL  = Normal
   | x == glut_OVERLAY = Overlay
   | otherwise = error ("unmarshalLayer: illegal value " ++ show x)

--------------------------------------------------------------------------------

-- | Controls the per-window /layer in use/ for the /current window/, which can
-- either be the normal plane or the overlay. Selecting the overlay should only
-- be done if an overlay exists, however windows without an overlay may still
-- set the /layer in use/ to 'Normal'. OpenGL commands for the window are
-- directed to the current /layer in use/.

layerInUse :: StateVar Layer
layerInUse =
   makeStateVar getLayerInUse setLayerInUse

setLayerInUse :: Layer -> IO ()
setLayerInUse = glutUseLayer . marshalLayer

getLayerInUse :: IO Layer
getLayerInUse = layerGet (unmarshalLayer . fromIntegral) glut_LAYER_IN_USE

--------------------------------------------------------------------------------

-- | Mark the overlay of the given window (or the /current window/, if none is
-- supplied) as needing to be redisplayed. The next iteration through
-- 'Graphics.UI.GLUT.Begin.mainLoop', the window\'s overlay display callback
-- (or simply the display callback if no overlay display callback is registered)
-- will be called to redisplay the window\'s overlay plane. Multiple calls to
-- 'postOverlayRedisplay' before the next display callback opportunity (or
-- overlay display callback opportunity if one is registered) generate only a
-- single redisplay. 'postOverlayRedisplay' may be called within a window\'s
-- display or overlay display callback to re-mark that window for redisplay.
--
-- Logically, overlay damage notification for a window is treated as a
-- 'postOverlayRedisplay' on the damaged window. Unlike damage reported by the
-- window system, 'postOverlayRedisplay' will not set to true the overlay\'s
-- damaged status (see 'Graphics.UI.GLUT.State.damaged').
--
-- Also, see 'Graphics.UI.GLUT.Window.postRedisplay'.

postOverlayRedisplay :: MonadIO m => Maybe Window -> m ()
postOverlayRedisplay =
   maybe glutPostOverlayRedisplay (\(Window win) -> glutPostWindowOverlayRedisplay win)
