packages feed

monomer-1.0.0.0: src/Monomer/Widgets/Containers/DropTarget.hs

{-|
Module      : Monomer.Widgets.Containers.DropTarget
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : fjvallarino@gmail.com
Stability   : experimental
Portability : non-portable

Drop target container for a single element. Useful for adding drag support
without having to implement a custom widget. Usually works in tandem with
'Monomer.Widgets.Containers.Draggable'.

Raises a user provided event when an item is dropped. The type must match with
the dragged message, otherwise it will not be raised.

Configs:

- dropTargetStyle: The style to apply to the container when a dragged item is
on top.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Monomer.Widgets.Containers.DropTarget (
  dropTarget,
  dropTarget_,
  dropTargetStyle
) where

import Control.Lens ((&), (^.), (.~))
import Control.Monad (when)
import Data.Default
import Data.Maybe
import Data.Typeable (cast)

import qualified Data.Sequence as Seq

import Monomer.Widgets.Container

import qualified Monomer.Lens as L

newtype DropTargetCfg = DropTargetCfg {
  _dtcDropStyle :: Maybe StyleState
}

instance Default DropTargetCfg where
  def = DropTargetCfg {
    _dtcDropStyle = Nothing
  }

instance Semigroup DropTargetCfg where
  (<>) t1 t2 = DropTargetCfg {
    _dtcDropStyle = _dtcDropStyle t1 <> _dtcDropStyle t2
  }

instance Monoid DropTargetCfg where
  mempty = def

-- | The style to apply to the container when a dragged item is on top.
dropTargetStyle :: [StyleState] -> DropTargetCfg
dropTargetStyle styles = def {
  _dtcDropStyle = Just (mconcat styles)
}

-- | Creates a drop target container with a single node as child.
dropTarget
  :: (DragMsg a, WidgetEvent e) => (a -> e) -> WidgetNode s e -> WidgetNode s e
dropTarget dropEvt managed = dropTarget_ dropEvt def managed

-- | Creates a drop target container with a single node as child. Accepts
-- | config.
dropTarget_
  :: (DragMsg a, WidgetEvent e)
  => (a -> e)
  -> [DropTargetCfg]
  -> WidgetNode s e
  -> WidgetNode s e
dropTarget_ dropEvt configs managed = makeNode widget managed where
  config = mconcat configs
  widget = makeDropTarget dropEvt config

makeNode :: Widget s e -> WidgetNode s e -> WidgetNode s e
makeNode  widget managedWidget = defaultWidgetNode "dropTarget" widget
  & L.info . L.focusable .~ False
  & L.children .~ Seq.singleton managedWidget

makeDropTarget
  :: (DragMsg a, WidgetEvent e) => (a -> e) -> DropTargetCfg -> Widget s e
makeDropTarget dropEvt config = widget where
  widget = createContainer () def {
    containerGetCurrentStyle = getCurrentStyle,
    containerHandleEvent = handleEvent
  }

  getCurrentStyle wenv node
    | isJust style && isDropTarget wenv node && isValid = fromJust style
    | otherwise = currentStyle wenv node
    where
      mousePos = wenv ^. L.inputStatus . L.mousePos
      isHovered = isPointInNodeVp node mousePos
      isTopLevel = isNodeTopLevel wenv node
      isValid = isHovered && isTopLevel
      style = _dtcDropStyle config

  handleEvent wenv node target evt = case evt of
    Drop point path dragMsg
      | not (isNodeParentOfPath node path) -> Just result where
        widgetId = node ^. L.info . L.widgetId
        evts = msgToEvts dragMsg
        result = resultEvts node evts
    _ -> Nothing

  isDropTarget wenv node = case wenv ^. L.dragStatus of
    Just (path, msg) -> not (isNodeParentOfPath node path) && isValidMsg msg
    _ -> False
    where
      isValidMsg = not . null . msgToEvts

  msgToEvts (WidgetDragMsg dragMsg) = case cast dragMsg of
    Just msg -> [dropEvt msg]
    _ -> []