|
module Button (Model, Action, init, update, view) where |
|
|
|
import Html |
|
import Html.Attributes |
|
import Html.Events |
|
import Html.Animation |
|
import Easing |
|
import Effects |
|
|
|
|
|
-- Model |
|
|
|
|
|
type alias Model = |
|
{ open : Bool |
|
, children : List { style : Html.Animation.Animation, txt : String } |
|
, style : Html.Animation.Animation |
|
, radius : Float |
|
} |
|
|
|
|
|
type alias ID = |
|
Int |
|
|
|
|
|
init : Float -> ( Model, Effects.Effects Action ) |
|
init radius = |
|
let |
|
anim = |
|
Html.Animation.init |
|
[ Html.Animation.Width radius Html.Animation.Px |
|
, Html.Animation.Height radius Html.Animation.Px |
|
, Html.Animation.Top (-radius / 2) Html.Animation.Px |
|
, Html.Animation.Left (-radius / 2) Html.Animation.Px |
|
] |
|
in |
|
( { open = False |
|
, children = List.map (\i -> { style = anim, txt = Basics.toString i }) [1..5] |
|
, style = |
|
Html.Animation.init |
|
[ Html.Animation.Width 90 Html.Animation.Px |
|
, Html.Animation.Height 90 Html.Animation.Px |
|
, Html.Animation.Top (-90 / 2) Html.Animation.Px |
|
, Html.Animation.Left (-90 / 2) Html.Animation.Px |
|
, Html.Animation.Rotate 0 Html.Animation.Deg |
|
] |
|
, radius = radius |
|
} |
|
, Effects.none |
|
) |
|
|
|
|
|
|
|
-- Update |
|
|
|
|
|
type Action |
|
= NoOp |
|
| Open |
|
| Close |
|
| Toggle |
|
| OpenChild Int |
|
| CloseChild Int |
|
| AnimateChild Int Html.Animation.Action |
|
| Animate Html.Animation.Action |
|
|
|
|
|
children = |
|
5 |
|
|
|
|
|
flyOutRadius = |
|
120 |
|
|
|
|
|
separationAngle = |
|
40 |
|
|
|
|
|
fanAngle = |
|
(children - 1) * separationAngle |
|
|
|
|
|
baseAngle = |
|
((180 - fanAngle) / 2) |
|
|
|
|
|
update : Action -> Model -> ( Model, Effects.Effects Action ) |
|
update action model = |
|
case action of |
|
NoOp -> |
|
( model, Effects.none ) |
|
|
|
Animate action -> |
|
let |
|
( anim, fx ) = Html.Animation.update action model.style |
|
in |
|
( { model | style = anim }, Effects.map Animate fx ) |
|
|
|
AnimateChild i action -> |
|
let |
|
( children, fx ) = |
|
forwardToChild i model.children action |
|
in |
|
( { model | children = children }, Effects.map (AnimateChild i) fx ) |
|
|
|
OpenChild i -> |
|
let |
|
( x, y ) = finalPosition model.radius i |
|
|
|
( children, fx ) = |
|
Html.Animation.animate |
|
|> Html.Animation.duration (100 * toFloat i) |
|
|> Html.Animation.andThen |
|
|> Html.Animation.props |
|
[ Html.Animation.Left (Html.Animation.to x) Html.Animation.Px |
|
, Html.Animation.Top (Html.Animation.to -y) Html.Animation.Px |
|
] |
|
|> Html.Animation.easing Easing.easeOutSine |
|
|> Html.Animation.duration 100 |
|
|> forwardToChild i model.children |
|
in |
|
( { model | children = children }, Effects.map (AnimateChild i) fx ) |
|
|
|
CloseChild i -> |
|
let |
|
( children, fx ) = |
|
Html.Animation.animate |
|
|> Html.Animation.duration (100 * toFloat i) |
|
|> Html.Animation.andThen |
|
|> Html.Animation.props |
|
[ Html.Animation.Left (Html.Animation.to (-model.radius / 2)) Html.Animation.Px |
|
, Html.Animation.Top (Html.Animation.to (-model.radius / 2)) Html.Animation.Px |
|
] |
|
|> Html.Animation.easing Easing.easeOutSine |
|
|> Html.Animation.duration 100 |
|
|> forwardToChild i model.children |
|
in |
|
( { model | children = children }, Effects.map (AnimateChild i) fx ) |
|
|
|
Open -> |
|
let |
|
( anim, fx ) = |
|
Html.Animation.animate |
|
|> Html.Animation.props |
|
[ Html.Animation.Rotate (Html.Animation.to 45) Html.Animation.Deg ] |
|
|> Html.Animation.duration 100 |
|
|> Html.Animation.on model.style |
|
in |
|
List.foldl |
|
(\i ( model, fx ) -> |
|
let |
|
( model', fx' ) = update (OpenChild i) model |
|
in |
|
( model', Effects.batch [ fx, fx' ] ) |
|
) |
|
( { model | style = anim }, Effects.map Animate fx ) |
|
[0..List.length model.children] |
|
|
|
Close -> |
|
let |
|
( anim, fx ) = |
|
Html.Animation.animate |
|
|> Html.Animation.props |
|
[ Html.Animation.Rotate (Html.Animation.to 0) Html.Animation.Deg ] |
|
|> Html.Animation.duration 100 |
|
|> Html.Animation.on model.style |
|
in |
|
List.foldl |
|
(\i ( model, fx ) -> |
|
let |
|
( model', fx' ) = update (CloseChild i) model |
|
in |
|
( model', Effects.batch [ fx, fx' ] ) |
|
) |
|
( { model | style = anim }, Effects.map Animate fx ) |
|
[0..List.length model.children] |
|
|
|
Toggle -> |
|
if model.open then |
|
update Close { model | open = False } |
|
else |
|
update Open { model | open = True } |
|
|
|
|
|
forwardToChild = |
|
Html.Animation.forwardTo .style (\model style -> { model | style = style }) |
|
|
|
|
|
finalPosition : Float -> Int -> ( Float, Float ) |
|
finalPosition radius i = |
|
let |
|
angle = (baseAngle + (toFloat i * separationAngle)) |> Basics.degrees |
|
in |
|
( flyOutRadius * (Basics.cos angle) - (radius / 2) |
|
, flyOutRadius * (Basics.sin angle) + (radius / 2) |
|
) |
|
|
|
|
|
|
|
-- View |
|
|
|
|
|
view : Signal.Address Action -> Model -> Html.Html |
|
view address model = |
|
Html.div |
|
[ Html.Attributes.style |
|
[ ( "position", "absolute" ) ] |
|
] |
|
<| [ Html.div |
|
[ Html.Attributes.style |
|
<| [ ( "position", "absolute" ) |
|
, ( "zIndex", "999" ) |
|
, ( "borderRadius", "50%" ) |
|
, ( "background", "#68AEF0" ) |
|
, ( "textAlign", "center" ) |
|
, ( "verticalAlign", "middle" ) |
|
, ( "lineHeight", "90px" ) |
|
, ( "color", "#FFFFFF" ) |
|
, ( "fontSize", "40px" ) |
|
] |
|
++ (Html.Animation.render model.style) |
|
, Html.Events.onClick address Toggle |
|
] |
|
[ Html.text "+" ] |
|
] |
|
++ (List.map |
|
(\child -> |
|
Html.div |
|
[ Html.Attributes.style |
|
<| [ ( "position", "absolute" ) |
|
, ( "borderRadius", "50%" ) |
|
, ( "background", "#DDDDDD" ) |
|
] |
|
++ (Html.Animation.render child.style) |
|
] |
|
[] |
|
) |
|
model.children |
|
) |