tinytools-0.1.0.5: src/Potato/Flow/Controller/OwlLayers.hs
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
-- TODO move to Potato.Flow.Controller
module Potato.Flow.Controller.OwlLayers where
import Relude
import Potato.Flow.Controller.Types
import Potato.Flow.Types
import Potato.Flow.Serialization.Snake
import Potato.Flow.OwlItem
import Potato.Flow.Owl
import Potato.Flow.OwlState
import Potato.Flow.DebugHelpers
import Control.Lens (over, _2)
import Data.Foldable (foldl)
import Data.Default
import qualified Data.IntMap as IM
import qualified Data.IntSet as IS
import Data.Sequence ((><), (|>))
import qualified Data.Sequence as Seq
import qualified Data.Text as T
import qualified Text.Show
data LockHiddenState = LHS_True | LHS_False | LHS_True_InheritTrue | LHS_False_InheritTrue deriving (Eq, Show)
lockHiddenStateToBool :: LockHiddenState -> Bool
lockHiddenStateToBool = \case
LHS_False -> False
_ -> True
toggleLockHiddenState :: LockHiddenState -> LockHiddenState
toggleLockHiddenState = \case
LHS_True -> LHS_False
LHS_False -> LHS_True
LHS_True_InheritTrue -> LHS_False_InheritTrue
LHS_False_InheritTrue -> LHS_True_InheritTrue
setLockHiddenStateInChildren :: LockHiddenState -> Bool -> LockHiddenState
setLockHiddenStateInChildren parentstate = \case
False -> case parentstate of
LHS_False -> LHS_False
_ -> LHS_False_InheritTrue
True -> case parentstate of
LHS_False -> LHS_True
_ -> LHS_True_InheritTrue
-- ancestor state got set, update the child
updateLockHiddenStateInChildren :: LockHiddenState -> LockHiddenState -> LockHiddenState
updateLockHiddenStateInChildren parentstate = \case
LHS_False -> case parentstate of
LHS_True -> LHS_False_InheritTrue
LHS_False -> LHS_False
_ -> invalid
LHS_True -> case parentstate of
LHS_True -> LHS_True_InheritTrue
LHS_False -> LHS_True
_ -> invalid
LHS_True_InheritTrue -> case parentstate of
LHS_False -> LHS_True
LHS_True -> LHS_True_InheritTrue
_ -> invalid
LHS_False_InheritTrue -> case parentstate of
LHS_False -> LHS_False
LHS_True -> LHS_False_InheritTrue
_ -> invalid
where
invalid = error "toggling of LHS_XXX_InheritTrue elements disallowed"
-- TODO be careful with hidden cost of Eq SuperOwl
-- this stores info just for what is displayed, Seq LayerEntry is uniquely generated from LayerMetaMap and PFState
data LayerEntry = LayerEntry {
_layerEntry_lockState :: LockHiddenState
, _layerEntry_hideState :: LockHiddenState
, _layerEntry_isCollapsed :: Bool -- this parameter is ignored if not a folder, Maybe Bool instead?
, _layerEntry_superOwl :: SuperOwl
} deriving (Eq)
instance Show LayerEntry where
show LayerEntry {..} = "LayerEntry (lhc: "
<> show _layerEntry_lockState
<> "," <> show _layerEntry_hideState
<> "," <> show _layerEntry_isCollapsed
<> "):\n" <> (T.unpack $ potatoShow _layerEntry_superOwl)
layerEntry_depth :: LayerEntry -> Int
layerEntry_depth LayerEntry {..} = _owlItemMeta_depth . _superOwl_meta $ _layerEntry_superOwl
layerEntry_display :: LayerEntry -> Text
layerEntry_display LayerEntry {..} = hasOwlItem_name _layerEntry_superOwl
layerEntry_isFolder :: LayerEntry -> Bool
layerEntry_isFolder LayerEntry {..} = mommyOwl_hasKiddos _layerEntry_superOwl
layerEntry_rEltId :: LayerEntry -> REltId
layerEntry_rEltId LayerEntry {..} = _superOwl_id _layerEntry_superOwl
-- index type into Seq LayerEntry
type LayerEntryPos = Int
type LayerEntries = Seq LayerEntry
layerEntriesToPrettyText :: LayerEntries -> Text
layerEntriesToPrettyText lentries = foldr foldrfn "" lentries where
foldrfn le@LayerEntry {..} acc = r where
collapseText = if layerEntry_isFolder le
then if _layerEntry_isCollapsed
then ">"
else "v"
else " "
hideText = case _layerEntry_hideState of
LHS_True -> "▓"
LHS_False -> " "
LHS_True_InheritTrue -> "▓"
LHS_False_InheritTrue -> "▒"
lockText = case _layerEntry_lockState of
LHS_True -> "▓"
LHS_False -> " "
LHS_True_InheritTrue -> "▓"
LHS_False_InheritTrue -> "▒"
sowl = _layerEntry_superOwl
r = T.replicate (layerEntry_depth le) " " <> collapseText <> hideText <> lockText <> " " <> hasOwlItem_name sowl <> "\n" <> acc
data LayersState = LayersState {
-- mapping from REltId to element meta data
_layersState_meta :: LayerMetaMap
-- sequence of visible folders
, _layersState_entries :: LayerEntries
, _layersState_scrollPos :: Int
} deriving (Show, Eq)
instance PotatoShow LayersState where
potatoShow LayersState{..} = r where
r = "LayersState: "
<> show _layersState_meta
<> "\nLayerEntries:\n"
<> layerEntriesToPrettyText _layersState_entries
--instance Show LayersState where
-- show ls = T.unpack (potatoShow ls)
data LockHideCollapseOp = LHCO_ToggleLock | LHCO_ToggleHide | LHCO_ToggleCollapse deriving (Show)
alterWithDefault :: (Eq a, Default a) => (a -> a) -> REltId -> REltIdMap a -> REltIdMap a
alterWithDefault f k m = IM.alter f' k m where
apply x = if fx == def then Nothing else Just fx where
fx = f x
f' = \case
Nothing -> apply def
Just x -> apply x
lookupWithDefault :: (Default a) => REltId -> REltIdMap a -> a
lookupWithDefault rid ridm = case IM.lookup rid ridm of
Nothing -> def
Just x -> x
-- TODO test
-- | assumes LayersState is after hide state of given lepos has just been toggled
changesFromToggleHide :: OwlPFState -> LayersState -> LayerEntryPos -> SuperOwlChanges
changesFromToggleHide OwlPFState {..} LayersState {..} lepos = r where
le = Seq.index _layersState_entries lepos
sowl = _layerEntry_superOwl le
lerid = _superOwl_id sowl
lm = lookupWithDefault lerid _layersState_meta
isHidden = _layerMeta_isHidden lm
-- find all children that weren't already hidden
children = owliteratechildrenat _owlPFState_owlTree lerid
isunhidden sowl' = not . _layerMeta_isHidden $ lookupWithDefault (_superOwl_id sowl') _layersState_meta
unhiddenChildren = toList . fmap (\sowl' -> (_superOwl_id sowl', sowl')) $ Seq.filter isunhidden children
r = if isHidden
then IM.fromList $ (lerid, Nothing) : (fmap (over _2 (const Nothing)) unhiddenChildren)
else IM.fromList $ (lerid,Just sowl) : (fmap (over _2 Just) unhiddenChildren)
-- iterates over LayerEntryPos, skipping all children of entries where skipfn evaluates to true
doChildrenRecursive :: (LayerEntry -> Bool) -> (LayerEntry -> LayerEntry) -> Seq LayerEntry -> Seq LayerEntry
doChildrenRecursive skipfn entryfn = snd . mapAccumL mapaccumlfn maxBound where
mapaccumlfn skipdepth le = (newskipdepth, newle) where
depth = layerEntry_depth le
newskipdepth
-- skip, so keep skipping
| depth >= skipdepth = skipdepth
-- skip all children
-- note, no need to check for collapsed state because we are iterating over LayerEntry which do not include children of collapsed entries
| skipfn le = depth + 1
-- either we exited a skipped folder or aren't skipping, reset skip counter (since we skip subfolders of skipped entries, maximal skip stack depth is 1 so reset is OK)
| otherwise = maxBound
newle = if depth >= skipdepth
then le -- no changes to skipped elts
else entryfn le
toggleLayerEntry :: OwlPFState -> LayersState -> LayerEntryPos -> LockHideCollapseOp -> LayersState
toggleLayerEntry OwlPFState {..} LayersState {..} lepos op = r where
le = Seq.index _layersState_entries lepos
lerid = layerEntry_rEltId le
ledepth = layerEntry_depth le
childFrom nextLayerEntry = layerEntry_depth nextLayerEntry > ledepth
-- visible children of le
childles = Seq.takeWhileL childFrom . Seq.drop (lepos+1) $ _layersState_entries
-- everything before le
frontOfLe = Seq.take lepos _layersState_entries
-- everything after childles
backOfChildles = Seq.drop (lepos + 1 + Seq.length childles) _layersState_entries
-- simple helper function for setting lock/hidden state
togglefn fn setlmfn setlefn = (LayersState newlmm newlentries 0) where
newlhsstate = toggleLockHiddenState $ fn le
newlmm = alterWithDefault (\lm' -> setlmfn lm' (lockHiddenStateToBool newlhsstate)) lerid _layersState_meta
entryfn childle = setlefn childle $ updateLockHiddenStateInChildren newlhsstate (fn childle)
newchildles = doChildrenRecursive (lockHiddenStateToBool . fn) entryfn childles
newle = setlefn le newlhsstate
newlentries = (frontOfLe |> newle) >< newchildles >< backOfChildles
r = case op of
LHCO_ToggleCollapse -> (LayersState newlmm newlentries 0) where
newcollapse = not $ _layerEntry_isCollapsed le
newlmm = alterWithDefault (\le' -> le' { _layerMeta_isCollapsed = newcollapse }) lerid _layersState_meta
newle = le { _layerEntry_isCollapsed = newcollapse }
newchildles = buildLayerEntriesRecursive _owlPFState_owlTree _layersState_meta Seq.empty (Just newle)
newlentries = if newcollapse
then (frontOfLe |> newle) >< backOfChildles
else (frontOfLe |> newle) >< newchildles >< backOfChildles
LHCO_ToggleLock -> togglefn _layerEntry_lockState (\lm' x -> lm' { _layerMeta_isLocked = x }) (\le' x -> le' { _layerEntry_lockState = x })
LHCO_ToggleHide -> togglefn _layerEntry_hideState (\lm' x -> lm' { _layerMeta_isHidden = x }) (\le' x -> le' { _layerEntry_hideState = x })
expandAllCollapsedParents :: Selection -> OwlPFState -> LayersState -> LayersState
expandAllCollapsedParents selection pfs ls = r where
ops = owlParliamentSet_findParents (hasOwlTree_owlTree pfs) . superOwlParliament_toOwlParliamentSet $ selection
oldlmm = _layersState_meta ls
iscollapsedfilterfn rid = case IM.lookup rid oldlmm of
Just lm -> _layerMeta_isCollapsed lm
Nothing -> defaultFolderCollapseState
collapsedParents = IS.filter iscollapsedfilterfn ops
alterfn mlm = case mlm of
Nothing -> Just (def { _layerMeta_isCollapsed = False })
Just x -> Just (x { _layerMeta_isCollapsed = False })
newlmm = IS.foldr (IM.alter alterfn) oldlmm collapsedParents
r = if IS.null collapsedParents
then ls
-- can we do this more efficiently?
else (makeLayersStateFromOwlPFState pfs newlmm) { _layersState_scrollPos = _layersState_scrollPos ls }
makeLayersStateFromOwlPFState :: OwlPFState -> LayerMetaMap -> LayersState
makeLayersStateFromOwlPFState pfs lmm = LayersState {
_layersState_meta = lmm
, _layersState_entries = generateLayersNew (_owlPFState_owlTree pfs) lmm
, _layersState_scrollPos = 0
}
updateLayers :: OwlPFState -> SuperOwlChanges -> LayersState -> LayersState
updateLayers pfs changes LayersState {..} = r where
-- update _layersState_meta
(deletestuff, maybenewstuff) = IM.partition isNothing changes
maybenewstuffcollapsed = (fmap (const (def {_layerMeta_isCollapsed = True})) maybenewstuff)
newlmm = IM.difference (IM.union _layersState_meta maybenewstuffcollapsed) deletestuff
-- keep deleted elts so that folder state is preserved after undos/redos
--newlmm = IM.union _layersState_meta (fmap (const def) maybenewstuff)
-- TODO incremental rather than regenerate...
newlentries = generateLayersNew (_owlPFState_owlTree pfs) newlmm
r = LayersState newlmm newlentries _layersState_scrollPos
buildLayerEntriesRecursive :: OwlTree -> LayerMetaMap -> Seq LayerEntry -> Maybe LayerEntry -> Seq LayerEntry
buildLayerEntriesRecursive ot lmm acc mparent = r where
foldlfn acclentries rid = newacclentries where
sowl = owlTree_mustFindSuperOwl ot rid
lm = lookupWithDefault rid lmm
-- add self
lentry = case mparent of
Nothing -> LayerEntry {
_layerEntry_lockState = if _layerMeta_isLocked lm then LHS_True else LHS_False
, _layerEntry_hideState = if _layerMeta_isHidden lm then LHS_True else LHS_False
, _layerEntry_isCollapsed = _layerMeta_isCollapsed lm
, _layerEntry_superOwl = sowl
}
Just parent -> LayerEntry {
_layerEntry_lockState = setLockHiddenStateInChildren (_layerEntry_lockState parent) $ _layerMeta_isLocked lm
, _layerEntry_hideState = setLockHiddenStateInChildren (_layerEntry_hideState parent) $ _layerMeta_isHidden lm
, _layerEntry_isCollapsed = _layerMeta_isCollapsed lm
, _layerEntry_superOwl = sowl
}
newacclentries' = acclentries |> lentry
-- recursively add children
newacclentries = if _layerMeta_isCollapsed lm
then newacclentries'
else buildLayerEntriesRecursive ot lmm newacclentries' (Just lentry)
r = foldl foldlfn acc $ fromMaybe Seq.empty $ case mparent of
Nothing -> mommyOwl_kiddos ot
Just lentry -> mommyOwl_kiddos (_layerEntry_superOwl lentry)
generateLayersNew :: OwlTree -> LayerMetaMap -> Seq LayerEntry
generateLayersNew ot lmm = buildLayerEntriesRecursive ot lmm Seq.empty Nothing
layerMetaMap_isInheritHiddenOrLocked :: OwlTree -> REltId -> LayerMetaMap -> Bool
layerMetaMap_isInheritHiddenOrLocked ot rid lmm = case IM.lookup rid lmm of
-- these may both be false, but it may inherit from a parent where these are true therefore we still need to walk up the tree if these are both false
Just lm | _layerMeta_isLocked lm || _layerMeta_isHidden lm -> True
_ -> case IM.lookup rid (_owlTree_mapping ot) of
Nothing -> False
Just (oem,_) -> layerMetaMap_isInheritHiddenOrLocked ot (_owlItemMeta_parent oem) lmm
layerMetaMap_isInheritHidden :: OwlTree -> REltId -> LayerMetaMap -> Bool
layerMetaMap_isInheritHidden ot rid lmm = case IM.lookup rid lmm of
Just lm | _layerMeta_isHidden lm -> True
_ -> case IM.lookup rid (_owlTree_mapping ot) of
Nothing -> False
Just (oem,_) -> layerMetaMap_isInheritHidden ot (_owlItemMeta_parent oem) lmm