{-# 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. . -- | 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