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.
- package/.gitlab-ci.yml +21 -0
- package/README.md +176 -0
- package/desck.cjs +2 -0
- package/elm.json +39 -0
- package/justfile +13 -0
- package/package.json +25 -0
- package/public/index.html +30 -0
- package/public/palette.json +16 -0
- package/public/projects/something.json +139 -0
- package/public/projects/template.json +69 -0
- package/public/script.js +490 -0
- package/public/style.css +471 -0
- package/public/svg/backface.svg +1 -0
- package/public/svg/courts.svg +30 -0
- package/public/svg/digits.svg +18 -0
- package/public/svg/suits.svg +43 -0
- package/server.cjs +316 -0
- package/settings.json +10 -0
- package/src/Card.elm +38 -0
- package/src/ColorPicker.elm +113 -0
- package/src/Deck.elm +571 -0
- package/src/DeckView.elm +415 -0
- package/src/Layout.elm +224 -0
- package/src/Main.elm +3743 -0
- package/src/Palette.elm +302 -0
- package/src/Project.elm +1029 -0
- package/src/Rect.elm +292 -0
- package/src/SaveFile.elm +422 -0
- package/src/Theme.elm +99 -0
- package/src/Util.elm +337 -0
- package/src/Vec.elm +196 -0
- package/src/Zone.elm +364 -0
package/src/Project.elm
ADDED
|
@@ -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
|
+
}
|