diagrams-core-0.1: src/Graphics/Rendering/Diagrams/Style.hs
{-# LANGUAGE ScopedTypeVariables
, GADTs
, KindSignatures
, FlexibleInstances
, MultiParamTypeClasses
#-}
-----------------------------------------------------------------------------
-- |
-- Module : Graphics.Rendering.Diagrams.Style
-- Copyright : (c) 2011 diagrams-core team (see LICENSE)
-- License : BSD-style (see LICENSE)
-- Maintainer : diagrams-discuss@googlegroups.com
--
-- A definition of /styles/ for diagrams as extensible, heterogeneous
-- collections of attributes.
--
-----------------------------------------------------------------------------
module Graphics.Rendering.Diagrams.Style
( -- * Attributes
-- $attr
AttributeClass
, Attribute(..)
, mkAttr, unwrapAttr
, applyAttr
-- * Styles
-- $style
, Style(..)
, attrToStyle
, getAttr, setAttr, addAttr
, HasStyle(..)
) where
import Graphics.Rendering.Diagrams.Monoids
import Graphics.Rendering.Diagrams.Util
import Data.Typeable
import Data.Monoid
import qualified Data.Map as M
------------------------------------------------------------
-- Attributes --------------------------------------------
------------------------------------------------------------
-- $attr
-- An /attribute/ is anything that determines some aspect of a
-- diagram's rendering. The standard diagrams library defines several
-- standard attributes (line color, line width, fill color, etc.) but
-- additional attributes may easily be created. Additionally, a given
-- backend need not handle (or even know about) attributes used in
-- diagrams it renders.
--
-- The attribute code is inspired by xmonad's @Message@ type, which
-- was in turn based on ideas in:
--
-- Simon Marlow.
-- /An Extensible Dynamically-Typed Hierarchy of Exceptions/.
-- Proceedings of the 2006 ACM SIGPLAN workshop on
-- Haskell. <http://research.microsoft.com/apps/pubs/default.aspx?id=67968>.
-- | Every attribute must be an instance of @AttributeClass@, which
-- simply guarantees a 'Typeable' constraint.
class Typeable a => AttributeClass a where
-- | An existential wrapper type to hold attributes.
data Attribute :: * where
Attribute :: AttributeClass a => a -> Attribute
-- | Wrap up an attribute.
mkAttr :: AttributeClass a => a -> Attribute
mkAttr = Attribute
-- | Unwrap an unknown 'Attribute' type, performing a dynamic (but
-- safe) check on the type of the result. If the required type
-- matches the type of the attribute, the attribute value is
-- returned wrapped in @Just@; if the types do not match, @Nothing@
-- is returned.
unwrapAttr :: AttributeClass a => Attribute -> Maybe a
unwrapAttr (Attribute a) = cast a
------------------------------------------------------------
-- Styles ------------------------------------------------
------------------------------------------------------------
-- $style
-- A 'Style' is a heterogeneous collection of attributes, containing
-- at most one attribute of any given type. This is also based on
-- ideas stolen from xmonad, specifically xmonad's implementation of
-- user-extensible state.
-- | A @Style@ is a heterogeneous collection of attributes, containing
-- at most one attribute of any given type.
newtype Style = Style (M.Map String Attribute)
-- The String keys are serialized TypeRep values, corresponding to
-- the type of the stored attribute.
-- | Helper function for operating on styles.
inStyle :: (M.Map String Attribute -> M.Map String Attribute)
-> Style -> Style
inStyle f (Style s) = Style (f s)
-- | Extract an attribute from a style of a particular type. If the
-- style contains an attribute of the requested type, it will be
-- returned wrapped in @Just@; otherwise, @Nothing@ is returned.
getAttr :: forall a. AttributeClass a => Style -> Maybe a
getAttr (Style s) = M.lookup ty s >>= unwrapAttr
where ty = (show . typeOf $ (undefined :: a))
-- the unwrapAttr should never fail, since we maintain the invariant
-- that attributes of type T are always stored with the key "T".
-- | Create a style from a single attribute.
attrToStyle :: forall a. AttributeClass a => a -> Style
attrToStyle a = Style (M.singleton (show . typeOf $ (undefined :: a)) (mkAttr a))
-- | Add a new attribute to a style, or replace the old attribute of
-- the same type if one exists.
setAttr :: forall a. AttributeClass a => a -> Style -> Style
setAttr a = inStyle $ M.insert (show . typeOf $ (undefined :: a)) (mkAttr a)
-- | Attempt to add a new attribute to a style, but if an attribute of
-- the same type already exists, do not replace it.
addAttr :: AttributeClass a => a -> Style -> Style
addAttr a s = attrToStyle a <> s
-- | The empty style contains no attributes; composition of styles is
-- right-biased union; i.e. if the two styles contain attributes of
-- the same type, the one from the right is taken.
instance Monoid Style where
mempty = Style M.empty
(Style s1) `mappend` (Style s2) = Style $ s2 `M.union` s1
-- | Styles have no action on other monoids.
instance Action Style m
-- | Type class for things which have a style.
class HasStyle a where
-- | /Apply/ a style by combining it (on the left) with the
-- existing style.
applyStyle :: Style -> a -> a
instance HasStyle Style where
applyStyle = mappend
-- | Apply an attribute to an instance of 'HasStyle' (such as a
-- diagram or a style). @applyAttr@ has no effect if an attribute of
-- the same type already exists.
applyAttr :: (AttributeClass a, HasStyle d) => a -> d -> d
applyAttr = applyStyle . attrToStyle