desck 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1029 @@
1
+ module Project exposing (..)
2
+
3
+ import Card exposing (Card, defaultCard)
4
+ import Deck exposing (modelAsShort, rankAsChar)
5
+ import DeckView exposing (cardSize)
6
+ import Html.Attributes exposing (maxlength)
7
+ import Layout exposing (Spread, spreadTransforms)
8
+ import Palette exposing (Color(..), colorFromString)
9
+ import Random exposing (initialSeed)
10
+ import Rect exposing (Rect, RectCoordinate, center, enlarge, fromPoints, includeRect, modifyRect, normalizeRect, rect2, toPoints)
11
+ import Util exposing (Id, mapNothing, moveTowardsLast, randf, randomFloat, randomOffset, shuffleList)
12
+ import Vec exposing (Vec2)
13
+ import Zone exposing (Arrow, Stamp, Zone)
14
+
15
+
16
+ type Zorder
17
+ = MoveUp
18
+ | MoveDown
19
+ | MoveTop
20
+
21
+
22
+ type alias Rotation =
23
+ { angle : Float, pivot : Vec2 }
24
+
25
+
26
+ type alias Project =
27
+ { description : String
28
+ , background : Color
29
+ , viewport : Rect
30
+ , cards : List Card
31
+ , zones : List Zone
32
+ , arrows : List Arrow
33
+ , stamps : List Stamp
34
+ }
35
+
36
+
37
+ type alias Selection =
38
+ List Id
39
+
40
+
41
+ type Msg
42
+ = Zoom Float
43
+ | Pan Vec2
44
+ | Spawn (List Shape)
45
+ | Move Vec2 Selection
46
+ | Turn Float Selection
47
+ | Remove Selection
48
+ | Crop Selection
49
+ | Order Zorder Selection
50
+ | Shuffle Selection
51
+ | Humanize Selection
52
+ | UpdateViewport RectCoordinate String
53
+ | UpdateZone Id (Zone -> Zone)
54
+ | UpdateShapes Selection (Shape -> Shape)
55
+ | Describe String
56
+ | SetBackground String
57
+
58
+
59
+ selectWithCovering : Project -> Id -> List Id
60
+ selectWithCovering p id =
61
+ let
62
+ all =
63
+ mergeAll p
64
+
65
+ selected =
66
+ case all |> List.filter (\( i, _ ) -> i == id) |> List.head of
67
+ Just ( _, shape ) ->
68
+ case shape of
69
+ Card card ->
70
+ List.foldl
71
+ (\( i, s ) ls ->
72
+ let
73
+ overlaps =
74
+ case s of
75
+ Card c ->
76
+ (c.z
77
+ >= card.z
78
+ )
79
+ && List.any (\( _, prev ) -> Rect.overlaps c.rect (shapeRect p prev)) ls
80
+
81
+ _ ->
82
+ False
83
+ in
84
+ if overlaps then
85
+ ( i, s ) :: ls
86
+
87
+ else
88
+ ls
89
+ )
90
+ [ ( id, shape ) ]
91
+ all
92
+
93
+ Zone zone ->
94
+ [ ( id, shape ) ]
95
+
96
+ _ ->
97
+ [ ( id, shape ) ]
98
+
99
+ Nothing ->
100
+ []
101
+ in
102
+ selected |> List.map Tuple.first
103
+
104
+
105
+ filterBySelection : Selection -> List { a | id : Id } -> List { a | id : Id }
106
+ filterBySelection sel ls =
107
+ ls
108
+ |> List.filter (\x -> not <| List.member x.id sel)
109
+
110
+
111
+ allIds : Project -> List Id
112
+ allIds self =
113
+ mergeAll self |> List.map Tuple.first
114
+
115
+
116
+ transformShape : Shape -> Transform -> Shape
117
+ transformShape s ( rect, pivot, angle ) =
118
+ case s of
119
+ Card c ->
120
+ Card { c | rect = rect, rot = { pivot = pivot, angle = angle } }
121
+
122
+ Zone z ->
123
+ Zone { z | rect = rect, rot = { pivot = pivot, angle = angle } }
124
+
125
+ Stamp t ->
126
+ Stamp { t | pos = rect.pos }
127
+
128
+ Arrow _ ->
129
+ s
130
+
131
+
132
+ update : Msg -> Project -> Project
133
+ update =
134
+ updateWithSeed 0
135
+
136
+
137
+ updateWithSeed : Int -> Msg -> Project -> Project
138
+ updateWithSeed seed msg self =
139
+ case msg of
140
+ SetBackground s ->
141
+ { self | background = colorFromString s }
142
+
143
+ Describe desc ->
144
+ { self | description = desc }
145
+
146
+ UpdateZone id fn ->
147
+ { self
148
+ | zones =
149
+ self.zones
150
+ |> List.map
151
+ (\z ->
152
+ if z.id == id then
153
+ fn z
154
+
155
+ else
156
+ z
157
+ )
158
+ }
159
+
160
+ UpdateShapes sel fn ->
161
+ self
162
+ |> mutateItems
163
+ (\( id, shape ) ->
164
+ if List.member id sel then
165
+ ( id, fn shape )
166
+
167
+ else
168
+ ( id, shape )
169
+ )
170
+
171
+ -- { self
172
+ -- | cards =
173
+ -- self.cards
174
+ -- |> List.map
175
+ -- (\c ->
176
+ -- if c.id == id then
177
+ -- fn c
178
+ -- else
179
+ -- c
180
+ -- )
181
+ -- }
182
+ UpdateViewport coord value ->
183
+ { self | viewport = modifyRect self.viewport { pos = cardSize, size = cardSize } coord (String.toFloat value |> Maybe.withDefault 0.0) }
184
+
185
+ Zoom z ->
186
+ let
187
+ -- TODO Editor - zoom to cursor
188
+ ( viewport, _ ) =
189
+ zoomViewport self.viewport z Vec.zero
190
+ in
191
+ { self | viewport = viewport }
192
+
193
+ Pan delta ->
194
+ let
195
+ viewport =
196
+ self.viewport
197
+ in
198
+ { self | viewport = { viewport | pos = Vec.sub viewport.pos delta } }
199
+
200
+ Spawn shapes ->
201
+ let
202
+ (Shapes zones unorderedCards arrows stamps) =
203
+ shapes
204
+ |> List.map (\s -> ( shapeId s, s ))
205
+ |> splitAll
206
+
207
+ maxZ =
208
+ self.cards |> List.map .z |> List.foldl max 0
209
+
210
+ -- FIX Editor - order zones, stamps by area
211
+ cards =
212
+ unorderedCards |> List.indexedMap (\i c -> { c | z = maxZ + i + 1 })
213
+ in
214
+ { self
215
+ | cards = self.cards ++ cards
216
+ , zones = self.zones ++ zones
217
+ , arrows = self.arrows ++ arrows
218
+ , stamps = self.stamps ++ stamps
219
+ }
220
+
221
+ Remove selection ->
222
+ { self
223
+ | cards = filterBySelection selection self.cards
224
+ , zones = filterBySelection selection self.zones
225
+ , arrows = filterBySelection selection self.arrows
226
+ , stamps = filterBySelection selection self.stamps
227
+ }
228
+ |> validateArrows
229
+
230
+ Turn offset selected ->
231
+ self
232
+ |> mutateItems
233
+ (\( id, s ) ->
234
+ if List.member id selected then
235
+ let
236
+ ( _, _, angle ) =
237
+ shapeTransform self s
238
+ in
239
+ case s of
240
+ Card c ->
241
+ ( id
242
+ , Card
243
+ { c
244
+ | rot = { angle = angle + offset, pivot = c.rot.pivot }
245
+ }
246
+ )
247
+
248
+ Zone c ->
249
+ ( id
250
+ , Zone
251
+ { c
252
+ | rot = { angle = angle + offset, pivot = c.rot.pivot }
253
+ }
254
+ )
255
+
256
+ -- Zone z ->
257
+ -- let
258
+ -- r =
259
+ -- z.rect
260
+ -- in
261
+ -- ( id
262
+ -- , Zone
263
+ -- { z
264
+ -- | rect =
265
+ -- { r
266
+ -- | pos =
267
+ -- snapPos model <| shapePos model (Zone z)
268
+ -- }
269
+ -- -- , angle = snapAngle model <| c.angle + model.mouseDelta.x
270
+ -- }
271
+ -- )
272
+ _ ->
273
+ ( id, s )
274
+
275
+ else
276
+ ( id, s )
277
+ )
278
+
279
+ Move offset selected ->
280
+ self
281
+ |> mutateItems
282
+ (\( id, s ) ->
283
+ if List.member id selected then
284
+ let
285
+ ( r, _, _ ) =
286
+ shapeTransform self s
287
+ in
288
+ case s of
289
+ Card c ->
290
+ ( id
291
+ , Card
292
+ { c
293
+ | rect = { r | pos = Vec.add r.pos offset }
294
+ }
295
+ )
296
+
297
+ Zone z ->
298
+ ( id
299
+ , Zone
300
+ { z
301
+ | rect =
302
+ { r
303
+ | pos = Vec.add r.pos offset
304
+ }
305
+ }
306
+ )
307
+
308
+ Stamp t ->
309
+ ( id
310
+ , Stamp
311
+ { t
312
+ | pos = Vec.add t.pos offset
313
+ }
314
+ )
315
+
316
+ _ ->
317
+ ( id, s )
318
+
319
+ else
320
+ ( id, s )
321
+ )
322
+
323
+ Crop sel ->
324
+ let
325
+ ids =
326
+ if List.isEmpty sel then
327
+ allIds self
328
+
329
+ else
330
+ sel
331
+ in
332
+ { self | viewport = cropShapes ids self }
333
+
334
+ Order d sel ->
335
+ let
336
+ ( selected, rest ) =
337
+ self.cards |> List.partition (\c -> List.member c.id sel)
338
+ in
339
+ case List.head selected of
340
+ Nothing ->
341
+ self
342
+
343
+ Just _ ->
344
+ let
345
+ highestInRest =
346
+ rest
347
+ |> List.sortBy .z
348
+ |> List.map .z
349
+ |> List.reverse
350
+ |> List.head
351
+ |> Maybe.withDefault 0
352
+
353
+ moved =
354
+ selected
355
+ |> List.map
356
+ (\c ->
357
+ if c.z <= highestInRest then
358
+ { c | z = c.z + (highestInRest - c.z) + 1 }
359
+
360
+ else
361
+ { c | z = c.z }
362
+ )
363
+
364
+ cards =
365
+ [ rest, moved ]
366
+ |> List.concat
367
+ |> List.sortBy .z
368
+ |> List.indexedMap (\i c -> { c | z = i })
369
+ in
370
+ case d of
371
+ MoveTop ->
372
+ { self | cards = cards }
373
+
374
+ _ ->
375
+ self
376
+
377
+ -- case sel of
378
+ -- [ id ] ->
379
+ -- let
380
+ -- withId =
381
+ -- \c -> c.id == id
382
+ -- cs =
383
+ -- case d of
384
+ -- MoveDown ->
385
+ -- moveTowardsLast withId self.cards
386
+ -- MoveUp ->
387
+ -- List.reverse self.cards
388
+ -- |> moveTowardsLast withId
389
+ -- |> List.reverse
390
+ -- MoveTop ->
391
+ -- let
392
+ -- in
393
+ -- { self | cards = cs }
394
+ -- _ ->
395
+ -- self
396
+ Shuffle sel ->
397
+ let
398
+ partitionCards : List Card -> ( List Card, List Card )
399
+ partitionCards cards =
400
+ List.partition (\c -> List.member c.id sel) cards
401
+
402
+ ( targets, rest ) =
403
+ partitionCards self.cards
404
+
405
+ shuffleModels =
406
+ [ targets
407
+ |> List.tail
408
+ |> Maybe.withDefault []
409
+ , targets
410
+ |> List.head
411
+ |> Maybe.map (\h -> [ h ])
412
+ |> Maybe.withDefault []
413
+ ]
414
+ |> List.concat
415
+ |> List.map .model
416
+ |> shuffleList seed
417
+
418
+ shuffled =
419
+ List.map2 (\c model -> { c | model = model }) targets shuffleModels
420
+ in
421
+ { self | cards = List.concat [ shuffled, rest ] }
422
+
423
+ Humanize sel ->
424
+ let
425
+ humanized =
426
+ self.cards
427
+ |> List.indexedMap
428
+ (\i c ->
429
+ if List.isEmpty sel || List.member c.id sel then
430
+ { c | rot = { pivot = c.rot.pivot, angle = c.rot.angle + randomOffset (seed + i) 5.0 } }
431
+
432
+ else
433
+ c
434
+ )
435
+ in
436
+ { self | cards = humanized }
437
+
438
+
439
+ cropViewport : Project -> Rect
440
+ cropViewport p =
441
+ let
442
+ firstCard =
443
+ Maybe.withDefault defaultCard (List.head p.cards)
444
+ |> Card
445
+ |> shapeBounds p
446
+
447
+ cardsRect =
448
+ List.foldl (\a c -> includeRect c a) firstCard (List.map (\c -> shapeBounds p (Card c)) p.cards)
449
+
450
+ firstZone =
451
+ List.head p.zones
452
+
453
+ zonesRect =
454
+ case firstZone of
455
+ Just zone ->
456
+ List.foldl (\a c -> includeRect c a) zone.rect (List.map .rect p.zones)
457
+
458
+ Nothing ->
459
+ cardsRect
460
+ in
461
+ includeRect cardsRect zonesRect
462
+ |> enlarge 10
463
+
464
+
465
+ cropShapes : Selection -> Project -> Rect
466
+ cropShapes sel p =
467
+ let
468
+ (Shapes zones cards _ _) =
469
+ mergeAll p
470
+ |> List.filter (\( id, _ ) -> List.member id sel)
471
+ |> splitAll
472
+
473
+ firstCard =
474
+ Maybe.withDefault defaultCard (List.head cards)
475
+ |> Card
476
+ |> shapeBounds p
477
+
478
+ cardsRect =
479
+ List.foldl (\a c -> includeRect c a) firstCard (List.map (\c -> shapeBounds p (Card c)) cards)
480
+
481
+ firstZone =
482
+ List.head zones
483
+
484
+ zonesRect =
485
+ case firstZone of
486
+ Just zone ->
487
+ List.foldl (\a c -> includeRect c a) zone.rect (List.map .rect zones)
488
+
489
+ Nothing ->
490
+ cardsRect
491
+ in
492
+ includeRect cardsRect zonesRect
493
+ |> enlarge 50
494
+
495
+
496
+ type Shapes
497
+ = Shapes (List Zone) (List Card) (List Arrow) (List Stamp)
498
+
499
+
500
+ splitAll : List ( Id, Shape ) -> Shapes
501
+ splitAll merged =
502
+ let
503
+ (Shapes zss css ass sts) =
504
+ merged
505
+ |> List.foldl
506
+ (\( _, s ) (Shapes zones cards arrows stamps) ->
507
+ case s of
508
+ Card c ->
509
+ Shapes zones (c :: cards) arrows stamps
510
+
511
+ Zone z ->
512
+ Shapes (z :: zones) cards arrows stamps
513
+
514
+ Arrow a ->
515
+ Shapes zones cards (a :: arrows) stamps
516
+
517
+ Stamp t ->
518
+ Shapes zones cards arrows (t :: stamps)
519
+ )
520
+ (Shapes [] [] [] [])
521
+ in
522
+ Shapes (List.reverse zss) (List.reverse css) (List.reverse ass) (List.reverse sts)
523
+
524
+
525
+ mergeAll : Project -> List ( Id, Shape )
526
+ mergeAll { zones, cards, arrows, stamps } =
527
+ List.concat
528
+ [ List.map (\c -> ( c.id, Card c )) cards
529
+ , List.map (\z -> ( z.id, Zone z )) zones
530
+ , List.map (\a -> ( a.id, Arrow a )) arrows
531
+ , List.map (\s -> ( s.id, Stamp s )) stamps
532
+ ]
533
+
534
+
535
+ mutateItems : (( Id, Shape ) -> ( Id, Shape )) -> Project -> Project
536
+ mutateItems fun p =
537
+ let
538
+ (Shapes zones cards arrows stamps) =
539
+ mergeAll p
540
+ |> List.map (\is -> fun is)
541
+ |> splitAll
542
+ in
543
+ { p | cards = cards, zones = zones, arrows = arrows, stamps = stamps }
544
+
545
+
546
+ findById : List { a | id : Id } -> Id -> Maybe { a | id : Id }
547
+ findById list id =
548
+ list |> List.filter (\x -> x.id == id) |> List.head
549
+
550
+
551
+ getShapeById : Project -> Id -> Maybe Shape
552
+ getShapeById p id =
553
+ let
554
+ selectedCard =
555
+ findById p.cards id
556
+
557
+ selectedZone =
558
+ findById p.zones id
559
+
560
+ selectedArrow =
561
+ findById p.arrows id
562
+
563
+ selectedStamp =
564
+ findById p.stamps id
565
+ in
566
+ Maybe.map Card selectedCard
567
+ |> mapNothing Zone selectedZone
568
+ |> mapNothing Arrow selectedArrow
569
+ |> mapNothing Stamp selectedStamp
570
+
571
+
572
+ shapeId : Shape -> Id
573
+ shapeId s =
574
+ case s of
575
+ Zone z ->
576
+ z.id
577
+
578
+ Card c ->
579
+ c.id
580
+
581
+ Arrow a ->
582
+ a.id
583
+
584
+ Stamp t ->
585
+ t.id
586
+
587
+
588
+ getShapesByPos : Project -> Vec2 -> List ( Id, Shape )
589
+ getShapesByPos self pos =
590
+ let
591
+ hovered =
592
+ List.filter
593
+ (\s ->
594
+ Rect.hasPoint pos s.rect
595
+ )
596
+
597
+ hoveredShapes =
598
+ [ hovered self.cards |> List.map Card
599
+ , hovered self.zones |> List.map Zone
600
+ ]
601
+ |> List.concat
602
+ in
603
+ hoveredShapes
604
+ |> List.map (\s -> ( shapeId s, s ))
605
+
606
+
607
+ getHighestShapeByPos : Project -> Vec2 -> Maybe ( Id, Shape )
608
+ getHighestShapeByPos self pos =
609
+ getShapesByPos self pos
610
+ |> List.map Tuple.second
611
+ |> List.filter
612
+ (\s ->
613
+ case s of
614
+ Arrow _ ->
615
+ False
616
+
617
+ _ ->
618
+ True
619
+ )
620
+ |> List.sortWith
621
+ (\a b ->
622
+ case ( a, b ) of
623
+ ( Zone _, Card _ ) ->
624
+ Basics.GT
625
+
626
+ ( Card _, Zone _ ) ->
627
+ Basics.LT
628
+
629
+ ( Card ca, Card cb ) ->
630
+ compare ca.z cb.z
631
+
632
+ _ ->
633
+ LT
634
+ )
635
+ |> List.head
636
+ |> Maybe.map (\s -> ( shapeId s, s ))
637
+
638
+
639
+ stampRect : Stamp -> Rect
640
+ stampRect s =
641
+ { pos = Vec.sub s.pos (Vec.scale (s.radius * 2.0) Vec.one), size = Vec.scale (s.radius * 2.0) Vec.one }
642
+
643
+
644
+ shapeRect : Project -> Shape -> Rect
645
+ shapeRect self shape =
646
+ case shape of
647
+ Zone z ->
648
+ z.rect
649
+
650
+ Card c ->
651
+ c.rect
652
+
653
+ Arrow a ->
654
+ arrowRect self a
655
+
656
+ Stamp s ->
657
+ stampRect s
658
+
659
+
660
+ shapeBounds : Project -> Shape -> Rect
661
+ shapeBounds m s =
662
+ let
663
+ ( rect, pivot, angle ) =
664
+ shapeTransform m s
665
+ in
666
+ rect
667
+ |> toPoints
668
+ |> List.map (Vec.rotateAround (Vec.mult pivot rect.size |> Vec.add rect.pos) angle)
669
+ |> fromPoints
670
+
671
+
672
+ arrowPointsToPos : Project -> Shape -> Vec2 -> ( Vec2, Vec2 )
673
+ arrowPointsToPos model from pos =
674
+ let
675
+ r =
676
+ shapeRect model from
677
+
678
+ c =
679
+ Rect.center r
680
+
681
+ d =
682
+ 0.42
683
+ in
684
+ ( Vec.mult
685
+ (Vec.direction c pos)
686
+ (Vec.scale d r.size)
687
+ |> Vec.add c
688
+ , Vec.mult
689
+ (Vec.direction pos c)
690
+ (Vec.scale (d * 0.2) r.size)
691
+ |> Vec.add pos
692
+ )
693
+
694
+
695
+
696
+ -- TODO Arrow - better endpoint control with distance
697
+ -- TODO Arrow - layer behind or above
698
+
699
+
700
+ type alias Transform =
701
+ ( Rect, Vec2, Float )
702
+
703
+
704
+ arrowPoints : ( Transform, Transform ) -> ( Vec2, Vec2 )
705
+ arrowPoints ( ( fr, _, _ ), ( tr, _, _ ) ) =
706
+ let
707
+ -- TODO guard against infinite loop
708
+ -- in case arrow ids somehow are pointing to arrows
709
+ fc =
710
+ Rect.center fr
711
+
712
+ tc =
713
+ Rect.center tr
714
+
715
+ d =
716
+ 0.42
717
+
718
+ -- v1 =
719
+ -- Rect.clamp fc (Rect.center tc)
720
+ -- v2 =
721
+ -- Rect.clamp tc v1
722
+ -- Rect.clamp {pos = }
723
+ v1 =
724
+ fc
725
+
726
+ -- Vec.mult
727
+ -- (Vec.direction fc tc)
728
+ -- (Vec.scale d fr.size)
729
+ -- |> Vec.add fc
730
+ -- |> Rect.clamp fr
731
+ v2 =
732
+ tc
733
+
734
+ -- Vec.mult
735
+ -- (Vec.direction tc fc)
736
+ -- (Vec.scale d tr.size)
737
+ -- |> Vec.add tc
738
+ -- |> Rect.clamp tr
739
+ in
740
+ ( v1, v2 )
741
+
742
+
743
+ arrowEnds : Project -> Arrow -> ( Vec2, Vec2 )
744
+ arrowEnds p a =
745
+ case ( getShapeById p a.from, getShapeById p a.to ) of
746
+ ( Just fromShape, Just toShape ) ->
747
+ arrowPoints ( shapeTransform p fromShape, shapeTransform p toShape )
748
+
749
+ _ ->
750
+ ( Vec.zero, Vec.zero )
751
+
752
+
753
+ arrowRect : Project -> Arrow -> Rect
754
+ arrowRect m a =
755
+ case ( getShapeById m a.from, getShapeById m a.to ) of
756
+ ( Just fromShape, Just toShape ) ->
757
+ let
758
+ ( from, to ) =
759
+ arrowPoints ( shapeTransform m fromShape, shapeTransform m toShape )
760
+
761
+ r =
762
+ normalizeRect from to
763
+
764
+ c =
765
+ center r
766
+
767
+ dir =
768
+ Vec.sub from to
769
+
770
+ l =
771
+ Vec.length dir
772
+ * 0.5
773
+
774
+ rad =
775
+ atan2 dir.y dir.x
776
+ / (pi * 2.0)
777
+
778
+ angle =
779
+ rad
780
+ * 360.0
781
+ in
782
+ normalizeRect from to
783
+
784
+ _ ->
785
+ Rect.rect2 Vec.zero Vec.zero
786
+
787
+
788
+ shapePos : Project -> Shape -> Vec2
789
+ shapePos m s =
790
+ let
791
+ rect =
792
+ shapeRect m s
793
+ in
794
+ rect.pos
795
+
796
+
797
+ arrowTransform : Arrow -> ( Vec2, Vec2 ) -> ( Rect, Vec2, Float )
798
+ arrowTransform a ( from, to ) =
799
+ let
800
+ r =
801
+ normalizeRect from to
802
+
803
+ c =
804
+ center r
805
+
806
+ dir =
807
+ Vec.sub from to
808
+
809
+ l =
810
+ Vec.length dir
811
+ * 0.5
812
+
813
+ rad =
814
+ atan2 dir.y dir.x
815
+ / (pi * 2.0)
816
+
817
+ angle =
818
+ rad
819
+ * 360.0
820
+
821
+ w =
822
+ max 50.0 a.width
823
+
824
+ rect =
825
+ rect2 (Vec.vec2 (c.x - l) (c.y - w * 0.5)) (Vec.vec2 (l * 2) w)
826
+ in
827
+ ( rect, Vec.half Vec.one, angle )
828
+
829
+
830
+ shapeTransform : Project -> Shape -> ( Rect, Vec2, Float )
831
+ shapeTransform model s =
832
+ case s of
833
+ Card c ->
834
+ ( c.rect
835
+ , c.rot.pivot
836
+ , c.rot.angle
837
+ )
838
+
839
+ Zone z ->
840
+ ( z.rect
841
+ , z.rot.pivot
842
+ , z.rot.angle
843
+ )
844
+
845
+ Arrow a ->
846
+ case ( getShapeById model a.from, getShapeById model a.to ) of
847
+ ( Just fromShape, Just toShape ) ->
848
+ arrowPoints ( shapeTransform model fromShape, shapeTransform model toShape )
849
+ |> arrowTransform a
850
+
851
+ _ ->
852
+ ( Rect.rect2 Vec.zero Vec.zero
853
+ , Vec.half Vec.one
854
+ , 0.0
855
+ )
856
+
857
+ Stamp t ->
858
+ ( stampRect t
859
+ , Vec.half Vec.one
860
+ , 0.0
861
+ )
862
+
863
+
864
+
865
+ -- ( arrowRect model a, Vec.scale 0.5 Vec.one, 0 )
866
+
867
+
868
+ getSelectedCardModels : List Card -> List Id -> String
869
+ getSelectedCardModels cards selected =
870
+ List.filterMap (findById cards) selected
871
+ |> List.map (\c -> modelAsShort c.model)
872
+ |> List.reverse
873
+ |> String.join " "
874
+
875
+
876
+ positionCards : Spread -> Int -> Vec2 -> List Card -> List Card
877
+ positionCards spreadType spreadValue offset targets =
878
+ let
879
+ transforms =
880
+ spreadTransforms (List.length targets) cardSize spreadValue spreadType
881
+ in
882
+ transforms
883
+ |> List.map2
884
+ (\c ( pos, pivot, deg ) ->
885
+ { c
886
+ | rect = { pos = Vec.add pos offset, size = c.rect.size }
887
+ , rot =
888
+ { pivot = pivot
889
+ , angle =
890
+ if isNaN deg then
891
+ 0.0
892
+
893
+ else
894
+ deg
895
+ }
896
+ }
897
+ )
898
+ targets
899
+
900
+
901
+
902
+ -- zoomViewport : Rect -> Float -> Vec2 -> ( Rect, Vec2 )
903
+ -- zoomViewport viewport delta pos =
904
+ -- let
905
+ -- origin =
906
+ -- Vec.add viewport.pos (Vec.scale 0.5 viewport.size)
907
+ -- oldPosRatio =
908
+ -- Vec.div viewport.size (Vec.sub origin viewport.pos)
909
+ -- factor =
910
+ -- if delta > 0.0 then
911
+ -- 1.1
912
+ -- else
913
+ -- 1.0 / 1.1
914
+ -- newSize =
915
+ -- Vec.scale factor viewport.size
916
+ -- oldScale =
917
+ -- 1.0
918
+ -- newScale =
919
+ -- factor * oldScale
920
+ -- scaleChange =
921
+ -- newScale - 1.0
922
+ -- offset =
923
+ -- Vec2 -(origin.x * scaleChange) -(origin.y * scaleChange)
924
+ -- newPos =
925
+ -- Vec.add viewport.pos offset
926
+ -- in
927
+ -- ( { pos = newPos
928
+ -- , size = newSize
929
+ -- }
930
+ -- , Vec.add newPos (Vec.mult newSize oldPosRatio)
931
+ -- )
932
+
933
+
934
+ zoomViewport : Rect -> Float -> Vec2 -> ( Rect, Vec2 )
935
+ zoomViewport viewport delta pos =
936
+ let
937
+ factor =
938
+ if delta > 0.0 then
939
+ 1.1
940
+
941
+ else
942
+ 1.0 / 1.1
943
+
944
+ rect =
945
+ Rect.scale factor viewport
946
+ in
947
+ if rect.size.y / cardSize.y >= 1.0 then
948
+ ( rect, pos )
949
+
950
+ else
951
+ ( viewport, pos )
952
+
953
+
954
+
955
+ -- | RotateSelected String
956
+ -- -- | ActOnCard ( CardAction, CardAction ) String
957
+ -- | Crop
958
+ -- | Shuffle
959
+ -- | UpdateViewportRect RectCoordinate String
960
+ -- | SpreadByType Spread
961
+ -- | SpreadByValue String
962
+ -- | SpreadCards Id
963
+ -- | ApplySelector
964
+ -- | Spawn
965
+ -- | Remove
966
+ -- | Resize Vec2
967
+ -- | UpdateZoneRect Id RectCoordinate String
968
+ -- | UpdateZoneString Id ZoneField String
969
+ -- | SetArrowColor String
970
+ -- | SetArrowWidth String
971
+ -- | SetArrowHead Bool
972
+ -- | Order Zorder
973
+ --
974
+ --
975
+ --
976
+ --
977
+
978
+
979
+ type Shape
980
+ = Card Card
981
+ | Zone Zone
982
+ | Arrow Arrow
983
+ | Stamp Stamp
984
+
985
+
986
+ initialViewport : Rect
987
+ initialViewport =
988
+ Rect (Vec2 0 0) (Vec2 (8.0 * cardSize.x) (3.0 * cardSize.y))
989
+
990
+
991
+ empty : Project
992
+ empty =
993
+ { description = ""
994
+ , background = Transparent
995
+ , viewport = initialViewport
996
+ , cards = []
997
+ , zones = []
998
+ , arrows = []
999
+ , stamps = []
1000
+ }
1001
+
1002
+
1003
+ getLastId : Project -> Int
1004
+ getLastId p =
1005
+ let
1006
+ parseId id =
1007
+ String.dropLeft 1 id |> String.toInt |> Maybe.withDefault 0
1008
+
1009
+ ids =
1010
+ List.map (\( i, _ ) -> parseId i)
1011
+ in
1012
+ 1 + List.foldl max 0 (ids <| mergeAll p)
1013
+
1014
+
1015
+ validateArrows : Project -> Project
1016
+ validateArrows p =
1017
+ { p
1018
+ | arrows =
1019
+ p.arrows
1020
+ |> List.filter
1021
+ (\a ->
1022
+ let
1023
+ ids =
1024
+ mergeAll p
1025
+ |> List.map Tuple.first
1026
+ in
1027
+ List.member a.from ids && List.member a.to ids
1028
+ )
1029
+ }