packages feed

movie-monad-0.0.5.0: src/Window.hs

{-
  Movie Monad
  (C) 2017 David Lettier
  lettier.com
-}

{-# LANGUAGE OverloadedStrings #-}

module Window where

import GHC.Word
import Control.Monad
import Data.Maybe
import Data.Int
import Data.IORef
import Data.Time.Clock.POSIX
import GI.GLib
import qualified GI.Gdk
import qualified GI.Gtk
import qualified GI.Gst

import qualified Records as R
import Constants
import Reset
import Mouse
import PlayPause
import Seek

addWindowHandlers :: R.Application -> [R.Application -> IO ()] -> IO ()
addWindowHandlers
  application@R.Application
    { R.guiObjects =
        R.GuiObjects
          { R.window      = window
          , R.videoWidget = videoWidget
          }
    , R.ioRefs =
        R.IORefs
          { R.isWindowFullScreenRef = isWindowFullScreenRef
          }
    , R.playbin = playbin
    }
  onWidgetRealizeCallbacks
  = do
  void $
    GI.Gtk.onWidgetRealize videoWidget $
      windowRealizedHandler
        application
        onWidgetRealizeCallbacks
  void $
    GI.Gtk.onWidgetWindowStateEvent window (widgetWindowStateEventHandler isWindowFullScreenRef)
  void $
    GI.Gtk.onWidgetDestroy window (windowDestroyHandler playbin)
  void $
    GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (adjustWindow application)

windowRealizedHandler
  ::  R.Application
  ->  [R.Application -> IO ()]
  ->  GI.Gtk.WidgetRealizeCallback
windowRealizedHandler
  application@R.Application
    { R.guiObjects =
        guiObjects@R.GuiObjects
          { R.videoWidget = videoWidget
          , R.seekScale   = seekScale
          }
    }
  onWidgetRealizeCallbacks
  = do
  let eventMask = [GI.Gdk.EventMaskAllEventsMask]
  GI.Gtk.widgetAddEvents videoWidget eventMask
  GI.Gtk.widgetAddEvents seekScale   eventMask
  resetGuiObjects guiObjects
  mapM_ (\ f -> f application) onWidgetRealizeCallbacks

widgetWindowStateEventHandler :: IORef Bool -> GI.Gdk.EventWindowState -> IO Bool
widgetWindowStateEventHandler isWindowFullScreenRef eventWindowState = do
  windowStates <- GI.Gdk.getEventWindowStateNewWindowState eventWindowState
  let isWindowFullScreen =
        Prelude.foldl
          (\ acc x -> acc || GI.Gdk.WindowStateFullscreen == x)
          False
          windowStates
  atomicWriteIORef isWindowFullScreenRef isWindowFullScreen
  return True

windowDestroyHandler :: GI.Gst.Element -> IO ()
windowDestroyHandler playbin = do
  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull
  _ <- GI.Gst.objectUnref playbin
  GI.Gtk.mainQuit

adjustWindow :: R.Application -> IO Bool
adjustWindow
  application@R.Application
    { R.guiObjects =
        R.GuiObjects
          { R.window               = window
          , R.fileChooserButton    = fileChooserButton
          , R.bottomControlsGtkBox = bottomControlsGtkBox
          }
    , R.ioRefs =
        R.IORefs
          { R.videoInfoRef      = videoInfoRef
          , R.mouseMovedLastRef = mouseMovedLastRef
          }
    , R.playbin = playbin
    }
  = do
  videoInfo            <- readIORef videoInfoRef
  mouseMovedLast       <- readIORef mouseMovedLastRef
  timeNow              <- fmap round getPOSIXTime
  (_, playBinState, _) <- GI.Gst.elementGetState playbin (fromIntegral GI.Gst.MSECOND :: GHC.Word.Word64)
  let isPlaying = GI.Gst.StatePlaying == playBinState
  let delta     = timeNow - mouseMovedLast
  let isVideo   = R.isVideo videoInfo
  when (isPlaying && isVideo && delta >= hideOnScreenControlsInterval) $ do
    GI.Gtk.widgetHide fileChooserButton
    GI.Gtk.widgetHide bottomControlsGtkBox
    styleContext <- GI.Gtk.widgetGetStyleContext bottomControlsGtkBox
    GI.Gtk.styleContextRemoveClass styleContext "movie-monad-fade-in"
    setCursor window (Just "none")
    atomicWriteIORef mouseMovedLastRef timeNow
  fillWindowWithVideo application
  return True

fillWindowWithVideo :: R.Application -> IO ()
fillWindowWithVideo
  R.Application
    { R.guiObjects =
        guiObjects@R.GuiObjects
          { R.window = window
          }
    , R.ioRefs =
        R.IORefs
          { R.videoInfoRef          = videoInfoRef
          , R.isWindowFullScreenRef = isWindowFullScreenRef
          }
    }
  = do
  isWindowFullScreen   <- readIORef isWindowFullScreenRef
  videoInfo            <- readIORef videoInfoRef
  when (R.isVideo videoInfo && not isWindowFullScreen) $ do
    (width, _)      <- GI.Gtk.windowGetSize window
    maybeWindowSize <- calculateWindowSize guiObjects (fromIntegral width :: Int) videoInfo
    when (isJust maybeWindowSize) $ do
      let (windowWidth, windowHeight) = fromMaybe (0, 0) maybeWindowSize
      setWindowSize guiObjects windowWidth windowHeight
  return ()

calculateWindowSize :: R.GuiObjects -> Int -> R.VideoInfo -> IO (Maybe (Int32, Int32))
calculateWindowSize _ _ R.VideoInfo { R.isVideo = False } = return Nothing
calculateWindowSize
  R.GuiObjects
    { R.fileChooserButton = fileChooserButton
    }
  desiredWidth
  R.VideoInfo
    { R.videoWidth  = videoWidth
    , R.videoHeight = videoHeight
    }
  = do
  fileChooserButtonIsVisible <- GI.Gtk.widgetGetVisible fileChooserButton
  fileChooserButtonHeight    <- GI.Gtk.widgetGetAllocation fileChooserButton >>= GI.Gdk.getRectangleHeight
  let videoWidthDouble  = fromIntegral videoWidth  :: Double
  let videoHeightDouble = fromIntegral videoHeight :: Double
  let ratio =
        if videoWidthDouble <= 0.0
          then 0.0
          else videoHeightDouble / videoWidthDouble
  let desiredWidthDouble = fromIntegral desiredWidth :: Double
  let topMargin =
        if fileChooserButtonIsVisible
          then (fromIntegral fileChooserButtonHeight :: Double)
          else 0.0
  let bottomMargin = 0.0
  let height = topMargin + (desiredWidthDouble * ratio) + bottomMargin
  return $ Just (fromIntegral desiredWidth:: Int32, round height :: Int32)

setWindowSize :: R.GuiObjects -> Int32 -> Int32 -> IO ()
setWindowSize
  R.GuiObjects
    { R.window = window
    }
  width
  height
  =
  GI.Gtk.windowResize window width (if height <= 0 then 1 else height)

setupWindowForPlayback :: R.GuiObjects -> Bool -> Bool -> Int32 -> Int32 -> IO ()
setupWindowForPlayback
  guiObjects@R.GuiObjects
    { R.window               = window
    , R.videoWidget          = videoWidget
    , R.bottomControlsGtkBox = bottomControlsGtkBox
    , R.seekScale            = seekScale
    , R.playPauseButton      = playPauseButton
    , R.repeatCheckButton    = repeatCheckButton
    , R.fullscreenButton     = fullscreenButton
    , R.playImage            = playImage
    , R.pauseImage           = pauseImage
    }
  isSeekable
  isWindowFullScreen
  windowWidth
  windowHeight
  = do
  GI.Gtk.widgetShow bottomControlsGtkBox
  GI.Gtk.widgetShow videoWidget
  GI.Gtk.widgetShow playPauseButton
  GI.Gtk.widgetShow repeatCheckButton
  GI.Gtk.widgetShow fullscreenButton
  GI.Gtk.setToggleButtonActive repeatCheckButton False
  GI.Gtk.widgetSetSizeRequest window windowMinimumSize 100
  setPlayPauseButton playPauseButton playImage pauseImage True
  unless isWindowFullScreen $
    setWindowSize guiObjects windowWidth windowHeight
  hideOrShowSeekScale seekScale isSeekable