packages feed

chiasma-0.10.2.0: lib/Chiasma/Ui/ViewTree.hs

module Chiasma.Ui.ViewTree where

import Control.Lens (cosmos, ix, transformM)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)
import Control.Monad.Trans.Writer (WriterT, runWriterT, tell)
import Data.Composition ((.:))
import Data.Semigroup (Sum (Sum))
import Exon (exon)
import Prelude hiding (ix, tell)

import Chiasma.Data.Ident (Ident)
import Chiasma.Lens.Tree (LeafIndexTree (..), _litTree, leafDataTraversal)
import Chiasma.Ui.Data.TreeModError (TreeModError (AmbiguousLayout, AmbiguousPane, LayoutMissing, PaneMissing))
import Chiasma.Ui.Data.View (
  Pane (Pane),
  PaneView,
  Tree (Tree),
  TreeSub (TreeLeaf, TreeNode),
  View (View),
  ViewTree,
  ViewTreeSub,
  )
import Chiasma.Ui.Data.ViewState (ViewState (ViewState))
import Chiasma.Ui.Pane (paneSetOpen, paneToggleOpen)

modCounted :: Monad m => (a -> m a) -> a -> WriterT (Sum Int) m a
modCounted f a = do
  tell (Sum 1)
  lift $ f a

treeToggleOpen :: ViewTree -> ViewTree
treeToggleOpen (Tree l sub) =
  Tree l (snd $ mapAccumL toggle False sub)
  where
    toggle False (TreeLeaf p) = (True, TreeLeaf (paneToggleOpen p))
    toggle a b = (a, b)

modifyTreeUniqueM :: Monad m => (ViewTree -> m ViewTree) -> Ident -> ViewTree -> ExceptT TreeModError m ViewTree
modifyTreeUniqueM f ident tree = do
  let st = (transformM $ mapMOf (ix ident) (modCounted f)) tree
  (result, Sum count) <- lift $ runWriterT st
  case count of
    1 -> pure result
    0 -> ExceptT (pure (Left (LayoutMissing ident)))
    n -> ExceptT (pure (Left (AmbiguousLayout ident n)))

toggleLayout1 :: Ident -> ViewTree -> Either TreeModError ViewTree
toggleLayout1 ident tree =
  runIdentity $ runExceptT $ modifyTreeUniqueM (Identity . treeToggleOpen) ident tree

modifyPaneUniqueM :: Monad m => (PaneView -> m PaneView) -> Ident -> ViewTree -> ExceptT TreeModError m ViewTree
modifyPaneUniqueM f ident tree = do
  let st = (transformM $ mapMOf (ix ident) (modCounted f)) (LeafIndexTree tree)
  (result, Sum count) <- lift $ runWriterT st
  case count of
    1 -> pure $ (.litTree) result
    0 -> ExceptT (pure (Left (PaneMissing ident)))
    n -> ExceptT (pure (Left (AmbiguousPane ident n)))

modifyPane :: (PaneView -> PaneView) -> Ident -> ViewTree -> Either TreeModError ViewTree
modifyPane modification ident tree =
  runIdentity $ runExceptT $ modifyPaneUniqueM (Identity . modification) ident tree

openPane :: Ident -> ViewTree -> Either TreeModError ViewTree
openPane =
  modifyPane paneSetOpen

hasOpenPanes :: ViewTree -> Bool
hasOpenPanes tree =
  has (cosmos . _litTree . leafDataTraversal . filtered isOpen) (LeafIndexTree tree)
  where
    isOpen (View _ _ _ (Pane open _ _)) = open

depthTraverseTree ::
  ∀ a.
  Monoid a =>
  (a -> ViewTree -> (a, ViewTree)) ->
  (PaneView -> (a, PaneView)) ->
  ViewTree ->
  (a, ViewTree)
depthTraverseTree transformNode transformLeaf =
  recur
  where
    recur :: ViewTree -> (a, ViewTree)
    recur (Tree l sub) =
      uncurry transformNode . bimap fold (Tree l) . unzip $ (recSub <$> sub)
    recSub :: ViewTreeSub -> (a, ViewTreeSub)
    recSub (TreeNode t) =
      second TreeNode $ recur t
    recSub (TreeLeaf l) =
      second TreeLeaf $ transformLeaf l

data ToggleStatus =
  Minimized
  |
  Opened
  |
  Pristine
  |
  Multiple Int
  |
  Consistent
  deriving stock (Eq, Show)

instance Semigroup ToggleStatus where
  Pristine <> a = a
  a <> Pristine = a
  Multiple a <> Multiple b = Multiple (a + b)
  Multiple a <> _ = Multiple (a + 1)
  _ <> Multiple a = Multiple (a + 1)
  _ <> _ = Multiple 2

instance Monoid ToggleStatus where
  mempty = Pristine

data ToggleResult a =
  Success a
  |
  NotFound
  |
  Ambiguous Int
  deriving stock (Eq, Show, Functor)

instance Semigroup (ToggleResult a) where
  NotFound <> a = a
  a <> NotFound = a
  Ambiguous a <> Ambiguous b = Ambiguous (a + b)
  Ambiguous a <> _ = Ambiguous (a + 1)
  _ <> Ambiguous a = Ambiguous (a + 1)
  _ <> _ = Ambiguous 2

instance Monoid (ToggleResult a) where
  mempty = NotFound

instance Applicative ToggleResult where
  pure = Success
  (Success f) <*> fa = fmap f fa
  NotFound <*> _ = NotFound
  Ambiguous n <*> _ = Ambiguous n

instance Monad ToggleResult where
    Success a >>= f = f a
    NotFound >>= _ = NotFound
    Ambiguous n >>= _ = Ambiguous n

toggleResultEither :: ToggleResult a -> Either Text a
toggleResultEither = \case
  Success a -> Right a
  NotFound -> Left "not found"
  Ambiguous n -> Left [exon|ambiguous: #{show n}|]

openPinnedSubs :: ToggleStatus -> ViewTree -> (ToggleStatus, ViewTree)
openPinnedSubs Pristine t =
  (Pristine, t)
openPinnedSubs Opened (Tree l sub) =
  (Opened, Tree l (openPinnedPane <$> sub))
  where
    openPinnedPane :: ViewTreeSub -> ViewTreeSub
    openPinnedPane (TreeLeaf (View i s g (Pane False True cwd))) =
      TreeLeaf $ View i s g (Pane True True cwd)
    openPinnedPane v =
      v
openPinnedSubs a t =
  (a, t)

checkToggleResult ::
  ToggleStatus ->
  a ->
  ToggleResult a
checkToggleResult =
  checkResult
  where
    checkResult Pristine _ = NotFound
    checkResult (Multiple n) _ = Ambiguous n
    checkResult _ result = Success result

togglePaneView :: Ident -> PaneView -> (ToggleStatus, PaneView)
togglePaneView ident (View i s g (Pane False p c)) | ident == i =
  (Opened, View i s g (Pane True p c))
togglePaneView ident (View i (ViewState minimized) g (Pane True p c)) | ident == i =
  (Minimized, View i (ViewState (not minimized)) g (Pane False p c))
togglePaneView _ v =
  (Pristine, v)

togglePaneNode :: Ident -> ViewTreeSub -> (ToggleStatus, ViewTreeSub)
togglePaneNode ident (TreeLeaf v) =
  second TreeLeaf (togglePaneView ident v)
togglePaneNode _ t =
  (Pristine, t)

togglePane :: Ident -> ViewTree -> ToggleResult ViewTree
togglePane ident =
  uncurry checkToggleResult . depthTraverseTree openPinnedSubs (togglePaneView ident)

togglePaneOpenTraversal' ::
  Traversal' a ViewTree ->
  Ident ->
  a ->
  ToggleResult a
togglePaneOpenTraversal' lens =
  mapMOf lens . togglePane

ensurePaneViewOpen :: Ident -> PaneView -> (ToggleStatus, PaneView)
ensurePaneViewOpen ident (View i s g (Pane False p c)) | ident == i =
  (Opened, View i s g (Pane True p c))
ensurePaneViewOpen ident v@(View i _ _ _) | ident == i =
  (Consistent, v)
ensurePaneViewOpen _ v =
  (Pristine, v)

ensurePaneOpen :: Ident -> ViewTree -> ToggleResult ViewTree
ensurePaneOpen ident =
  uncurry checkToggleResult . depthTraverseTree openPinnedSubs (ensurePaneViewOpen ident)

ensurePaneOpenTraversal ::
  Traversal a (ToggleResult a) ViewTree (ToggleResult ViewTree) ->
  Ident ->
  a ->
  ToggleResult a
ensurePaneOpenTraversal lens =
  over lens . ensurePaneOpen

ensurePaneOpenTraversal' ::
  Traversal' a ViewTree ->
  Ident ->
  a ->
  ToggleResult a
ensurePaneOpenTraversal' lens =
  mapMOf lens . ensurePaneOpen

skipFold ::
  Traversable t =>
  (a -> (ToggleStatus, a)) ->
  ToggleStatus ->
  t a ->
  (ToggleStatus, t a)
skipFold f =
  mapAccumL skipper
  where
    skipper Pristine a =
      f a
    skipper status a =
      (status, a)

isOpenPaneNode :: ViewTreeSub -> Bool
isOpenPaneNode =
  anyOf (#_TreeLeaf . #extra . #open) id

openPinnedPaneView :: PaneView -> (ToggleStatus, PaneView)
openPinnedPaneView (View i s g (Pane False True c)) =
  (Opened, View i s g (Pane True True c))
openPinnedPaneView v =
  (Pristine, v)

openFirstPinnedPaneNode :: ViewTreeSub -> (ToggleStatus, ViewTreeSub)
openFirstPinnedPaneNode (TreeLeaf v) =
  second TreeLeaf (openPinnedPaneView v)
openFirstPinnedPaneNode a =
  (Pristine, a)

openPaneView :: PaneView -> (ToggleStatus, PaneView)
openPaneView (View i s g (Pane False p c)) =
  (Opened, View i s g (Pane True p c))
openPaneView v =
  (Pristine, v)

openFirstPaneNode :: ViewTreeSub -> (ToggleStatus, ViewTreeSub)
openFirstPaneNode (TreeLeaf v) =
  second TreeLeaf (openPaneView v)
openFirstPaneNode a =
  (Pristine, a)

-- TODO recurse when opening pane
toggleLayoutNode :: Ident -> ToggleStatus -> ViewTree -> (ToggleStatus, ViewTree)
toggleLayoutNode ident previous (Tree v@(View i (ViewState minimized) g l) sub) | ident == i =
  first (previous <>) (if open then toggleMinimized else openPane')
  where
    open =
      any isOpenPaneNode sub
    toggleMinimized =
      (Minimized, Tree (View i (ViewState (not minimized)) g l) sub)
    openPane' =
      second (Tree v) (uncurry regularIfPristine openFirstPinned)
    openFirstPinned =
      skipFold openFirstPinnedPaneNode Pristine sub
    openFirstRegular =
      skipFold openFirstPaneNode Pristine sub
    regularIfPristine Pristine _ =
      openFirstRegular
    regularIfPristine status a =
      (status, a)
toggleLayoutNode _ a t =
  (a, t)

toggleLayout :: Ident -> ViewTree -> ToggleResult ViewTree
toggleLayout ident =
  uncurry checkToggleResult . depthTraverseTree (uncurry openPinnedSubs .: toggleLayoutNode ident) (Pristine,)

toggleLayoutOpenTraversal' ::
  Traversal' a ViewTree ->
  Ident ->
  a ->
  ToggleResult a
toggleLayoutOpenTraversal' lens =
  mapMOf lens . toggleLayout