packages feed

spade-0.1.0.9: src/UI/Widgets/Spade/Layout.hs

module UI.Widgets.Spade.Layout where

import Data.Maybe
import Data.List (sortBy)

import Common
import UI.Widgets.Common

data Orientation
  = Vertical
  | Horizontal
  deriving Show

newtype ContentId = ContentId Text
  deriving (Show, Ord, Eq)

data LayoutWidget = LayoutWidget
  { lowDim              :: Dimensions
  , lowPos              :: ScreenPos
  , lowContent          :: [StackedWidget]
  , lowOrientation      :: Orientation
  , lowVisibility       :: Bool
  , lowFloatingContent  :: [SomeWidgetRef]
  , lowDimensionDistribution :: [[Double]]
  , lowFocusWidget      :: Maybe (Int, SomeWidgetRef)
  , lowFocused          :: Bool
  , lowResizeChildren   :: Bool
  }

instance Focusable LayoutWidget where
  getFocus ref = lowFocused <$> readWRef ref
  setFocus ref b = do
    modifyWRef ref (\w' -> w' { lowFocused = b })
    w <- readWRef ref
    case lowFocusWidget w of
      Just (_, SomeWidgetRef elm) -> do
        withCapability (FocusableCap elm) $ setFocus elm b
      Nothing -> void $ setFocusAt ref 0 1 True

instance KeyInput LayoutWidget where
  getCursorInfo _ = pure Nothing
  handleInput ref keyEvent = do
    case keyEvent of
      KeyCtrl _ False _ Tab -> void $ focusNext ref 1
      KeyCtrl _ True _ Tab -> void $ focusNext ref (-1)
      _ -> do
        w <- readWRef ref
        case lowFocusWidget w of
          Just (_, (SomeWidgetRef r)) -> case hasCapability (KeyInputCap r) of
            Just Dict -> handleInput r keyEvent
            Nothing -> pass
          Nothing -> pass

computeDim :: forall m. WidgetC m => Dimensions -> Orientation -> Bool -> [StackedWidget] -> m Dimensions
computeDim dim _ True _ = pure dim
computeDim _ _ False [] = pure $ Dimensions 0 0
computeDim _ ori False children = do
  dimensions <- mapM getChildDimension children
  let heights = diH <$> dimensions
  let widths = diW <$> dimensions
  case ori of
    Vertical -> pure $ Dimensions (Prelude.maximum widths) (sum heights)
    Horizontal -> do
      pure $ Dimensions (sum widths) (Prelude.maximum heights)
  where
    getChildDimension :: StackedWidget -> m Dimensions
    getChildDimension (StackedWidget _ (SomeWidgetRef ref)) = withCapability (MoveableCap ref) $ getDim ref

instance Layout LayoutWidget where
  addWidget' ref so child =
    modifyWRefM ref (\low -> do
      let newContent = (lowContent low) ++ [(StackedWidget so (SomeWidgetRef child))]
      pure low { lowContent =  newContent })
  focusNext ref d = do
    w <- readWRef ref
    case lowFocusWidget w of
      Just (cidx, (SomeWidgetRef cref)) -> do
        case hasCapability (LayoutCap cref) of
          Nothing -> do
            setFocusAt ref (cidx + d) d True >>= \case
              True -> setFocusAt ref cidx d False
              _ -> pure False
          Just Dict -> do
            focusNext cref d >>= \case
              True -> pure True
              False -> do
                setFocusAt ref (cidx + d) d True >>= \case
                  True -> setFocusAt ref cidx d False
                  _ -> pure False

      Nothing -> pure False

setFocusAt :: WidgetC m => WRef LayoutWidget -> Int -> Int -> Bool -> m Bool
setFocusAt ref idx dir b = do
  w <- readWRef ref
  let cSize = Prelude.length (lowContent w)
  if idx >= 0 && idx < cSize
    then do
      case (lowContent w) !! idx of
        (StackedWidget _ (SomeWidgetRef c)) -> do
          r <- case hasCapability (FocusableCap c) of
            Just Dict -> do
              setFocus c b
              if b then modifyWRef ref (\w' -> w' { lowFocusWidget = Just (idx, SomeWidgetRef c) }) else pass
              case hasCapability (KeyInputCap c) of
                Just Dict -> do
                  modifyWidgetState (\ws -> ws { wsCursorVisible = True, wsCursorWidget = Just (SomeKeyInputWidget c) })
                _ -> pass
              pure True
            Nothing -> do
              setFocusAt ref (idx + dir) dir b
          pure r
    else pure False

instance Widget LayoutWidget where
  hasCapability (DrawableCap _)  = Just Dict
  hasCapability (MoveableCap _)  = Just Dict
  hasCapability (LayoutCap _)  = Just Dict
  hasCapability (FocusableCap _)  = Just Dict
  hasCapability (KeyInputCap _)  = Just Dict
  hasCapability _                = Nothing

instance Moveable LayoutWidget where
  getPos ref = lowPos <$> readWRef ref
  move ref pos =
    modifyWRef ref (\low -> low { lowPos = pos })
  getDim ref = do
    low <- readWRef ref
    computeDim (lowDim low) (lowOrientation low) (lowResizeChildren low) (lowContent low)
  resize ref cb =
    modifyWRef ref (\low -> low { lowDim = cb $ lowDim low })

instance Drawable LayoutWidget where
  setVisibility ref v = modifyWRef ref (\b -> b { lowVisibility = v })
  getVisibility ref = lowVisibility <$> readWRef ref
  draw ref = do
    w <- readWRef ref
    let
      dimension = case lowOrientation w of
        Vertical   -> (diH $ lowDim w)
        Horizontal -> (diW $ lowDim w)
    visibileItems <- catMaybes <$> mapM (\sw@(StackedWidget _ (SomeWidgetRef cw)) -> case hasCapability (DrawableCap cw) of
      Just Dict -> do
        b <- getVisibility cw
        case b of
          True -> pure $ Just sw
          _    -> pure Nothing
      Nothing -> pure Nothing) (lowContent w)

    let
      itemCount = Prelude.length visibileItems
      dimensionDistribution = (lowDimensionDistribution w) !! itemCount
      resizeCallback d = case lowOrientation w of
        Vertical -> (\dm -> dm { diW = diW $ lowDim w , diH = d})
        Horizontal -> (\dm -> dm { diH = diH $ lowDim w, diW = d })

    if (lowResizeChildren w)
      then do
        foldM_ (\totalDim (idx, (fraction, StackedWidget _ (SomeWidgetRef cw))) ->
          withCapability (MoveableCap cw) $ do
            let thisDim' =
                  if (idx == itemCount) then (dimension - totalDim) else round $ (realToFrac dimension) * fraction
            let thisDim =  max thisDim' 3
            resize cw (resizeCallback thisDim)
            pure (totalDim + thisDim)
            ) 0 (Prelude.zip [1..] (Prelude.zip dimensionDistribution visibileItems))
      else do
        let
          resizeCallback' = case lowOrientation w of
            Vertical -> (\dm -> dm { diW = diW $ lowDim w })
            Horizontal -> (\dm -> dm { diH = diH $ lowDim w })
        mapM_ (\(StackedWidget _ (SomeWidgetRef cw)) -> withCapability (MoveableCap cw) $ resize cw resizeCallback') (lowContent w)

    case (lowOrientation w) of
      Vertical   -> foldM_ (fny (sX $ lowPos w)) (sY $ lowPos w) $ swSw <$> visibileItems
      Horizontal -> foldM_ (fn (sY $ lowPos w)) (sX $ lowPos w) $ swSw <$> visibileItems
    mapM_ (\(SomeWidgetRef a) -> withCapability (DrawableCap a) $ draw a) $ sortBySo visibileItems
    flip mapM_ (lowFloatingContent w) $ \(SomeWidgetRef f) -> do
      withCapability (DrawableCap f) $ draw f
    where
      sortBySo :: [StackedWidget] -> [SomeWidgetRef]
      sortBySo ws = swSw <$> (sortBy (\a b -> compare (swSo a) (swSo b)) ws)

      fny x y w = case w of
        SomeWidgetRef a ->
          withCapability (MoveableCap a) $ do
            dim <- getDim a
            move a (ScreenPos { sY = y, sX = x })
            pure (y + (diH dim))
      fn y x w = case w of
        SomeWidgetRef a ->
          withCapability (MoveableCap a) $ do
            dim <- getDim a
            move a (ScreenPos { sY = y, sX = x })
            pure (x + (diW dim))

layoutWidget
  :: WidgetC m
  => Orientation
  -> [[Double]]
  -> [SomeWidgetRef]
  -> m (WRef LayoutWidget)
layoutWidget ori distr children = do
  newWRef $
    LayoutWidget
    { lowDim = Dimensions 600 1
    , lowPos = origin
    , lowContent = (\c -> StackedWidget 0 c) <$> children
    , lowOrientation = ori
    , lowVisibility = True
    , lowFloatingContent = []
    , lowDimensionDistribution = distr
    , lowFocusWidget = Nothing
    , lowFocused = False
    , lowResizeChildren = True
    }

simpleLayoutWidget
  :: WidgetC m
  => Orientation
  -> [SomeWidgetRef]
  -> m (WRef LayoutWidget)
simpleLayoutWidget ori children = do
  newWRef $
    LayoutWidget
    { lowDim = Dimensions 1 1
    , lowPos = origin
    , lowContent = (\c -> StackedWidget 0 c) <$> children
    , lowOrientation = ori
    , lowVisibility = True
    , lowFloatingContent = []
    , lowDimensionDistribution = []
    , lowFocusWidget = Nothing
    , lowFocused = False
    , lowResizeChildren = False
    }