packages feed

brush-stroking-0.1.0.0: src/BrushStroking/Layer.hs

module BrushStroking.Layer where

-- base
import Data.Word
  ( Word32 )
import GHC.Generics
  ( Generically(..), Generic )
import GHC.Stack

-- containers
import Data.Map.Strict
  ( Map )
import qualified Data.Map.Strict as Map
import Data.Set
  ( Set )

-- deepseq
import Control.DeepSeq
  ( NFData(..) )

-- text
import Data.Text
  ( Text )

-- MetaBrush
import Debug.Utils
  ( trace )
import BrushStroking.Unique
  ( Unique )

--------------------------------------------------------------------------------

-- | A layer: either a stroke or a group.
data Layer
  = StrokeLayer { layerUnique :: !Unique }
  | GroupLayer  { layerUnique :: !Unique }
  deriving stock ( Show, Eq, Generic )
  deriving anyclass NFData

-- | Metadata about layers, e.g. their names and their visibilities.
data LayerMetadata =
  LayerMetadata
    { layerNames       :: !( Map Unique Text )
    , invisibleLayers  :: !( Set Unique )
    , lockedLayers     :: !( Set Unique )
    }
  deriving stock    ( Show, Generic )
  deriving anyclass NFData
  deriving ( Semigroup, Monoid )
    via Generically LayerMetadata

-- | A parent of a layer.
data Parent a
  -- | The layer is at the top level.
  = Root
  -- | The layer has this parent.
  | Parent !a
  deriving stock ( Show, Eq, Ord, Functor, Generic )
  deriving anyclass NFData

-- | An item within a parent.
data WithinParent a =
  WithinParent
    { parent :: !( Parent Unique )
    , item   :: !a
    }
  deriving stock ( Show, Eq, Generic, Functor )
  deriving anyclass NFData

-- | A child layer within a parent.
type ChildLayer         = WithinParent Unique
-- | A child layer, with its index among the list of children of its parent.
type ChildLayerPosition = WithinParent Word32

-- | Where to position something relative to another layer.
data RelativePosition
  = Above
  | Below
  deriving stock ( Eq, Ord, Show )

-- | Content in a hierarchical tree-like structure.
data Hierarchy a =
  Hierarchy
    { topLevel :: ![ Unique ]
    , groups   :: !( Map Unique [ Unique ] )
    , content  :: !( Map Unique a )
    }
  deriving stock ( Show, Eq, Functor, Generic )
  deriving anyclass NFData

emptyHierarchy :: Hierarchy a
emptyHierarchy =
  Hierarchy
    { topLevel = []
    , groups   = Map.empty
    , content  = Map.empty
    }

lookupChildren :: HasCallStack => Parent Unique -> Hierarchy a -> [ Unique ]
lookupChildren p h = case lookupChildren_maybe p h of
  Nothing ->
    trace ( unlines [ "internal error in 'lookupChildren'"
                    , "no data for parent " ++ show p
                    , ""
                    , "call stack: " ++ prettyCallStack callStack ]
                    ) []
  Just cs -> cs

lookupChildren_maybe :: Parent Unique -> Hierarchy a -> Maybe [ Unique ]
lookupChildren_maybe Root ( Hierarchy { topLevel } ) = Just topLevel
lookupChildren_maybe ( Parent u ) ( Hierarchy { groups } ) = groups Map.!? u

insertGroup :: Parent Unique -> [ Unique ] -> Hierarchy a -> Hierarchy a
insertGroup Root us h = h { topLevel = us }
insertGroup ( Parent u ) us h = h { groups = Map.insert u us ( groups h ) }

-- | Delete the key of a layer in a 'Hierarchy'.
--
-- Does not remove it from any child lists, just from the "keys" of the maps.
deleteLayerKey :: Unique -> Hierarchy a -> ( Hierarchy a, Maybe [ Unique ] )
deleteLayerKey u ( Hierarchy tl gs cs ) =
  case Map.updateLookupWithKey ( \ _ _ -> Nothing ) u gs of
    ( mbChildren, gs' ) ->
      let cs' = Map.delete u cs
      in ( Hierarchy tl gs' cs', mbChildren )