forked from fjvallarino/monomer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBoxShadow.hs
187 lines (157 loc) · 5.38 KB
/
BoxShadow.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
{-|
Module : Monomer.Widgets.Containers.BoxShadow
Copyright : (c) 2022 Gareth Smith, Francisco Vallarino
License : BSD-3-Clause (see the LICENSE file)
Maintainer : [email protected]
Stability : experimental
Portability : non-portable
A rectangular drop-shadow. Normally used around alert boxes to give the illusion
they are floating above the widgets underneath them.
-}
{-# LANGUAGE Strict #-}
module Monomer.Widgets.Containers.BoxShadow (
-- * Configuration
BoxShadowCfg,
-- * Constructors
boxShadow,
boxShadow_
) where
import Control.Applicative ((<|>))
import Control.Lens ((&), (.~), (^.))
import Data.Default
import Data.Maybe
import qualified Data.Sequence as Seq
import Monomer.Core
import Monomer.Core.Combinators
import Monomer.Widgets.Container
import qualified Monomer.Lens as L
{-|
Configuration options for boxShadow:
- 'radius': the radius of the corners of the shadow.
- 'alignLeft': aligns the shadow to the left.
- 'alignCenter': aligns the shadow to the horizontal center.
- 'alignRight': aligns the shadow to the right.
- 'alignTop': aligns the shadow to the top.
- 'alignMiddle': aligns the shadow to the vertical middle.
- 'alignBottom': aligns the shadow to the bottom.
-}
data BoxShadowCfg = BoxShadowCfg {
_bscRadius :: Maybe Double,
_bscAlignH :: Maybe AlignH,
_bscAlignV :: Maybe AlignV
}
instance Default BoxShadowCfg where
def = BoxShadowCfg {
_bscRadius = Nothing,
_bscAlignH = Nothing,
_bscAlignV = Nothing
}
instance Semigroup BoxShadowCfg where
(<>) c1 c2 = BoxShadowCfg {
_bscRadius = _bscRadius c2 <|> _bscRadius c1,
_bscAlignH = _bscAlignH c2 <|> _bscAlignH c1,
_bscAlignV = _bscAlignV c2 <|> _bscAlignV c1
}
instance Monoid BoxShadowCfg where
mempty = def
instance CmbRadius BoxShadowCfg where
radius r = def {
_bscRadius = Just r
}
instance CmbAlignLeft BoxShadowCfg where
alignLeft_ False = def
alignLeft_ True = def {
_bscAlignH = Just ALeft
}
instance CmbAlignCenter BoxShadowCfg where
alignCenter_ False = def
alignCenter_ True = def {
_bscAlignH = Just ACenter
}
instance CmbAlignRight BoxShadowCfg where
alignRight_ False = def
alignRight_ True = def {
_bscAlignH = Just ARight
}
instance CmbAlignTop BoxShadowCfg where
alignTop_ False = def
alignTop_ True = def {
_bscAlignV = Just ATop
}
instance CmbAlignMiddle BoxShadowCfg where
alignMiddle_ False = def
alignMiddle_ True = def {
_bscAlignV = Just AMiddle
}
instance CmbAlignBottom BoxShadowCfg where
alignBottom_ False = def
alignBottom_ True = def {
_bscAlignV = Just ABottom
}
-- | Creates a boxShadow around the provided content.
boxShadow
:: WidgetNode s e -- ^ The content to display inside the boxShadow.
-> WidgetNode s e -- ^ The created boxShadow.
boxShadow = boxShadow_ def
-- | Creates a boxShadow around the provided content. Accepts config.
boxShadow_
:: [BoxShadowCfg] -- ^ The config options for the boxShadow.
-> WidgetNode s e -- ^ The content to display inside the boxShadow.
-> WidgetNode s e -- ^ The created boxShadow.
boxShadow_ config child =
defaultWidgetNode "boxShadow" (boxShadowWidget (mconcat config))
& L.children .~ Seq.singleton child
boxShadowWidget :: BoxShadowCfg -> Widget s e
boxShadowWidget config = widget where
widget = createContainer () def {
containerGetSizeReq = getSizeReq,
containerResize = resize,
containerRender = render
}
shadowRadius = fromMaybe 8 (_bscRadius config)
shadowDiameter = shadowRadius * 2
getSizeReq wenv node children = (sizeReqW, sizeReqH) where
sizeReqW = maybe (fixedSize 0) (addFixed shadowDiameter . _wniSizeReqW . _wnInfo) vchild
sizeReqH = maybe (fixedSize 0) (addFixed shadowDiameter. _wniSizeReqH . _wnInfo) vchild
vchildren = Seq.filter (_wniVisible . _wnInfo) children
vchild = Seq.lookup 0 vchildren
resize wenv node viewport children = (resultNode node, fmap assignArea children) where
style = currentStyle wenv node
contentArea = fromMaybe def (removeOuterBounds style viewport)
assignArea child
| visible = moveRect childOffset (subtractShadow contentArea)
| otherwise = def
where
visible = (_wniVisible . _wnInfo) child
childOffset = Point offsetX offsetY where
theme = currentTheme wenv node
shadowAlignH = fromMaybe (theme ^. L.shadowAlignH) (_bscAlignH config)
shadowAlignV = fromMaybe (theme ^. L.shadowAlignV) (_bscAlignV config)
offset = shadowRadius / 4
offsetX = case shadowAlignH of
ALeft -> offset
ACenter -> 0
ARight -> -offset
offsetY = case shadowAlignV of
ATop -> offset
AMiddle -> 0
ABottom -> -offset
render wenv node renderer = do
beginPath renderer
setFillBoxGradient renderer (subtractShadow vp) shadowRadius shadowDiameter shadowColor transparent
renderRect renderer vp
fill renderer
where
style = currentStyle wenv node
vp = getContentArea node style
shadowColor = wenv ^. L.theme . L.basic . L.shadowColor
transparent = rgba 0 0 0 0
subtractShadow (Rect l t w h) = Rect l' t' w' h' where
(l', w') = subtractDim l w
(t', h') = subtractDim t h
subtractDim pos size
| size > shadowDiameter = (pos + shadowRadius, size - shadowDiameter)
| otherwise = (pos + size / 2, 0)
addFixed :: Double -> SizeReq -> SizeReq
addFixed f sReq =
sReq { _szrFixed = _szrFixed sReq + f }