packages feed

elm-core-sources-1.0.0: src/Graphics/Element.elm

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