on debfd18 help sign in
elk/libraries/Graphics/Element.elm
306 lines 9.6 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 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 open Basics
41import Native.Utils
42import JavaScript as JS
43import JavaScript (JSString)
44import List as List
45import open 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 }
59type Element = { props : Properties, element : ElementPrim }
61{-| Get the width of an Element -}
62widthOf : Element -> Int
63widthOf e = e.props.width
65{-| Get the height of an Element -}
66heightOf : Element -> Int
67heightOf e = e.props.height
69{-| Get the width and height of an Element -}
70sizeOf : Element -> (Int,Int)
71sizeOf e = (e.props.width, e.props.height)
73{-| Create an `Element` with a given width. -}
74width : Int -> Element -> Element
75width nw e =
76 let p = e.props
77 props = case e.element of
78 Image _ w h _ -> {p| height <- round (toFloat h / toFloat w * toFloat nw) }
79 RawHtml html -> {p| height <- snd (Native.Utils.htmlHeight nw html)}
80 _ -> p
81 in { element=e.element, props={ props | width <- nw } }
83{-| Create an `Element` with a given height. -}
84height : Int -> Element -> Element
85height nh e =
86 let p = e.props
87 props = case e.element of
88 Image _ w h _ -> {p| width <- round (toFloat w / toFloat h * toFloat nh) }
89 _ -> p
90 in { element=e.element, props={ p | height <- nh} }
92{-| Create an `Element` with a new width and height. -}
93size : Int -> Int -> Element -> Element
94size w h e = height h (width w e)
96{-| Create an `Element` with a given opacity. Opacity is a number between 0 and 1
97where 0 means totally clear.
98-}
99opacity : Float -> Element -> Element
100opacity o e = let p = e.props in { element=e.element, props={p| opacity <- o} }
102{-| Create an `Element` with a given background color. -}
103color : Color -> Element -> Element
104color c e = let p = e.props in
105 { element = e.element
106 , props = { p | color <- Just c}
107 }
109{-| Create an `Element` with a tag. This lets you link directly to it.
110The element `(tag "all-about-badgers" thirdParagraph)` can be reached
111with a link lik this: `/facts-about-animals.elm#all-about-badgers`
112-}
113tag : String -> Element -> Element
114tag name e = let p = e.props in
115 { element=e.element, props={p | tag <- JS.fromString name} }
117{-| Create an `Element` that is a hyper-link. -}
118link : String -> Element -> Element
119link href e = let p = e.props in
120 { element=e.element, props={p | href <- JS.fromString href} }
122emptyStr = JS.fromString ""
123newElement w h e =
124 { props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr (), element = e }
126data ElementPrim
127 = Image ImageStyle Int Int JSString
128 | Container Position Element
129 | Flow Direction [Element]
130 | Spacer
131 | RawHtml JSString
132 | Custom -- for custom Elements implemented in JS, see collage for example
134data ImageStyle = Plain | Fitted | Cropped (Int,Int) | Tiled
136{-| Create an image given a width, height, and image source. -}
137image : Int -> Int -> String -> Element
138image w h src = newElement w h (Image Plain w h (JS.fromString src))
140{-| Create a fitted image given a width, height, and image source.
141This will crop the picture to best fill the given dimensions.
142-}
143fittedImage : Int -> Int -> String -> Element
144fittedImage w h src = newElement w h (Image Fitted w h (JS.fromString src))
146{-| Create a cropped image. Take a rectangle out of the picture starting
147at the given top left coordinate. If you have a 140-by-140 image,
148the following will cut a 100-by-100 square out of the middle of it.
150 croppedImage (20,20) 100 100 "yogi.jpg"
151-}
152croppedImage : (Int,Int) -> Int -> Int -> String -> Element
153croppedImage pos w h src =
154 newElement w h (Image (Cropped pos) w h (JS.fromString src))
156tiledImage : Int -> Int -> String -> Element
157tiledImage w h src =
158 newElement w h (Image Tiled w h (JS.fromString src))
160data Three = P | Z | N
161data Pos = Absolute Int | Relative Float
162type Position = { horizontal : Three, vertical : Three, x : Pos, y : Pos }
164{-| Put an element in a container. This lets you position the element really
165easily, and there are tons of ways to set the `Position`.
166To center `element` exactly in a 300-by-300 square you would say:
168 container 300 300 middle element
170By setting the color of the container, you can create borders.
171-}
172container : Int -> Int -> Position -> Element -> Element
173container w h pos e = newElement w h (Container pos e)
175{-| Create an empty box. This is useful for getting your spacing right and
176for making borders.
177-}
178spacer : Int -> Int -> Element
179spacer w h = newElement w h Spacer
181data Direction = DUp | DDown | DLeft | DRight | DIn | DOut
183{-| Have a list of elements flow in a particular direction.
184The `Direction` starts from the first element in the list.
186 flow right [a,b,c]
188 +---+---+---+
189 | a | b | c |
190 +---+---+---+
191-}
192flow : Direction -> [Element] -> Element
193flow dir es =
194 let ws = List.map widthOf es
195 hs = List.map heightOf es
196 newFlow w h = newElement w h (Flow dir es)
197 in
198 if es == [] then spacer 0 0 else
199 case dir of
200 DUp -> newFlow (List.maximum ws) (List.sum hs)
201 DDown -> newFlow (List.maximum ws) (List.sum hs)
202 DLeft -> newFlow (List.sum ws) (List.maximum hs)
203 DRight -> newFlow (List.sum ws) (List.maximum hs)
204 DIn -> newFlow (List.maximum ws) (List.maximum hs)
205 DOut -> newFlow (List.maximum ws) (List.maximum hs)
207{-| Stack elements vertically. To put `a` above `b` you would say:
209 a `above` b
210-}
211above : Element -> Element -> Element
212above hi lo =
213 newElement (max (widthOf hi) (widthOf lo))
214 (heightOf hi + heightOf lo)
215 (Flow DDown [hi,lo])
217{-| Stack elements vertically. To put `a` below `b` you would say:
219 a `below` b
220-}
221below : Element -> Element -> Element
222below lo hi =
223 newElement (max (widthOf hi) (widthOf lo))
224 (heightOf hi + heightOf lo)
225 (Flow DDown [hi,lo])
227{-| Put elements beside each other horizontally.
228-}
229beside : Element -> Element -> Element
230beside lft rht =
231 newElement (widthOf lft + widthOf rht)
232 (max (heightOf lft) (heightOf rht))
233 (Flow right [lft,rht])
235{-| Layer elements on top of each other, starting from the bottom.
236`(layers == flow outward)`
237-}
238layers : [Element] -> Element
239layers es =
240 let ws = List.map widthOf es
241 hs = List.map heightOf es
242 in newElement (List.maximum ws) (List.maximum hs) (Flow DOut es)
245-- Repetitive things --
247absolute : Int -> Pos
248absolute = Absolute
249relative : Float -> Pos
250relative = Relative
252middle : Position
253middle = { horizontal=Z, vertical=Z, x=Relative 0.5, y=Relative 0.5 }
254topLeft : Position
255topLeft = { horizontal=N, vertical=P, x=Absolute 0, y=Absolute 0 }
256topRight : Position
257topRight = { topLeft | horizontal <- P }
258bottomLeft : Position
259bottomLeft = { topLeft | vertical <- N }
260bottomRight : Position
261bottomRight = { bottomLeft | horizontal <- P }
262midLeft : Position
263midLeft = { middle | horizontal <- N, x <- Absolute 0 }
264midRight : Position
265midRight = { midLeft | horizontal <- P }
266midTop : Position
267midTop = { middle | vertical <- P, y <- Absolute 0 }
268midBottom : Position
269midBottom = { midTop | vertical <- N }
271middleAt : Pos -> Pos -> Position
272middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y }
273topLeftAt : Pos -> Pos -> Position
274topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y }
275topRightAt : Pos -> Pos -> Position
276topRightAt x y = { horizontal = P, vertical = P, x = x, y = y }
277bottomLeftAt : Pos -> Pos -> Position
278bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y }
279bottomRightAt : Pos -> Pos -> Position
280bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y }
281midLeftAt : Pos -> Pos -> Position
282midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y }
283midRightAt : Pos -> Pos -> Position
284midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y }
285midTopAt : Pos -> Pos -> Position
286midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y }
287midBottomAt : Pos -> Pos -> Position
288midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y }
290up : Direction
291up = DUp
293down : Direction
294down = DDown
296left : Direction
297left = DLeft
299right : Direction
300right = DRight
302inward : Direction
303inward = DIn
305outward : Direction
306outward = DOut