frpnow-gtk3-0.2.0: Control/FRPNow/GTK/Misc.hs
{-# LANGUAGE RecursiveDo #-}
{- |
Module : Control.FRPNow.GTK.Misc
Copyright : (c) George Steel 2017
License : BSD3
Maintainer : george.steel@gmail.org
Utility Functions which do not fit anywhere else in the package.
-}
module Control.FRPNow.GTK.Misc where
import Control.FRPNow.GTK.Core
import Graphics.UI.Gtk
import Graphics.UI.Gtk.General.StyleContext
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import Control.FRPNow
import qualified Data.Text as T
import Data.Text (Text)
import System.IO
import Control.Exception
import Control.Concurrent
import Control.Concurrent.MVar
-- | Map an expensive computation over a 'Behavior' to WHNF in a background thread ('force' is useful to get deep evaluation). The output behavior will lag behind the input behavior and thus requires an initial value.
-- To control the running of the computation, this function also takes an event after which all changes are disregarded (can be provided by 'getUnrealize' to tie this ti widget lifetimes) and returns a boolean Behavior which indicated if a new result is currently pending.
mapBAsync :: (Eq a) => Event () -> b -> (a -> b) -> Behavior a -> Now (Behavior b, Behavior Bool)
mapBAsync stop yinit f xb = do
xinit <- sample xb
let xchanged = toChanges xb `beforeEs` stop
(pendingSet, setPending) <- callbackStream
(yset, sety) <- callbackStream
pending <- sample $ fromChanges True (merge (False <$ yset) pendingSet)
q <- sync $ newMVar xinit
flip callStream xchanged $ \xs -> let
x = last xs
in sync $ tryTakeMVar q >> putMVar q x
sync . forkIO . forever $ do
x <- takeMVar q
setPending True
y <- evaluate (f x)
sety y
setPending False
yb <- sample $ fromChanges yinit yset
return (yb, pending)
-- | Filter an event so that it only resolves if it does so before a cutoff event.
beforeE :: Event a -> Event () -> Behavior (Event a)
beforeE ev cutoff = fmap join $ first (never <$ cutoff) (pure <$> ev)
-- | Disable a widget when the condition is true. Does not check the initial state iof the condition and assumes it to be False initially. This isso it can be used inside 'mfix' where the condition has not been defined yet..
setLockedFuturistic :: (WidgetClass w) => w -> Behavior Bool -> Now ()
setLockedFuturistic w lock = do
done <- getUnrealize w
let lockchange = toChanges lock `beforeEs` done
callIOStream (widgetSetSensitive w . not) lockchange
-- | Run a FileChooserDialog without blocking and return an Evemnt containing the resulting selected path.
runFileChooserDialog :: FileChooserDialog -> Now (Event (Maybe FilePath))
runFileChooserDialog dialog = do
(retev, cb) <- callback
sync $ mdo
conn <- on dialog response $ \resp -> do
widgetHide dialog
case resp of
ResponseAccept -> do
mfn <- fileChooserGetFilename dialog
cb mfn
_ -> cb Nothing
signalDisconnect conn
widgetShow dialog
return retev
-- | Check any 'IOError's returned by the action so that they result in a 'Nothing' value.
checkIOError :: IO a -> IO (Maybe a)
checkIOError action = catch (fmap Just action) handler where
handler :: IOError -> IO (Maybe a)
handler _ = return Nothing
-- | Add CSS classes to a widget.
widgetAddClasses :: (MonadIO m, WidgetClass widget) => [Text] -> widget -> m widget
widgetAddClasses cs w = liftIO $ do
sc <- widgetGetStyleContext w
forM_ cs $ \c -> styleContextAddClass sc c
return w