src/elm_src/LayersNode.elm (332 lines of code) (raw):
port module LayersNode exposing (..)
import Browser
import Dict as Dict exposing (Dict)
import Array as Array exposing (Array)
import Html
import Html.Attributes as HA exposing (attribute)
import Svg exposing (..)
import Svg.Attributes as SA exposing (..)
import Svg.Events as SE exposing (..)
import WebGL.Blend as WGLB
import Html.Blend as HTMLB
type Blend
= None
| WebGLBlend WGLB.Blend
| HtmlBlend HTMLB.Blend
-- kinda Either, but for ports:
-- ( Just WebGLBlend, Nothing ) --> WebGL Blend
-- ( Nothing, Just String ) --> SVG Blend
-- ( Nothing, Nothing ) --> None
-- ( Just WebGLBlend, Just String ) --> ¯\_(ツ)_/¯
type alias PortBlend = (Maybe WGLB.Blend, Maybe HTMLB.PortBlend)
type alias Blends = Dict.Dict Int Blend
type alias Colors = Dict Int (Array String)
type alias Model =
{ layerCount : Int
, size : ( Int, Int )
, blends : Blends
, colors : Colors
}
type Msg
= ChangeBlend Int Blend
| ApplyAllBlends String
| ApplyColors Colors
| ChangeLayerCount Int
| SetBlendType Int String
| Resize ( Int, Int )
convertBlend : Blend -> PortBlend
convertBlend blend =
case blend of
None -> ( Nothing, Nothing )
WebGLBlend webglBlend -> ( Just webglBlend, Nothing )
HtmlBlend htmlBlend -> ( Nothing, Just (HTMLB.encode htmlBlend) )
adaptBlend : PortBlend -> Blend
adaptBlend portBlend =
case portBlend of
( Just webGlBlend, Nothing ) -> WebGLBlend webGlBlend
( Nothing, Just htmlBlend ) -> HtmlBlend (HTMLB.decode htmlBlend )
_ -> None
decodeOne : String -> Blend
decodeOne str =
if (String.startsWith "_" str) then
HTMLB.decode (String.dropLeft 1 str) |> HtmlBlend
else
case str of
"" -> None
someStr ->
(WGLB.decodeOne someStr)
|> Maybe.map WebGLBlend
|> Maybe.withDefault None
encodeOne : Blend -> String
encodeOne blend =
case blend of
None -> "-"
WebGLBlend webglBlend -> WGLB.encodeOne webglBlend
HtmlBlend htmlBlend -> "_" ++ HTMLB.encode htmlBlend
decodeAll : String -> List Blend
decodeAll src =
src
|> String.split ":"
|> List.map decodeOne
encodeAll : List Blend -> String
encodeAll blends =
blends |> List.map encodeOne |> String.join ":"
move : Int -> Int -> Svg.Attribute Msg
move x y =
transform ("translate(" ++ String.fromInt x ++ ", " ++ String.fromInt y ++ ")")
renderBlendFrom : Blends -> Int -> Int -> Svg Msg
renderBlendFrom blends count idx =
g
[ SA.style "alignment-baseline: hanging;"
, class ("layer layer-" ++ String.fromInt idx)
, move 0 ((count - idx - 1) * 90)
]
[ text_ [ fill "black" ] [ text ("Layer " ++ String.fromInt idx) ]
, blends
|> Dict.get idx
|> Maybe.withDefault None
|> renderBlend idx
]
renderBlend : Int -> Blend -> Svg Msg
renderBlend idx blend =
case blend of
WebGLBlend webglBlend ->
g
[ class "blend", move 0 10 ]
[ rect [ width "10", height "10", fill (getFill webglBlend)
, stroke "black", strokeWidth "1", rx "3", ry "3", move 1 4 ] []
-- , text_ [ fill "black", move 15 5 ] [ text "Color EQ" ]
, g
[ class "color-eq", move 22 8 ]
[ webglBlend.colorEq |> renderEq "color"
(\eq -> ChangeBlend idx (WebGLBlend { webglBlend | colorEq = eq })) ]
--, text_ [ fill "black", move 15 55 ] [ text "Alpha EQ" ]
, g
[ class "alpha-eq", move 22 45 ]
[ webglBlend.alphaEq |> renderEq "alpha"
(\eq -> ChangeBlend idx (WebGLBlend { webglBlend | alphaEq = eq })) ]
]
HtmlBlend htmlBlend ->
g
[ class "blend", move 0 10 ]
[ text_ [] [ text ("SVG:" ++ HTMLB.encode htmlBlend) ] ]
None ->
g
[ class "blend", move 0 10 ]
[ text_ [] [ text "None" ] ]
renderEq : String -> (WGLB.Equation -> Msg) -> WGLB.Equation -> Svg Msg
renderEq eqType upd ( func, factor1, factor2 ) =
let
updFunc = (\newFunc -> upd ( newFunc, factor1, factor2 ))
updFact1 = (\newFact1 -> upd ( func, newFact1, factor2 ))
updFact2 = (\newFact2 -> upd ( func, factor1, newFact2 ))
in
g
[ class ("equation equation-" ++ eqType) ]
[ g [ class "func" ]
( WGLB.allFuncs |> Array.indexedMap (renderFunc updFunc func) |> Array.toList )
, g [ class "factor-1", move 0 12 ]
( WGLB.allFactors |> Array.indexedMap (renderFactor updFact1 factor1) |> Array.toList )
, g [ class "factor-2", move 0 24 ]
( WGLB.allFactors |> Array.indexedMap (renderFactor updFact2 factor2) |> Array.toList )
]
renderFunc : (Int -> Msg) -> Int -> Int -> a -> Svg Msg
renderFunc select curN n _ =
circle
[ SA.style "cursor: pointer;"
, SA.r "3"
, fill (if (n == curN) then "white" else "black")
, move (n * 12) 0
, HA.attribute "data-label" <| WGLB.labelOfFunc n
, SE.onClick (select n)
]
[ ]
renderFactor : (Int -> Msg) -> Int -> Int -> a -> Svg Msg
renderFactor select curN n _ =
circle
[ SA.style "cursor: pointer;"
, SA.r "3"
, fill (if (n == curN) then "white" else "black")
, move (n * 12) 0
, HA.attribute "data-label" <| WGLB.labelOfFactor n
, SE.onClick (select n)
]
[ ]
getFill : WGLB.Blend -> String
getFill { color } =
color
|> Maybe.withDefault { r = 0, g = 0, b = 0, a = 0 }
|> (\c -> "rgba(" ++ String.fromFloat c.r ++ "," ++ String.fromFloat c.g ++ ","
++ String.fromFloat c.b ++ "," ++ String.fromFloat c.a ++ ")")
init : ( Model, Cmd Msg )
init =
(
{ layerCount = 0
, size = ( 100, 100 )
, blends = Dict.empty
, colors = Dict.empty
}
, Cmd.none
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ChangeBlend layerId newBlend ->
let
model_
= { model
| blends = model.blends |> Dict.insert layerId newBlend
}
in
( model_
, Cmd.batch
[ sendNewBlend { layer = layerId, blend = convertBlend newBlend }
, sendNewCodeFrom model_.blends
]
)
ApplyAllBlends blends ->
let newBlends =
decodeAll blends
-- TODO: do not convert twice
|> Array.fromList
|> Array.toIndexedList
model_ =
{ model
| blends = Dict.fromList newBlends
}
sendOneBlend (layerId, newBlend) =
sendNewBlend { layer = layerId, blend = convertBlend newBlend }
in
( model_
, Cmd.batch
((newBlends |> List.map sendOneBlend) ++ [ sendNewCodeFrom model_.blends ])
)
ApplyColors colors ->
( { model
| colors = colors
}
, Cmd.batch <|
let
colors_ =
Dict.map
(\layerId layerColors ->
sendNewColors { layer = layerId, colors = layerColors }
) colors
in colors_ |> Dict.values
)
-- ! ( colors
-- |> Dict.map
-- (\layerId colors ->
-- sendNewColors { layer = layerId, colors = colors }
-- )
-- |> Dict.values
-- )
ChangeLayerCount newCount ->
(
{ model
| layerCount = newCount
, blends =
List.range 0 (model.layerCount - 1)
|> List.map (\idx ->
( idx
, model.blends
|> Dict.get idx
|> Maybe.withDefault None
)
)
|> Dict.fromList
}
, Cmd.none
)
SetBlendType layerId blendType ->
let
curBlend = Dict.get layerId model.blends |> Maybe.withDefault None
newBlend = case blendType of
"webgl" -> case curBlend of
WebGLBlend _ -> curBlend
_ -> WebGLBlend WGLB.default
"html" -> case curBlend of
HtmlBlend _ -> curBlend
_ -> HtmlBlend HTMLB.default
_ -> None
model_ =
{ model
| blends = model.blends |> Dict.insert layerId newBlend
}
in
( model_, Cmd.batch
[ sendNewBlend { layer = layerId, blend = convertBlend newBlend }
, sendNewCodeFrom model_.blends
]
)
Resize newSize -> ( { model | size = newSize }, Cmd.none )
queueBlends : List (Int, Blend) -> List (Cmd Msg)
queueBlends blends =
blends |> List.map
(\(layerId, newBlend) ->
sendNewBlend { layer = layerId, blend = convertBlend newBlend }
)
sendNewCodeFrom : Blends -> Cmd Msg
sendNewCodeFrom blends = Dict.values blends |> encodeAll |> sendNewCode
adaptColors : Array (Array String) -> Colors
adaptColors source =
source
|> Array.toIndexedList
|> Dict.fromList
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ changeBlend (\{ layer, blend } ->
ChangeBlend layer (blend |> adaptBlend)
)
, setBlendType (\{ layer, blendType } ->
SetBlendType layer blendType
)
, applyAllBlends (\encodedBlends ->
ApplyAllBlends encodedBlends
)
, applyColors (\colors ->
colors |> adaptColors |> ApplyColors
)
, resize Resize
, changeLayerCount ChangeLayerCount
]
view : Model -> Html.Html Msg
view { layerCount, size, blends } =
svg
(case size of ( w, h ) -> [ width (String.fromInt w), height (String.fromInt h) ])
(List.range 0 (layerCount - 1) |> List.map (renderBlendFrom blends layerCount) )
main : Program {} Model Msg
main =
Browser.element
{ init = \_ -> init
, view = view
, subscriptions = subscriptions
, update = update
}
port changeLayerCount : (Int -> msg) -> Sub msg
port setBlendType :
( { layer : Int
, blendType : String
}
-> msg) -> Sub msg
port applyColors : (Array (Array String) -> msg) -> Sub msg
port resize : ( ( Int, Int ) -> msg ) -> Sub msg
port changeBlend :
( { layer : Int
, blend : PortBlend
}
-> msg) -> Sub msg
port applyAllBlends :
( String
-> msg) -> Sub msg
port sendNewBlend :
{ layer: Int
, blend: PortBlend
} -> Cmd msg
port sendNewColors :
{ layer: Int
, colors: Array String
} -> Cmd msg
port sendNewCode :
String -> Cmd msg