on 7c670bb help sign in
elk/libraries/Graphics/Element.elm
320 lines 9.7 kB history raw
1module Graphics.Element where
2{-| Graphical elements that snap together to build complex widgets and layouts.
3Each Element is a rectangle with a known width and height, making them easy to
4combine and position.
6# Images
7@docs image, fittedImage, croppedImage, tiledImage
9# Styling
10@docs width, height, size, color, opacity, link, tag
12# Inspection
13@docs widthOf, heightOf, sizeOf
15# Layout
16@docs flow, up, down, left, right, inward, outward
18## Layout Aliases
19There are also some convenience functions for working
20with `flow` in specific cases:
22@docs layers, above, below, beside
24# Positioning
25@docs empty, spacer, container
27## Specific Positions
29To create a `Position` you can use any of the built-in positions
30which cover nine common positions.
31@docs middle, midTop, midBottom, midLeft, midRight, topLeft, topRight,
32 bottomLeft, bottomRight
34If you need more precision, you can create custom positions.
36@docs absolute, relative, middleAt, midTopAt, midBottomAt, midLeftAt,
37 midRightAt, topLeftAt, topRightAt, bottomLeftAt, bottomRightAt
38-}
40import Basics (..)
41import Native.Utils
42import List as List
43import Color (..)
44import Maybe ( Maybe(..) )
46type alias Properties = {
47 id : Int,
48 width : Int,
49 height : Int,
50 opacity : Float,
51 color : Maybe Color,
52 href : String,
53 tag : String,
54 hover : (),
55 click : ()
56 }
58type alias Element =
59 { props : Properties
60 , element : ElementPrim
61 }
63{-| An Element that takes up no space. Good for things that appear conditionally:
65 flow down [ img1, if showMore then img2 else empty ]
66-}
67empty : Element
68empty = spacer 0 0
70{-| Get the width of an Element -}
71widthOf : Element -> Int
72widthOf e = e.props.width
74{-| Get the height of an Element -}
75heightOf : Element -> Int
76heightOf e = e.props.height
78{-| Get the width and height of an Element -}
79sizeOf : Element -> (Int,Int)
80sizeOf e = (e.props.width, e.props.height)
82{-| Create an `Element` with a given width. -}
83width : Int -> Element -> Element
84width nw e =
85 let p = e.props
86 props = case e.element of
87 Image _ w h _ -> {p| height <- round (toFloat h / toFloat w * toFloat nw) }
88 RawHtml -> {p| height <- snd (Native.Utils.htmlHeight nw e.element) }
89 _ -> p
90 in { element=e.element, props={ props | width <- nw } }
92{-| Create an `Element` with a given height. -}
93height : Int -> Element -> Element
94height nh e =
95 let p = e.props
96 props = case e.element of
97 Image _ w h _ -> {p| width <- round (toFloat w / toFloat h * toFloat nh) }
98 _ -> p
99 in { element=e.element, props={ p | height <- nh} }
101{-| Create an `Element` with a new width and height. -}
102size : Int -> Int -> Element -> Element
103size w h e = height h (width w e)
105{-| Create an `Element` with a given opacity. Opacity is a number between 0 and 1
106where 0 means totally clear.
107-}
108opacity : Float -> Element -> Element
109opacity o e = let p = e.props in { element=e.element, props={p| opacity <- o} }
111{-| Create an `Element` with a given background color. -}
112color : Color -> Element -> Element
113color c e = let p = e.props in
114 { element = e.element
115 , props = { p | color <- Just c}
116 }
118{-| Create an `Element` with a tag. This lets you link directly to it.
119The element `(tag "all-about-badgers" thirdParagraph)` can be reached
120with a link like this: `/facts-about-animals.elm#all-about-badgers`
121-}
122tag : String -> Element -> Element
123tag name e = let p = e.props in
124 { element=e.element, props={p | tag <- name} }
126{-| Create an `Element` that is a hyper-link. -}
127link : String -> Element -> Element
128link href e = let p = e.props in
129 { element=e.element, props={p | href <- href} }
131newElement w h e =
132 { props = Properties (Native.Utils.guid ()) w h 1 Nothing "" "" () ()
133 , element = e
134 }
136type ElementPrim
137 = Image ImageStyle Int Int String
138 | Container Position Element
139 | Flow Direction [Element]
140 | Spacer
141 | RawHtml
142 | Custom -- for custom Elements implemented in JS, see collage for example
144type ImageStyle = Plain | Fitted | Cropped (Int,Int) | Tiled
146{-| Create an image given a width, height, and image source. -}
147image : Int -> Int -> String -> Element
148image w h src = newElement w h (Image Plain w h src)
150{-| Create a fitted image given a width, height, and image source.
151This will crop the picture to best fill the given dimensions.
152-}
153fittedImage : Int -> Int -> String -> Element
154fittedImage w h src = newElement w h (Image Fitted w h src)
156{-| Create a cropped image. Take a rectangle out of the picture starting
157at the given top left coordinate. If you have a 140-by-140 image,
158the following will cut a 100-by-100 square out of the middle of it.
160 croppedImage (20,20) 100 100 "yogi.jpg"
161-}
162croppedImage : (Int,Int) -> Int -> Int -> String -> Element
163croppedImage pos w h src =
164 newElement w h (Image (Cropped pos) w h src)
166tiledImage : Int -> Int -> String -> Element
167tiledImage w h src =
168 newElement w h (Image Tiled w h src)
170type Three = P | Z | N
171type Pos = Absolute Int | Relative Float
172type alias Position =
173 { horizontal : Three
174 , vertical : Three
175 , x : Pos
176 , y : Pos
177 }
179{-| Put an element in a container. This lets you position the element really
180easily, and there are tons of ways to set the `Position`.
181To center `element` exactly in a 300-by-300 square you would say:
183 container 300 300 middle element
185By setting the color of the container, you can create borders.
186-}
187container : Int -> Int -> Position -> Element -> Element
188container w h pos e = newElement w h (Container pos e)
190{-| Create an empty box. This is useful for getting your spacing right and
191for making borders.
192-}
193spacer : Int -> Int -> Element
194spacer w h = newElement w h Spacer
196type Direction = DUp | DDown | DLeft | DRight | DIn | DOut
198{-| Have a list of elements flow in a particular direction.
199The `Direction` starts from the first element in the list.
201 flow right [a,b,c]
203 +---+---+---+
204 | a | b | c |
205 +---+---+---+
206-}
207flow : Direction -> [Element] -> Element
208flow dir es =
209 let ws = List.map widthOf es
210 hs = List.map heightOf es
211 newFlow w h = newElement w h (Flow dir es)
212 in
213 if es == [] then empty else
214 case dir of
215 DUp -> newFlow (List.maximum ws) (List.sum hs)
216 DDown -> newFlow (List.maximum ws) (List.sum hs)
217 DLeft -> newFlow (List.sum ws) (List.maximum hs)
218 DRight -> newFlow (List.sum ws) (List.maximum hs)
219 DIn -> newFlow (List.maximum ws) (List.maximum hs)
220 DOut -> newFlow (List.maximum ws) (List.maximum hs)
222{-| Stack elements vertically.
223To put `a` above `b` you would say: ``a `above` b``
224-}
225above : Element -> Element -> Element
226above hi lo =
227 newElement (max (widthOf hi) (widthOf lo))
228 (heightOf hi + heightOf lo)
229 (Flow DDown [hi,lo])
231{-| Stack elements vertically.
232To put `a` below `b` you would say: ``a `below` b``
233-}
234below : Element -> Element -> Element
235below lo hi =
236 newElement (max (widthOf hi) (widthOf lo))
237 (heightOf hi + heightOf lo)
238 (Flow DDown [hi,lo])
240{-| Put elements beside each other horizontally.
241To put `a` beside `b` you would say: ``a `beside` b``
242-}
243beside : Element -> Element -> Element
244beside lft rht =
245 newElement (widthOf lft + widthOf rht)
246 (max (heightOf lft) (heightOf rht))
247 (Flow right [lft,rht])
249{-| Layer elements on top of each other, starting from the bottom:
250`layers == flow outward`
251-}
252layers : [Element] -> Element
253layers es =
254 let ws = List.map widthOf es
255 hs = List.map heightOf es
256 in newElement (List.maximum ws) (List.maximum hs) (Flow DOut es)
259-- Repetitive things --
261absolute : Int -> Pos
262absolute = Absolute
263relative : Float -> Pos
264relative = Relative
266middle : Position
267middle = { horizontal=Z, vertical=Z, x=Relative 0.5, y=Relative 0.5 }
268topLeft : Position
269topLeft = { horizontal=N, vertical=P, x=Absolute 0, y=Absolute 0 }
270topRight : Position
271topRight = { topLeft | horizontal <- P }
272bottomLeft : Position
273bottomLeft = { topLeft | vertical <- N }
274bottomRight : Position
275bottomRight = { bottomLeft | horizontal <- P }
276midLeft : Position
277midLeft = { middle | horizontal <- N, x <- Absolute 0 }
278midRight : Position
279midRight = { midLeft | horizontal <- P }
280midTop : Position
281midTop = { middle | vertical <- P, y <- Absolute 0 }
282midBottom : Position
283midBottom = { midTop | vertical <- N }
285middleAt : Pos -> Pos -> Position
286middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y }
287topLeftAt : Pos -> Pos -> Position
288topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y }
289topRightAt : Pos -> Pos -> Position
290topRightAt x y = { horizontal = P, vertical = P, x = x, y = y }
291bottomLeftAt : Pos -> Pos -> Position
292bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y }
293bottomRightAt : Pos -> Pos -> Position
294bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y }
295midLeftAt : Pos -> Pos -> Position
296midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y }
297midRightAt : Pos -> Pos -> Position
298midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y }
299midTopAt : Pos -> Pos -> Position
300midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y }
301midBottomAt : Pos -> Pos -> Position
302midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y }
304up : Direction
305up = DUp
307down : Direction
308down = DDown
310left : Direction
311left = DLeft
313right : Direction
314right = DRight
316inward : Direction
317inward = DIn
319outward : Direction
320outward = DOut