on eaee137 help sign in
elk/libraries/Graphics/Element.elm
315 lines 9.9 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 JavaScript as JS
43import JavaScript (JSString)
44import List as List
45import Color (..)
46import Maybe (Maybe, Just, Nothing)
48type Properties = {
49 id : Int,
50 width : Int,
51 height : Int,
52 opacity : Float,
53 color : Maybe Color,
54 href : JSString,
55 tag : JSString,
56 hover : (),
57 click : ()
58 }
60type Element = { props : Properties, element : ElementPrim }
62{-| An Element that takes up no space. Good for things that appear conditionally:
64 flow down [ img1, if showMore then img2 else empty ]
65-}
66empty : Element
67empty = spacer 0 0
69{-| Get the width of an Element -}
70widthOf : Element -> Int
71widthOf e = e.props.width
73{-| Get the height of an Element -}
74heightOf : Element -> Int
75heightOf e = e.props.height
77{-| Get the width and height of an Element -}
78sizeOf : Element -> (Int,Int)
79sizeOf e = (e.props.width, e.props.height)
81{-| Create an `Element` with a given width. -}
82width : Int -> Element -> Element
83width nw e =
84 let p = e.props
85 props = case e.element of
86 Image _ w h _ -> {p| height <- round (toFloat h / toFloat w * toFloat nw) }
87 RawHtml -> {p| height <- snd (Native.Utils.htmlHeight nw e.element) }
88 _ -> p
89 in { element=e.element, props={ props | width <- nw } }
91{-| Create an `Element` with a given height. -}
92height : Int -> Element -> Element
93height nh e =
94 let p = e.props
95 props = case e.element of
96 Image _ w h _ -> {p| width <- round (toFloat w / toFloat h * toFloat nh) }
97 _ -> p
98 in { element=e.element, props={ p | height <- nh} }
100{-| Create an `Element` with a new width and height. -}
101size : Int -> Int -> Element -> Element
102size w h e = height h (width w e)
104{-| Create an `Element` with a given opacity. Opacity is a number between 0 and 1
105where 0 means totally clear.
106-}
107opacity : Float -> Element -> Element
108opacity o e = let p = e.props in { element=e.element, props={p| opacity <- o} }
110{-| Create an `Element` with a given background color. -}
111color : Color -> Element -> Element
112color c e = let p = e.props in
113 { element = e.element
114 , props = { p | color <- Just c}
115 }
117{-| Create an `Element` with a tag. This lets you link directly to it.
118The element `(tag "all-about-badgers" thirdParagraph)` can be reached
119with a link like this: `/facts-about-animals.elm#all-about-badgers`
120-}
121tag : String -> Element -> Element
122tag name e = let p = e.props in
123 { element=e.element, props={p | tag <- JS.fromString name} }
125{-| Create an `Element` that is a hyper-link. -}
126link : String -> Element -> Element
127link href e = let p = e.props in
128 { element=e.element, props={p | href <- JS.fromString href} }
130emptyStr = JS.fromString ""
131newElement w h e =
132 { props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr () ()
133 , element = e
134 }
136data ElementPrim
137 = Image ImageStyle Int Int JSString
138 | Container Position Element
139 | Flow Direction [Element]
140 | Spacer
141 | RawHtml
142 | Custom -- for custom Elements implemented in JS, see collage for example
144data 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 (JS.fromString 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 (JS.fromString 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 (JS.fromString src))
166tiledImage : Int -> Int -> String -> Element
167tiledImage w h src =
168 newElement w h (Image Tiled w h (JS.fromString src))
170data Three = P | Z | N
171data Pos = Absolute Int | Relative Float
172type Position = { horizontal : Three, vertical : Three, x : Pos, y : Pos }
174{-| Put an element in a container. This lets you position the element really
175easily, and there are tons of ways to set the `Position`.
176To center `element` exactly in a 300-by-300 square you would say:
178 container 300 300 middle element
180By setting the color of the container, you can create borders.
181-}
182container : Int -> Int -> Position -> Element -> Element
183container w h pos e = newElement w h (Container pos e)
185{-| Create an empty box. This is useful for getting your spacing right and
186for making borders.
187-}
188spacer : Int -> Int -> Element
189spacer w h = newElement w h Spacer
191data Direction = DUp | DDown | DLeft | DRight | DIn | DOut
193{-| Have a list of elements flow in a particular direction.
194The `Direction` starts from the first element in the list.
196 flow right [a,b,c]
198 +---+---+---+
199 | a | b | c |
200 +---+---+---+
201-}
202flow : Direction -> [Element] -> Element
203flow dir es =
204 let ws = List.map widthOf es
205 hs = List.map heightOf es
206 newFlow w h = newElement w h (Flow dir es)
207 in
208 if es == [] then empty else
209 case dir of
210 DUp -> newFlow (List.maximum ws) (List.sum hs)
211 DDown -> newFlow (List.maximum ws) (List.sum hs)
212 DLeft -> newFlow (List.sum ws) (List.maximum hs)
213 DRight -> newFlow (List.sum ws) (List.maximum hs)
214 DIn -> newFlow (List.maximum ws) (List.maximum hs)
215 DOut -> newFlow (List.maximum ws) (List.maximum hs)
217{-| Stack elements vertically.
218To put `a` above `b` you would say: ``a `above` b``
219-}
220above : Element -> Element -> Element
221above hi lo =
222 newElement (max (widthOf hi) (widthOf lo))
223 (heightOf hi + heightOf lo)
224 (Flow DDown [hi,lo])
226{-| Stack elements vertically.
227To put `a` below `b` you would say: ``a `below` b``
228-}
229below : Element -> Element -> Element
230below lo hi =
231 newElement (max (widthOf hi) (widthOf lo))
232 (heightOf hi + heightOf lo)
233 (Flow DDown [hi,lo])
235{-| Put elements beside each other horizontally.
236To put `a` beside `b` you would say: ``a `beside` b``
237-}
238beside : Element -> Element -> Element
239beside lft rht =
240 newElement (widthOf lft + widthOf rht)
241 (max (heightOf lft) (heightOf rht))
242 (Flow right [lft,rht])
244{-| Layer elements on top of each other, starting from the bottom:
245`layers == flow outward`
246-}
247layers : [Element] -> Element
248layers es =
249 let ws = List.map widthOf es
250 hs = List.map heightOf es
251 in newElement (List.maximum ws) (List.maximum hs) (Flow DOut es)
254-- Repetitive things --
256absolute : Int -> Pos
257absolute = Absolute
258relative : Float -> Pos
259relative = Relative
261middle : Position
262middle = { horizontal=Z, vertical=Z, x=Relative 0.5, y=Relative 0.5 }
263topLeft : Position
264topLeft = { horizontal=N, vertical=P, x=Absolute 0, y=Absolute 0 }
265topRight : Position
266topRight = { topLeft | horizontal <- P }
267bottomLeft : Position
268bottomLeft = { topLeft | vertical <- N }
269bottomRight : Position
270bottomRight = { bottomLeft | horizontal <- P }
271midLeft : Position
272midLeft = { middle | horizontal <- N, x <- Absolute 0 }
273midRight : Position
274midRight = { midLeft | horizontal <- P }
275midTop : Position
276midTop = { middle | vertical <- P, y <- Absolute 0 }
277midBottom : Position
278midBottom = { midTop | vertical <- N }
280middleAt : Pos -> Pos -> Position
281middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y }
282topLeftAt : Pos -> Pos -> Position
283topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y }
284topRightAt : Pos -> Pos -> Position
285topRightAt x y = { horizontal = P, vertical = P, x = x, y = y }
286bottomLeftAt : Pos -> Pos -> Position
287bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y }
288bottomRightAt : Pos -> Pos -> Position
289bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y }
290midLeftAt : Pos -> Pos -> Position
291midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y }
292midRightAt : Pos -> Pos -> Position
293midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y }
294midTopAt : Pos -> Pos -> Position
295midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y }
296midBottomAt : Pos -> Pos -> Position
297midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y }
299up : Direction
300up = DUp
302down : Direction
303down = DDown
305left : Direction
306left = DLeft
308right : Direction
309right = DRight
311inward : Direction
312inward = DIn
314outward : Direction
315outward = DOut