-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathStyle.hs
361 lines (299 loc) · 14 KB
/
Style.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeOperators #-}
-- The UndecidableInstances flag is needed under 6.12.3 for the
-- HasStyle (a,b) instance.
-----------------------------------------------------------------------------
-- |
-- Module : Diagrams.Core.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 Diagrams.Core.Style
( -- * Attributes
-- $attr
AttributeClass
, Attribute(..)
, _Attribute, _TAttribute, _GTAttribute
, mkAttr, mkTAttr, mkGTAttr, unwrapAttr, attr, attr'
, applyAttr, applyTAttr, applyGTAttr, applyAttr'
-- * Styles
-- $style
, Style(..)
, attrToStyle, tAttrToStyle, gtAttrToStyle, attrToStyle'
, getAttr, setAttr, addAttr, combineAttr
, gmapAttrs
, HasStyle(..)
) where
import Control.Applicative (Applicative(..), (<$>))
import Control.Arrow ((***))
import Control.Lens hiding (Action, transform)
import Data.Data
import Data.Data.Lens (template)
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Data.Semigroup
import qualified Data.Set as S
import Data.Typeable.Lens (_cast)
import Data.Monoid.Action
import Diagrams.Core.Transform
import Diagrams.Core.V
------------------------------------------------------------
-- 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 'Typeable' and 'Semigroup' constraints. The
-- 'Semigroup' instance for an attribute determines how it will combine
-- with other attributes of the same type.
class (Typeable a, Semigroup a) => AttributeClass a where
-- | An existential wrapper type to hold attributes. Some attributes
-- are simply inert/static; some are affected by transformations;
-- and some are affected by transformations and can be modified
-- generically.
data Attribute v :: * where
Attribute :: AttributeClass a => a -> Attribute v
TAttribute :: (AttributeClass a, Transformable a, V a ~ v) => a -> Attribute v
GTAttribute :: (AttributeClass a, Data a, Transformable a, V a ~ v) => a -> Attribute v
-- Note: one could imagine requiring all attributes to be generic,
-- but adding Data instances for everything would be a big pain in
-- the butt, especially for things in other packages which don't
-- export their constructors (e.g. FingerTree). Having three
-- different attribute wrappers is not ideal but it's far less work
-- than the alternative.
type instance V (Attribute v) = v
-- | Wrap up an attribute.
--
-- @ mkAttr = 'review' '_Attribute' @
mkAttr :: AttributeClass a => a -> Attribute v
mkAttr = Attribute
-- | Wrap up a transformable attribute.
--
-- @ mkTAttr = 'review' '_TAttribute' @
mkTAttr :: (AttributeClass a, Transformable a, V a ~ v) => a -> Attribute v
mkTAttr = TAttribute
-- | Wrap up a transformable and generic attribute.
--
-- @ mkGTAttr = 'review' '_GTAttribute' @
mkGTAttr :: (AttributeClass a, Data a, Transformable a, V a ~ v) => a -> Attribute v
mkGTAttr = GTAttribute
-- | (Proper) prisms for the three distinct types of attributes
_Attribute :: AttributeClass a => Prism' (Attribute v) a
_Attribute = prism' Attribute $ \case Attribute a -> cast a; _ -> Nothing
_TAttribute :: (AttributeClass a, Transformable a, V a ~ v) => Prism' (Attribute v) a
_TAttribute = prism' TAttribute $ \case TAttribute a -> cast a; _ -> Nothing
_GTAttribute :: (AttributeClass a, Data a, Transformable a, V a ~ v) => Prism' (Attribute v) a
_GTAttribute = prism' GTAttribute $ \case GTAttribute a -> cast a; _ -> Nothing
-- | Traverse over an 'Attribute', if the types match.
attribute :: forall f a v. (AttributeClass a, Applicative f) => (a -> f a) -> Attribute v -> f (Attribute v)
attribute f a = case a of
Attribute v -> Attribute <$> f' v
TAttribute v -> TAttribute <$> f' v
GTAttribute v -> GTAttribute <$> f' v
where f' :: forall b. Typeable b => b -> f b
f' v = case eqT :: Maybe (a :~: b) of
Nothing -> pure v
Just Refl -> f v
-- | 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 v -> Maybe a
unwrapAttr (Attribute a) = cast a
unwrapAttr (TAttribute a) = cast a
unwrapAttr (GTAttribute a) = cast a
-- | Attributes form a semigroup, where the semigroup operation simply
-- returns the right-hand attribute when the types do not match, and
-- otherwise uses the semigroup operation specific to the (matching)
-- types.
instance Semigroup (Attribute v) where
(Attribute a1) <> a2 =
case unwrapAttr a2 of
Nothing -> a2
Just a2' -> Attribute (a1 <> a2')
(TAttribute a1) <> a2 =
case unwrapAttr a2 of
Nothing -> a2
Just a2' -> TAttribute (a1 <> a2')
(GTAttribute a1) <> a2 =
case unwrapAttr a2 of
Nothing -> a2
Just a2' -> GTAttribute (a1 <> a2')
instance HasLinearMap v => Transformable (Attribute v) where
transform _ (Attribute a) = Attribute a
transform t (TAttribute a) = TAttribute (transform t a)
transform t (GTAttribute a) = GTAttribute (transform t 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 v = Style (M.Map String (Attribute v))
-- The String keys are serialized TypeRep values, corresponding to
-- the type of the stored attribute.
instance Wrapped (Style v) where
type Unwrapped (Style v) = M.Map String (Attribute v)
_Wrapped' = iso (\(Style m) -> m) Style
instance Rewrapped (Style v) (Style v')
type instance V (Style v) = v
-- | Helper function for operating on styles.
inStyle :: (M.Map String (Attribute v) -> M.Map String (Attribute v))
-> Style v -> Style v
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 v. AttributeClass a => Style v -> 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".
-- | Given a prism to match a certain attribute, modify it in a style.
attr :: forall a v. AttributeClass a => APrism' (Attribute v) a -> Lens' (Style v) (Maybe a)
attr p = _Wrapped'.at ty.l
where ty = show . typeOf $ (undefined :: a)
-- View through the prism to get the value out of the attribute
l f Nothing = go <$> f Nothing
l f (Just a) = go <$> f (preview (clonePrism p) a)
-- Re-add the new value, if it exists, through this prism
go Nothing = Nothing
go (Just a) = Just $ review (clonePrism p) a
-- | Traverse over a given attribute, if present.
attr' :: forall a v. AttributeClass a => Traversal' (Style v) a
attr' = _Wrapped'.ix ty.attribute
where ty = show . typeOf $ (undefined :: a)
-- | Given a prism to form an attribute, create a style
attrToStyle' :: AttributeClass a => AReview' (Attribute v) a -> a -> Style v
attrToStyle' p a = Style $ M.singleton (show $ typeOf a) (review p a)
-- | Create a style from a single attribute.
--
-- @ attrToStyle = 'attrToStyle'' '_Attribute' @
attrToStyle :: forall a v. AttributeClass a => a -> Style v
attrToStyle a = Style (M.singleton (show . typeOf $ (undefined :: a)) (mkAttr a))
-- | Create a style from a single transformable attribute.
--
-- @ tAttrToStyle = 'attrToStyle'' '_TAttribute' @
tAttrToStyle :: forall a v. (AttributeClass a, Transformable a, V a ~ v) => a -> Style v
tAttrToStyle a = Style (M.singleton (show . typeOf $ (undefined :: a)) (mkTAttr a))
-- | Create a style from a single transformable, generic attribute.
--
-- @ gtAttrToStyle = 'attrToStyle'' '_GTAttribute' @
gtAttrToStyle :: forall a v. (AttributeClass a, Data a, Transformable a, V a ~ v) => a -> Style v
gtAttrToStyle a = Style (M.singleton (show . typeOf $ (undefined :: a)) (mkGTAttr a))
-- | Add a new attribute to a style, or replace the old attribute of
-- the same type if one exists.
setAttr :: forall a v. AttributeClass a => a -> Style v -> Style v
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 v -> Style v
addAttr a s = attrToStyle a <> s
-- | Add a new attribute to a style that does not already contain an
-- attribute of this type, or combine it on the left with an existing
-- attribute.
combineAttr :: AttributeClass a => a -> Style v -> Style v
combineAttr a s =
case getAttr s of
Nothing -> setAttr a s
Just a' -> setAttr (a <> a') s
-- | Map generically over all generic attributes in a style, applying
-- the given function to any values with the given type, even deeply
-- nested ones. Note that only attributes wrapped in 'GTAttribute'
-- are affected.
gmapAttrs :: forall v a. Typeable a => (a -> a) -> Style v -> Style v
gmapAttrs f = (inStyle . M.map) gmapAttr
where
gmapAttr :: Attribute v -> Attribute v
gmapAttr (GTAttribute a) = GTAttribute (a & template %~ f)
gmapAttr a = a
instance Semigroup (Style v) where
Style s1 <> Style s2 = Style $ M.unionWith (<>) s1 s2
-- | The empty style contains no attributes; composition of styles is
-- a union of attributes; if the two styles have attributes of the
-- same type they are combined according to their semigroup
-- structure.
instance Monoid (Style v) where
mempty = Style M.empty
mappend = (<>)
instance HasLinearMap v => Transformable (Style v) where
transform t = inStyle $ M.map (transform t)
instance (v ~ v', HasLinearMap v) => Action (Transformation v) (Style v') where
act = transform
-- | Styles have no action on other monoids.
instance Action (Style v) 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 (V a) -> a -> a
instance HasStyle (Style v) where
applyStyle = mappend
instance (HasStyle a, HasStyle b, V a ~ V b) => HasStyle (a,b) where
applyStyle s = applyStyle s *** applyStyle s
instance HasStyle a => HasStyle [a] where
applyStyle = fmap . applyStyle
instance HasStyle b => HasStyle (a -> b) where
applyStyle = fmap . applyStyle
instance HasStyle a => HasStyle (M.Map k a) where
applyStyle = fmap . applyStyle
instance (HasStyle a, Ord a) => HasStyle (S.Set a) where
applyStyle = S.map . applyStyle
-- | Apply an attribute to an instance of 'HasStyle' (such as a
-- diagram or a style). If the object already has an attribute of
-- the same type, the new attribute is combined on the left with the
-- existing attribute, according to their semigroup structure.
--
-- @ applyAttr = 'applyAttr'' '_Attribute' @
applyAttr :: (AttributeClass a, HasStyle d) => a -> d -> d
applyAttr = applyStyle . attrToStyle
-- | Apply a transformable attribute to an instance of 'HasStyle'
-- (such as a diagram or a style). If the object already has an
-- attribute of the same type, the new attribute is combined on the
-- left with the existing attribute, according to their semigroup
-- structure.
--
-- @ applyTAttr = 'applyAttr'' '_TAttribute' @
applyTAttr :: (AttributeClass a, Transformable a, V a ~ V d, HasStyle d) => a -> d -> d
applyTAttr = applyStyle . tAttrToStyle
-- | @ applyGTAttr = 'applyAttr'' '_GTAttribute' @
applyGTAttr :: (AttributeClass a, Data a, Transformable a, V a ~ V d, HasStyle d) => a -> d -> d
applyGTAttr = applyStyle . gtAttrToStyle
-- | Given a prism to construct an attribute, apply it to some object.
--
-- @ applyAttr' p = 'applyStyle' . 'attrToStyle'' p @
applyAttr' :: (AttributeClass a, HasStyle d) => AReview' (Attribute (V d)) a -> a -> d -> d
applyAttr' p = applyStyle . attrToStyle' p