| 
 Module       :  . Widgets . Util . Focus 
 Copyright    : ( c ) 2018   
 License      : BSD-3 - Clause ( see the LICENSE file ) 
 Maintainer   :  
 Stability    : experimental 
 Portability : non - portable 

 Helper functions for focus handling . 

Module      : Monomer.Widgets.Util.Focus
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : 
Stability   : experimental
Portability : non-portable

Helper functions for focus handling.
-}
{-# LANGUAGE Strict #-}

module Monomer.Widgets.Util.Focus (
  isNodeFocused,
  isNodeInfoFocused,
  isNodeParentOfFocused,
  parentPath,
  isPathParent,
  nextTargetStep,
  isFocusCandidate,
  isTargetReached,
  isTargetValid,
  isNodeParentOfPath,
  isNodeBeforePath,
  isNodeAfterPath,
  handleFocusChange
) where

import Control.Lens ((&), (^.), (.~), (%~))
import Data.Maybe
import Data.Sequence (Seq(..), (|>))
import Data.Typeable (Typeable)

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Helper
import Monomer.Widgets.Util.Widget

import qualified Monomer.Core.Lens as L

-- | Checks if the given node is focused.
isNodeFocused :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeFocused wenv node = wenv ^. L.focusedPath == node ^. L.info . L.path

-- | Checks if the given nodeInfo is focused.
isNodeInfoFocused :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoFocused wenv info = wenv ^. L.focusedPath == info ^. L.path

-- | Checks if the given node is a parent of the focused node.
isNodeParentOfFocused :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeParentOfFocused wenv node = seqStartsWith parentPath focusedPath where
  parentPath = node ^. L.info . L.path
  focusedPath = wenv ^. L.focusedPath

-- | Returns the parent path of a node.
parentPath :: WidgetNode s e -> Path
parentPath node = Seq.take (Seq.length path - 1) path where
  path = node ^. L.info . L.path

  | Returns whether the first path is parent of the second path .
isPathParent :: Path -> Path -> Bool
isPathParent path childPath = path == basePath where
  basePath = Seq.take (Seq.length path) childPath

-- | Returns the index of the child matching the next step implied by target.
nextTargetStep :: WidgetNode s e -> Path -> Maybe PathStep
nextTargetStep node target = nextStep where
  currentPath = node ^. L.info . L.path
  nextStep = Seq.lookup (Seq.length currentPath) target

{-|
Checks if the node is a candidate for next focus in the given direction. The
node must be focusable, enabled and visible, plus having the correct position
considering the direction.
-}
isFocusCandidate :: WidgetNode s e -> Path -> FocusDirection -> Bool
isFocusCandidate node path FocusFwd = isFocusFwdCandidate node path
isFocusCandidate node path FocusBwd = isFocusBwdCandidate node path

-- | Checks if the node's path matches the target.
isTargetReached :: WidgetNode s e -> Path -> Bool
isTargetReached node target = target == node ^. L.info . L.path

-- | Checks if the node has a child matching the next target step.
isTargetValid :: WidgetNode s e -> Path -> Bool
isTargetValid node target = valid where
  children = node ^. L.children
  valid = case nextTargetStep node target of
    Just step -> step < Seq.length children
    Nothing -> False

-- | Checks if the node is parent of the provided path.
isNodeParentOfPath :: WidgetNode s e -> Path -> Bool
isNodeParentOfPath node path = result where
  widgetPath = node ^. L.info . L.path
  lenWidgetPath = Seq.length widgetPath
  pathPrefix = Seq.take lenWidgetPath path
  result = widgetPath == pathPrefix

-- | Checks if the node's path is after the target (deeper or to the right).
isNodeAfterPath :: WidgetNode s e -> Path -> Bool
isNodeAfterPath node path = result where
  widgetPath = node ^. L.info . L.path
  lenPath = Seq.length path
  lenWidgetPath = Seq.length widgetPath
  widgetPathPrefix = Seq.take lenPath widgetPath
  result
    | lenWidgetPath > lenPath = path <= widgetPathPrefix
    | otherwise = path < widgetPath

-- | Checks if the node's path is after the target (higher or to the left).
isNodeBeforePath :: WidgetNode s e -> Path -> Bool
isNodeBeforePath node path = result where
  widgetPath = node ^. L.info . L.path
  result
    | path == emptyPath = True
    | otherwise = path > widgetPath

-- | Generates a result with events and requests associated to a focus change.
handleFocusChange
  :: WidgetNode s e              -- ^ The node receiving the event.
  -> Path                        -- ^ The path of next/prev target, accordingly.
  ^ Getter for  handler in a config type .
  -> Maybe (WidgetResult s e)    -- ^ The result.
handleFocusChange node path reqFns = result where
  reqs = ($ path) <$> reqFns
  result
    | not (null reqs) = Just $ resultReqs node reqs
    | otherwise = Nothing

-- Helpers
isFocusFwdCandidate :: WidgetNode s e -> Path -> Bool
isFocusFwdCandidate node startFrom = isValid where
  info = node ^. L.info
  isAfter = isNodeAfterPath node startFrom
  isFocusable = info ^. L.focusable
  isEnabled = info ^. L.visible && info ^. L.enabled
  isValid = isAfter && isFocusable && isEnabled

isFocusBwdCandidate :: WidgetNode s e -> Path -> Bool
isFocusBwdCandidate node startFrom = isValid where
  info = node ^. L.info
  isBefore = isNodeBeforePath node startFrom
  isFocusable = info ^. L.focusable
  isEnabled = info ^. L.visible && info ^. L.enabled
  isValid = isBefore && isFocusable && isEnabled

# LANGUAGE Strict #
 | Checks if the given node is focused.
 | Checks if the given nodeInfo is focused.
 | Checks if the given node is a parent of the focused node.
 | Returns the parent path of a node.
 | Returns the index of the child matching the next step implied by target.
|
Checks if the node is a candidate for next focus in the given direction. The
node must be focusable, enabled and visible, plus having the correct position
considering the direction.

 | Checks if the node's path matches the target.
 | Checks if the node has a child matching the next target step.
 | Checks if the node is parent of the provided path.
 | Checks if the node's path is after the target (deeper or to the right).
 | Checks if the node's path is after the target (higher or to the left).
 | Generates a result with events and requests associated to a focus change.
 ^ The node receiving the event.
 ^ The path of next/prev target, accordingly.
 ^ The result.
 Helpers
| 
 Module       :  . Widgets . Util . Focus 
 Copyright    : ( c ) 2018   
 License      : BSD-3 - Clause ( see the LICENSE file ) 
 Maintainer   :  
 Stability    : experimental 
 Portability : non - portable 

 Helper functions for focus handling . 

Module      : Monomer.Widgets.Util.Focus
Copyright   : (c) 2018 Francisco Vallarino
License     : BSD-3-Clause (see the LICENSE file)
Maintainer  : 
Stability   : experimental
Portability : non-portable

Helper functions for focus handling.
-}

module Monomer.Widgets.Util.Focus (
  isNodeFocused,
  isNodeInfoFocused,
  isNodeParentOfFocused,
  parentPath,
  isPathParent,
  nextTargetStep,
  isFocusCandidate,
  isTargetReached,
  isTargetValid,
  isNodeParentOfPath,
  isNodeBeforePath,
  isNodeAfterPath,
  handleFocusChange
) where

import Control.Lens ((&), (^.), (.~), (%~))
import Data.Maybe
import Data.Sequence (Seq(..), (|>))
import Data.Typeable (Typeable)

import qualified Data.Sequence as Seq

import Monomer.Core
import Monomer.Helper
import Monomer.Widgets.Util.Widget

import qualified Monomer.Core.Lens as L

isNodeFocused :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeFocused wenv node = wenv ^. L.focusedPath == node ^. L.info . L.path

isNodeInfoFocused :: WidgetEnv s e -> WidgetNodeInfo -> Bool
isNodeInfoFocused wenv info = wenv ^. L.focusedPath == info ^. L.path

isNodeParentOfFocused :: WidgetEnv s e -> WidgetNode s e -> Bool
isNodeParentOfFocused wenv node = seqStartsWith parentPath focusedPath where
  parentPath = node ^. L.info . L.path
  focusedPath = wenv ^. L.focusedPath

parentPath :: WidgetNode s e -> Path
parentPath node = Seq.take (Seq.length path - 1) path where
  path = node ^. L.info . L.path

  | Returns whether the first path is parent of the second path .
isPathParent :: Path -> Path -> Bool
isPathParent path childPath = path == basePath where
  basePath = Seq.take (Seq.length path) childPath

nextTargetStep :: WidgetNode s e -> Path -> Maybe PathStep
nextTargetStep node target = nextStep where
  currentPath = node ^. L.info . L.path
  nextStep = Seq.lookup (Seq.length currentPath) target

isFocusCandidate :: WidgetNode s e -> Path -> FocusDirection -> Bool
isFocusCandidate node path FocusFwd = isFocusFwdCandidate node path
isFocusCandidate node path FocusBwd = isFocusBwdCandidate node path

isTargetReached :: WidgetNode s e -> Path -> Bool
isTargetReached node target = target == node ^. L.info . L.path

isTargetValid :: WidgetNode s e -> Path -> Bool
isTargetValid node target = valid where
  children = node ^. L.children
  valid = case nextTargetStep node target of
    Just step -> step < Seq.length children
    Nothing -> False

isNodeParentOfPath :: WidgetNode s e -> Path -> Bool
isNodeParentOfPath node path = result where
  widgetPath = node ^. L.info . L.path
  lenWidgetPath = Seq.length widgetPath
  pathPrefix = Seq.take lenWidgetPath path
  result = widgetPath == pathPrefix

isNodeAfterPath :: WidgetNode s e -> Path -> Bool
isNodeAfterPath node path = result where
  widgetPath = node ^. L.info . L.path
  lenPath = Seq.length path
  lenWidgetPath = Seq.length widgetPath
  widgetPathPrefix = Seq.take lenPath widgetPath
  result
    | lenWidgetPath > lenPath = path <= widgetPathPrefix
    | otherwise = path < widgetPath

isNodeBeforePath :: WidgetNode s e -> Path -> Bool
isNodeBeforePath node path = result where
  widgetPath = node ^. L.info . L.path
  result
    | path == emptyPath = True
    | otherwise = path > widgetPath

handleFocusChange
  ^ Getter for  handler in a config type .
handleFocusChange node path reqFns = result where
  reqs = ($ path) <$> reqFns
  result
    | not (null reqs) = Just $ resultReqs node reqs
    | otherwise = Nothing

isFocusFwdCandidate :: WidgetNode s e -> Path -> Bool
isFocusFwdCandidate node startFrom = isValid where
  info = node ^. L.info
  isAfter = isNodeAfterPath node startFrom
  isFocusable = info ^. L.focusable
  isEnabled = info ^. L.visible && info ^. L.enabled
  isValid = isAfter && isFocusable && isEnabled

isFocusBwdCandidate :: WidgetNode s e -> Path -> Bool
isFocusBwdCandidate node startFrom = isValid where
  info = node ^. L.info
  isBefore = isNodeBeforePath node startFrom
  isFocusable = info ^. L.focusable
  isEnabled = info ^. L.visible && info ^. L.enabled
  isValid = isBefore && isFocusable && isEnabled
