packages feed

lgtk-0.8: src/LGtk.hs

{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE CPP #-}
-- | Main LGtk interface.
module LGtk
    (
    -- * References
      module Data.LensRef

    -- * GUI

    -- ** Running a widget
    , Widget
    , runWidget

    -- ** GUI elements
    , empty
    , vcat
    , hcat
    , label
    , button
    , smartButton
    , checkbox
    , combobox
    , entry
    , entryShow
    , hscale
    , cell
    , cellNoMemo
    , notebook

    -- ** Other elements
    , button_
    , button__
    , cell_

    -- ** Diagrams canvas
    , canvas
    , inCanvas

    -- * Aux types
    , Dia
    , MouseEvent (..)
    , MousePos (..)
    , Colour
    , sRGB
    , module LGtk.Key

    -- * I/O
    , getArgs
    , getProgName
    , lookupEnv

    , EffIORef
    , asyncWrite
    , putStr_
    , getLine_
    , fileRef
    , putStrLn_

    -- * Utils
    , undoTr
    , showLens
    , listLens

    ) where

--import Data.String
import Data.Maybe
import Data.Monoid
import Data.Semigroup
import Control.Monad
import Control.Lens

import Data.LensRef
import LGtk.Effects
import LGtk.Widgets
import LGtk.Render
import LGtk.Key

#ifdef __GTK__
import LGtk.Backend.Gtk
#else
import LGtk.Backend.GLFW
#endif


{- |
Gtk widget descriptions.
Construction of a @(w :: forall m . EffIORef m => Widget m)@ value is side-effect free,
side-effects happen at running @('runWidget' w)@.

@Widget@ should be abstract data type, but it is also safe to keep it as a type synonym because
the operations of the revealed implementation are hidden.
-}
--runWidgetGLFW = GLFW.runWidget

{- |
Run a Gtk widget description.

The widget is shown in a window and the thread enters into the Gtk event cycle.
It leaves the event cycle when the window is closed.
-}
--runWidget :: (forall m . EffIORef m => Widget m) -> IO ()
--runWidget = Gtk.runWidget
{-
instance MonadRefState m => IsString (RefStateReader m String) where
    fromString = return
-}

-- | Vertical composition of widgets.
vcat :: Monad m => [Widget m] -> Widget m
vcat = return . List Vertical

-- | Horizontal composition of widgets.
hcat :: Monad m => [Widget m] -> Widget m
hcat = return . List Horizontal

-- | Empty widget.
empty :: Monad m => Widget m
empty = hcat []

-- | Dynamic label.
label :: MonadRegister m => RefReader m String -> Widget m
label = return . Label

-- | Low-level button with changeable background color.
button__
    :: MonadRegister m
    => RefReader m String     -- ^ dynamic label of the button
    -> RefReader m Bool       -- ^ the button is active when this returns @True@
    -> RefReader m (Colour Double)      -- ^ dynamic background color
    -> Modifier m ()        -- ^ the action to do when the button is pressed
    -> Widget m
button__ r x c y = return $ Button (r) (x) (Just c) (\() -> y)

-- | Low-level button.
button_
    :: MonadRegister m
    => RefReader m String     -- ^ dynamic label of the button
    -> RefReader m Bool       -- ^ the button is active when this returns @True@
    -> Modifier m ()        -- ^ the action to do when the button is pressed
    -> Widget m
button_ r x y = return $ Button (r) (x) Nothing (\() -> y)

-- | Button
button
    :: MonadRegister m
    => RefReader m String     -- ^ dynamic label of the button
    -> RefReader m (Maybe (Modifier m ()))     -- ^ when the @Maybe@ value is @Nothing@, the button is inactive
    -> Widget m
button r fm = button_ r (liftM isJust fm) (liftRefReader fm >>= maybe (return ()) id)

-- | Button which inactivates itself automatically.
smartButton
    :: (MonadRegister m, EqRefClass r, RefReaderSimple r ~ RefReader m) 
    => RefReader m String     -- ^ dynamic label of the button
    -> RefSimple r a              -- ^ underlying reference
    -> (a -> a)   -- ^ The button is active when this function is not identity on value of the reference. When the button is pressed, the value of the reference is modified with this function.
    -> Widget m
smartButton s r f
    = button_ s (hasEffect r f) (modRef r f)

-- | Checkbox.
checkbox :: MonadRegister m => Ref m Bool -> Widget m
checkbox r = return $ Checkbox ((readRef r), writeRef r)

-- | Combo box.
combobox :: MonadRegister m => [String] -> Ref m Int -> Widget m
combobox ss r = return $ Combobox ss ((readRef r), writeRef r)

-- | Text entry.
entry :: (MonadRegister m, RefClass r, RefReaderSimple r ~ RefReader m)  => RefSimple r String -> Widget m
entry r = return $ Entry (const True) ((readRef r), writeRef r)

-- | Text entry with automatic show-read conversion.
entryShow :: forall m a r . (MonadRegister m, Show a, Read a, RefClass r, RefReaderSimple r ~ RefReader m) => RefSimple r a -> Widget m
entryShow r_ = return $ Entry isOk ((readRef r), writeRef r)
  where
    r = showLens `lensMap` r_
    isOk s = case (reads s :: [(a, String)]) of
        ((_,""):_) -> True
        _ -> False

showLens :: (Show a, Read a) => Lens' a String
showLens = lens show $ \def s -> maybe def fst $ listToMaybe $ reads s


{- | Notebook (tabs).

The tabs are created lazily.
-}
notebook :: MonadRegister m => [(String, Widget m)] -> Widget m
notebook xs = do
    currentPage <- newRef 0
    let f index (title, w) = (,) title $ cell (liftM (== index) $ readRef currentPage) $ \b -> case b of
           False -> hcat []
           True -> w
    return $ Notebook' (writeRef currentPage) $ zipWith f [0..] xs

{- | Dynamic cell.

The monadic action for inner widget creation is memoised in the first monad layer.
-}
cell_ :: (MonadRegister m, Eq a) => RefReader m a -> (forall x . (Widget m -> m x) -> a -> m (m x)) -> Widget m
cell_ r f = return $ Cell r f

{- | Dynamic cell.

The inner widgets are memoised.
-}
cell :: (MonadRegister m, Eq a) => RefReader m a -> (a -> Widget m) -> Widget m
cell r m = cell_ r $ \mk -> liftM return . mk . m

{- | Dynamic cell.

The inner widgets are not memoised.
-}
cellNoMemo :: (MonadRegister m, Eq a) => RefReader m a -> (a -> Widget m) -> Widget m
cellNoMemo r m = cell_ r $ \mk -> return . mk . m

-- | Diagrams canvas.
canvas
    :: (MonadRegister m, Eq b, Monoid a, Semigroup a)
    => Int   -- ^ width
    -> Int   -- ^ height
    -> Double  -- ^ scale
    -> ((MouseEvent a, Dia a) -> Modifier m ()) -- ^ mouse event handler
    -> KeyboardHandler (Modifier m) -- ^ keyboard event handler
    -> RefReader m b -- ^ state references
    -> (b -> Dia a) -- ^ diagrams renderer
    -> Widget m
canvas w h sc me kh r f = return $ Canvas w h sc me kh r f

hscale
    :: (MonadRegister m)
    => Double   -- ^ min
    -> Double   -- ^ max
    -> Double   -- ^ step
    -> Ref m Double
    -> Widget m
hscale a b c r = return $ Scale a b c (readRef r, writeRef r)

listLens :: Lens' (Bool, (a, [a])) [a]
listLens = lens get set where
    get (False, _) = []
    get (True, (l, r)) = l: r
    set (_, x) [] = (False, x)
    set _ (l: r) = (True, (l, r))


-- | Undo-redo state transformation.
undoTr
    :: MonadRegister m =>
       (a -> a -> Bool)     -- ^ equality on state
    -> Ref m a             -- ^ reference of state
    ->   m ( RefReader m (Maybe (Modifier m ()))
           , RefReader m (Maybe (Modifier m ()))
           )  -- ^ undo and redo actions
undoTr eq r = do
    ku <- extRef r (undoLens eq) ([], [])
    let try f = liftM (liftM (writeRef ku) . f) $ readRef ku
    return (try undo, try redo)
  where
    undo (x: xs@(_:_), ys) = Just (xs, x: ys)
    undo _ = Nothing

    redo (xs, y: ys) = Just (y: xs, ys)
    redo _ = Nothing

undoLens :: (a -> a -> Bool) -> Lens' ([a],[a]) a
undoLens eq = lens get set where
    get = head . fst
    set (x' : xs, ys) x | eq x x' = (x: xs, ys)
    set (xs, _) x = (x : xs, [])