elm/libraries/Graphics/Element.elm

306 lines
9.4 KiB
Elm
Raw Normal View History

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.
2013-09-12 19:34:44 +00:00
# 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 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 open Basics
import Native.Utils
import JavaScript as JS
import JavaScript (JSString)
import List as List
import open Color
import Maybe (Maybe, Just, Nothing)
type Properties = {
id : Int,
width : Int,
height : Int,
opacity : Float,
2013-03-07 19:06:48 +00:00
color : Maybe Color,
href : JSString,
2013-06-15 07:49:22 +00:00
tag : JSString,
hover : ()
}
2013-03-07 19:06:48 +00:00
type Element = { props : Properties, element : ElementPrim }
{-| Get the width of an Element -}
widthOf : Element -> Int
widthOf e = e.props.width
{-| Get the height of an Element -}
heightOf : Element -> Int
2013-03-07 19:06:48 +00:00
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 html -> {p| height <- snd (Native.Utils.htmlHeight nw html)}
_ -> 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
2013-03-07 19:06:48 +00:00
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 lik this: `/facts-about-animals.elm#all-about-badgers`
-}
tag : String -> Element -> Element
2013-03-07 19:06:48 +00:00
tag name e = let p = e.props in
{ element=e.element, props={p | tag <- JS.fromString name} }
{-| Create an `Element` that is a hyper-link. -}
link : String -> Element -> Element
2013-03-07 19:06:48 +00:00
link href e = let p = e.props in
{ element=e.element, props={p | href <- JS.fromString href} }
emptyStr = JS.fromString ""
newElement w h e =
{ props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr (), element = e }
data ElementPrim
2013-03-07 19:06:48 +00:00
= Image ImageStyle Int Int JSString
| Container Position Element
| Flow Direction [Element]
| Spacer
| RawHtml JSString
| Custom -- for custom Elements implemented in JS, see collage for example
data ImageStyle = Plain | Fitted | Cropped (Int,Int) | Tiled
2013-03-07 19:06:48 +00:00
{-| 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 (JS.fromString 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 (JS.fromString 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 (JS.fromString src))
tiledImage : Int -> Int -> String -> Element
tiledImage w h src =
newElement w h (Image Tiled w h (JS.fromString src))
2013-03-07 19:06:48 +00:00
data Three = P | Z | N
data Pos = Absolute Int | Relative Float
type 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
2013-03-07 19:06:48 +00:00
data 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 -> [Element] -> Element
flow dir es =
2013-03-12 08:51:54 +00:00
let ws = List.map widthOf es
hs = List.map heightOf es
newFlow w h = newElement w h (Flow dir es)
in
if es == [] then spacer 0 0 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.
-}
beside : Element -> Element -> Element
beside lft rht =
newElement (widthOf lft + widthOf rht)
(max (heightOf lft) (heightOf rht))
(Flow right [lft,rht])
2013-03-07 19:06:48 +00:00
{-| Layer elements on top of each other, starting from the bottom.
`(layers == flow outward)`
-}
layers : [Element] -> Element
2013-03-07 19:06:48 +00:00
layers es =
2013-03-12 08:51:54 +00:00
let ws = List.map widthOf es
hs = List.map heightOf es
in newElement (List.maximum ws) (List.maximum hs) (Flow DOut es)
2013-03-07 19:06:48 +00:00
-- Repetitive things --
absolute : Int -> Pos
2013-03-07 19:06:48 +00:00
absolute = Absolute
relative : Float -> Pos
2013-03-07 19:06:48 +00:00
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
2013-03-07 19:06:48 +00:00
topRight = { topLeft | horizontal <- P }
bottomLeft : Position
2013-03-07 19:06:48 +00:00
bottomLeft = { topLeft | vertical <- N }
bottomRight : Position
2013-03-07 19:06:48 +00:00
bottomRight = { bottomLeft | horizontal <- P }
midLeft : Position
2013-03-07 19:06:48 +00:00
midLeft = { middle | horizontal <- N, x <- Absolute 0 }
midRight : Position
2013-03-07 19:06:48 +00:00
midRight = { midLeft | horizontal <- P }
midTop : Position
2013-03-07 19:06:48 +00:00
midTop = { middle | vertical <- P, y <- Absolute 0 }
midBottom : Position
2013-03-07 19:06:48 +00:00
midBottom = { midTop | vertical <- N }
middleAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
middleAt x y = { horizontal = Z, vertical = Z, x = x, y = y }
topLeftAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
topLeftAt x y = { horizontal = N, vertical = P, x = x, y = y }
topRightAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
topRightAt x y = { horizontal = P, vertical = P, x = x, y = y }
bottomLeftAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
bottomLeftAt x y = { horizontal = N, vertical = N, x = x, y = y }
bottomRightAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
bottomRightAt x y = { horizontal = P, vertical = N, x = x, y = y }
midLeftAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
midLeftAt x y = { horizontal = N, vertical = Z, x = x, y = y }
midRightAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
midRightAt x y = { horizontal = P, vertical = Z, x = x, y = y }
midTopAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
midTopAt x y = { horizontal = Z, vertical = P, x = x, y = y }
midBottomAt : Pos -> Pos -> Position
2013-03-07 19:06:48 +00:00
midBottomAt x y = { horizontal = Z, vertical = N, x = x, y = y }
up : Direction
2013-03-07 19:06:48 +00:00
up = DUp
down : Direction
2013-03-07 19:06:48 +00:00
down = DDown
left : Direction
2013-03-07 19:06:48 +00:00
left = DLeft
right : Direction
2013-03-07 19:06:48 +00:00
right = DRight
inward : Direction
2013-03-07 19:06:48 +00:00
inward = DIn
outward : Direction
2013-03-07 19:06:48 +00:00
outward = DOut