packages feed

monomer-1.5.1.0: src/Monomer/Widgets/Util/Hover.hs

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

Helper functions for hover related actions.
-}
{-# LANGUAGE Strict #-}

module Monomer.Widgets.Util.Hover (
  isPointInNodeVp,
  isPointInNodeEllipse,
  isNodeActive,
  isNodeInfoActive,
  isNodePressed,
  isNodeInfoPressed,
  isNodeTreeActive,
  isNodeTreePressed,
  isNodeDragged,
  isNodeInfoDragged,
  isNodeHovered,
  isNodeInfoHovered,
  isNodeHoveredEllipse_,
  isNodeTopLevel,
  isNodeInfoTopLevel,
  isNodeInOverlay,
  isNodeInfoInOverlay
) where

import Control.Lens ((&), (^.), (^?), _1, _Just)
import Data.Maybe

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Helper

import qualified Monomer.Core.Lens as L
import qualified Monomer.Event.Lens as L

-- | Checks if the given point is inside the node's viewport.
isPointInNodeVp :: WidgetNode s e -> Point -> Bool
isPointInNodeVp node p = pointInRect p (node ^. L.info . L.viewport)

-- | Checks if the given point is inside the ellipse delimited by the viewport.
isPointInNodeEllipse :: WidgetNode s e -> Point -> Bool
isPointInNodeEllipse node p = pointInEllipse p (node ^. L.info . L.viewport)

-- | Checks if the main button is pressed and pointer inside the viewport.
isNodeActive :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeActive wenv node = isNodeInfoActive False wenv (node ^. L.info)

-- | Checks if the main button is pressed inside the viewport.
isNodePressed :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodePressed wenv node = isNodeInfoPressed False wenv (node ^. L.info)

-- | Checks if the node or any of its children is active.
isNodeTreeActive :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeTreeActive wenv node = isNodeInfoActive True wenv (node ^. L.info)

-- | Checks if the node or any of its children is pressed.
isNodeTreePressed :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeTreePressed wenv node = isNodeInfoPressed True wenv (node ^. L.info)

{-|
Checks if the node is active, optionally including children. An active node was
clicked with the main button and has the pointer inside its viewport.
-}
isNodeInfoActive :: Bool -> WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoActive checkChildren wenv info = validPos && pressed && topLevel where
  viewport = info ^. L.viewport
  mousePos = wenv ^. L.inputStatus . L.mousePos
  validPos = pointInRect mousePos viewport
  pressed = isNodeInfoPressed checkChildren wenv info
  topLevel = isNodeInfoTopLevel wenv info

{-|
Checks if the node is pressed, optionally including children. A pressed node was
clicked with the main button, but the pointer may not be inside its viewport.
-}
isNodeInfoPressed :: Bool -> WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoPressed includeChildren wenv info = result == Just True where
  path = info ^. L.path
  pressed = wenv ^. L.mainBtnPress ^? _Just . _1
  result
    | includeChildren = seqStartsWith path <$> pressed
    | otherwise = (path ==) <$> pressed

{-|
Checks if the node is being dragged. The node must have made a previous request
to be in that state.
-}
isNodeDragged :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeDragged wenv node = isNodeInfoDragged wenv (node ^. L.info)

-- | Checks if the nodeInfo is being dragged.
isNodeInfoDragged :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoDragged wenv info = mainPressed && draggedPath == Just nodePath where
  mainPressed = isJust (wenv ^. L.mainBtnPress)
  draggedPath = wenv ^? L.dragStatus . _Just . _1
  nodePath = info ^. L.path

-- | Checks if node is hovered. Pointer must be in viewport and node top layer.
isNodeHovered :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeHovered wenv node = isNodeInfoHovered wenv (node ^. L.info)

-- | Checks if nodeInfo is hovered.
isNodeInfoHovered :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoHovered wenv info = validPos && validPress && topLevel where
  viewport = info ^. L.viewport
  mousePos = wenv ^. L.inputStatus . L.mousePos
  validPos = pointInRect mousePos viewport
  pressed = wenv ^. L.mainBtnPress ^? _Just . _1
  validPress = isNothing pressed || isNodeInfoPressed False wenv info
  topLevel = isNodeInfoTopLevel wenv info

-- | Checks if node is hovered, limited to an elliptical shape.
isNodeHoveredEllipse_ :: Rect -> WidgetEnv s e -> WidgetNode s e -> Bool
isNodeHoveredEllipse_ area wenv node = validPos && validPress && topLevel where
  mousePos = wenv ^. L.inputStatus . L.mousePos
  validPos = pointInEllipse mousePos area
  pressed = wenv ^. L.mainBtnPress ^? _Just . _1
  validPress = isNothing pressed || isNodePressed wenv node
  topLevel = isNodeTopLevel wenv node

{-|
Checks if a node is in a top layer. Being in zstack can cause this to be False.
-}
isNodeTopLevel :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeTopLevel wenv node = isNodeInfoTopLevel wenv (node ^. L.info)

-- | Checks if a nodeInfo is in a top layer.
isNodeInfoTopLevel :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoTopLevel wenv info = result where
  mousePos = wenv ^. L.inputStatus . L.mousePos
  inTopLayer = wenv ^. L.inTopLayer $ mousePos
  path = info ^. L.path
  isPrefix parent = Seq.take (Seq.length parent) path == parent
  result = maybe inTopLayer isPrefix (wenv ^. L.overlayPath)

-- | Checks if the node is part of the active overlay, if any.
isNodeInOverlay :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeInOverlay wenv node = isNodeInfoInOverlay wenv (node ^. L.info)

-- | Checks if the nodeInfo is part of the active overlay, if any.
isNodeInfoInOverlay :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoInOverlay wenv info = result where
  path = info ^. L.path
  isPrefix overlayPath = Seq.take (Seq.length overlayPath) path == overlayPath
  result = maybe False isPrefix (wenv ^. L.overlayPath)