This repository has been archived by the owner on Aug 23, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Element.elm
394 lines (304 loc) · 9.91 KB
/
Element.elm
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
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
module Graphics.Element where
{-| Graphical elements that snap together to build complex widgets and layouts.
Each Element is a rectangle with a known width and height, making them easy to
combine and position.
# Images
@docs image, fittedImage, croppedImage, tiledImage
# Styling
@docs width, height, size, color, opacity, link, tag
# Inspection
@docs widthOf, heightOf, sizeOf
# Layout
@docs flow, up, down, left, right, inward, outward
## Layout Aliases
There are also some convenience functions for working
with `flow` in specific cases:
@docs layers, above, below, beside
# Positioning
@docs empty, spacer, container
## Specific Positions
To create a `Position` you can use any of the built-in positions
which cover nine common positions.
@docs middle, midTop, midBottom, midLeft, midRight, topLeft, topRight,
bottomLeft, bottomRight
If you need more precision, you can create custom positions.
@docs absolute, relative, middleAt, midTopAt, midBottomAt, midLeftAt,
midRightAt, topLeftAt, topRightAt, bottomLeftAt, bottomRightAt
-}
import Basics (..)
import Native.Graphics.Element
import List as List
import Color (..)
import Maybe ( Maybe(..) )
type alias Properties = {
id : Int,
width : Int,
height : Int,
opacity : Float,
color : Maybe Color,
href : String,
tag : String,
hover : (),
click : ()
}
type alias Element =
{ props : Properties
, element : ElementPrim
}
{-| An Element that takes up no space. Good for things that appear conditionally:
flow down [ img1, if showMore then img2 else empty ]
-}
empty : Element
empty = spacer 0 0
{-| Get the width of an Element -}
widthOf : Element -> Int
widthOf e =
e.props.width
{-| Get the height of an Element -}
heightOf : Element -> Int
heightOf e =
e.props.height
{-| Get the width and height of an Element -}
sizeOf : Element -> (Int,Int)
sizeOf e =
(e.props.width, e.props.height)
{-| Create an `Element` with a given width. -}
width : Int -> Element -> Element
width nw e =
let p = e.props
props =
case e.element of
Image _ w h _ ->
{ p |
height <- round (toFloat h / toFloat w * toFloat nw)
}
RawHtml ->
{ p |
height <- snd (Native.Graphics.Element.htmlHeight nw e.element)
}
_ -> p
in
{ element = e.element
, props = { props | width <- nw }
}
{-| Create an `Element` with a given height. -}
height : Int -> Element -> Element
height nh e =
let p = e.props
props =
case e.element of
Image _ w h _ ->
{ p |
width <- round (toFloat w / toFloat h * toFloat nh)
}
_ -> p
in
{ element = e.element
, props = { p | height <- nh }
}
{-| Create an `Element` with a new width and height. -}
size : Int -> Int -> Element -> Element
size w h e =
height h (width w e)
{-| Create an `Element` with a given opacity. Opacity is a number between 0 and 1
where 0 means totally clear.
-}
opacity : Float -> Element -> Element
opacity o e =
let p = e.props
in
{ element = e.element
, props = { p | opacity <- o }
}
{-| Create an `Element` with a given background color. -}
color : Color -> Element -> Element
color c e = let p = e.props in
{ element = e.element
, props = { p | color <- Just c}
}
{-| Create an `Element` with a tag. This lets you link directly to it.
The element `(tag "all-about-badgers" thirdParagraph)` can be reached
with a link like this: `/facts-about-animals.elm#all-about-badgers`
-}
tag : String -> Element -> Element
tag name e =
let p = e.props
in
{ element = e.element
, props = { p | tag <- name }
}
{-| Create an `Element` that is a hyper-link. -}
link : String -> Element -> Element
link href e =
let p = e.props
in
{ element = e.element
, props = { p | href <- href }
}
newElement w h e =
{ props = Properties (Native.Graphics.Element.guid ()) w h 1 Nothing "" "" () ()
, element = e
}
type ElementPrim
= Image ImageStyle Int Int String
| Container Position Element
| Flow Direction (List Element)
| Spacer
| RawHtml
| Custom -- for custom Elements implemented in JS, see collage for example
type ImageStyle = Plain | Fitted | Cropped (Int,Int) | Tiled
{-| Create an image given a width, height, and image source. -}
image : Int -> Int -> String -> Element
image w h src =
newElement w h (Image Plain w h src)
{-| Create a fitted image given a width, height, and image source.
This will crop the picture to best fill the given dimensions.
-}
fittedImage : Int -> Int -> String -> Element
fittedImage w h src =
newElement w h (Image Fitted w h src)
{-| Create a cropped image. Take a rectangle out of the picture starting
at the given top left coordinate. If you have a 140-by-140 image,
the following will cut a 100-by-100 square out of the middle of it.
croppedImage (20,20) 100 100 "yogi.jpg"
-}
croppedImage : (Int,Int) -> Int -> Int -> String -> Element
croppedImage pos w h src =
newElement w h (Image (Cropped pos) w h src)
tiledImage : Int -> Int -> String -> Element
tiledImage w h src =
newElement w h (Image Tiled w h src)
type Three = P | Z | N
type Pos = Absolute Int | Relative Float
type alias Position =
{ horizontal : Three
, vertical : Three
, x : Pos
, y : Pos
}
{-| Put an element in a container. This lets you position the element really
easily, and there are tons of ways to set the `Position`.
To center `element` exactly in a 300-by-300 square you would say:
container 300 300 middle element
By setting the color of the container, you can create borders.
-}
container : Int -> Int -> Position -> Element -> Element
container w h pos e =
newElement w h (Container pos e)
{-| Create an empty box. This is useful for getting your spacing right and
for making borders.
-}
spacer : Int -> Int -> Element
spacer w h =
newElement w h Spacer
type Direction = DUp | DDown | DLeft | DRight | DIn | DOut
{-| Have a list of elements flow in a particular direction.
The `Direction` starts from the first element in the list.
flow right [a,b,c]
+---+---+---+
| a | b | c |
+---+---+---+
-}
flow : Direction -> List Element -> Element
flow dir es =
let ws = List.map widthOf es
hs = List.map heightOf es
newFlow w h = newElement w h (Flow dir es)
in
if es == [] then empty else
case dir of
DUp -> newFlow (List.maximum ws) (List.sum hs)
DDown -> newFlow (List.maximum ws) (List.sum hs)
DLeft -> newFlow (List.sum ws) (List.maximum hs)
DRight -> newFlow (List.sum ws) (List.maximum hs)
DIn -> newFlow (List.maximum ws) (List.maximum hs)
DOut -> newFlow (List.maximum ws) (List.maximum hs)
{-| Stack elements vertically.
To put `a` above `b` you would say: ``a `above` b``
-}
above : Element -> Element -> Element
above hi lo =
newElement
(max (widthOf hi) (widthOf lo))
(heightOf hi + heightOf lo)
(Flow DDown [hi,lo])
{-| Stack elements vertically.
To put `a` below `b` you would say: ``a `below` b``
-}
below : Element -> Element -> Element
below lo hi =
newElement
(max (widthOf hi) (widthOf lo))
(heightOf hi + heightOf lo)
(Flow DDown [hi,lo])
{-| Put elements beside each other horizontally.
To put `a` beside `b` you would say: ``a `beside` b``
-}
beside : Element -> Element -> Element
beside lft rht =
newElement
(widthOf lft + widthOf rht)
(max (heightOf lft) (heightOf rht))
(Flow right [lft,rht])
{-| Layer elements on top of each other, starting from the bottom:
`layers == flow outward`
-}
layers : List Element -> Element
layers es =
let ws = List.map widthOf es
hs = List.map heightOf es
in
newElement (List.maximum ws) (List.maximum hs) (Flow DOut es)
-- Repetitive things --
absolute : Int -> Pos
absolute = Absolute
relative : Float -> Pos
relative = Relative
middle : Position
middle = { horizontal=Z, vertical=Z, x=Relative 0.5, y=Relative 0.5 }
topLeft : Position
topLeft = { horizontal=N, vertical=P, x=Absolute 0, y=Absolute 0 }
topRight : Position
topRight = { topLeft | horizontal <- P }
bottomLeft : Position
bottomLeft = { topLeft | vertical <- N }
bottomRight : Position
bottomRight = { bottomLeft | horizontal <- P }
midLeft : Position
midLeft = { middle | horizontal <- N, x <- Absolute 0 }
midRight : Position
midRight = { midLeft | horizontal <- P }
midTop : Position
midTop = { middle | vertical <- P, y <- Absolute 0 }
midBottom : Position
midBottom = { midTop | vertical <- N }
middleAt : Pos -> Pos -> Position
middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y }
topLeftAt : Pos -> Pos -> Position
topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y }
topRightAt : Pos -> Pos -> Position
topRightAt x y = { horizontal = P, vertical = P, x = x, y = y }
bottomLeftAt : Pos -> Pos -> Position
bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y }
bottomRightAt : Pos -> Pos -> Position
bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y }
midLeftAt : Pos -> Pos -> Position
midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y }
midRightAt : Pos -> Pos -> Position
midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y }
midTopAt : Pos -> Pos -> Position
midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y }
midBottomAt : Pos -> Pos -> Position
midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y }
up : Direction
up = DUp
down : Direction
down = DDown
left : Direction
left = DLeft
right : Direction
right = DRight
inward : Direction
inward = DIn
outward : Direction
outward = DOut