touchpage 0.2.1

control panel server with shared-state web controls
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
module SvgControl exposing (ID, Model(..), Msg(..), Spec(..), SzModel, SzMsg(..), SzSpec, border, controlId, controlName, findControl, firstJust, init, jsCs, jsSpec, jsSzSpec, jsUmType, jsUpdateMessage, mkRlist, myTail, onTextSize, processProps, resize, szFindControl, szOnTextSize, szinit, szresize, szupdate, szview, toCtrlMsg, tupMap2, update, update_list, view, viewSvgControls, zip)

import Dict exposing (..)
import Html
import Json.Decode as JD
import List exposing (..)
import Svg exposing (Svg)
import Svg.Attributes as SA
import SvgButton
import SvgCommand exposing (Command(..))
import SvgLabel
import SvgSlider
import SvgTextSize exposing (TextSizeReply, calcTextSvg, resizeCommand)
import SvgThings exposing (Orientation(..), UiColor(..), UiTheme)
import SvgXY
import Task
import VirtualDom as VD



----------------------------------------------------------
-- Both control container and sizer ard in this file.
-- Normally I'd break them out into separate files, but
-- they are mutually recursive so they have to
-- both be in a single file.
-------------------- control container -------------------


type Spec
    = CsButton SvgButton.Spec
    | CsSlider SvgSlider.Spec
    | CsXY SvgXY.Spec
    | CsLabel SvgLabel.Spec
    | CsSizer SzSpec


type Model
    = CmButton SvgButton.Model
    | CmSlider SvgSlider.Model
    | CmXY SvgXY.Model
    | CmLabel SvgLabel.Model
    | CmSizer SzModel


type Msg
    = CaButton SvgButton.Msg
    | CaSlider SvgSlider.Msg
    | CaXY SvgXY.Msg
    | CaLabel SvgLabel.Msg
    | CaSizer SzMsg


findControl : Int -> Int -> Model -> Maybe Model
findControl x y mod =
    case mod of
        CmButton bmod ->
            if SvgThings.containsXY bmod.rect x y then
                Just mod

            else
                Nothing

        CmSlider smod ->
            if SvgThings.containsXY smod.rect x y then
                Just mod

            else
                Nothing

        CmXY smod ->
            if SvgThings.containsXY smod.rect x y then
                Just mod

            else
                Nothing

        CmLabel smod ->
            Nothing

        CmSizer szmod ->
            szFindControl szmod x y


controlId : Model -> SvgThings.ControlId
controlId mod =
    case mod of
        CmButton bmod ->
            bmod.cid

        CmSlider smod ->
            smod.cid

        CmXY smod ->
            smod.cid

        CmLabel smod ->
            smod.cid

        CmSizer szmod ->
            szmod.cid


controlName : Model -> Maybe String
controlName mod =
    case mod of
        CmButton bmod ->
            Just bmod.name

        CmSlider smod ->
            Just smod.name

        CmXY smod ->
            Just smod.name

        CmLabel smod ->
            Just smod.name

        CmSizer szmod ->
            Nothing


tupMap2 : (a -> c) -> ( a, b ) -> ( c, b )
tupMap2 fa ab =
    ( fa (Tuple.first ab), Tuple.second ab )


resize : Model -> SvgThings.Rect -> ( Model, Command )
resize model rect =
    let
        aptg =
            \f ( m, c ) -> ( f m, c )
    in
    case model of
        CmButton mod ->
            aptg CmButton <| SvgButton.resize mod (SvgThings.shrinkRect border rect)

        CmSlider mod ->
            aptg CmSlider <| SvgSlider.resize mod (SvgThings.shrinkRect border rect)

        CmXY mod ->
            aptg CmXY <| SvgXY.resize mod (SvgThings.shrinkRect border rect)

        CmLabel mod ->
            aptg CmLabel <| SvgLabel.resize mod (SvgThings.shrinkRect border rect)

        CmSizer mod ->
            aptg CmSizer <| szresize mod rect


jsSpec : JD.Decoder Spec
jsSpec =
    JD.field "type" JD.string |> JD.andThen jsCs


jsCs : String -> JD.Decoder Spec
jsCs t =
    case t of
        "button" ->
            SvgButton.jsSpec |> JD.andThen (\a -> JD.succeed (CsButton a))

        "slider" ->
            SvgSlider.jsSpec |> JD.andThen (\a -> JD.succeed (CsSlider a))

        "xy" ->
            SvgXY.jsSpec |> JD.andThen (\a -> JD.succeed (CsXY a))

        "label" ->
            SvgLabel.jsSpec |> JD.andThen (\a -> JD.succeed (CsLabel a))

        "sizer" ->
            jsSzSpec |> JD.andThen (\a -> JD.succeed (CsSizer a))

        _ ->
            JD.fail ("unkown type: " ++ t)


jsUpdateMessage : JD.Decoder Msg
jsUpdateMessage =
    JD.field "controlType" JD.string |> JD.andThen jsUmType


jsUmType : String -> JD.Decoder Msg
jsUmType wat =
    case wat of
        "button" ->
            SvgButton.jsUpdateMessage
                |> JD.andThen
                    (\x -> JD.succeed (toCtrlMsg x.controlId (CaButton (SvgButton.SvgUpdate x))))

        "slider" ->
            SvgSlider.jsUpdateMessage
                |> JD.andThen
                    (\x -> JD.succeed (toCtrlMsg x.controlId (CaSlider (SvgSlider.SvgUpdate x))))

        "xy" ->
            SvgXY.jsUpdateMessage
                |> JD.andThen
                    (\x -> JD.succeed (toCtrlMsg x.controlId (CaXY (SvgXY.SvgUpdate x))))

        "label" ->
            SvgLabel.jsUpdateMessage
                |> JD.andThen
                    (\x -> JD.succeed (toCtrlMsg x.controlId (CaLabel (SvgLabel.SvgUpdate x))))

        _ ->
            JD.fail ("unknown update type" ++ wat)


myTail : List a -> List a
myTail lst =
    let
        tl =
            tail lst
    in
    case tl of
        Just l ->
            l

        Nothing ->
            []


toCtrlMsg : SvgThings.ControlId -> Msg -> Msg
toCtrlMsg id msg =
    case head id of
        Nothing ->
            msg

        Just x ->
            CaSizer (SzCMsg x (toCtrlMsg (myTail id) msg))


onTextSize : UiTheme -> TextSizeReply -> Model -> Model
onTextSize theme tsr model =
    case model of
        CmButton m ->
            CmButton <|
                SvgTextSize.onTextSizeReply theme tsr m

        CmSlider m ->
            CmSlider <|
                SvgTextSize.onTextSizeReply theme tsr m

        CmXY m ->
            CmXY <|
                SvgTextSize.onTextSizeReply theme tsr m

        CmLabel m ->
            CmLabel <| SvgTextSize.onTextSizeReply theme tsr m

        CmSizer m ->
            CmSizer <| szOnTextSize theme tsr m


update : Msg -> Model -> ( Model, Command )
update msg model =
    case ( msg, model ) of
        ( CaButton ms, CmButton m ) ->
            let
                ( a, b ) =
                    SvgButton.update ms m
            in
            ( CmButton a, b )

        ( CaSlider ms, CmSlider m ) ->
            let
                ( a, b ) =
                    SvgSlider.update ms m
            in
            ( CmSlider a, b )

        ( CaXY ms, CmXY m ) ->
            let
                ( a, b ) =
                    SvgXY.update ms m
            in
            ( CmXY a, b )

        ( CaLabel ms, CmLabel m ) ->
            let
                ( md, c ) =
                    SvgLabel.update ms m
            in
            ( CmLabel md, c )

        ( CaSizer ms, CmSizer m ) ->
            let
                ( a, b ) =
                    szupdate ms m
            in
            ( CmSizer a, b )

        _ ->
            ( model, None )



-- should probably produce an error.  to the user??


update_list : List Msg -> Model -> ( Model, List Command )
update_list msgs model =
    List.foldl
        (\msg ( mod, cmds ) ->
            let
                ( modnew, cmd ) =
                    update msg mod
            in
            ( modnew, cmd :: cmds )
        )
        ( model, [] )
        msgs


init :
    SvgThings.Rect
    -> SvgThings.ControlId
    -> Spec
    -> ( Model, Command )
init rect cid spec =
    let
        aptg =
            \f ( m, c ) -> ( f m, c )
    in
    case spec of
        CsButton s ->
            aptg CmButton <| SvgButton.init (SvgThings.shrinkRect border rect) cid s

        CsSlider s ->
            aptg CmSlider <| SvgSlider.init (SvgThings.shrinkRect border rect) cid s

        CsXY s ->
            aptg CmXY <| SvgXY.init (SvgThings.shrinkRect border rect) cid s

        CsLabel s ->
            aptg CmLabel <| SvgLabel.init (SvgThings.shrinkRect border rect) cid s

        CsSizer s ->
            aptg CmSizer <| szinit rect cid s


view : UiTheme -> Model -> Svg Msg
view theme model =
    case model of
        CmButton m ->
            VD.map CaButton (SvgButton.view theme m)

        CmSlider m ->
            VD.map CaSlider (SvgSlider.view theme m)

        CmXY m ->
            VD.map CaXY (SvgXY.view theme m)

        CmLabel m ->
            VD.map CaLabel (SvgLabel.view theme m)

        CmSizer m ->
            VD.map CaSizer (szview m theme)



-------------------- sizer -------------------


{-| json spec
-}
type alias SzSpec =
    { orientation : SvgThings.Orientation
    , proportions : Maybe (List Float)
    , controls : List Spec
    }



-- proportions should all add up to 1.0


processProps : List Float -> List Float
processProps lst =
    let
        s =
            sum lst
    in
    List.map (\x -> x / s) lst


jsSzSpec : JD.Decoder SzSpec
jsSzSpec =
    JD.map3 SzSpec
        (JD.field "orientation" JD.string |> JD.andThen SvgThings.jsOrientation)
        (JD.maybe (JD.field "proportions" (JD.list JD.float))
            |> JD.andThen
                (\x -> JD.succeed (Maybe.map processProps x))
        )
        (JD.field "controls" (JD.list (JD.lazy (\_ -> jsSpec))))


type alias SzModel =
    { cid : SvgThings.ControlId
    , rect : SvgThings.Rect
    , controls : Dict ID Model
    , orientation : SvgThings.Orientation
    , proportions : Maybe (List Float)
    }


type alias ID =
    Int


szFindControl : SzModel -> Int -> Int -> Maybe Model
szFindControl mod x y =
    if SvgThings.containsXY mod.rect x y then
        firstJust (findControl x y) (values mod.controls)

    else
        Nothing


firstJust : (a -> Maybe b) -> List a -> Maybe b
firstJust f xs =
    case head xs of
        Nothing ->
            Nothing

        Just x ->
            case f x of
                Just v ->
                    Just v

                Nothing ->
                    Maybe.andThen (firstJust f) (tail xs)



-- UPDATE


type SzMsg
    = SzCMsg ID Msg


zip =
    List.map2 Tuple.pair


szupdate : SzMsg -> SzModel -> ( SzModel, Command )
szupdate msg model =
    case msg of
        SzCMsg id act ->
            let
                bb =
                    get id model.controls
            in
            case bb of
                Just bm ->
                    let
                        wha =
                            update act bm

                        updcontrols =
                            insert id (Tuple.first wha) model.controls

                        newmod =
                            { model | controls = updcontrols }
                    in
                    ( newmod, Tuple.second wha )

                Nothing ->
                    ( model, None )


szOnTextSize : UiTheme -> TextSizeReply -> SzModel -> SzModel
szOnTextSize theme tsr model =
    case tsr.controlId of
        idx :: rst ->
            case Dict.get idx model.controls of
                Just control ->
                    let
                        nc =
                            onTextSize theme { tsr | controlId = rst } control
                    in
                    { model | controls = Dict.insert idx nc model.controls }

                Nothing ->
                    model

        [] ->
            model


szresize : SzModel -> SvgThings.Rect -> ( SzModel, Command )
szresize model rect =
    let
        clist =
            Dict.toList model.controls

        rlist =
            mkRlist model.orientation rect (List.length clist) model.proportions

        rlist2 =
            List.map (\( ( i, c ), r ) -> ( i, resize c r )) (zip clist rlist)

        controls =
            List.map (\( i, ( m, c ) ) -> ( i, m )) rlist2

        cmds =
            List.map (\( i, ( m, c ) ) -> c) rlist2

        cdict =
            Dict.fromList controls

        nm =
            { model | rect = rect, controls = cdict }
    in
    ( nm, Batch cmds )


mkRlist : SvgThings.Orientation -> SvgThings.Rect -> Int -> Maybe (List Float) -> List SvgThings.Rect
mkRlist orientation rect count mbproportions =
    case orientation of
        SvgThings.Horizontal ->
            case mbproportions of
                Nothing ->
                    SvgThings.hrects rect count

                Just p ->
                    SvgThings.hrectsp rect count p

        SvgThings.Vertical ->
            case mbproportions of
                Nothing ->
                    SvgThings.vrects rect count

                Just p ->
                    SvgThings.vrectsp rect count p


szinit :
    SvgThings.Rect
    -> SvgThings.ControlId
    -> SzSpec
    -> ( SzModel, Command )
szinit rect cid szspec =
    let
        rlist =
            mkRlist szspec.orientation rect (List.length szspec.controls) szspec.proportions

        blist =
            List.map
                (\( spec, rect_, idx ) -> init rect_ (cid ++ [ idx ]) spec)
                (map3 (\a b c -> ( a, b, c )) szspec.controls rlist idxs)

        mods =
            List.map Tuple.first blist

        cmds =
            List.map Tuple.second blist

        idxs =
            List.range 0 (length szspec.controls)

        controlz =
            zip idxs mods

        model =
            SzModel cid rect (Dict.fromList controlz) szspec.orientation szspec.proportions
    in
    ( model, Batch cmds )



-- VIEW


szview : SzModel -> UiTheme -> Svg SzMsg
szview model theme =
    let
        controllst =
            Dict.toList model.controls
    in
    Svg.g [] (List.map (viewSvgControls theme) controllst)


viewSvgControls : UiTheme -> ( ID, Model ) -> Svg.Svg SzMsg
viewSvgControls theme ( id, model ) =
    VD.map (SzCMsg id) (view theme model)


border : Int
border =
    1