-- | This module provides a type and functions for handling focus rings
-- of values.
--
-- This interface is experimental.
module Brick.Focus
  ( FocusRing
  , focusRing
  , focusNext
  , focusPrev
  , focusGetCurrent
  , focusSetCurrent
  , focusRingLength
  , focusRingToList
  , focusRingCursor
  , withFocusRing
  , focusRingModify
  )
where

import Lens.Micro ((^.))
import Data.Maybe (listToMaybe)
import qualified Data.CircularList as C

import Brick.Types
import Brick.Widgets.Core (Named(..))

-- | A focus ring containing a sequence of resource names to focus and a
-- currently-focused name.
newtype FocusRing n = FocusRing (C.CList n)

-- | Construct a focus ring from the list of resource names.
focusRing :: [n] -> FocusRing n
focusRing :: [n] -> FocusRing n
focusRing = CList n -> FocusRing n
forall n. CList n -> FocusRing n
FocusRing (CList n -> FocusRing n) -> ([n] -> CList n) -> [n] -> FocusRing n
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [n] -> CList n
forall a. [a] -> CList a
C.fromList

-- | Advance focus to the next value in the ring.
focusNext :: FocusRing n -> FocusRing n
focusNext :: FocusRing n -> FocusRing n
focusNext r :: FocusRing n
r@(FocusRing CList n
l)
    | CList n -> Bool
forall a. CList a -> Bool
C.isEmpty CList n
l = FocusRing n
r
    | Bool
otherwise = CList n -> FocusRing n
forall n. CList n -> FocusRing n
FocusRing (CList n -> FocusRing n) -> CList n -> FocusRing n
forall a b. (a -> b) -> a -> b
$ CList n -> CList n
forall a. CList a -> CList a
C.rotR CList n
l

-- | Advance focus to the previous value in the ring.
focusPrev :: FocusRing n -> FocusRing n
focusPrev :: FocusRing n -> FocusRing n
focusPrev r :: FocusRing n
r@(FocusRing CList n
l)
    | CList n -> Bool
forall a. CList a -> Bool
C.isEmpty CList n
l = FocusRing n
r
    | Bool
otherwise = CList n -> FocusRing n
forall n. CList n -> FocusRing n
FocusRing (CList n -> FocusRing n) -> CList n -> FocusRing n
forall a b. (a -> b) -> a -> b
$ CList n -> CList n
forall a. CList a -> CList a
C.rotL CList n
l

-- | This function is a convenience function to look up a widget state
-- value's resource name in a focus ring and set its focus setting
-- according to the focus ring's state. This function determines whether
-- a given widget state value is the focus of the ring and passes the
-- resulting boolean to a rendering function, along with the state value
-- (a), to produce whatever comes next (b).
--
-- Focus-aware widgets have rendering functions that should be
-- usable with this combinator; see 'Brick.Widgets.List.List' and
-- 'Brick.Widgets.Edit.Edit'.
withFocusRing :: (Eq n, Named a n)
              => FocusRing n
              -- ^ The focus ring to use as the source of focus state.
              -> (Bool -> a -> b)
              -- ^ A function that takes a value and its focus state.
              -> a
              -- ^ The wiget state value that we need to check for focus.
              -> b
              -- ^ The rest of the computation.
withFocusRing :: FocusRing n -> (Bool -> a -> b) -> a -> b
withFocusRing FocusRing n
ring Bool -> a -> b
f a
a = Bool -> a -> b
f (FocusRing n -> Maybe n
forall n. FocusRing n -> Maybe n
focusGetCurrent FocusRing n
ring Maybe n -> Maybe n -> Bool
forall a. Eq a => a -> a -> Bool
== n -> Maybe n
forall a. a -> Maybe a
Just (a -> n
forall a n. Named a n => a -> n
getName a
a)) a
a

-- | Get the currently-focused resource name from the ring. If the ring
-- is emtpy, return 'Nothing'.
focusGetCurrent :: FocusRing n -> Maybe n
focusGetCurrent :: FocusRing n -> Maybe n
focusGetCurrent (FocusRing CList n
l) = CList n -> Maybe n
forall a. CList a -> Maybe a
C.focus CList n
l

-- | Set the currently-focused resource name in the ring, provided the
-- name is in the ring. Otherwise return the ring unmodified.
focusSetCurrent :: (Eq n) => n -> FocusRing n -> FocusRing n
focusSetCurrent :: n -> FocusRing n -> FocusRing n
focusSetCurrent n
n r :: FocusRing n
r@(FocusRing CList n
l) =
    case n -> CList n -> Maybe (CList n)
forall a. Eq a => a -> CList a -> Maybe (CList a)
C.rotateTo n
n CList n
l of
        Maybe (CList n)
Nothing -> FocusRing n
r
        Just CList n
l' -> CList n -> FocusRing n
forall n. CList n -> FocusRing n
FocusRing CList n
l'

-- | Get the size of the FocusRing.
focusRingLength :: FocusRing n -> Int
focusRingLength :: FocusRing n -> Int
focusRingLength (FocusRing CList n
l) = CList n -> Int
forall a. CList a -> Int
C.size CList n
l

-- | Return all of the entries in the focus ring, starting with the
-- currently-focused entry and wrapping around the ring.
--
-- For example, if a ring contains A, B, C, and D, and the current entry
-- is B, the result will be [B, C, D, A].
focusRingToList :: FocusRing n -> [n]
focusRingToList :: FocusRing n -> [n]
focusRingToList (FocusRing CList n
l) = CList n -> [n]
forall a. CList a -> [a]
C.rightElements CList n
l

-- | Modify the internal circular list structure of a focus ring
-- directly. This function permits modification of the circular list
-- using the rich Data.CircularList API.
focusRingModify :: (C.CList n -> C.CList n) -> FocusRing n -> FocusRing n
focusRingModify :: (CList n -> CList n) -> FocusRing n -> FocusRing n
focusRingModify CList n -> CList n
f (FocusRing CList n
l) = CList n -> FocusRing n
forall n. CList n -> FocusRing n
FocusRing (CList n -> FocusRing n) -> CList n -> FocusRing n
forall a b. (a -> b) -> a -> b
$ CList n -> CList n
f CList n
l

-- | Cursor selection convenience function for use as an
-- 'Brick.Main.appChooseCursor' value.
focusRingCursor :: (Eq n)
                => (a -> FocusRing n)
                -- ^ The function used to get the focus ring out of your
                -- application state.
                -> a
                -- ^ Your application state.
                -> [CursorLocation n]
                -- ^ The list of available cursor positions.
                -> Maybe (CursorLocation n)
                -- ^ The cursor position, if any, that matches the
                -- resource name currently focused by the 'FocusRing'.
focusRingCursor :: (a -> FocusRing n)
-> a -> [CursorLocation n] -> Maybe (CursorLocation n)
focusRingCursor a -> FocusRing n
getRing a
st [CursorLocation n]
ls =
    [CursorLocation n] -> Maybe (CursorLocation n)
forall a. [a] -> Maybe a
listToMaybe ([CursorLocation n] -> Maybe (CursorLocation n))
-> [CursorLocation n] -> Maybe (CursorLocation n)
forall a b. (a -> b) -> a -> b
$ (CursorLocation n -> Bool)
-> [CursorLocation n] -> [CursorLocation n]
forall a. (a -> Bool) -> [a] -> [a]
filter CursorLocation n -> Bool
isCurrent [CursorLocation n]
ls
    where
        isCurrent :: CursorLocation n -> Bool
isCurrent CursorLocation n
cl = CursorLocation n
clCursorLocation n
-> Getting (Maybe n) (CursorLocation n) (Maybe n) -> Maybe n
forall s a. s -> Getting a s a -> a
^.Getting (Maybe n) (CursorLocation n) (Maybe n)
forall n1 n2.
Lens (CursorLocation n1) (CursorLocation n2) (Maybe n1) (Maybe n2)
cursorLocationNameL Maybe n -> Maybe n -> Bool
forall a. Eq a => a -> a -> Bool
==
                       (FocusRing n -> Maybe n
forall n. FocusRing n -> Maybe n
focusGetCurrent (FocusRing n -> Maybe n) -> FocusRing n -> Maybe n
forall a b. (a -> b) -> a -> b
$ a -> FocusRing n
getRing a
st)