packages feed

reflex-gi-gtk-0.2.0.0: src/Reflex/GI/Gtk/Widget/Box.hs

-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.

{-# LANGUAGE FlexibleContexts, LambdaCase, TupleSections #-}

{-|
Description : Reactive helpers for 'Box'es
Copyright   : Sven Bartscher 2020
License     : MPL-2.0
Maintainer  : sven.bartscher@weltraumschlangen.de
Stability   : experimental

This module provides helpers for dealing with 'Box'es in reactive
contexts.
-}
module Reflex.GI.Gtk.Widget.Box
  ( sinkBox
  , sinkBoxUniform
  ) where

import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO)
import Data.Align ( Semialign
                  , align
                  )
import Data.Foldable (foldl')
import Data.GI.Base (GObject)
import Data.GI.Base.Overloading (IsDescendantOf)
import qualified Data.Map as M
import Data.These ( These( This
                         , That
                         , These
                         )
                  )
import Data.Word (Word32)
import GI.Gtk ( Box
              , Container
              , PackType( PackTypeStart
                        , PackTypeEnd
                        )
              , Widget
              , boxPackEnd
              , boxPackStart
              , boxReorderChild
              , boxSetChildPacking
              , containerRemove
              )
import Reflex ( MonadHold
              , PerformEvent
              , Performable
              , PostBuild
              , (<@>)
              , performEvent_
              , hold
              )
import Reflex.GI.Gtk.Output ( Sinkable
                            , toSinkEvent
                            )
import Reflex.GI.Gtk.Run.Class ( MonadRunGtk
                               , runGtk
                               )
import Reflex.GI.Gtk.Widget.Ord (OrdWidget(OrdWidget))

pack :: ( MonadIO m
        , GObject box
        , IsDescendantOf Box box
        , GObject child
        , IsDescendantOf Widget child
        )
       => box -> child -> Bool -> Bool -> Word32 -> PackType -> m ()
pack box child expand fill padding PackTypeStart = boxPackStart box child expand fill padding
pack box child expand fill padding PackTypeEnd = boxPackEnd box child expand fill padding
pack box child expand fill padding unknownPackType = do
  -- We don't know this packing, but boxSetChildPacking may know it,
  -- so we pack at start and then set the correct packing.
  boxPackStart box child expand fill padding
  boxSetChildPacking box child expand fill padding unknownPackType

-- | Pack a dynamically changing sequence of widgets into a box. Each
-- widget has individual dynamic packing parameters.
--
-- The widgets will be packed into the 'Box' in left-fold order.
sinkBox :: ( GObject box
           , IsDescendantOf Container box
           , IsDescendantOf Box box
           , Foldable f
           , Semialign f
           , GObject w
           , IsDescendantOf Widget w
           , Eq w
           , PerformEvent t m
           , PostBuild t m
           , MonadHold t m
           , MonadRunGtk m
           , MonadRunGtk (Performable m)
           , Sinkable t s
           )
        => box
        -- ^ The 'Box' to pack into
        -> s (f (w, Bool, Bool, Word32, PackType))
        -- ^ The dynamic sequence of 'Widget's. The arguments are the
        -- same as those for 'boxSetChildPacking'.
        -> m ()
sinkBox box widgetSinkable = do
  widgetUpdates <- toSinkEvent widgetSinkable
  currentWidgets <- hold Nothing $ Just <$> widgetUpdates
  performEvent_ $ update <$> currentWidgets <@> widgetUpdates
  where update Nothing widgets =
          runGtk $ foldl' (\acc (w, expand, fill, padding, packType) ->
                              acc
                              >> pack box w expand fill padding packType
                          ) (pure ()) widgets
        update (Just olds) neww =
          let (reorder, _, removed) =
                foldl' (\wacc -> \case
                           This old -> markOld wacc old
                           That new -> otherWidget wacc new
                           These
                             wold@(old, oldExpand, oldFill, oldPadding, oldPackType)
                             wnew@(new, newExpand, newFill, newPadding, newPackType)
                             | old == new ->
                                 repackWidget wacc new
                                 (oldExpand, oldFill, oldPadding, oldPackType)
                                 (newExpand, newFill, newPadding, newPackType)
                             | otherwise ->
                                 otherWidget (markOld wacc wold) wnew
                       ) (pure (), 0, M.empty) $ align olds neww
          in runGtk $
             M.foldlWithKey' (\acc (OrdWidget w) _ ->
                                acc >> containerRemove box w
                             ) (pure ()) removed
             >> reorder
        markOld (acc, i, oldWidgets) (w, expand, fill, padding, packType) =
          ( acc
          , i
          , M.insert (OrdWidget w) (expand, fill, padding, packType) oldWidgets
          )
        otherWidget wacc@(_, _, oldWidgets) ww@(w, _, _, _, _) =
          case oldWidgets M.!? OrdWidget w of
            Nothing -> newWidget wacc ww
            Just oldPacking -> reuseWidget wacc ww oldPacking
        newWidget (acc, i, oldWidgets) (w, expand, fill, padding, packType) =
          ( do
              _ <- acc
              pack box w expand fill padding packType
              boxReorderChild box w i
          , succ i
          , oldWidgets
          )
        repackWidget (acc, i, oldWidgets) w oldPacking
          newPacking@(expand, fill, padding, packType) =
          ( do
              _ <- acc
              when (oldPacking /= newPacking)
                $ boxSetChildPacking box w expand fill padding packType
          , succ i
          , oldWidgets
          )
        reuseWidget (acc, i, oldWidgets) (w, expand, fill, padding, packType) oldPacking =
          repackWidget ( do
                           _ <- acc
                           boxReorderChild box w i
                       , i -- repack widget already increases this
                       , M.delete (OrdWidget w) oldWidgets
                       ) w oldPacking (expand, fill, padding, packType)

-- | Like 'sinkBox', but the packing parameters are statically
-- specified for all widgets.
sinkBoxUniform :: ( GObject box
                  , IsDescendantOf Container box
                  , IsDescendantOf Box box
                  , Foldable f
                  , Semialign f
                  , GObject w
                  , IsDescendantOf Widget w
                  , Eq w
                  , PerformEvent t m
                  , PostBuild t m
                  , MonadRunGtk m
                  , MonadRunGtk (Performable m)
                  , MonadHold t m
                  , Sinkable t s
                  )
               => box
               -- ^ The 'Box' to pack into
               -> s (f w)
               -- ^ The dynamic sequence of 'Widget's
               -> Bool
               -> Bool
               -> Word32
               -> PackType
               -> m ()
sinkBoxUniform box widgets expand fill padding packType =
  sinkBox box $ fmap (, expand, fill, padding, packType) <$> widgets