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/Main.elm
ADDED
|
@@ -0,0 +1,3743 @@
|
|
|
1
|
+
port module Main exposing (main)
|
|
2
|
+
|
|
3
|
+
import Browser
|
|
4
|
+
import Card exposing (Card, makeCard)
|
|
5
|
+
import Deck exposing (modelUsing, parseDeck)
|
|
6
|
+
import DeckView exposing (backface, cardSize, cornerRadius, frontface, includes)
|
|
7
|
+
import Draggable
|
|
8
|
+
import Draggable.Events exposing (onDragBy, onDragEnd)
|
|
9
|
+
import File exposing (File)
|
|
10
|
+
import File.Select as Select
|
|
11
|
+
import Html as H exposing (Html)
|
|
12
|
+
import Html.Attributes as HA exposing (style)
|
|
13
|
+
import Html.Events as E
|
|
14
|
+
import Json.Decode as Decode
|
|
15
|
+
import Json.Encode exposing (Value, encode, list, string)
|
|
16
|
+
import Layout exposing (Spread(..), defaultSpread, spreadAsString, updateSpreadValue, validateSpreadValue)
|
|
17
|
+
import Markdown
|
|
18
|
+
import Palette exposing (Color(..), Palette, colorAsString, colorFromString, colorPicker, colorPickerWithTransparent, setPaletteColor)
|
|
19
|
+
import Project exposing (Project, Shape, Shapes(..), Zorder(..), arrowEnds, arrowPoints, arrowPointsToPos, arrowTransform, getHighestShapeByPos, getLastId, getSelectedCardModels, getShapeById, mergeAll, positionCards, selectWithCovering, shapeId, shapeRect, shapeTransform, splitAll, stampRect)
|
|
20
|
+
import Rect exposing (..)
|
|
21
|
+
import SaveFile exposing (Settings, decodeSaveFile, encodePalette, encodeSaveFile)
|
|
22
|
+
import Svg exposing (Svg)
|
|
23
|
+
import Svg.Attributes as Attr
|
|
24
|
+
import Svg.Events exposing (onMouseUp)
|
|
25
|
+
import Svg.Keyed
|
|
26
|
+
import Task
|
|
27
|
+
import Theme
|
|
28
|
+
import Util exposing (..)
|
|
29
|
+
import Vec exposing (Vec2)
|
|
30
|
+
import Zone exposing (..)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
-- INPUTS
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
port resizeMessage : (Vec2 -> msg) -> Sub msg
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
type alias InputMessage =
|
|
41
|
+
{ kind : String
|
|
42
|
+
, data : String
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
port input : (InputMessage -> msg) -> Sub msg
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
-- OUTPUTS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
port output : Value -> Cmd msg
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
type alias StartFlags =
|
|
57
|
+
{ seed : Int
|
|
58
|
+
, offline : Bool
|
|
59
|
+
, window : Vec2
|
|
60
|
+
, initial : String
|
|
61
|
+
, help : String
|
|
62
|
+
, saveFile : Maybe String
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
dragState : Maybe Shape -> (Vec2 -> Vec2) -> DragEvent -> DragState
|
|
67
|
+
dragState shape toLocal event =
|
|
68
|
+
{ shape = shape
|
|
69
|
+
, button = parseMouseButton event.button
|
|
70
|
+
, start = toLocal <| Vec.vec2 event.x event.y
|
|
71
|
+
, offset = Vec.zero
|
|
72
|
+
, selecting = False
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
type alias DragState =
|
|
77
|
+
{ start : Vec2
|
|
78
|
+
, offset : Vec2
|
|
79
|
+
, shape : Maybe Shape
|
|
80
|
+
, button : MouseButton
|
|
81
|
+
, selecting : Bool
|
|
82
|
+
|
|
83
|
+
-- PLAN Editor - rotate selection around pivot : Maybe Vec2
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
type Format
|
|
88
|
+
= SVG
|
|
89
|
+
| PNG
|
|
90
|
+
| JSON
|
|
91
|
+
| Symbols
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
type Panel
|
|
95
|
+
= Help
|
|
96
|
+
| Settings
|
|
97
|
+
| ExportSettings
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
-- | IO
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
type SettingFlag
|
|
105
|
+
= EmbedSymbols
|
|
106
|
+
| StripData
|
|
107
|
+
| MapColors
|
|
108
|
+
| MapFont
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
type ToggleTarget
|
|
112
|
+
= Boxed
|
|
113
|
+
| BringForward
|
|
114
|
+
| StackSelect
|
|
115
|
+
| ShowPanel (Maybe Panel)
|
|
116
|
+
| Setting SettingFlag
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
type Layer
|
|
120
|
+
= Zones
|
|
121
|
+
| Cards
|
|
122
|
+
| Arrows
|
|
123
|
+
| Stamps
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
type alias Drag =
|
|
127
|
+
{ state : DragState
|
|
128
|
+
, mode : DragMode
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
type Msg
|
|
133
|
+
= Noop String
|
|
134
|
+
-- PORTS
|
|
135
|
+
| Input InputMessage
|
|
136
|
+
| Resize Vec2
|
|
137
|
+
| SetKeyMods KeyMods
|
|
138
|
+
-- FILES
|
|
139
|
+
| FileSelected File
|
|
140
|
+
| FileLoaded String
|
|
141
|
+
| SaveProject
|
|
142
|
+
| ListFiles (List String)
|
|
143
|
+
| LoadProject String
|
|
144
|
+
| ChangeFilename
|
|
145
|
+
| TypeFilename String
|
|
146
|
+
| NewProject
|
|
147
|
+
| DuplicateProject
|
|
148
|
+
| Export Format
|
|
149
|
+
| UploadFile
|
|
150
|
+
-- DRAG & SELECT
|
|
151
|
+
| DragMsg (Draggable.Msg Id)
|
|
152
|
+
| StartDragWithPos Id (Draggable.Msg Id) DragEvent
|
|
153
|
+
| DragBy Vec2
|
|
154
|
+
| EndDrag
|
|
155
|
+
| Click MouseButton (Maybe Shape) Vec2 Bool
|
|
156
|
+
| FinishSelection Selection
|
|
157
|
+
| SelectArrow Id
|
|
158
|
+
| SelectAll
|
|
159
|
+
-- MODIFY
|
|
160
|
+
| Mod Project.Msg
|
|
161
|
+
| ModRandom Project.Msg
|
|
162
|
+
| Spawn
|
|
163
|
+
| SpawnStamp
|
|
164
|
+
| ApplySelector
|
|
165
|
+
| Remove
|
|
166
|
+
| SpreadByType Spread
|
|
167
|
+
| SpreadByValue String
|
|
168
|
+
| SpreadCards Id
|
|
169
|
+
| UpdateZoneString ZoneField String
|
|
170
|
+
| UpdateZoneRect Id RectCoordinate String
|
|
171
|
+
| SetArrowColor String
|
|
172
|
+
| SetStampColor Bool String
|
|
173
|
+
| SetStampLabel String
|
|
174
|
+
| SetStampSize Bool String
|
|
175
|
+
| SetArrowWidth String
|
|
176
|
+
| SetArrowHead String
|
|
177
|
+
-- TEXT
|
|
178
|
+
| UpdateFont FontType
|
|
179
|
+
| TypeFont FontType String
|
|
180
|
+
| TypeCommand String
|
|
181
|
+
| TypeStamp String
|
|
182
|
+
| TypeSelector String
|
|
183
|
+
-- EDITOR
|
|
184
|
+
| ToggleLaterMask Layer
|
|
185
|
+
| Toggle ToggleTarget
|
|
186
|
+
| Undo
|
|
187
|
+
| UpdatePaletteColor Color String
|
|
188
|
+
| UpdatePalette Palette
|
|
189
|
+
| UpdateHeightPixels String
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
-- | UpdateCardField Id CardField String
|
|
194
|
+
-- | OrderSelected SortType
|
|
195
|
+
-- | UpdateSnap String
|
|
196
|
+
-- | UpdateColorPicker ColorPicker.Msg
|
|
197
|
+
-- | UpdatePaletteString String
|
|
198
|
+
-- | OpenColorPicker Color C.Color
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
type alias LayerMask =
|
|
202
|
+
{ cards : Bool
|
|
203
|
+
, zones : Bool
|
|
204
|
+
, arrows : Bool
|
|
205
|
+
, stamps : Bool
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
type alias Model =
|
|
210
|
+
{ flags : StartFlags
|
|
211
|
+
, debug : Bool
|
|
212
|
+
|
|
213
|
+
-- Files
|
|
214
|
+
, files : List String
|
|
215
|
+
, nameInput : String
|
|
216
|
+
, fileState : FileState
|
|
217
|
+
|
|
218
|
+
-- Editor Settings
|
|
219
|
+
, unlockedLayers : LayerMask
|
|
220
|
+
, palette : Palette
|
|
221
|
+
, moveToTop : Bool
|
|
222
|
+
, stackSelect : Bool
|
|
223
|
+
, boxed : Bool
|
|
224
|
+
, zonePreset : Zone
|
|
225
|
+
, arrowPreset : Arrow
|
|
226
|
+
, stampPreset : Stamp
|
|
227
|
+
|
|
228
|
+
-- Editor State
|
|
229
|
+
, mods : KeyMods
|
|
230
|
+
, window : Vec2
|
|
231
|
+
, command : String
|
|
232
|
+
, stampInput : String
|
|
233
|
+
, panel : Maybe Panel
|
|
234
|
+
, undos : List Value
|
|
235
|
+
, id : Int
|
|
236
|
+
, selected : List Id
|
|
237
|
+
, selectedCardModels : String
|
|
238
|
+
, drag : Draggable.State Id
|
|
239
|
+
, dragState : Maybe Drag
|
|
240
|
+
|
|
241
|
+
-- Project
|
|
242
|
+
, spreadType : Spread
|
|
243
|
+
, spreadValue : Int
|
|
244
|
+
, project : Project
|
|
245
|
+
, name : String
|
|
246
|
+
, settings : Settings
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
-- TODO Help - make a tutorial template
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
init : StartFlags -> ( Model, Cmd Msg )
|
|
255
|
+
init flags =
|
|
256
|
+
let
|
|
257
|
+
model : Model
|
|
258
|
+
model =
|
|
259
|
+
{ flags = flags
|
|
260
|
+
, debug = False
|
|
261
|
+
, files = []
|
|
262
|
+
, nameInput = flags.initial
|
|
263
|
+
, fileState = New
|
|
264
|
+
|
|
265
|
+
-- settings
|
|
266
|
+
, unlockedLayers = { cards = True, zones = True, arrows = True, stamps = True }
|
|
267
|
+
, palette = Theme.default
|
|
268
|
+
, moveToTop = False
|
|
269
|
+
, stackSelect = False
|
|
270
|
+
, boxed = True
|
|
271
|
+
, zonePreset = defaultZone
|
|
272
|
+
, arrowPreset = defaultArrow
|
|
273
|
+
, stampPreset = defaultStamp
|
|
274
|
+
|
|
275
|
+
-- state
|
|
276
|
+
, mods = { ctrl = False, shift = False, alt = False }
|
|
277
|
+
, window = flags.window
|
|
278
|
+
, command = ""
|
|
279
|
+
, stampInput = ""
|
|
280
|
+
, panel = Nothing
|
|
281
|
+
, undos = []
|
|
282
|
+
, id = 0
|
|
283
|
+
, selected = []
|
|
284
|
+
, selectedCardModels = ""
|
|
285
|
+
, drag = Draggable.init
|
|
286
|
+
, dragState = Nothing
|
|
287
|
+
|
|
288
|
+
-- project
|
|
289
|
+
, spreadType = Horizontal
|
|
290
|
+
, spreadValue = defaultSpread Horizontal
|
|
291
|
+
, project = Project.empty
|
|
292
|
+
, name = flags.initial
|
|
293
|
+
, settings = SaveFile.default_settings
|
|
294
|
+
}
|
|
295
|
+
in
|
|
296
|
+
case flags.saveFile of
|
|
297
|
+
Just s ->
|
|
298
|
+
let
|
|
299
|
+
( m, cmds ) =
|
|
300
|
+
decodeSaveFileIntoModel False
|
|
301
|
+
model
|
|
302
|
+
s
|
|
303
|
+
(\m_ ->
|
|
304
|
+
Cmd.batch []
|
|
305
|
+
)
|
|
306
|
+
in
|
|
307
|
+
( m
|
|
308
|
+
, cmds
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
Nothing ->
|
|
312
|
+
( model
|
|
313
|
+
, Cmd.batch <|
|
|
314
|
+
[ outputString LoadPalette <| Json.Encode.encode 0 <| encodePalette Theme.default
|
|
315
|
+
, outputString GetSize ""
|
|
316
|
+
, outputString Import flags.initial
|
|
317
|
+
]
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
subscriptions : Model -> Sub Msg
|
|
322
|
+
subscriptions model =
|
|
323
|
+
Sub.batch
|
|
324
|
+
[ Draggable.subscriptions DragMsg model.drag
|
|
325
|
+
, resizeMessage Resize
|
|
326
|
+
, input Input
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
main : Program StartFlags Model Msg
|
|
331
|
+
main =
|
|
332
|
+
Browser.element
|
|
333
|
+
{ init = init
|
|
334
|
+
, update = update
|
|
335
|
+
, subscriptions = subscriptions
|
|
336
|
+
, view = view
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
layerString : Layer -> String
|
|
341
|
+
layerString t =
|
|
342
|
+
case t of
|
|
343
|
+
Cards ->
|
|
344
|
+
"Cards"
|
|
345
|
+
|
|
346
|
+
Zones ->
|
|
347
|
+
"Zones"
|
|
348
|
+
|
|
349
|
+
Arrows ->
|
|
350
|
+
"Arrows"
|
|
351
|
+
|
|
352
|
+
Stamps ->
|
|
353
|
+
"Stamps"
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
type alias KeyMods =
|
|
357
|
+
{ ctrl : Bool
|
|
358
|
+
, shift : Bool
|
|
359
|
+
, alt : Bool
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
type alias Selection =
|
|
364
|
+
{ start : Vec2
|
|
365
|
+
, end : Vec2
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
type DragMode
|
|
370
|
+
= Move
|
|
371
|
+
| Connect
|
|
372
|
+
| Select
|
|
373
|
+
| CreateZone
|
|
374
|
+
| Turn
|
|
375
|
+
| Pan
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
type FileState
|
|
379
|
+
= New
|
|
380
|
+
| NewModified
|
|
381
|
+
| Loading
|
|
382
|
+
| Opened
|
|
383
|
+
| Modified
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
partitionSelected : Model -> ( List Card, List Card )
|
|
387
|
+
partitionSelected model =
|
|
388
|
+
List.partition (\c -> List.member c.id model.selected) model.project.cards
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
-- FEAT snap to grid, card, zone
|
|
393
|
+
-- dragSelectedBy : Vec2 -> Model -> Model
|
|
394
|
+
-- dragSelectedBy delta model =
|
|
395
|
+
-- let
|
|
396
|
+
-- ( dragged, idle ) =
|
|
397
|
+
-- partitionSelected model
|
|
398
|
+
-- dragCard c =
|
|
399
|
+
-- { c | pos = c.pos |> Vec.add delta }
|
|
400
|
+
-- cards =
|
|
401
|
+
-- if model.bringForward then
|
|
402
|
+
-- List.concat
|
|
403
|
+
-- [ List.map
|
|
404
|
+
-- dragCard
|
|
405
|
+
-- dragged
|
|
406
|
+
-- , idle
|
|
407
|
+
-- ]
|
|
408
|
+
-- else
|
|
409
|
+
-- model.cards
|
|
410
|
+
-- |> List.map
|
|
411
|
+
-- (\c ->
|
|
412
|
+
-- if List.member c.id model.selected then
|
|
413
|
+
-- dragCard c
|
|
414
|
+
-- else
|
|
415
|
+
-- c
|
|
416
|
+
-- )
|
|
417
|
+
-- zones =
|
|
418
|
+
-- model.zones
|
|
419
|
+
-- |> List.map
|
|
420
|
+
-- (\z ->
|
|
421
|
+
-- if List.member z.id model.selected then
|
|
422
|
+
-- { z | rect = { pos = Vec.add delta z.rect.pos, size = z.rect.size } }
|
|
423
|
+
-- else
|
|
424
|
+
-- z
|
|
425
|
+
-- )
|
|
426
|
+
-- in
|
|
427
|
+
-- { model | cards = cards, zones = zones }
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
dragConfig : Draggable.Config Id Msg
|
|
431
|
+
dragConfig =
|
|
432
|
+
Draggable.customConfig
|
|
433
|
+
[ onDragBy (\( dx, dy ) -> Vec2 dx dy |> DragBy)
|
|
434
|
+
|
|
435
|
+
-- , onDragStart StartDrag
|
|
436
|
+
, onDragEnd EndDrag
|
|
437
|
+
|
|
438
|
+
-- , onClick ClickTarget
|
|
439
|
+
-- , onMouseDown MouseDown
|
|
440
|
+
]
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
getContained : Project -> Rect -> List Shape
|
|
444
|
+
getContained p r =
|
|
445
|
+
mergeAll p
|
|
446
|
+
|> List.filter
|
|
447
|
+
(\( _, shape ) ->
|
|
448
|
+
case shape of
|
|
449
|
+
Project.Arrow a ->
|
|
450
|
+
let
|
|
451
|
+
( from, to ) =
|
|
452
|
+
arrowEnds p a
|
|
453
|
+
in
|
|
454
|
+
Rect.hasPoint from r && Rect.hasPoint to r
|
|
455
|
+
|
|
456
|
+
_ ->
|
|
457
|
+
let
|
|
458
|
+
( rect, _, _ ) =
|
|
459
|
+
shapeTransform p shape
|
|
460
|
+
in
|
|
461
|
+
Rect.hasRect r rect
|
|
462
|
+
)
|
|
463
|
+
|> List.map Tuple.second
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
-- toggleAct : CardAction -> Model -> List Card
|
|
468
|
+
-- toggleAct action model =
|
|
469
|
+
-- let
|
|
470
|
+
-- { selected, cards } =
|
|
471
|
+
-- model
|
|
472
|
+
-- ( targets, rest ) =
|
|
473
|
+
-- partitionSelected model
|
|
474
|
+
-- center =
|
|
475
|
+
-- Vec.scale 0.5 Vec.one
|
|
476
|
+
-- act : Card -> Card -> Card
|
|
477
|
+
-- act h c =
|
|
478
|
+
-- case action of
|
|
479
|
+
-- Flip ->
|
|
480
|
+
-- { c | front = not h.front }
|
|
481
|
+
-- Rotate ->
|
|
482
|
+
-- if c.rot.angle == 0.0 then
|
|
483
|
+
-- { c | rot = { angle = 90.0, pivot = center } }
|
|
484
|
+
-- else
|
|
485
|
+
-- { c | rot = { angle = 0.0, pivot = center } }
|
|
486
|
+
-- Highlight ->
|
|
487
|
+
-- { c | highlight = not h.highlight }
|
|
488
|
+
-- Erase ->
|
|
489
|
+
-- c
|
|
490
|
+
-- head =
|
|
491
|
+
-- List.head selected
|
|
492
|
+
-- |> Maybe.andThen
|
|
493
|
+
-- (\n -> List.head <| List.filter (\c -> n == c.id) targets)
|
|
494
|
+
-- in
|
|
495
|
+
-- case head of
|
|
496
|
+
-- Nothing ->
|
|
497
|
+
-- cards
|
|
498
|
+
-- Just h ->
|
|
499
|
+
-- if model.bringForward then
|
|
500
|
+
-- List.concat
|
|
501
|
+
-- [ targets |> List.map (act h)
|
|
502
|
+
-- , rest
|
|
503
|
+
-- ]
|
|
504
|
+
-- else
|
|
505
|
+
-- List.map
|
|
506
|
+
-- (\c ->
|
|
507
|
+
-- if List.member c.id model.selected then
|
|
508
|
+
-- act h c
|
|
509
|
+
-- else
|
|
510
|
+
-- c
|
|
511
|
+
-- )
|
|
512
|
+
-- model.cards
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
serializeModel : Model -> Value
|
|
516
|
+
serializeModel model =
|
|
517
|
+
{ name = model.name
|
|
518
|
+
, project = model.project
|
|
519
|
+
, palette = model.palette
|
|
520
|
+
, settings = model.settings
|
|
521
|
+
}
|
|
522
|
+
|> encodeSaveFile
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
stringifyPalette : Palette -> String
|
|
526
|
+
stringifyPalette p =
|
|
527
|
+
encodePalette p
|
|
528
|
+
|> Json.Encode.encode 0
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
stringifyModel : Model -> String
|
|
532
|
+
stringifyModel model =
|
|
533
|
+
serializeModel model
|
|
534
|
+
|> Json.Encode.encode 0
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
type OutputType
|
|
538
|
+
= ExportAs Format
|
|
539
|
+
| Save
|
|
540
|
+
| Fail
|
|
541
|
+
| Success
|
|
542
|
+
| Import
|
|
543
|
+
| GetSize
|
|
544
|
+
| LoadPalette
|
|
545
|
+
| LoadFont FontType
|
|
546
|
+
| LoadFile
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
outputKindAsString : OutputType -> String
|
|
550
|
+
outputKindAsString kind =
|
|
551
|
+
case kind of
|
|
552
|
+
ExportAs format ->
|
|
553
|
+
case format of
|
|
554
|
+
PNG ->
|
|
555
|
+
"export-png"
|
|
556
|
+
|
|
557
|
+
SVG ->
|
|
558
|
+
"export-svg"
|
|
559
|
+
|
|
560
|
+
JSON ->
|
|
561
|
+
"export-json"
|
|
562
|
+
|
|
563
|
+
Symbols ->
|
|
564
|
+
"export-symbols"
|
|
565
|
+
|
|
566
|
+
Save ->
|
|
567
|
+
"save"
|
|
568
|
+
|
|
569
|
+
Fail ->
|
|
570
|
+
"fail"
|
|
571
|
+
|
|
572
|
+
Success ->
|
|
573
|
+
"success"
|
|
574
|
+
|
|
575
|
+
Import ->
|
|
576
|
+
"import"
|
|
577
|
+
|
|
578
|
+
LoadPalette ->
|
|
579
|
+
"load-palette"
|
|
580
|
+
|
|
581
|
+
LoadFont ft ->
|
|
582
|
+
case ft of
|
|
583
|
+
PrimaryFont ->
|
|
584
|
+
"load-primary-font"
|
|
585
|
+
|
|
586
|
+
SecondaryFont ->
|
|
587
|
+
"load-secondary-font"
|
|
588
|
+
|
|
589
|
+
LoadFile ->
|
|
590
|
+
"load-file"
|
|
591
|
+
|
|
592
|
+
GetSize ->
|
|
593
|
+
"get-size"
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
outputData : OutputType -> Value -> Cmd msg
|
|
597
|
+
outputData kind data =
|
|
598
|
+
Json.Encode.object
|
|
599
|
+
[ ( "kind", Json.Encode.string <| outputKindAsString kind )
|
|
600
|
+
, ( "data", data )
|
|
601
|
+
]
|
|
602
|
+
|> output
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
outputString : OutputType -> String -> Cmd msg
|
|
606
|
+
outputString kind s =
|
|
607
|
+
Json.Encode.object
|
|
608
|
+
[ ( "kind", Json.Encode.string <| outputKindAsString kind )
|
|
609
|
+
, ( "data", Json.Encode.string s )
|
|
610
|
+
]
|
|
611
|
+
|> output
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
type InputKind
|
|
615
|
+
= Open
|
|
616
|
+
| Files
|
|
617
|
+
| Saved
|
|
618
|
+
| Exported
|
|
619
|
+
| Mods
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
inputKindFromString : String -> Maybe InputKind
|
|
623
|
+
inputKindFromString s =
|
|
624
|
+
case s of
|
|
625
|
+
"open" ->
|
|
626
|
+
Just Open
|
|
627
|
+
|
|
628
|
+
"files" ->
|
|
629
|
+
Just Files
|
|
630
|
+
|
|
631
|
+
"saved" ->
|
|
632
|
+
Just Saved
|
|
633
|
+
|
|
634
|
+
"exported" ->
|
|
635
|
+
Just Exported
|
|
636
|
+
|
|
637
|
+
"mods" ->
|
|
638
|
+
Just Mods
|
|
639
|
+
|
|
640
|
+
_ ->
|
|
641
|
+
Nothing
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
decodeSaveFileIntoClearedModel : Model -> String -> (Model -> Cmd Msg) -> ( Model, Cmd Msg )
|
|
645
|
+
decodeSaveFileIntoClearedModel =
|
|
646
|
+
decodeSaveFileIntoModel True
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
decodeSaveFileIntoModel : Bool -> Model -> String -> (Model -> Cmd Msg) -> ( Model, Cmd Msg )
|
|
650
|
+
decodeSaveFileIntoModel clear model data success =
|
|
651
|
+
let
|
|
652
|
+
decoded =
|
|
653
|
+
decodeSaveFile data
|
|
654
|
+
in
|
|
655
|
+
case decoded of
|
|
656
|
+
Ok p ->
|
|
657
|
+
let
|
|
658
|
+
cleared =
|
|
659
|
+
if clear then
|
|
660
|
+
clearProject model p.name
|
|
661
|
+
|
|
662
|
+
else
|
|
663
|
+
model
|
|
664
|
+
|
|
665
|
+
zonePreset =
|
|
666
|
+
p.project.zones |> List.reverse |> List.head |> Maybe.withDefault model.zonePreset
|
|
667
|
+
in
|
|
668
|
+
( { cleared
|
|
669
|
+
| fileState = Opened
|
|
670
|
+
, undos = model.undos
|
|
671
|
+
, window = model.window
|
|
672
|
+
, palette = p.palette
|
|
673
|
+
, settings = p.settings
|
|
674
|
+
, id = getLastId p.project
|
|
675
|
+
, project = p.project
|
|
676
|
+
, zonePreset =
|
|
677
|
+
{ zonePreset | rect = { pos = Vec.zero, size = cardSize }, rot = { pivot = Vec.half Vec.one, angle = 0.0 } }
|
|
678
|
+
, arrowPreset = p.project.arrows |> List.reverse |> List.head |> Maybe.withDefault model.arrowPreset
|
|
679
|
+
|
|
680
|
+
-- , paletteString = String.join " " p.palette
|
|
681
|
+
}
|
|
682
|
+
, Cmd.batch <|
|
|
683
|
+
List.concat
|
|
684
|
+
[ [ success model ]
|
|
685
|
+
, case p.settings.primaryFont of
|
|
686
|
+
Just font ->
|
|
687
|
+
[ outputString (LoadFont PrimaryFont) font ]
|
|
688
|
+
|
|
689
|
+
Nothing ->
|
|
690
|
+
[]
|
|
691
|
+
, case p.settings.secondaryFont of
|
|
692
|
+
Just font ->
|
|
693
|
+
[ outputString (LoadFont SecondaryFont) font ]
|
|
694
|
+
|
|
695
|
+
Nothing ->
|
|
696
|
+
[]
|
|
697
|
+
, [ outputString LoadPalette <| stringifyPalette p.palette ]
|
|
698
|
+
]
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
Err err ->
|
|
702
|
+
( model, outputString Fail <| Decode.errorToString err )
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
-- decodeProjectIntoModel : Model -> String -> (Model -> Cmd Msg) -> ( Model, Cmd Msg )
|
|
707
|
+
-- decodeProjectIntoModel model data success =
|
|
708
|
+
-- decodeProjectIntoModelCleared True
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
processInput : Model -> InputMessage -> ( Model, Cmd Msg )
|
|
712
|
+
processInput model im =
|
|
713
|
+
let
|
|
714
|
+
n =
|
|
715
|
+
\m -> ( m, Cmd.none )
|
|
716
|
+
in
|
|
717
|
+
case inputKindFromString im.kind of
|
|
718
|
+
Nothing ->
|
|
719
|
+
n model
|
|
720
|
+
|
|
721
|
+
Just kind ->
|
|
722
|
+
case kind of
|
|
723
|
+
Open ->
|
|
724
|
+
update (FileLoaded im.data) model
|
|
725
|
+
|
|
726
|
+
Saved ->
|
|
727
|
+
n { model | fileState = Opened }
|
|
728
|
+
|
|
729
|
+
Mods ->
|
|
730
|
+
let
|
|
731
|
+
decoded =
|
|
732
|
+
im.data
|
|
733
|
+
|> Decode.decodeString
|
|
734
|
+
(Decode.map3 KeyMods
|
|
735
|
+
(Decode.field "ctrl" Decode.bool)
|
|
736
|
+
(Decode.field "shift" Decode.bool)
|
|
737
|
+
(Decode.field "alt" Decode.bool)
|
|
738
|
+
)
|
|
739
|
+
|> Result.withDefault { ctrl = False, shift = False, alt = False }
|
|
740
|
+
in
|
|
741
|
+
n { model | mods = decoded }
|
|
742
|
+
|
|
743
|
+
Files ->
|
|
744
|
+
let
|
|
745
|
+
decoded =
|
|
746
|
+
im.data
|
|
747
|
+
|> Decode.decodeString (Decode.list Decode.string)
|
|
748
|
+
|
|
749
|
+
files =
|
|
750
|
+
case decoded of
|
|
751
|
+
Ok list ->
|
|
752
|
+
list
|
|
753
|
+
|
|
754
|
+
Err _ ->
|
|
755
|
+
[]
|
|
756
|
+
in
|
|
757
|
+
n { model | files = files }
|
|
758
|
+
|
|
759
|
+
Exported ->
|
|
760
|
+
n model
|
|
761
|
+
|
|
762
|
+
|
|
763
|
+
clearProject : Model -> String -> Model
|
|
764
|
+
clearProject model name =
|
|
765
|
+
let
|
|
766
|
+
( initial, _ ) =
|
|
767
|
+
init model.flags
|
|
768
|
+
in
|
|
769
|
+
{ initial
|
|
770
|
+
| files = model.files
|
|
771
|
+
, name = name
|
|
772
|
+
, nameInput = name
|
|
773
|
+
, fileState = New
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
makeArrow : Arrow -> Id -> Id -> Id -> Arrow
|
|
778
|
+
makeArrow preset from to id =
|
|
779
|
+
{ preset
|
|
780
|
+
| from = from
|
|
781
|
+
, to = to
|
|
782
|
+
, id = id
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
selectInsideSelection : Model -> Selection -> Model
|
|
787
|
+
selectInsideSelection m s =
|
|
788
|
+
let
|
|
789
|
+
rect =
|
|
790
|
+
normalizeRect s.start s.end
|
|
791
|
+
|
|
792
|
+
selected =
|
|
793
|
+
getContained m.project rect
|
|
794
|
+
|> List.filter
|
|
795
|
+
(\shape ->
|
|
796
|
+
case shape of
|
|
797
|
+
Project.Card _ ->
|
|
798
|
+
m.unlockedLayers.cards
|
|
799
|
+
|
|
800
|
+
Project.Zone _ ->
|
|
801
|
+
m.unlockedLayers.zones
|
|
802
|
+
|
|
803
|
+
Project.Arrow _ ->
|
|
804
|
+
m.unlockedLayers.arrows
|
|
805
|
+
|
|
806
|
+
Project.Stamp _ ->
|
|
807
|
+
m.unlockedLayers.stamps
|
|
808
|
+
)
|
|
809
|
+
|> List.sortWith
|
|
810
|
+
(\a b ->
|
|
811
|
+
let
|
|
812
|
+
( ar, _, _ ) =
|
|
813
|
+
shapeTransform m.project a
|
|
814
|
+
|
|
815
|
+
( br, _, _ ) =
|
|
816
|
+
shapeTransform m.project b
|
|
817
|
+
in
|
|
818
|
+
case compare br.pos.x ar.pos.x of
|
|
819
|
+
EQ ->
|
|
820
|
+
compare br.pos.y ar.pos.y
|
|
821
|
+
|
|
822
|
+
o ->
|
|
823
|
+
o
|
|
824
|
+
)
|
|
825
|
+
|> List.map shapeId
|
|
826
|
+
in
|
|
827
|
+
{ m | selected = selected, selectedCardModels = getSelectedCardModels m.project.cards selected }
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
toggleArrow : Model -> Maybe Shape -> Vec2 -> Maybe Model
|
|
831
|
+
toggleArrow m maybeShape pos =
|
|
832
|
+
case ( maybeShape, getHighestShapeByPos m.project pos ) of
|
|
833
|
+
( Just fromShape, Just ( toId, toShape ) ) ->
|
|
834
|
+
let
|
|
835
|
+
fromId =
|
|
836
|
+
shapeId fromShape
|
|
837
|
+
in
|
|
838
|
+
case toShape of
|
|
839
|
+
Project.Arrow _ ->
|
|
840
|
+
Nothing
|
|
841
|
+
|
|
842
|
+
_ ->
|
|
843
|
+
if fromId == toId then
|
|
844
|
+
Nothing
|
|
845
|
+
|
|
846
|
+
else
|
|
847
|
+
case
|
|
848
|
+
m.project.arrows
|
|
849
|
+
|> List.filter (\a -> a.from == fromId && a.to == toId)
|
|
850
|
+
|> List.head
|
|
851
|
+
of
|
|
852
|
+
Just existing ->
|
|
853
|
+
Just
|
|
854
|
+
{ m
|
|
855
|
+
| project =
|
|
856
|
+
Project.update (Project.Remove [ existing.id ]) m.project
|
|
857
|
+
, selected = m.selected |> List.filter (\s -> s /= existing.id)
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
Nothing ->
|
|
861
|
+
let
|
|
862
|
+
arrowId =
|
|
863
|
+
"a" ++ String.fromInt m.id
|
|
864
|
+
in
|
|
865
|
+
Just
|
|
866
|
+
{ m
|
|
867
|
+
| project =
|
|
868
|
+
m.project
|
|
869
|
+
|> Project.update
|
|
870
|
+
(Project.Spawn
|
|
871
|
+
[ Project.Arrow <| makeArrow m.arrowPreset fromId toId arrowId ]
|
|
872
|
+
)
|
|
873
|
+
, selected =
|
|
874
|
+
[ arrowId ]
|
|
875
|
+
, id = m.id + 1
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
_ ->
|
|
879
|
+
Nothing
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
updateArrows : (Arrow -> Arrow) -> Model -> Model
|
|
883
|
+
updateArrows modify model =
|
|
884
|
+
let
|
|
885
|
+
project =
|
|
886
|
+
model.project
|
|
887
|
+
in
|
|
888
|
+
{ model
|
|
889
|
+
| arrowPreset = modify model.arrowPreset
|
|
890
|
+
, project =
|
|
891
|
+
{ project
|
|
892
|
+
| arrows =
|
|
893
|
+
List.map
|
|
894
|
+
(\a ->
|
|
895
|
+
if List.member a.id model.selected then
|
|
896
|
+
modify a
|
|
897
|
+
|
|
898
|
+
else
|
|
899
|
+
a
|
|
900
|
+
)
|
|
901
|
+
model.project.arrows
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
updateStamps : (Stamp -> Stamp) -> Model -> Model
|
|
907
|
+
updateStamps modify model =
|
|
908
|
+
let
|
|
909
|
+
project =
|
|
910
|
+
model.project
|
|
911
|
+
in
|
|
912
|
+
{ model
|
|
913
|
+
| stampPreset = modify model.stampPreset
|
|
914
|
+
, project =
|
|
915
|
+
{ project
|
|
916
|
+
| stamps =
|
|
917
|
+
List.map
|
|
918
|
+
(\a ->
|
|
919
|
+
if List.member a.id model.selected then
|
|
920
|
+
modify a
|
|
921
|
+
|
|
922
|
+
else
|
|
923
|
+
a
|
|
924
|
+
)
|
|
925
|
+
model.project.stamps
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
931
|
+
update msg model =
|
|
932
|
+
let
|
|
933
|
+
settings =
|
|
934
|
+
model.settings
|
|
935
|
+
|
|
936
|
+
cards =
|
|
937
|
+
model.project.cards
|
|
938
|
+
|
|
939
|
+
n m =
|
|
940
|
+
( m, Cmd.none )
|
|
941
|
+
|
|
942
|
+
save m =
|
|
943
|
+
update SaveProject m
|
|
944
|
+
|
|
945
|
+
withUndo m =
|
|
946
|
+
{ m
|
|
947
|
+
| fileState =
|
|
948
|
+
if List.member m.fileState [ New, NewModified ] then
|
|
949
|
+
NewModified
|
|
950
|
+
|
|
951
|
+
else
|
|
952
|
+
Modified
|
|
953
|
+
, undos = serializeModel model :: m.undos
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
undoable m =
|
|
957
|
+
withUndo m
|
|
958
|
+
|> save
|
|
959
|
+
|
|
960
|
+
-- undoableDesk m =
|
|
961
|
+
-- ( withUndo m
|
|
962
|
+
-- , Task.attempt (\_ -> Noop <| "couldn't focus element") (Browser.Dom.focus "#view")
|
|
963
|
+
-- )
|
|
964
|
+
rect =
|
|
965
|
+
fitToCenter
|
|
966
|
+
model.project.viewport.size
|
|
967
|
+
(Vec2 model.window.x model.window.y)
|
|
968
|
+
|
|
969
|
+
toLocal p =
|
|
970
|
+
Vec.add
|
|
971
|
+
(Vec.scale
|
|
972
|
+
(model.project.viewport.size.x / rect.size.x)
|
|
973
|
+
(Vec.sub p rect.pos)
|
|
974
|
+
)
|
|
975
|
+
model.project.viewport.pos
|
|
976
|
+
|
|
977
|
+
processSelected m id =
|
|
978
|
+
case id of
|
|
979
|
+
"background" ->
|
|
980
|
+
[]
|
|
981
|
+
|
|
982
|
+
_ ->
|
|
983
|
+
if m.mods.shift || m.stackSelect then
|
|
984
|
+
selectWithCovering model.project id
|
|
985
|
+
|> List.append
|
|
986
|
+
(if m.mods.ctrl then
|
|
987
|
+
m.selected
|
|
988
|
+
|
|
989
|
+
else
|
|
990
|
+
[]
|
|
991
|
+
)
|
|
992
|
+
|> List.foldl
|
|
993
|
+
(\i ls ->
|
|
994
|
+
if List.member i ls then
|
|
995
|
+
ls
|
|
996
|
+
|
|
997
|
+
else
|
|
998
|
+
i :: ls
|
|
999
|
+
)
|
|
1000
|
+
[]
|
|
1001
|
+
|
|
1002
|
+
else if m.mods.ctrl then
|
|
1003
|
+
if List.member id m.selected then
|
|
1004
|
+
m.selected
|
|
1005
|
+
-- List.filter (\i -> i /= id) m.selected
|
|
1006
|
+
|
|
1007
|
+
else
|
|
1008
|
+
id :: m.selected
|
|
1009
|
+
|
|
1010
|
+
else if List.member id m.selected then
|
|
1011
|
+
m.selected
|
|
1012
|
+
|
|
1013
|
+
else
|
|
1014
|
+
[ id ]
|
|
1015
|
+
in
|
|
1016
|
+
case msg of
|
|
1017
|
+
-- Noop s ->
|
|
1018
|
+
-- Debug.log s (n model)
|
|
1019
|
+
Noop _ ->
|
|
1020
|
+
n model
|
|
1021
|
+
|
|
1022
|
+
-- ⡤⠤⡀⡠⠤⡀⡤⠤⡀⠤⡤⠄
|
|
1023
|
+
-- ⡧⠤⠃⡇⠀⡇⡧⡤⠃⠀⡇⠀
|
|
1024
|
+
-- ⠃⠀⠀⠑⠒⠁⠃⠈⠂⠀⠃⠀
|
|
1025
|
+
SetKeyMods km ->
|
|
1026
|
+
n { model | mods = km }
|
|
1027
|
+
|
|
1028
|
+
Input im ->
|
|
1029
|
+
processInput model im
|
|
1030
|
+
|
|
1031
|
+
Resize ws ->
|
|
1032
|
+
n { model | window = ws }
|
|
1033
|
+
|
|
1034
|
+
-- ⡤⠤⠄⠠⡤⠀⡄⠀⠀⡤⠤⠄⡠⠤⡀
|
|
1035
|
+
-- ⡗⠒⠀⠀⡇⠀⡇⠀⠀⡗⠒⠀⠑⠒⡄
|
|
1036
|
+
-- ⠃⠀⠀⠐⠓⠀⠓⠒⠂⠓⠒⠂⠑⠒⠁
|
|
1037
|
+
FileSelected file ->
|
|
1038
|
+
( model, Task.perform FileLoaded (File.toString file) )
|
|
1039
|
+
|
|
1040
|
+
FileLoaded s ->
|
|
1041
|
+
decodeSaveFileIntoClearedModel model s (\m -> outputString Success m.name)
|
|
1042
|
+
|
|
1043
|
+
TypeFilename f ->
|
|
1044
|
+
n { model | nameInput = f }
|
|
1045
|
+
|
|
1046
|
+
ChangeFilename ->
|
|
1047
|
+
let
|
|
1048
|
+
name =
|
|
1049
|
+
if List.member model.nameInput model.files then
|
|
1050
|
+
generateDuplicateName model.files model.nameInput
|
|
1051
|
+
|
|
1052
|
+
else
|
|
1053
|
+
model.nameInput
|
|
1054
|
+
in
|
|
1055
|
+
update SaveProject
|
|
1056
|
+
{ model
|
|
1057
|
+
| name = name
|
|
1058
|
+
, nameInput = name
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
ListFiles ls ->
|
|
1062
|
+
n { model | files = ls }
|
|
1063
|
+
|
|
1064
|
+
LoadProject f ->
|
|
1065
|
+
( { model | fileState = Loading, name = f, nameInput = f }, outputString Import f )
|
|
1066
|
+
|
|
1067
|
+
SaveProject ->
|
|
1068
|
+
( model, outputData Save (serializeModel model) )
|
|
1069
|
+
|
|
1070
|
+
NewProject ->
|
|
1071
|
+
n <| clearProject model "untitled"
|
|
1072
|
+
|
|
1073
|
+
DuplicateProject ->
|
|
1074
|
+
let
|
|
1075
|
+
name =
|
|
1076
|
+
generateDuplicateName model.files model.name
|
|
1077
|
+
in
|
|
1078
|
+
n { model | name = name, nameInput = name, fileState = New }
|
|
1079
|
+
|
|
1080
|
+
Export format ->
|
|
1081
|
+
let
|
|
1082
|
+
data =
|
|
1083
|
+
if format == JSON then
|
|
1084
|
+
stringifyModel model
|
|
1085
|
+
|
|
1086
|
+
else
|
|
1087
|
+
model.name
|
|
1088
|
+
in
|
|
1089
|
+
( model, outputString (ExportAs format) data )
|
|
1090
|
+
|
|
1091
|
+
UploadFile ->
|
|
1092
|
+
( model, Select.file [ "application/json" ] FileSelected )
|
|
1093
|
+
|
|
1094
|
+
-- ⡤⢄⠀⡤⠤⡀⡠⠤⡀⡠⠤⡀
|
|
1095
|
+
-- ⡇⠀⡇⡧⡤⠃⡧⠤⡇⡇⠤⡄
|
|
1096
|
+
-- ⠓⠊⠀⠃⠈⠂⠃⠀⠃⠑⠒⠁
|
|
1097
|
+
Click button shape _ selecting ->
|
|
1098
|
+
case shape of
|
|
1099
|
+
Nothing ->
|
|
1100
|
+
n { model | selected = [] }
|
|
1101
|
+
|
|
1102
|
+
Just s ->
|
|
1103
|
+
let
|
|
1104
|
+
updateIfCards fn =
|
|
1105
|
+
case s of
|
|
1106
|
+
Project.Card card ->
|
|
1107
|
+
undoable
|
|
1108
|
+
{ model
|
|
1109
|
+
| project =
|
|
1110
|
+
Project.update
|
|
1111
|
+
(Project.UpdateShapes
|
|
1112
|
+
model.selected
|
|
1113
|
+
(\ss ->
|
|
1114
|
+
case ss of
|
|
1115
|
+
Project.Card c ->
|
|
1116
|
+
Project.Card <| fn card c
|
|
1117
|
+
|
|
1118
|
+
_ ->
|
|
1119
|
+
ss
|
|
1120
|
+
)
|
|
1121
|
+
)
|
|
1122
|
+
model.project
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
_ ->
|
|
1126
|
+
n model
|
|
1127
|
+
in
|
|
1128
|
+
case button of
|
|
1129
|
+
RMB ->
|
|
1130
|
+
-- TODO UX - context menu to create/delete?
|
|
1131
|
+
if model.mods.alt then
|
|
1132
|
+
updateIfCards
|
|
1133
|
+
(\card c ->
|
|
1134
|
+
{ c | highlight = not card.highlight }
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
else if model.mods.shift then
|
|
1138
|
+
undoable { model | project = Project.update (Project.Remove model.selected) model.project }
|
|
1139
|
+
|
|
1140
|
+
else if model.mods.ctrl then
|
|
1141
|
+
updateIfCards
|
|
1142
|
+
(\card c ->
|
|
1143
|
+
{ c | highlight = not card.highlight }
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
else
|
|
1147
|
+
updateIfCards
|
|
1148
|
+
(\card c ->
|
|
1149
|
+
{ c | front = not card.front }
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
LMB ->
|
|
1153
|
+
if model.mods.alt then
|
|
1154
|
+
updateIfCards
|
|
1155
|
+
(\card c ->
|
|
1156
|
+
let
|
|
1157
|
+
angle =
|
|
1158
|
+
card.rot.angle
|
|
1159
|
+
in
|
|
1160
|
+
{ c
|
|
1161
|
+
| rot =
|
|
1162
|
+
{ angle =
|
|
1163
|
+
let
|
|
1164
|
+
a =
|
|
1165
|
+
round (angle / 90.0)
|
|
1166
|
+
|> modBy 4
|
|
1167
|
+
in
|
|
1168
|
+
toFloat ((a + 1) * 90)
|
|
1169
|
+
, pivot = Vec.half Vec.one
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
)
|
|
1173
|
+
-- else if model.mods.shift then
|
|
1174
|
+
-- updateIfCards
|
|
1175
|
+
-- (\card c ->
|
|
1176
|
+
-- { c | highlight = not card.highlight }
|
|
1177
|
+
-- )
|
|
1178
|
+
|
|
1179
|
+
else if selecting then
|
|
1180
|
+
undoable { model | project = Project.update (Project.Order Project.MoveTop model.selected) model.project }
|
|
1181
|
+
|
|
1182
|
+
else
|
|
1183
|
+
n model
|
|
1184
|
+
|
|
1185
|
+
_ ->
|
|
1186
|
+
n model
|
|
1187
|
+
|
|
1188
|
+
DragMsg dragMsg ->
|
|
1189
|
+
Draggable.update dragConfig dragMsg model
|
|
1190
|
+
|
|
1191
|
+
StartDragWithPos id dragMsg event ->
|
|
1192
|
+
let
|
|
1193
|
+
maybeShape =
|
|
1194
|
+
Project.getShapeById model.project id
|
|
1195
|
+
|
|
1196
|
+
state =
|
|
1197
|
+
dragState maybeShape toLocal event
|
|
1198
|
+
|
|
1199
|
+
mode =
|
|
1200
|
+
if id == "background" then
|
|
1201
|
+
case state.button of
|
|
1202
|
+
LMB ->
|
|
1203
|
+
if model.mods.shift then
|
|
1204
|
+
CreateZone
|
|
1205
|
+
|
|
1206
|
+
else if model.mods.alt then
|
|
1207
|
+
Turn
|
|
1208
|
+
|
|
1209
|
+
else
|
|
1210
|
+
Select
|
|
1211
|
+
|
|
1212
|
+
MMB ->
|
|
1213
|
+
Pan
|
|
1214
|
+
|
|
1215
|
+
RMB ->
|
|
1216
|
+
CreateZone
|
|
1217
|
+
|
|
1218
|
+
else
|
|
1219
|
+
case state.button of
|
|
1220
|
+
LMB ->
|
|
1221
|
+
if model.mods.alt then
|
|
1222
|
+
Turn
|
|
1223
|
+
|
|
1224
|
+
else if model.mods.shift then
|
|
1225
|
+
Connect
|
|
1226
|
+
|
|
1227
|
+
else
|
|
1228
|
+
Move
|
|
1229
|
+
|
|
1230
|
+
MMB ->
|
|
1231
|
+
Pan
|
|
1232
|
+
|
|
1233
|
+
RMB ->
|
|
1234
|
+
Connect
|
|
1235
|
+
|
|
1236
|
+
selected =
|
|
1237
|
+
processSelected model id
|
|
1238
|
+
in
|
|
1239
|
+
{ model
|
|
1240
|
+
| dragState =
|
|
1241
|
+
Just
|
|
1242
|
+
{ state = { state | selecting = List.member id model.selected }
|
|
1243
|
+
, mode = mode
|
|
1244
|
+
}
|
|
1245
|
+
, project =
|
|
1246
|
+
if model.moveToTop then
|
|
1247
|
+
-- HACK this should be undoable
|
|
1248
|
+
Project.update (Project.Order MoveTop selected) model.project
|
|
1249
|
+
|
|
1250
|
+
else
|
|
1251
|
+
model.project
|
|
1252
|
+
, selected = selected
|
|
1253
|
+
, selectedCardModels = getSelectedCardModels model.project.cards selected
|
|
1254
|
+
}
|
|
1255
|
+
|> Draggable.update dragConfig dragMsg
|
|
1256
|
+
|
|
1257
|
+
DragBy delta ->
|
|
1258
|
+
case model.dragState of
|
|
1259
|
+
Nothing ->
|
|
1260
|
+
n model
|
|
1261
|
+
|
|
1262
|
+
Just ds ->
|
|
1263
|
+
let
|
|
1264
|
+
{ state, mode } =
|
|
1265
|
+
ds
|
|
1266
|
+
|
|
1267
|
+
localDelta d =
|
|
1268
|
+
Vec.scale (model.project.viewport.size.x / rect.size.x) d
|
|
1269
|
+
|
|
1270
|
+
deltaMult =
|
|
1271
|
+
case mode of
|
|
1272
|
+
Turn ->
|
|
1273
|
+
0.25
|
|
1274
|
+
|
|
1275
|
+
_ ->
|
|
1276
|
+
1.0
|
|
1277
|
+
|
|
1278
|
+
nextState =
|
|
1279
|
+
{ state | offset = Vec.add state.offset <| localDelta (Vec.scale deltaMult delta) }
|
|
1280
|
+
|
|
1281
|
+
pos =
|
|
1282
|
+
Vec.add nextState.start nextState.offset
|
|
1283
|
+
|
|
1284
|
+
m =
|
|
1285
|
+
{ model | dragState = Just { ds | state = nextState } }
|
|
1286
|
+
in
|
|
1287
|
+
case mode of
|
|
1288
|
+
Pan ->
|
|
1289
|
+
update (Mod <| Project.Pan <| localDelta delta) m
|
|
1290
|
+
|
|
1291
|
+
Connect ->
|
|
1292
|
+
{ m
|
|
1293
|
+
| selected = getHighestShapeByPos model.project pos |> Maybe.map (\( i, _ ) -> [ i ]) |> Maybe.withDefault []
|
|
1294
|
+
}
|
|
1295
|
+
|> n
|
|
1296
|
+
|
|
1297
|
+
_ ->
|
|
1298
|
+
n m
|
|
1299
|
+
|
|
1300
|
+
FinishSelection s ->
|
|
1301
|
+
selectInsideSelection model s
|
|
1302
|
+
|> n
|
|
1303
|
+
|
|
1304
|
+
EndDrag ->
|
|
1305
|
+
case model.dragState of
|
|
1306
|
+
Nothing ->
|
|
1307
|
+
n model
|
|
1308
|
+
|
|
1309
|
+
Just { state, mode } ->
|
|
1310
|
+
let
|
|
1311
|
+
m =
|
|
1312
|
+
{ model | dragState = Nothing }
|
|
1313
|
+
in
|
|
1314
|
+
if Vec.length state.offset <= 5.0 then
|
|
1315
|
+
update (Click state.button state.shape state.start state.selecting) m
|
|
1316
|
+
|
|
1317
|
+
else
|
|
1318
|
+
case mode of
|
|
1319
|
+
CreateZone ->
|
|
1320
|
+
let
|
|
1321
|
+
s =
|
|
1322
|
+
{ start = state.start, end = Vec.add state.start state.offset }
|
|
1323
|
+
in
|
|
1324
|
+
if Rect.area (normalizeRect s.start s.end) > 100 then
|
|
1325
|
+
let
|
|
1326
|
+
zonePreset =
|
|
1327
|
+
model.zonePreset
|
|
1328
|
+
|
|
1329
|
+
r =
|
|
1330
|
+
normalizeRect (snapPos model s.start) (snapPos model s.end)
|
|
1331
|
+
|
|
1332
|
+
newZone =
|
|
1333
|
+
{ zonePreset
|
|
1334
|
+
| rect = r
|
|
1335
|
+
, id = "z" ++ String.fromInt model.id
|
|
1336
|
+
}
|
|
1337
|
+
in
|
|
1338
|
+
undoable
|
|
1339
|
+
{ m
|
|
1340
|
+
| project = Project.update (Project.Spawn [ Project.Zone newZone ]) m.project
|
|
1341
|
+
, selected = [ newZone.id ]
|
|
1342
|
+
, selectedCardModels = ""
|
|
1343
|
+
, id = model.id + 1
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
else
|
|
1347
|
+
n m
|
|
1348
|
+
|
|
1349
|
+
Select ->
|
|
1350
|
+
update (FinishSelection { start = state.start, end = Vec.add state.start state.offset }) m
|
|
1351
|
+
|
|
1352
|
+
Move ->
|
|
1353
|
+
{ m
|
|
1354
|
+
| project =
|
|
1355
|
+
Project.update
|
|
1356
|
+
(Project.UpdateShapes model.selected
|
|
1357
|
+
(\s ->
|
|
1358
|
+
case s of
|
|
1359
|
+
Project.Arrow a ->
|
|
1360
|
+
Project.Arrow a
|
|
1361
|
+
|
|
1362
|
+
_ ->
|
|
1363
|
+
draggedTransform model s
|
|
1364
|
+
|> Project.transformShape s
|
|
1365
|
+
)
|
|
1366
|
+
)
|
|
1367
|
+
model.project
|
|
1368
|
+
}
|
|
1369
|
+
|> undoable
|
|
1370
|
+
|
|
1371
|
+
-- { m
|
|
1372
|
+
-- | project = Project.update (Project.Move state.offset model.selected) model.project
|
|
1373
|
+
-- }
|
|
1374
|
+
-- |> undoable
|
|
1375
|
+
Pan ->
|
|
1376
|
+
undoable model
|
|
1377
|
+
|
|
1378
|
+
Turn ->
|
|
1379
|
+
{ m | project = Project.update (Project.Turn state.offset.x model.selected) model.project }
|
|
1380
|
+
|> undoable
|
|
1381
|
+
|
|
1382
|
+
Connect ->
|
|
1383
|
+
let
|
|
1384
|
+
pos =
|
|
1385
|
+
state.start |> Vec.add state.offset
|
|
1386
|
+
in
|
|
1387
|
+
toggleArrow m state.shape pos
|
|
1388
|
+
|> Maybe.map undoable
|
|
1389
|
+
|> Maybe.withDefault (n m)
|
|
1390
|
+
|
|
1391
|
+
-- ⣄⢀⡄⡠⠤⡀⡤⢄⠀⠠⡤⠀⡤⠤⠄⡄⠀⡄
|
|
1392
|
+
-- ⡇⠃⡇⡇⠀⡇⡇⠀⡇⠀⡇⠀⡗⠒⠀⠘⡜⠀
|
|
1393
|
+
-- ⠃⠀⠃⠑⠒⠁⠓⠊⠀⠐⠓⠀⠃⠀⠀⠀⠃⠀
|
|
1394
|
+
Mod message ->
|
|
1395
|
+
let
|
|
1396
|
+
project =
|
|
1397
|
+
Project.update message model.project
|
|
1398
|
+
in
|
|
1399
|
+
undoable { model | project = project }
|
|
1400
|
+
|
|
1401
|
+
ModRandom message ->
|
|
1402
|
+
let
|
|
1403
|
+
flags =
|
|
1404
|
+
model.flags
|
|
1405
|
+
|
|
1406
|
+
project =
|
|
1407
|
+
Project.updateWithSeed flags.seed message model.project
|
|
1408
|
+
in
|
|
1409
|
+
undoable { model | project = project, flags = { flags | seed = flags.seed + 1 } }
|
|
1410
|
+
|
|
1411
|
+
ApplySelector ->
|
|
1412
|
+
let
|
|
1413
|
+
nextModels =
|
|
1414
|
+
parseDeck model.selectedCardModels
|
|
1415
|
+
|> List.map2 (\id cardModel -> ( id, cardModel )) model.selected
|
|
1416
|
+
|
|
1417
|
+
project =
|
|
1418
|
+
Project.update
|
|
1419
|
+
(Project.UpdateShapes model.selected
|
|
1420
|
+
(\s ->
|
|
1421
|
+
case s of
|
|
1422
|
+
Project.Card c ->
|
|
1423
|
+
case
|
|
1424
|
+
nextModels
|
|
1425
|
+
|> List.filter (\( i, _ ) -> i == c.id)
|
|
1426
|
+
|> List.head
|
|
1427
|
+
of
|
|
1428
|
+
Just ( _, cardModel ) ->
|
|
1429
|
+
Project.Card { c | model = cardModel }
|
|
1430
|
+
|
|
1431
|
+
Nothing ->
|
|
1432
|
+
s
|
|
1433
|
+
|
|
1434
|
+
_ ->
|
|
1435
|
+
s
|
|
1436
|
+
)
|
|
1437
|
+
)
|
|
1438
|
+
model.project
|
|
1439
|
+
in
|
|
1440
|
+
undoable
|
|
1441
|
+
{ model
|
|
1442
|
+
| project = project
|
|
1443
|
+
, selectedCardModels = getSelectedCardModels project.cards model.selected
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
UpdateZoneString field string ->
|
|
1447
|
+
undoable
|
|
1448
|
+
{ model
|
|
1449
|
+
| zonePreset = updateZoneString field string model.zonePreset
|
|
1450
|
+
, project =
|
|
1451
|
+
Project.update
|
|
1452
|
+
(Project.UpdateShapes model.selected
|
|
1453
|
+
(\s ->
|
|
1454
|
+
case s of
|
|
1455
|
+
Project.Zone z ->
|
|
1456
|
+
Project.Zone <|
|
|
1457
|
+
updateZoneString field string z
|
|
1458
|
+
|
|
1459
|
+
_ ->
|
|
1460
|
+
s
|
|
1461
|
+
)
|
|
1462
|
+
)
|
|
1463
|
+
model.project
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
UpdateZoneRect id rc string ->
|
|
1467
|
+
undoable
|
|
1468
|
+
{ model
|
|
1469
|
+
| project =
|
|
1470
|
+
Project.update
|
|
1471
|
+
(Project.UpdateZone id
|
|
1472
|
+
(\z ->
|
|
1473
|
+
{ z
|
|
1474
|
+
| rect =
|
|
1475
|
+
modifyRect
|
|
1476
|
+
z.rect
|
|
1477
|
+
{ pos = cardSize, size = cardSize }
|
|
1478
|
+
rc
|
|
1479
|
+
(String.toFloat string |> Maybe.withDefault 0.0)
|
|
1480
|
+
|> minSized cardSize
|
|
1481
|
+
}
|
|
1482
|
+
)
|
|
1483
|
+
)
|
|
1484
|
+
model.project
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
SpawnStamp ->
|
|
1488
|
+
case model.stampInput of
|
|
1489
|
+
"" ->
|
|
1490
|
+
n model
|
|
1491
|
+
|
|
1492
|
+
s ->
|
|
1493
|
+
let
|
|
1494
|
+
stampPreset =
|
|
1495
|
+
model.stampPreset
|
|
1496
|
+
|
|
1497
|
+
text =
|
|
1498
|
+
stampPreset.text
|
|
1499
|
+
|
|
1500
|
+
stampId =
|
|
1501
|
+
"s" ++ String.fromInt model.id
|
|
1502
|
+
|
|
1503
|
+
stamp =
|
|
1504
|
+
{ stampPreset
|
|
1505
|
+
| text = { text | label = s }
|
|
1506
|
+
, id = stampId
|
|
1507
|
+
, pos = center model.project.viewport
|
|
1508
|
+
}
|
|
1509
|
+
in
|
|
1510
|
+
undoable
|
|
1511
|
+
{ model
|
|
1512
|
+
| project = Project.update (Project.Spawn [ Project.Stamp stamp ]) model.project
|
|
1513
|
+
, id = model.id + 1
|
|
1514
|
+
, stampInput = ""
|
|
1515
|
+
, selected = [ stampId ]
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
Spawn ->
|
|
1519
|
+
case parseDeck model.command of
|
|
1520
|
+
[] ->
|
|
1521
|
+
n model
|
|
1522
|
+
|
|
1523
|
+
ls ->
|
|
1524
|
+
let
|
|
1525
|
+
additions =
|
|
1526
|
+
List.length ls
|
|
1527
|
+
|
|
1528
|
+
indices =
|
|
1529
|
+
List.range model.id (model.id + additions)
|
|
1530
|
+
|
|
1531
|
+
newCards =
|
|
1532
|
+
List.map2 (\cm id -> makeCard id cm (Vec2 0.0 0.0)) ls indices
|
|
1533
|
+
|> positionCards
|
|
1534
|
+
model.spreadType
|
|
1535
|
+
model.spreadValue
|
|
1536
|
+
(Vec.sub (center model.project.viewport) (Vec.half cardSize))
|
|
1537
|
+
|> List.reverse
|
|
1538
|
+
|> List.map (\c -> Project.Card c)
|
|
1539
|
+
|
|
1540
|
+
selected =
|
|
1541
|
+
indices |> List.map (String.fromInt >> (\i -> "c" ++ i))
|
|
1542
|
+
in
|
|
1543
|
+
undoable
|
|
1544
|
+
{ model
|
|
1545
|
+
| project = Project.update (Project.Spawn newCards) model.project
|
|
1546
|
+
, command = ""
|
|
1547
|
+
, selected = selected
|
|
1548
|
+
, selectedCardModels = getSelectedCardModels model.project.cards selected
|
|
1549
|
+
, id = model.id + additions + 1
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
Remove ->
|
|
1553
|
+
if List.isEmpty model.selected then
|
|
1554
|
+
n model
|
|
1555
|
+
|
|
1556
|
+
else
|
|
1557
|
+
undoable
|
|
1558
|
+
{ model
|
|
1559
|
+
| project = Project.update (Project.Remove model.selected) model.project
|
|
1560
|
+
, selected = []
|
|
1561
|
+
, selectedCardModels = ""
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
SelectAll ->
|
|
1565
|
+
let
|
|
1566
|
+
selected =
|
|
1567
|
+
List.map .id cards
|
|
1568
|
+
in
|
|
1569
|
+
n
|
|
1570
|
+
{ model
|
|
1571
|
+
| selected = selected
|
|
1572
|
+
, selectedCardModels = getSelectedCardModels model.project.cards selected
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
SelectArrow id ->
|
|
1576
|
+
n { model | selected = [ id ], selectedCardModels = "" }
|
|
1577
|
+
|
|
1578
|
+
SetArrowWidth s ->
|
|
1579
|
+
updateArrows
|
|
1580
|
+
(\a -> { a | width = Maybe.withDefault a.width <| String.toFloat s })
|
|
1581
|
+
model
|
|
1582
|
+
|> undoable
|
|
1583
|
+
|
|
1584
|
+
SetArrowHead s ->
|
|
1585
|
+
updateArrows
|
|
1586
|
+
(\a -> { a | head = headFromString s })
|
|
1587
|
+
model
|
|
1588
|
+
|> undoable
|
|
1589
|
+
|
|
1590
|
+
SetArrowColor c ->
|
|
1591
|
+
updateArrows
|
|
1592
|
+
(\a -> { a | fill = colorFromString c })
|
|
1593
|
+
model
|
|
1594
|
+
|> undoable
|
|
1595
|
+
|
|
1596
|
+
SetStampLabel s ->
|
|
1597
|
+
updateStamps
|
|
1598
|
+
(\a ->
|
|
1599
|
+
let
|
|
1600
|
+
text =
|
|
1601
|
+
a.text
|
|
1602
|
+
in
|
|
1603
|
+
{ a | text = { text | label = s } }
|
|
1604
|
+
)
|
|
1605
|
+
model
|
|
1606
|
+
|> undoable
|
|
1607
|
+
|
|
1608
|
+
SetStampColor fill c ->
|
|
1609
|
+
if fill then
|
|
1610
|
+
updateStamps
|
|
1611
|
+
(\a -> { a | fill = colorFromString c })
|
|
1612
|
+
model
|
|
1613
|
+
|> undoable
|
|
1614
|
+
|
|
1615
|
+
else
|
|
1616
|
+
updateStamps
|
|
1617
|
+
(\a ->
|
|
1618
|
+
let
|
|
1619
|
+
t =
|
|
1620
|
+
a.text
|
|
1621
|
+
in
|
|
1622
|
+
{ a | text = { t | color = colorFromString c } }
|
|
1623
|
+
)
|
|
1624
|
+
model
|
|
1625
|
+
|> undoable
|
|
1626
|
+
|
|
1627
|
+
SetStampSize fill c ->
|
|
1628
|
+
if fill then
|
|
1629
|
+
updateStamps
|
|
1630
|
+
(\a -> { a | radius = String.toFloat c |> Maybe.withDefault defaultStamp.radius })
|
|
1631
|
+
model
|
|
1632
|
+
|> undoable
|
|
1633
|
+
|
|
1634
|
+
else
|
|
1635
|
+
updateStamps
|
|
1636
|
+
(\a ->
|
|
1637
|
+
let
|
|
1638
|
+
t =
|
|
1639
|
+
a.text
|
|
1640
|
+
in
|
|
1641
|
+
{ a | text = { t | size = String.toInt c |> Maybe.withDefault defaultStamp.text.size } }
|
|
1642
|
+
)
|
|
1643
|
+
model
|
|
1644
|
+
|> undoable
|
|
1645
|
+
|
|
1646
|
+
ToggleLaterMask layer ->
|
|
1647
|
+
let
|
|
1648
|
+
layers =
|
|
1649
|
+
model.unlockedLayers
|
|
1650
|
+
in
|
|
1651
|
+
undoable
|
|
1652
|
+
{ model
|
|
1653
|
+
| unlockedLayers =
|
|
1654
|
+
case layer of
|
|
1655
|
+
Zones ->
|
|
1656
|
+
{ layers | zones = not layers.zones }
|
|
1657
|
+
|
|
1658
|
+
Cards ->
|
|
1659
|
+
{ layers | cards = not layers.cards }
|
|
1660
|
+
|
|
1661
|
+
Arrows ->
|
|
1662
|
+
{ layers | arrows = not layers.arrows }
|
|
1663
|
+
|
|
1664
|
+
Stamps ->
|
|
1665
|
+
{ layers | stamps = not layers.stamps }
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
SpreadByType spreadType ->
|
|
1669
|
+
let
|
|
1670
|
+
next =
|
|
1671
|
+
{ model
|
|
1672
|
+
| spreadType = spreadType
|
|
1673
|
+
, spreadValue = updateSpreadValue model.spreadType spreadType model.spreadValue
|
|
1674
|
+
}
|
|
1675
|
+
in
|
|
1676
|
+
case List.head model.selected of
|
|
1677
|
+
Just head ->
|
|
1678
|
+
update (SpreadCards head) next
|
|
1679
|
+
|
|
1680
|
+
Nothing ->
|
|
1681
|
+
n next
|
|
1682
|
+
|
|
1683
|
+
SpreadByValue string ->
|
|
1684
|
+
let
|
|
1685
|
+
next =
|
|
1686
|
+
{ model | spreadValue = validateSpreadValue model.spreadType <| Maybe.withDefault 0 (String.toInt string) }
|
|
1687
|
+
in
|
|
1688
|
+
case List.head model.selected of
|
|
1689
|
+
Just head ->
|
|
1690
|
+
update (SpreadCards head) next
|
|
1691
|
+
|
|
1692
|
+
Nothing ->
|
|
1693
|
+
n next
|
|
1694
|
+
|
|
1695
|
+
-- FIX Editor - only move and snap selection leader
|
|
1696
|
+
SpreadCards _ ->
|
|
1697
|
+
let
|
|
1698
|
+
( targets, _ ) =
|
|
1699
|
+
partitionSelected model
|
|
1700
|
+
|
|
1701
|
+
topLeft =
|
|
1702
|
+
targets
|
|
1703
|
+
|> (if model.spreadValue < 0 then
|
|
1704
|
+
List.foldl
|
|
1705
|
+
(\c a -> Vec.max c.rect.pos a)
|
|
1706
|
+
(Vec2 -10000.0 -10000.0)
|
|
1707
|
+
|
|
1708
|
+
else
|
|
1709
|
+
List.foldl
|
|
1710
|
+
(\c a -> Vec.min c.rect.pos a)
|
|
1711
|
+
(Vec2 10000.0 10000.0)
|
|
1712
|
+
)
|
|
1713
|
+
|
|
1714
|
+
lowest =
|
|
1715
|
+
targets |> List.map .z |> List.foldl min 0
|
|
1716
|
+
|
|
1717
|
+
spreadCards =
|
|
1718
|
+
targets
|
|
1719
|
+
|> List.reverse
|
|
1720
|
+
|> positionCards
|
|
1721
|
+
model.spreadType
|
|
1722
|
+
model.spreadValue
|
|
1723
|
+
topLeft
|
|
1724
|
+
|> List.reverse
|
|
1725
|
+
|> List.indexedMap (\i c -> { c | z = lowest + i })
|
|
1726
|
+
|
|
1727
|
+
-- |> List.reverse
|
|
1728
|
+
in
|
|
1729
|
+
undoable
|
|
1730
|
+
{ model
|
|
1731
|
+
| project =
|
|
1732
|
+
model.project
|
|
1733
|
+
|> Project.update
|
|
1734
|
+
(Project.UpdateShapes (spreadCards |> List.map .id)
|
|
1735
|
+
(\s ->
|
|
1736
|
+
case s of
|
|
1737
|
+
Project.Card c ->
|
|
1738
|
+
case
|
|
1739
|
+
spreadCards |> List.filter (\sc -> sc.id == c.id) |> List.head
|
|
1740
|
+
of
|
|
1741
|
+
Just h ->
|
|
1742
|
+
Project.Card h
|
|
1743
|
+
|
|
1744
|
+
Nothing ->
|
|
1745
|
+
s
|
|
1746
|
+
|
|
1747
|
+
_ ->
|
|
1748
|
+
s
|
|
1749
|
+
)
|
|
1750
|
+
)
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
-- ⡤⠤⠄⡤⢄⠀⠠⡤⠀⠤⡤⠄⡠⠤⡀⡤⠤⡀
|
|
1754
|
+
-- ⡗⠒⠀⡇⠀⡇⠀⡇⠀⠀⡇⠀⡇⠀⡇⡧⡤⠃
|
|
1755
|
+
-- ⠓⠒⠂⠓⠊⠀⠐⠓⠀⠀⠃⠀⠑⠒⠁⠃⠈⠂
|
|
1756
|
+
Toggle t ->
|
|
1757
|
+
case t of
|
|
1758
|
+
Setting s ->
|
|
1759
|
+
case s of
|
|
1760
|
+
EmbedSymbols ->
|
|
1761
|
+
save { model | settings = { settings | embedSymbols = not settings.embedSymbols } }
|
|
1762
|
+
|
|
1763
|
+
StripData ->
|
|
1764
|
+
save { model | settings = { settings | stripData = not settings.stripData } }
|
|
1765
|
+
|
|
1766
|
+
MapColors ->
|
|
1767
|
+
save { model | settings = { settings | mapColors = not settings.mapColors } }
|
|
1768
|
+
|
|
1769
|
+
MapFont ->
|
|
1770
|
+
save { model | settings = { settings | mapFont = not settings.mapFont } }
|
|
1771
|
+
|
|
1772
|
+
Boxed ->
|
|
1773
|
+
undoable { model | boxed = not model.boxed }
|
|
1774
|
+
|
|
1775
|
+
BringForward ->
|
|
1776
|
+
save { model | moveToTop = not model.moveToTop }
|
|
1777
|
+
|
|
1778
|
+
StackSelect ->
|
|
1779
|
+
save { model | stackSelect = not model.stackSelect }
|
|
1780
|
+
|
|
1781
|
+
ShowPanel p ->
|
|
1782
|
+
save { model | panel = p }
|
|
1783
|
+
|
|
1784
|
+
Undo ->
|
|
1785
|
+
case model.undos of
|
|
1786
|
+
head :: rest ->
|
|
1787
|
+
decodeSaveFileIntoClearedModel
|
|
1788
|
+
{ model | undos = rest }
|
|
1789
|
+
(Json.Encode.encode 1 head)
|
|
1790
|
+
(\_ -> Cmd.none)
|
|
1791
|
+
|
|
1792
|
+
_ ->
|
|
1793
|
+
save model
|
|
1794
|
+
|
|
1795
|
+
UpdatePaletteColor target s ->
|
|
1796
|
+
let
|
|
1797
|
+
( m, _ ) =
|
|
1798
|
+
undoable { model | palette = setPaletteColor model.palette target s }
|
|
1799
|
+
in
|
|
1800
|
+
( m, outputString LoadPalette <| stringifyPalette m.palette )
|
|
1801
|
+
|
|
1802
|
+
TypeCommand s ->
|
|
1803
|
+
n { model | command = s }
|
|
1804
|
+
|
|
1805
|
+
TypeStamp s ->
|
|
1806
|
+
n { model | stampInput = s }
|
|
1807
|
+
|
|
1808
|
+
TypeSelector s ->
|
|
1809
|
+
n { model | selectedCardModels = s }
|
|
1810
|
+
|
|
1811
|
+
UpdateHeightPixels string ->
|
|
1812
|
+
let
|
|
1813
|
+
pixels =
|
|
1814
|
+
String.toInt string
|
|
1815
|
+
in
|
|
1816
|
+
case pixels of
|
|
1817
|
+
Just px ->
|
|
1818
|
+
n { model | settings = { settings | heightPixels = px } }
|
|
1819
|
+
|
|
1820
|
+
Nothing ->
|
|
1821
|
+
n model
|
|
1822
|
+
|
|
1823
|
+
-- UpdateSnap string ->
|
|
1824
|
+
-- case String.toInt string of
|
|
1825
|
+
-- Nothing ->
|
|
1826
|
+
-- n model
|
|
1827
|
+
-- Just i ->
|
|
1828
|
+
-- n { model | snap = Basics.clamp 0 8 i }
|
|
1829
|
+
-- UpdatePaletteString string ->
|
|
1830
|
+
-- n { model | paletteString = string }
|
|
1831
|
+
UpdatePalette palette ->
|
|
1832
|
+
let
|
|
1833
|
+
-- colors =
|
|
1834
|
+
-- String.split " " model.paletteString
|
|
1835
|
+
-- |> List.map String.trim
|
|
1836
|
+
-- |> List.filter (\s -> not <| String.isEmpty s)
|
|
1837
|
+
-- palette =
|
|
1838
|
+
-- if List.isEmpty colors then
|
|
1839
|
+
-- [ model.palette.black ]
|
|
1840
|
+
-- else
|
|
1841
|
+
-- colors
|
|
1842
|
+
( m, _ ) =
|
|
1843
|
+
undoable { model | palette = palette }
|
|
1844
|
+
in
|
|
1845
|
+
( m, outputString LoadPalette <| Json.Encode.encode 0 <| encodePalette palette )
|
|
1846
|
+
|
|
1847
|
+
TypeFont ft s ->
|
|
1848
|
+
let
|
|
1849
|
+
f =
|
|
1850
|
+
if s == "" then
|
|
1851
|
+
Nothing
|
|
1852
|
+
|
|
1853
|
+
else
|
|
1854
|
+
Just s
|
|
1855
|
+
in
|
|
1856
|
+
n
|
|
1857
|
+
{ model
|
|
1858
|
+
| settings =
|
|
1859
|
+
case ft of
|
|
1860
|
+
PrimaryFont ->
|
|
1861
|
+
{ settings
|
|
1862
|
+
| primaryFont = f
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
SecondaryFont ->
|
|
1866
|
+
{ settings
|
|
1867
|
+
| secondaryFont = f
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
UpdateFont ft ->
|
|
1872
|
+
( model
|
|
1873
|
+
, outputString (LoadFont ft) <|
|
|
1874
|
+
Maybe.withDefault ""
|
|
1875
|
+
(if ft == PrimaryFont then
|
|
1876
|
+
model.settings.primaryFont
|
|
1877
|
+
|
|
1878
|
+
else
|
|
1879
|
+
model.settings.secondaryFont
|
|
1880
|
+
)
|
|
1881
|
+
)
|
|
1882
|
+
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
-- OpenColorPicker c v ->
|
|
1886
|
+
-- n { model | pickedColor = Just c, colorPicker = ColorPicker.fromColor v }
|
|
1887
|
+
-- UpdateColorPicker cmsg ->
|
|
1888
|
+
-- case model.pickedColor of
|
|
1889
|
+
-- Nothing ->
|
|
1890
|
+
-- n model
|
|
1891
|
+
-- Just target ->
|
|
1892
|
+
-- let
|
|
1893
|
+
-- ( cp, color ) =
|
|
1894
|
+
-- ColorPicker.update cmsg model.colorPicker
|
|
1895
|
+
-- in
|
|
1896
|
+
-- let
|
|
1897
|
+
-- ( m, _ ) =
|
|
1898
|
+
-- undoable
|
|
1899
|
+
-- { model
|
|
1900
|
+
-- | colorPicker = cp
|
|
1901
|
+
-- , palette = setPaletteColor model.palette target color
|
|
1902
|
+
-- -- , pickedColor = Nothing
|
|
1903
|
+
-- }
|
|
1904
|
+
-- in
|
|
1905
|
+
-- UpdateCardField id field string ->
|
|
1906
|
+
-- let
|
|
1907
|
+
-- updateCard : Card -> Card
|
|
1908
|
+
-- updateCard c =
|
|
1909
|
+
-- if c.id == id then
|
|
1910
|
+
-- { c
|
|
1911
|
+
-- | model =
|
|
1912
|
+
-- case field of
|
|
1913
|
+
-- CardRank ->
|
|
1914
|
+
-- { rank =
|
|
1915
|
+
-- String.toInt string
|
|
1916
|
+
-- |> Maybe.withDefault (rankAsInt c.model.rank)
|
|
1917
|
+
-- |> rankFromInt
|
|
1918
|
+
-- , suit = c.model.suit
|
|
1919
|
+
-- }
|
|
1920
|
+
-- CardSuit ->
|
|
1921
|
+
-- { rank = c.model.rank
|
|
1922
|
+
-- , suit = Maybe.withDefault c.model.suit (suitFromString string)
|
|
1923
|
+
-- }
|
|
1924
|
+
-- }
|
|
1925
|
+
-- else
|
|
1926
|
+
-- c
|
|
1927
|
+
-- in
|
|
1928
|
+
-- mod { model | cards = model.cards |> List.map updateCard }
|
|
1929
|
+
|
|
1930
|
+
|
|
1931
|
+
snapFloat : Float -> Float -> Float
|
|
1932
|
+
snapFloat s f =
|
|
1933
|
+
round (f / s) * round s |> toFloat
|
|
1934
|
+
|
|
1935
|
+
|
|
1936
|
+
snapPos : Model -> Vec2 -> Vec2
|
|
1937
|
+
snapPos model pos =
|
|
1938
|
+
if not model.mods.ctrl then
|
|
1939
|
+
pos
|
|
1940
|
+
|
|
1941
|
+
else
|
|
1942
|
+
let
|
|
1943
|
+
s =
|
|
1944
|
+
toFloat 4
|
|
1945
|
+
|
|
1946
|
+
xs =
|
|
1947
|
+
cardSize.x / s
|
|
1948
|
+
|
|
1949
|
+
ys =
|
|
1950
|
+
cardSize.y / s
|
|
1951
|
+
in
|
|
1952
|
+
{ x = snapFloat xs pos.x, y = snapFloat ys pos.y }
|
|
1953
|
+
|
|
1954
|
+
|
|
1955
|
+
snapAngle : Model -> Float -> Float
|
|
1956
|
+
snapAngle _ angle =
|
|
1957
|
+
-- if model.snap == 0 then
|
|
1958
|
+
-- angle
|
|
1959
|
+
-- else
|
|
1960
|
+
-- snapFloat (360.0 / (toFloat model.snap * 4)) angle
|
|
1961
|
+
angle
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
---------------------------------------- ⡄⠀⡄⠠⡤⠀⡤⠤⠄⡄⠀⡄⡠⠤⡀
|
|
1966
|
+
---------------------------------------- ⢱⢰⠁⠀⡇⠀⡗⠒⠀⡇⡄⡇⠑⠒⡄
|
|
1967
|
+
---------------------------------------- ⠀⠃⠀⠐⠓⠀⠓⠒⠂⠋⠈⠃⠑⠒⠁
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
rectCoordInput : (RectCoordinate -> String -> msg) -> Float -> RectCoordinate -> Html msg
|
|
1971
|
+
rectCoordInput message current rc =
|
|
1972
|
+
-- coordString : RectCoordinate -> String
|
|
1973
|
+
-- coordString rc =
|
|
1974
|
+
-- case rc of
|
|
1975
|
+
-- PosX ->
|
|
1976
|
+
-- "X"
|
|
1977
|
+
-- PosY ->
|
|
1978
|
+
-- "Y"
|
|
1979
|
+
-- SizeX ->
|
|
1980
|
+
-- "W"
|
|
1981
|
+
-- SizeY ->
|
|
1982
|
+
-- "H"
|
|
1983
|
+
H.span
|
|
1984
|
+
[]
|
|
1985
|
+
-- [ H.h5 [] [ H.text <| coordString rc ]
|
|
1986
|
+
[ H.input
|
|
1987
|
+
[ HA.type_ "number"
|
|
1988
|
+
, HA.value <| String.fromFloat current
|
|
1989
|
+
, HA.class "narrow"
|
|
1990
|
+
, E.onInput (message rc)
|
|
1991
|
+
]
|
|
1992
|
+
[]
|
|
1993
|
+
]
|
|
1994
|
+
|
|
1995
|
+
|
|
1996
|
+
rectInput : (RectCoordinate -> String -> msg) -> Rect -> Html msg
|
|
1997
|
+
rectInput fn rect =
|
|
1998
|
+
H.div [ HA.style "display" "flex" ]
|
|
1999
|
+
[ H.span []
|
|
2000
|
+
[ rectCoordInput fn (rect.pos.x / cardSize.x) PosX
|
|
2001
|
+
, rectCoordInput fn (rect.pos.y / cardSize.y) PosY
|
|
2002
|
+
]
|
|
2003
|
+
, H.span []
|
|
2004
|
+
[ rectCoordInput fn (rect.size.x / cardSize.x) SizeX
|
|
2005
|
+
, rectCoordInput fn (rect.size.y / cardSize.y) SizeY
|
|
2006
|
+
]
|
|
2007
|
+
]
|
|
2008
|
+
|
|
2009
|
+
|
|
2010
|
+
viewportView : Rect -> Svg Msg
|
|
2011
|
+
viewportView viewport =
|
|
2012
|
+
let
|
|
2013
|
+
p =
|
|
2014
|
+
4.0
|
|
2015
|
+
|
|
2016
|
+
pad =
|
|
2017
|
+
Vec2 p p
|
|
2018
|
+
|
|
2019
|
+
size =
|
|
2020
|
+
viewport.size |> Vec.add (Vec.scale 2.0 pad)
|
|
2021
|
+
in
|
|
2022
|
+
Svg.rect
|
|
2023
|
+
[ num Attr.x (viewport.pos.x - p)
|
|
2024
|
+
, num Attr.y (viewport.pos.y - p)
|
|
2025
|
+
, size.x |> num Attr.width
|
|
2026
|
+
, size.y |> num Attr.height
|
|
2027
|
+
, Attr.stroke "#fff"
|
|
2028
|
+
, Attr.fill "none"
|
|
2029
|
+
, Attr.strokeWidth (String.fromFloat <| p * 2.0)
|
|
2030
|
+
, Attr.pointerEvents "none"
|
|
2031
|
+
]
|
|
2032
|
+
[]
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
viewportId : Model -> String
|
|
2036
|
+
viewportId m =
|
|
2037
|
+
"viewport" ++ String.fromInt m.flags.seed
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
clipPath : Model -> Svg Msg
|
|
2041
|
+
clipPath m =
|
|
2042
|
+
let
|
|
2043
|
+
size =
|
|
2044
|
+
m.project.viewport.size
|
|
2045
|
+
in
|
|
2046
|
+
Svg.clipPath
|
|
2047
|
+
[ Attr.id <| viewportId m ]
|
|
2048
|
+
[ Svg.rect
|
|
2049
|
+
[ num Attr.x m.project.viewport.pos.x
|
|
2050
|
+
, num Attr.y m.project.viewport.pos.y
|
|
2051
|
+
, num Attr.width size.x
|
|
2052
|
+
, num Attr.height size.y
|
|
2053
|
+
]
|
|
2054
|
+
[]
|
|
2055
|
+
]
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
maybeHtml : Bool -> Html Msg -> Html Msg
|
|
2059
|
+
maybeHtml show element =
|
|
2060
|
+
if show then
|
|
2061
|
+
element
|
|
2062
|
+
|
|
2063
|
+
else
|
|
2064
|
+
H.text ""
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
-- isJust : Maybe a -> Bool
|
|
2069
|
+
-- isJust m =
|
|
2070
|
+
-- case m of
|
|
2071
|
+
-- Just _ ->
|
|
2072
|
+
-- True
|
|
2073
|
+
-- Nothing ->
|
|
2074
|
+
-- False
|
|
2075
|
+
|
|
2076
|
+
|
|
2077
|
+
enumOption : (String -> msg) -> (a -> String) -> (a -> Bool) -> List a -> Html msg
|
|
2078
|
+
enumOption message asString isCurrent options =
|
|
2079
|
+
H.select [ E.onInput message ]
|
|
2080
|
+
(List.map
|
|
2081
|
+
(\t ->
|
|
2082
|
+
let
|
|
2083
|
+
string =
|
|
2084
|
+
asString t
|
|
2085
|
+
in
|
|
2086
|
+
H.option
|
|
2087
|
+
[ HA.value string
|
|
2088
|
+
, isCurrent t |> HA.selected
|
|
2089
|
+
]
|
|
2090
|
+
[ H.text string ]
|
|
2091
|
+
)
|
|
2092
|
+
options
|
|
2093
|
+
)
|
|
2094
|
+
|
|
2095
|
+
|
|
2096
|
+
enumButtons : (a -> msg) -> (a -> String) -> (a -> Bool) -> List ( a, String ) -> Html msg
|
|
2097
|
+
enumButtons message asString isCurrent options =
|
|
2098
|
+
H.span
|
|
2099
|
+
[]
|
|
2100
|
+
(List.map
|
|
2101
|
+
(\( t, label ) ->
|
|
2102
|
+
H.button
|
|
2103
|
+
[ E.onClick (message t)
|
|
2104
|
+
, HA.class <|
|
|
2105
|
+
if isCurrent t then
|
|
2106
|
+
""
|
|
2107
|
+
|
|
2108
|
+
else
|
|
2109
|
+
"inactive"
|
|
2110
|
+
, HA.class "enum-button"
|
|
2111
|
+
, HA.title (asString t)
|
|
2112
|
+
]
|
|
2113
|
+
[ H.text label ]
|
|
2114
|
+
)
|
|
2115
|
+
options
|
|
2116
|
+
)
|
|
2117
|
+
|
|
2118
|
+
|
|
2119
|
+
|
|
2120
|
+
-- TODO UX - fix inputs not allowing backspace properly
|
|
2121
|
+
-- TODO UX - better number inputs?
|
|
2122
|
+
|
|
2123
|
+
|
|
2124
|
+
fieldInputRange : (String -> Msg) -> Maybe Int -> Maybe Int -> String -> Int -> Html Msg
|
|
2125
|
+
fieldInputRange msg mn mx n v =
|
|
2126
|
+
H.span
|
|
2127
|
+
[]
|
|
2128
|
+
[ H.h5 [ HA.style "display" "inline-block" ] [ H.text n ]
|
|
2129
|
+
, H.input
|
|
2130
|
+
(List.concat
|
|
2131
|
+
[ mn |> Maybe.map (\i -> [ HA.min <| String.fromInt i ]) |> Maybe.withDefault []
|
|
2132
|
+
, mx |> Maybe.map (\i -> [ HA.max <| String.fromInt i ]) |> Maybe.withDefault []
|
|
2133
|
+
, [ E.onInput msg
|
|
2134
|
+
, HA.value <| String.fromInt v
|
|
2135
|
+
, HA.type_ "number"
|
|
2136
|
+
, HA.class "narrow"
|
|
2137
|
+
, style "width" "6em"
|
|
2138
|
+
]
|
|
2139
|
+
]
|
|
2140
|
+
)
|
|
2141
|
+
[]
|
|
2142
|
+
]
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
fieldInput : (String -> Msg) -> String -> Int -> Html Msg
|
|
2146
|
+
fieldInput msg =
|
|
2147
|
+
fieldInputRange msg Nothing Nothing
|
|
2148
|
+
|
|
2149
|
+
|
|
2150
|
+
colorInput : Model -> (String -> Msg) -> String -> Color -> Html Msg
|
|
2151
|
+
colorInput model msg n v =
|
|
2152
|
+
H.span
|
|
2153
|
+
[ HA.class "inpector-column" ]
|
|
2154
|
+
[ H.text n
|
|
2155
|
+
, colorPickerWithTransparent model.palette msg v
|
|
2156
|
+
]
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
arrowInspector : Model -> Arrow -> Html Msg
|
|
2160
|
+
arrowInspector model a =
|
|
2161
|
+
H.div []
|
|
2162
|
+
[ H.text "" -- <| "arrow " ++ a.from ++ "->" ++ a.to
|
|
2163
|
+
, colorPicker False model.palette SetArrowColor a.fill
|
|
2164
|
+
, H.input
|
|
2165
|
+
[ HA.type_ "number"
|
|
2166
|
+
, E.onInput SetArrowWidth
|
|
2167
|
+
, HA.min "0"
|
|
2168
|
+
, HA.max "100"
|
|
2169
|
+
, HA.class "narrow"
|
|
2170
|
+
, HA.value <| String.fromFloat a.width
|
|
2171
|
+
]
|
|
2172
|
+
[]
|
|
2173
|
+
, enumOption
|
|
2174
|
+
SetArrowHead
|
|
2175
|
+
headString
|
|
2176
|
+
(\h -> h == a.head)
|
|
2177
|
+
[ Triangle, None, Cross ]
|
|
2178
|
+
]
|
|
2179
|
+
|
|
2180
|
+
|
|
2181
|
+
stampInspector : Model -> Stamp -> Html Msg
|
|
2182
|
+
stampInspector model a =
|
|
2183
|
+
let
|
|
2184
|
+
symbolPicker ls =
|
|
2185
|
+
H.span
|
|
2186
|
+
[ HA.class "inspector-row"
|
|
2187
|
+
, HA.style "gap" "0.04rem"
|
|
2188
|
+
, HA.style "flex-wrap" "wrap"
|
|
2189
|
+
, HA.style "justify-content" "start"
|
|
2190
|
+
]
|
|
2191
|
+
<|
|
|
2192
|
+
List.map
|
|
2193
|
+
(\s ->
|
|
2194
|
+
H.button
|
|
2195
|
+
[ E.onClick (SetStampLabel s)
|
|
2196
|
+
, HA.style "min-width" "1.5rem"
|
|
2197
|
+
]
|
|
2198
|
+
[ H.text s ]
|
|
2199
|
+
)
|
|
2200
|
+
ls
|
|
2201
|
+
in
|
|
2202
|
+
H.div []
|
|
2203
|
+
[ H.h5 [] [ H.text "Stamp" ]
|
|
2204
|
+
|
|
2205
|
+
-- TODO UX - disallow same bg and fg for Stamps and Zones?
|
|
2206
|
+
, colorPicker True model.palette (SetStampColor True) a.fill
|
|
2207
|
+
, H.input
|
|
2208
|
+
[ HA.type_ "number"
|
|
2209
|
+
, E.onInput (SetStampSize True)
|
|
2210
|
+
, HA.min "0"
|
|
2211
|
+
, HA.max "100"
|
|
2212
|
+
, HA.class "narrow"
|
|
2213
|
+
, HA.value <| String.fromFloat a.radius
|
|
2214
|
+
]
|
|
2215
|
+
[]
|
|
2216
|
+
, colorPicker False model.palette (SetStampColor False) a.text.color
|
|
2217
|
+
, H.input
|
|
2218
|
+
[ HA.type_ "number"
|
|
2219
|
+
, E.onInput (SetStampSize False)
|
|
2220
|
+
, HA.min "0"
|
|
2221
|
+
, HA.max "400"
|
|
2222
|
+
, HA.class "narrow"
|
|
2223
|
+
, HA.value <| String.fromInt a.text.size
|
|
2224
|
+
]
|
|
2225
|
+
[]
|
|
2226
|
+
, H.input
|
|
2227
|
+
[ E.onInput SetStampLabel
|
|
2228
|
+
, HA.value a.text.label
|
|
2229
|
+
]
|
|
2230
|
+
[]
|
|
2231
|
+
, H.div [ HA.class "inpector-column" ]
|
|
2232
|
+
[ symbolPicker [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" ]
|
|
2233
|
+
, symbolPicker [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" ]
|
|
2234
|
+
|
|
2235
|
+
-- TODO add geometric symbols and stamps
|
|
2236
|
+
, symbolPicker <|
|
|
2237
|
+
List.concat
|
|
2238
|
+
[ [ "✓", "✔", "✔️", "✅", "☑️", "☑", "🗹" ]
|
|
2239
|
+
, [ "✖️", "✖", "❌", "𐄂", "❎", "🚫", "🔒", "⛔", "🛑" ]
|
|
2240
|
+
, [ "❗"
|
|
2241
|
+
, "❓"
|
|
2242
|
+
, "⚠️"
|
|
2243
|
+
, "💡"
|
|
2244
|
+
, "🧠"
|
|
2245
|
+
, "🎯"
|
|
2246
|
+
, "🧩"
|
|
2247
|
+
]
|
|
2248
|
+
, [ "🏆", "🏅" ]
|
|
2249
|
+
, [ "⭐", "🌟", "✪", "★", "☆", "∗" ]
|
|
2250
|
+
]
|
|
2251
|
+
]
|
|
2252
|
+
, H.a [ HA.href "https://www.compart.com/en/unicode", HA.target "_blank" ] [ H.text "find more symbols to use..." ]
|
|
2253
|
+
|
|
2254
|
+
-- , enumOption
|
|
2255
|
+
-- SetArrowHead
|
|
2256
|
+
-- headString
|
|
2257
|
+
-- (\h -> h == a.head)
|
|
2258
|
+
-- [ Triangle, None, Cross ]
|
|
2259
|
+
]
|
|
2260
|
+
|
|
2261
|
+
|
|
2262
|
+
zoneInspector : Model -> Bool -> Zone -> Html Msg
|
|
2263
|
+
zoneInspector model multi z =
|
|
2264
|
+
H.div
|
|
2265
|
+
[ HA.class "inspector-column"
|
|
2266
|
+
]
|
|
2267
|
+
[ H.span [ HA.class "inspector-column" ]
|
|
2268
|
+
[ H.h5 [] [ H.text "Label" ]
|
|
2269
|
+
, H.textarea
|
|
2270
|
+
[ E.onInput (UpdateZoneString ZoneLabel)
|
|
2271
|
+
, HA.value z.text.label
|
|
2272
|
+
, HA.disabled multi
|
|
2273
|
+
, HA.classList [ ( "hidden", multi ) ]
|
|
2274
|
+
]
|
|
2275
|
+
[]
|
|
2276
|
+
]
|
|
2277
|
+
, H.div [ HA.class "inspector-row" ]
|
|
2278
|
+
[ H.div []
|
|
2279
|
+
[ enumOption
|
|
2280
|
+
(UpdateZoneString ZoneAnchor)
|
|
2281
|
+
anchorString
|
|
2282
|
+
(\a -> a == z.text.anchor)
|
|
2283
|
+
[ Zone.Center, Zone.Top, Zone.Bottom, Zone.Left, Zone.Right ]
|
|
2284
|
+
]
|
|
2285
|
+
, fieldInput
|
|
2286
|
+
(UpdateZoneString ZoneFontSize)
|
|
2287
|
+
"🗚"
|
|
2288
|
+
z.text.size
|
|
2289
|
+
]
|
|
2290
|
+
|
|
2291
|
+
-- TODO toggle secondary font
|
|
2292
|
+
, H.span [ HA.class "inspector-column" ]
|
|
2293
|
+
[ H.h5 [] [ H.text "Font" ]
|
|
2294
|
+
, H.div
|
|
2295
|
+
[ HA.class "inpector-column" ]
|
|
2296
|
+
[ enumOption
|
|
2297
|
+
(UpdateZoneString ZoneFont)
|
|
2298
|
+
(\b ->
|
|
2299
|
+
if b then
|
|
2300
|
+
"Primary"
|
|
2301
|
+
|
|
2302
|
+
else
|
|
2303
|
+
"Secondary"
|
|
2304
|
+
)
|
|
2305
|
+
(\a -> a == z.text.primary)
|
|
2306
|
+
[ True, False ]
|
|
2307
|
+
, colorPicker False model.palette (UpdateZoneString ZoneText) z.text.color
|
|
2308
|
+
]
|
|
2309
|
+
]
|
|
2310
|
+
, H.span [ HA.class "inspector-column" ]
|
|
2311
|
+
[ H.h5 [] [ H.text "Fill ◼" ]
|
|
2312
|
+
, H.div []
|
|
2313
|
+
[ colorInput model (UpdateZoneString ZoneFill) "" z.fill
|
|
2314
|
+
]
|
|
2315
|
+
]
|
|
2316
|
+
, H.span [ HA.class "inspector-column" ]
|
|
2317
|
+
[ H.h5 [] [ H.text "Border ◻" ]
|
|
2318
|
+
, H.div []
|
|
2319
|
+
[ colorInput model (UpdateZoneString ZoneStroke) "" z.border.color
|
|
2320
|
+
, H.div [ HA.class "inspector-row" ]
|
|
2321
|
+
[ fieldInput (UpdateZoneString ZoneWidth) "Width" z.border.width
|
|
2322
|
+
, fieldInput (UpdateZoneString ZoneDash) "Dash ⬚" z.border.dash
|
|
2323
|
+
]
|
|
2324
|
+
]
|
|
2325
|
+
]
|
|
2326
|
+
|
|
2327
|
+
-- , H.div [ HA.class "inspector-column" ]
|
|
2328
|
+
, H.div [ HA.classList [ ( "inspector-column", True ), ( "hidden", multi ) ] ]
|
|
2329
|
+
[ rectInput (UpdateZoneRect z.id) z.rect
|
|
2330
|
+
]
|
|
2331
|
+
, H.span [ HA.class "inspector-column" ]
|
|
2332
|
+
[ H.h5 [] [ H.text "Grid" ]
|
|
2333
|
+
, H.div [ HA.class "inspector-row" ]
|
|
2334
|
+
[ fieldInputRange (UpdateZoneString ZoneColumns) (Just 0) (Just 32) "" z.grid.columns
|
|
2335
|
+
, fieldInputRange (UpdateZoneString ZoneRows) (Just 0) (Just 32) "" z.grid.rows
|
|
2336
|
+
]
|
|
2337
|
+
]
|
|
2338
|
+
]
|
|
2339
|
+
|
|
2340
|
+
|
|
2341
|
+
cardInspector : Model -> Bool -> Card -> Html Msg
|
|
2342
|
+
cardInspector model multi _ =
|
|
2343
|
+
inspectorColumn
|
|
2344
|
+
[]
|
|
2345
|
+
[ spreadView model
|
|
2346
|
+
|> maybeHtml multi
|
|
2347
|
+
]
|
|
2348
|
+
|
|
2349
|
+
|
|
2350
|
+
selectedViews : Model -> Html Msg
|
|
2351
|
+
selectedViews model =
|
|
2352
|
+
let
|
|
2353
|
+
(Shapes zones cards arrows stamps) =
|
|
2354
|
+
model.project
|
|
2355
|
+
|> mergeAll
|
|
2356
|
+
|> List.filter (\( i, _ ) -> List.member i model.selected)
|
|
2357
|
+
|> splitAll
|
|
2358
|
+
in
|
|
2359
|
+
[ zones |> List.head |> Maybe.map (zoneInspector model (1 < List.length zones)) |> Maybe.withDefault (H.text "")
|
|
2360
|
+
, arrows |> List.head |> Maybe.map (arrowInspector model) |> Maybe.withDefault (H.text "")
|
|
2361
|
+
, cards |> List.head |> Maybe.map (cardInspector model (1 < List.length cards)) |> Maybe.withDefault (H.text "")
|
|
2362
|
+
, stamps |> List.head |> Maybe.map (stampInspector model) |> Maybe.withDefault (H.text "")
|
|
2363
|
+
]
|
|
2364
|
+
|> H.div
|
|
2365
|
+
[ HA.class "inspector-column"
|
|
2366
|
+
]
|
|
2367
|
+
|
|
2368
|
+
|
|
2369
|
+
fileViews : Model -> List (Html Msg)
|
|
2370
|
+
fileViews model =
|
|
2371
|
+
if List.member model.fileState [ New, NewModified ] then
|
|
2372
|
+
[ H.form
|
|
2373
|
+
[ E.onSubmit ChangeFilename ]
|
|
2374
|
+
[ H.input [ E.onInput TypeFilename, HA.value model.nameInput ] [] ]
|
|
2375
|
+
, H.button [ E.onClick SaveProject ] [ H.text "save" ]
|
|
2376
|
+
]
|
|
2377
|
+
|
|
2378
|
+
else
|
|
2379
|
+
[ enumOption
|
|
2380
|
+
LoadProject
|
|
2381
|
+
(\s -> s)
|
|
2382
|
+
(\t -> t == model.name)
|
|
2383
|
+
model.files
|
|
2384
|
+
, case model.fileState of
|
|
2385
|
+
Modified ->
|
|
2386
|
+
H.button [ E.onClick SaveProject ] [ H.text "save" ]
|
|
2387
|
+
|
|
2388
|
+
_ ->
|
|
2389
|
+
H.button [ E.onClick NewProject ] [ H.text "new" ]
|
|
2390
|
+
|
|
2391
|
+
-- TODO add rename option
|
|
2392
|
+
, H.button [ E.onClick DuplicateProject ] [ H.text "clone" ]
|
|
2393
|
+
]
|
|
2394
|
+
|
|
2395
|
+
|
|
2396
|
+
selectedRectsView : Model -> Svg msg
|
|
2397
|
+
selectedRectsView model =
|
|
2398
|
+
let
|
|
2399
|
+
selected =
|
|
2400
|
+
List.filterMap (getShapeById model.project) model.selected
|
|
2401
|
+
|
|
2402
|
+
selection =
|
|
2403
|
+
case model.dragState of
|
|
2404
|
+
Nothing ->
|
|
2405
|
+
[]
|
|
2406
|
+
|
|
2407
|
+
Just { state, mode } ->
|
|
2408
|
+
if mode == Select || mode == CreateZone then
|
|
2409
|
+
let
|
|
2410
|
+
start =
|
|
2411
|
+
state.start
|
|
2412
|
+
|
|
2413
|
+
end =
|
|
2414
|
+
state.start |> Vec.add state.offset
|
|
2415
|
+
in
|
|
2416
|
+
[ ( normalizeRect start end, Vec.vec2 0.5 0.5, 0 ) ]
|
|
2417
|
+
-- [ ( normalizeRect (snapPos model start) (snapPos model end), Vec.vec2 0.5 0.5, 0 ) ]
|
|
2418
|
+
|
|
2419
|
+
else
|
|
2420
|
+
[]
|
|
2421
|
+
|
|
2422
|
+
transforms =
|
|
2423
|
+
List.map
|
|
2424
|
+
(draggedTransform model)
|
|
2425
|
+
selected
|
|
2426
|
+
++ selection
|
|
2427
|
+
in
|
|
2428
|
+
transforms
|
|
2429
|
+
|> List.map
|
|
2430
|
+
(\( rect, pivot, a ) ->
|
|
2431
|
+
let
|
|
2432
|
+
pos =
|
|
2433
|
+
-- rect.pos
|
|
2434
|
+
case model.dragState of
|
|
2435
|
+
Nothing ->
|
|
2436
|
+
rect.pos
|
|
2437
|
+
|
|
2438
|
+
Just { mode } ->
|
|
2439
|
+
if mode == Move then
|
|
2440
|
+
snapPos model rect.pos
|
|
2441
|
+
|
|
2442
|
+
else
|
|
2443
|
+
rect.pos
|
|
2444
|
+
|
|
2445
|
+
r =
|
|
2446
|
+
enlarge 15 { rect | pos = pos }
|
|
2447
|
+
in
|
|
2448
|
+
Svg.rect
|
|
2449
|
+
[ num Attr.width r.size.x
|
|
2450
|
+
, num Attr.height r.size.y
|
|
2451
|
+
, Attr.rx cornerRadius
|
|
2452
|
+
, Attr.transform <|
|
|
2453
|
+
pivotRotate pivot r.pos r.size a
|
|
2454
|
+
++ translateXY r.pos
|
|
2455
|
+
, Attr.fill "none"
|
|
2456
|
+
, Attr.strokeDasharray ((String.fromFloat <| model.project.viewport.size.y / 100.0) ++ "px")
|
|
2457
|
+
, Attr.stroke "var(--fg)"
|
|
2458
|
+
, Attr.strokeWidth ((String.fromFloat <| model.project.viewport.size.y / 200.0) ++ "px")
|
|
2459
|
+
|
|
2460
|
+
-- , Attr.fill <| model.palette.yellow ++ "55"
|
|
2461
|
+
]
|
|
2462
|
+
[]
|
|
2463
|
+
)
|
|
2464
|
+
|> Svg.g
|
|
2465
|
+
[ style "pointer-events" "none"
|
|
2466
|
+
, Attr.class "selection"
|
|
2467
|
+
, removeOnExport
|
|
2468
|
+
]
|
|
2469
|
+
|
|
2470
|
+
|
|
2471
|
+
|
|
2472
|
+
-- debugRect : Rect -> Svg Msg
|
|
2473
|
+
-- debugRect r =
|
|
2474
|
+
-- Svg.rect
|
|
2475
|
+
-- -- [ Attr.transform <| translate r.pos.x r.pos.y
|
|
2476
|
+
-- [ num Attr.x r.pos.x
|
|
2477
|
+
-- , num Attr.y r.pos.y
|
|
2478
|
+
-- , num Attr.width r.size.x
|
|
2479
|
+
-- , num Attr.height r.size.y
|
|
2480
|
+
-- , Attr.stroke "#f0f"
|
|
2481
|
+
-- , Attr.fill "transparent"
|
|
2482
|
+
-- , Attr.class "debugRect"
|
|
2483
|
+
-- , Attr.pointerEvents "none"
|
|
2484
|
+
-- ]
|
|
2485
|
+
-- []
|
|
2486
|
+
-- debugPoint : Vec2 -> Svg Msg
|
|
2487
|
+
-- debugPoint p =
|
|
2488
|
+
-- Svg.circle
|
|
2489
|
+
-- [ Attr.transform <| translate p.x p.y
|
|
2490
|
+
-- , num Attr.r 10.0
|
|
2491
|
+
-- , Attr.fill "#f0f"
|
|
2492
|
+
-- ]
|
|
2493
|
+
-- []
|
|
2494
|
+
-- debugCards : List Card -> Svg Msg
|
|
2495
|
+
-- debugCards cards =
|
|
2496
|
+
-- -- cardxs
|
|
2497
|
+
-- -- -- |> List.map (\c -> getShapeBounds c |> debugRect)
|
|
2498
|
+
-- -- -- |> List.map (\c -> rect2 c.position cardSize |> toPoints |> List.map (Vec.rotateAround (pivotPos c) c.rotation >> debugPoint))
|
|
2499
|
+
-- -- -- |> List.concat
|
|
2500
|
+
-- -- |>
|
|
2501
|
+
-- Svg.g
|
|
2502
|
+
-- []
|
|
2503
|
+
-- []
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
svgView : Model -> Html Msg
|
|
2507
|
+
svgView model =
|
|
2508
|
+
Svg.svg
|
|
2509
|
+
[ HA.attribute "tabindex" "0"
|
|
2510
|
+
, Attr.id "view"
|
|
2511
|
+
, onWheel (Mod << Project.Zoom)
|
|
2512
|
+
, preventContextMenu
|
|
2513
|
+
, dragEvent "background"
|
|
2514
|
+
, onMouseUp EndDrag
|
|
2515
|
+
|
|
2516
|
+
-- , Draggable.customMouseTrigger "background" (positionDecoder "offset") CustomMouseDown
|
|
2517
|
+
-- , onMouseUp StopDragging
|
|
2518
|
+
-- , E.onDoubleClick SelectAll
|
|
2519
|
+
-- , HA.attribute "data-view" <| Debug.toString model.project.viewport
|
|
2520
|
+
-- , HA.attribute "data-mouse" <| Debug.toString model.mouse
|
|
2521
|
+
, HA.attribute "data-project" <| Json.Encode.encode 0 <| serializeModel model
|
|
2522
|
+
, Attr.viewBox <| rectString model.project.viewport
|
|
2523
|
+
|
|
2524
|
+
-- , if model.boxed then
|
|
2525
|
+
-- else
|
|
2526
|
+
-- Attr.visibility "visible"
|
|
2527
|
+
, Attr.overflow "hidden"
|
|
2528
|
+
]
|
|
2529
|
+
(List.append
|
|
2530
|
+
includes
|
|
2531
|
+
([ ( False, H.text "" ) -- ( True, cardMasks model )
|
|
2532
|
+
, ( True, arrowDefs )
|
|
2533
|
+
, ( True, background model )
|
|
2534
|
+
, ( True, clipPath model )
|
|
2535
|
+
, ( True, zonesView model )
|
|
2536
|
+
, maybeIncompleteZone model
|
|
2537
|
+
|
|
2538
|
+
-- , ( False, debugCards model.cards )
|
|
2539
|
+
, ( True, cardsView model )
|
|
2540
|
+
, ( True, arrowsView model )
|
|
2541
|
+
, ( True, stampsView model )
|
|
2542
|
+
, ( True, incompleteArrow model )
|
|
2543
|
+
|
|
2544
|
+
-- , ( False
|
|
2545
|
+
-- , case model.dragState of
|
|
2546
|
+
-- Just {state, _} ->
|
|
2547
|
+
-- Svg.circle
|
|
2548
|
+
-- [ Attr.transform <| translateXY
|
|
2549
|
+
-- , Attr.r "10"
|
|
2550
|
+
-- , Attr.fill "#f00"
|
|
2551
|
+
-- , Attr.pointerEvents "none"
|
|
2552
|
+
-- ]
|
|
2553
|
+
-- [ Svg.text id
|
|
2554
|
+
-- ]
|
|
2555
|
+
-- _ ->
|
|
2556
|
+
-- Svg.g [] []
|
|
2557
|
+
-- )
|
|
2558
|
+
, ( not model.boxed, viewportView model.project.viewport )
|
|
2559
|
+
, ( True, selectedRectsView model )
|
|
2560
|
+
]
|
|
2561
|
+
|> List.filter Tuple.first
|
|
2562
|
+
|> List.map Tuple.second
|
|
2563
|
+
)
|
|
2564
|
+
)
|
|
2565
|
+
|
|
2566
|
+
|
|
2567
|
+
maybeIncompleteZone : Model -> ( Bool, Html Msg )
|
|
2568
|
+
maybeIncompleteZone model =
|
|
2569
|
+
model.dragState
|
|
2570
|
+
|> Maybe.andThen
|
|
2571
|
+
(\{ state, mode } ->
|
|
2572
|
+
if mode == CreateZone then
|
|
2573
|
+
let
|
|
2574
|
+
s =
|
|
2575
|
+
{ start = state.start, end = Vec.add state.start state.offset }
|
|
2576
|
+
|
|
2577
|
+
zonePreset =
|
|
2578
|
+
model.zonePreset
|
|
2579
|
+
|
|
2580
|
+
r =
|
|
2581
|
+
normalizeRect (snapPos model s.start) (snapPos model s.end)
|
|
2582
|
+
|
|
2583
|
+
newZone =
|
|
2584
|
+
{ zonePreset
|
|
2585
|
+
| rect = r
|
|
2586
|
+
, id = "z" ++ String.fromInt model.id
|
|
2587
|
+
}
|
|
2588
|
+
in
|
|
2589
|
+
Just
|
|
2590
|
+
( True
|
|
2591
|
+
, Tuple.second <| zoneView model False newZone
|
|
2592
|
+
)
|
|
2593
|
+
|
|
2594
|
+
else
|
|
2595
|
+
Nothing
|
|
2596
|
+
)
|
|
2597
|
+
|> Maybe.withDefault ( False, H.text "" )
|
|
2598
|
+
|
|
2599
|
+
|
|
2600
|
+
spreadView : Model -> Html Msg
|
|
2601
|
+
spreadView model =
|
|
2602
|
+
H.span
|
|
2603
|
+
[ HA.class "inspector-column" ]
|
|
2604
|
+
[ -- H.button
|
|
2605
|
+
-- [ E.onClick (SpreadCards "") ]
|
|
2606
|
+
-- [ H.text "spread" ],
|
|
2607
|
+
enumButtons
|
|
2608
|
+
SpreadByType
|
|
2609
|
+
spreadAsString
|
|
2610
|
+
(\t -> t == model.spreadType)
|
|
2611
|
+
[ ( Horizontal, "🠚" ), ( Vertical, "🠛" ), ( Diagonal, "➘" ), ( Rows, "⇉" ), ( Columns, "⇊" ), ( Stack, "🂠" ), ( Fan, "⌓" ) ]
|
|
2612
|
+
, H.input
|
|
2613
|
+
[ E.onInput SpreadByValue
|
|
2614
|
+
, HA.type_ "number"
|
|
2615
|
+
, HA.class "narrow"
|
|
2616
|
+
, HA.value (String.fromInt model.spreadValue)
|
|
2617
|
+
]
|
|
2618
|
+
[]
|
|
2619
|
+
, H.button
|
|
2620
|
+
[ E.onClick (ModRandom (Project.Shuffle model.selected)) ]
|
|
2621
|
+
[ H.text "shuffle" ]
|
|
2622
|
+
]
|
|
2623
|
+
|
|
2624
|
+
|
|
2625
|
+
paletteSettings : Model -> Html Msg
|
|
2626
|
+
paletteSettings model =
|
|
2627
|
+
let
|
|
2628
|
+
themes =
|
|
2629
|
+
Theme.themes
|
|
2630
|
+
|> List.map
|
|
2631
|
+
(\( name, p ) ->
|
|
2632
|
+
H.button
|
|
2633
|
+
[ E.onClick (UpdatePalette p)
|
|
2634
|
+
]
|
|
2635
|
+
[ H.text name ]
|
|
2636
|
+
)
|
|
2637
|
+
|
|
2638
|
+
colors =
|
|
2639
|
+
Palette.paletteAsList model.palette
|
|
2640
|
+
|> List.map
|
|
2641
|
+
(\( color, value ) ->
|
|
2642
|
+
let
|
|
2643
|
+
name =
|
|
2644
|
+
Palette.colorAsString color
|
|
2645
|
+
in
|
|
2646
|
+
H.span
|
|
2647
|
+
[ HA.style "white-space" "nowrap" ]
|
|
2648
|
+
[ H.text <| String.toUpper name
|
|
2649
|
+
, H.input
|
|
2650
|
+
[ HA.type_ "color"
|
|
2651
|
+
, HA.value value
|
|
2652
|
+
, E.onInput <| UpdatePaletteColor color
|
|
2653
|
+
]
|
|
2654
|
+
[]
|
|
2655
|
+
]
|
|
2656
|
+
)
|
|
2657
|
+
in
|
|
2658
|
+
H.span
|
|
2659
|
+
[]
|
|
2660
|
+
[ H.div [] themes
|
|
2661
|
+
, H.div [] [ H.span [] colors ]
|
|
2662
|
+
]
|
|
2663
|
+
|
|
2664
|
+
|
|
2665
|
+
panelView : Model -> Html Msg
|
|
2666
|
+
panelView m =
|
|
2667
|
+
case m.panel of
|
|
2668
|
+
Just p ->
|
|
2669
|
+
H.div [ Attr.id "panel" ]
|
|
2670
|
+
[ H.div [ Attr.id "panel-header" ]
|
|
2671
|
+
[ H.h3
|
|
2672
|
+
[]
|
|
2673
|
+
[ H.text
|
|
2674
|
+
(case p of
|
|
2675
|
+
Help ->
|
|
2676
|
+
"Help Desck"
|
|
2677
|
+
|
|
2678
|
+
ExportSettings ->
|
|
2679
|
+
"Export Project"
|
|
2680
|
+
|
|
2681
|
+
Settings ->
|
|
2682
|
+
"Settings"
|
|
2683
|
+
)
|
|
2684
|
+
]
|
|
2685
|
+
, H.button
|
|
2686
|
+
[ Attr.id "panel-close"
|
|
2687
|
+
, E.onClick <| Toggle <| ShowPanel Nothing
|
|
2688
|
+
]
|
|
2689
|
+
[ H.text "X" ]
|
|
2690
|
+
]
|
|
2691
|
+
, H.div [ Attr.id "panel-body" ] <|
|
|
2692
|
+
case p of
|
|
2693
|
+
Help ->
|
|
2694
|
+
Markdown.toHtml Nothing
|
|
2695
|
+
m.flags.help
|
|
2696
|
+
++ [ H.div []
|
|
2697
|
+
[ H.text "If you enjoy this tool, consider "
|
|
2698
|
+
, H.a
|
|
2699
|
+
[ HA.href "https://ko-fi.com/unlessgames" ]
|
|
2700
|
+
[ H.text "donating♥" ]
|
|
2701
|
+
]
|
|
2702
|
+
, H.div []
|
|
2703
|
+
[ H.text "Check out the source on "
|
|
2704
|
+
, H.a
|
|
2705
|
+
[ HA.href "https://gitlab.com/unlessgames/desck" ]
|
|
2706
|
+
[ H.text "gitlab" ]
|
|
2707
|
+
]
|
|
2708
|
+
]
|
|
2709
|
+
|
|
2710
|
+
Settings ->
|
|
2711
|
+
[ H.h3 [] [ H.text "Fonts" ]
|
|
2712
|
+
, H.div [ Attr.class "panel-row" ]
|
|
2713
|
+
[ H.form
|
|
2714
|
+
[ E.onSubmit (UpdateFont PrimaryFont) ]
|
|
2715
|
+
[ H.input
|
|
2716
|
+
[ E.onInput (TypeFont PrimaryFont)
|
|
2717
|
+
, HA.value <| Maybe.withDefault "" m.settings.primaryFont
|
|
2718
|
+
, HA.placeholder "primary font"
|
|
2719
|
+
, HA.title "type the name of a custom font from Google Fonts (for example 'Roboto Condensed')"
|
|
2720
|
+
]
|
|
2721
|
+
[]
|
|
2722
|
+
]
|
|
2723
|
+
, H.form
|
|
2724
|
+
[ E.onSubmit (UpdateFont SecondaryFont) ]
|
|
2725
|
+
[ H.input
|
|
2726
|
+
[ E.onInput (TypeFont SecondaryFont)
|
|
2727
|
+
, HA.value <| Maybe.withDefault "" m.settings.secondaryFont
|
|
2728
|
+
, HA.placeholder "secondary font"
|
|
2729
|
+
, HA.title "type the name of a custom font from Google Fonts (for example 'Roboto Condensed')"
|
|
2730
|
+
]
|
|
2731
|
+
[]
|
|
2732
|
+
]
|
|
2733
|
+
]
|
|
2734
|
+
, H.h3 [] [ H.text "Palette" ]
|
|
2735
|
+
, paletteSettings m
|
|
2736
|
+
|
|
2737
|
+
-- TODO Editor - add automatic humanize on spawn and pick/drop
|
|
2738
|
+
-- TODO Editor - crop padding setting
|
|
2739
|
+
-- TODO Editor - contain or intersect selection mode
|
|
2740
|
+
, H.span []
|
|
2741
|
+
[ H.h3 [] [ H.text "Background" ]
|
|
2742
|
+
, colorPickerWithTransparent m.palette (Mod << Project.SetBackground) m.project.background
|
|
2743
|
+
]
|
|
2744
|
+
]
|
|
2745
|
+
|
|
2746
|
+
ExportSettings ->
|
|
2747
|
+
-- TODO Editor - toggle background include
|
|
2748
|
+
-- TODO Editor - reset defaults
|
|
2749
|
+
let
|
|
2750
|
+
-- TODO GUI - better switches
|
|
2751
|
+
settingFlag sf st on off desc =
|
|
2752
|
+
H.button
|
|
2753
|
+
[ E.onClick <| Toggle (Setting sf)
|
|
2754
|
+
, Attr.title desc
|
|
2755
|
+
]
|
|
2756
|
+
[ H.text <|
|
|
2757
|
+
if st then
|
|
2758
|
+
on
|
|
2759
|
+
|
|
2760
|
+
else
|
|
2761
|
+
off
|
|
2762
|
+
]
|
|
2763
|
+
in
|
|
2764
|
+
[ H.h3 [] [ H.text "Download" ]
|
|
2765
|
+
, H.div [ Attr.class "panel-row" ]
|
|
2766
|
+
[ H.button [ E.onClick <| Export SVG ] [ H.text "⤓ SVG" ]
|
|
2767
|
+
, H.button [ E.onClick <| Export PNG ] [ H.text "⤓ PNG" ]
|
|
2768
|
+
, H.button [ E.onClick <| Export JSON ] [ H.text "⤓ JSON" ]
|
|
2769
|
+
]
|
|
2770
|
+
, H.h3 [] [ H.text "SVG Settings" ]
|
|
2771
|
+
, H.div [ Attr.class "panel-row" ]
|
|
2772
|
+
[ settingFlag
|
|
2773
|
+
EmbedSymbols
|
|
2774
|
+
m.settings.embedSymbols
|
|
2775
|
+
"embed SVG symbols"
|
|
2776
|
+
"strip SVG symbols"
|
|
2777
|
+
"choose to embed or strip the symbols (like face cards, pips and digits) for the SVG output, when stripped multiple SVG can be embed on a page with a single symbol library at the top to save on bandwidth (you'll have to set up inlining all of the SVGs and the symbol library)"
|
|
2778
|
+
, H.button [ E.onClick <| Export Symbols ] [ H.text "⤓ Export SVG symbol library" ]
|
|
2779
|
+
|> maybeHtml (not m.settings.embedSymbols)
|
|
2780
|
+
]
|
|
2781
|
+
, settingFlag
|
|
2782
|
+
StripData
|
|
2783
|
+
m.settings.stripData
|
|
2784
|
+
"exclude project data"
|
|
2785
|
+
"include project data"
|
|
2786
|
+
"You may choose to strip the JSON formatted data that is inserted into the top of the SVG for desck to be able load the .svg file, if used in automated build, it is good to get rid of this to save some bandwidth"
|
|
2787
|
+
, settingFlag
|
|
2788
|
+
MapColors
|
|
2789
|
+
m.settings.mapColors
|
|
2790
|
+
"colors as values"
|
|
2791
|
+
"colors as references"
|
|
2792
|
+
"map the actual color values you selected in Desck to the SVG, when disabled, colors are kept as CSS variables like 'var(--red)' so that you can dynamically remap them later by swapping variable definitions on your page"
|
|
2793
|
+
, settingFlag
|
|
2794
|
+
MapFont
|
|
2795
|
+
m.settings.mapFont
|
|
2796
|
+
"fonts as values"
|
|
2797
|
+
"fonts as references"
|
|
2798
|
+
"map the actual font values you selected in Desck to the styles in the SVG, when disabled, fonts are kept as CSS variables like 'var(--desck-primary-font)' and 'var(--desck-secondary-font)' so that you can dynamically remap them later by including variable definitions on your page"
|
|
2799
|
+
, H.textarea
|
|
2800
|
+
[ E.onInput (Mod << Project.Describe)
|
|
2801
|
+
, HA.value m.project.description
|
|
2802
|
+
, HA.placeholder "🖹 describe the image"
|
|
2803
|
+
, HA.title "write a description that will be saved as the 'title' attribute for the SVG"
|
|
2804
|
+
]
|
|
2805
|
+
[]
|
|
2806
|
+
, H.h3 [] [ H.text "PNG Settings" ]
|
|
2807
|
+
, H.div [ Attr.class "panel-row" ]
|
|
2808
|
+
[ H.button [] [ H.text "height of cards" ]
|
|
2809
|
+
, H.input
|
|
2810
|
+
[ HA.type_ "number"
|
|
2811
|
+
, HA.min "10"
|
|
2812
|
+
, HA.max "1000"
|
|
2813
|
+
, HA.value <| String.fromInt m.settings.heightPixels
|
|
2814
|
+
, E.onInput UpdateHeightPixels
|
|
2815
|
+
, HA.class "narrow"
|
|
2816
|
+
, HA.id "heightPixels"
|
|
2817
|
+
, HA.title "set the height of cards in pixels in the exported PNG image"
|
|
2818
|
+
]
|
|
2819
|
+
[]
|
|
2820
|
+
]
|
|
2821
|
+
]
|
|
2822
|
+
]
|
|
2823
|
+
|
|
2824
|
+
Nothing ->
|
|
2825
|
+
H.text ""
|
|
2826
|
+
|
|
2827
|
+
|
|
2828
|
+
panelToggle : Panel -> String -> Html Msg
|
|
2829
|
+
panelToggle msg s =
|
|
2830
|
+
H.button
|
|
2831
|
+
[ E.onClick <| Toggle <| ShowPanel <| Just msg
|
|
2832
|
+
, Attr.class "panel-toggle"
|
|
2833
|
+
]
|
|
2834
|
+
[ H.text s
|
|
2835
|
+
]
|
|
2836
|
+
|
|
2837
|
+
|
|
2838
|
+
headerView : Model -> Html Msg
|
|
2839
|
+
headerView model =
|
|
2840
|
+
List.concat
|
|
2841
|
+
[ fileViews model
|
|
2842
|
+
, [ H.button [ E.onClick UploadFile ] [ H.text "load" ]
|
|
2843
|
+
, H.button [ E.onClick <| Toggle <| Boxed ] [ H.text "▭" ]
|
|
2844
|
+
, rectInput (\c i -> Mod <| Project.UpdateViewport c i) model.project.viewport
|
|
2845
|
+
, H.button [ E.onClick (Mod <| Project.Crop model.selected) ] [ H.text "crop" ]
|
|
2846
|
+
, H.button [ E.onClick <| Toggle BringForward ]
|
|
2847
|
+
[ H.text <|
|
|
2848
|
+
if model.moveToTop then
|
|
2849
|
+
"move to top"
|
|
2850
|
+
|
|
2851
|
+
else
|
|
2852
|
+
"keep order"
|
|
2853
|
+
]
|
|
2854
|
+
, H.button [ E.onClick <| Toggle StackSelect ]
|
|
2855
|
+
[ H.text <|
|
|
2856
|
+
if model.stackSelect then
|
|
2857
|
+
"stack select"
|
|
2858
|
+
|
|
2859
|
+
else
|
|
2860
|
+
"single select"
|
|
2861
|
+
]
|
|
2862
|
+
]
|
|
2863
|
+
]
|
|
2864
|
+
|> H.div
|
|
2865
|
+
[ Attr.id "header"
|
|
2866
|
+
]
|
|
2867
|
+
|
|
2868
|
+
|
|
2869
|
+
inspectorColumn : List (H.Attribute msg) -> List (Html msg) -> Html msg
|
|
2870
|
+
inspectorColumn attrs ls =
|
|
2871
|
+
H.div
|
|
2872
|
+
(HA.class "inspector-column"
|
|
2873
|
+
:: attrs
|
|
2874
|
+
)
|
|
2875
|
+
ls
|
|
2876
|
+
|
|
2877
|
+
|
|
2878
|
+
inspectorRow : List (Html msg) -> Html msg
|
|
2879
|
+
inspectorRow ls =
|
|
2880
|
+
H.div
|
|
2881
|
+
[ HA.class "inspector-row"
|
|
2882
|
+
]
|
|
2883
|
+
ls
|
|
2884
|
+
|
|
2885
|
+
|
|
2886
|
+
inspectorView : Model -> Html Msg
|
|
2887
|
+
inspectorView model =
|
|
2888
|
+
let
|
|
2889
|
+
layerToggle layer icon ls field =
|
|
2890
|
+
inspectorColumn
|
|
2891
|
+
[ HA.classList
|
|
2892
|
+
[ ( "inactive", not (field model.unlockedLayers) )
|
|
2893
|
+
]
|
|
2894
|
+
]
|
|
2895
|
+
[ H.button
|
|
2896
|
+
[ E.onClick (ToggleLaterMask layer)
|
|
2897
|
+
, HA.title <| layerString layer
|
|
2898
|
+
]
|
|
2899
|
+
[ H.text icon ]
|
|
2900
|
+
, inspectorRow [ H.text <| String.fromInt <| List.length ls ]
|
|
2901
|
+
]
|
|
2902
|
+
in
|
|
2903
|
+
[ H.text ""
|
|
2904
|
+
, H.div []
|
|
2905
|
+
[ inspectorRow
|
|
2906
|
+
[ layerToggle Zones "⌧" model.project.zones .zones
|
|
2907
|
+
, layerToggle Cards "🂠" model.project.cards .cards
|
|
2908
|
+
, layerToggle Arrows "↣" model.project.arrows .arrows
|
|
2909
|
+
, layerToggle Stamps "✪" model.project.stamps .stamps
|
|
2910
|
+
]
|
|
2911
|
+
, inspectorRow
|
|
2912
|
+
[ H.button
|
|
2913
|
+
[ E.onClick <|
|
|
2914
|
+
ModRandom <|
|
|
2915
|
+
Project.Humanize model.selected
|
|
2916
|
+
]
|
|
2917
|
+
[ H.text <|
|
|
2918
|
+
"humanize"
|
|
2919
|
+
++ (if List.isEmpty model.selected then
|
|
2920
|
+
" all"
|
|
2921
|
+
|
|
2922
|
+
else
|
|
2923
|
+
" selected"
|
|
2924
|
+
)
|
|
2925
|
+
]
|
|
2926
|
+
]
|
|
2927
|
+
|
|
2928
|
+
-- enumButtons
|
|
2929
|
+
-- ToggleLaterMask
|
|
2930
|
+
-- layerString
|
|
2931
|
+
-- (\t -> t == )
|
|
2932
|
+
--, ( Colors, "◍" ) ]
|
|
2933
|
+
]
|
|
2934
|
+
, H.div
|
|
2935
|
+
[]
|
|
2936
|
+
[-- , H.span
|
|
2937
|
+
-- []
|
|
2938
|
+
-- [ H.button [ E.onClick (Mod <| Project.Order Project.MoveUp model.selected) ] [ H.text "⯅" ]
|
|
2939
|
+
-- , H.button [ E.onClick (Mod <| Project.Order Project.MoveDown model.selected) ] [ H.text "⯆" ]
|
|
2940
|
+
-- -- , H.input [ E.onInput RotateSelected, HA.type_ "number", HA.class "narrow" ] []
|
|
2941
|
+
-- ]
|
|
2942
|
+
-- |> maybeHtml (1 == List.length model.selected && model.tool == Cards)
|
|
2943
|
+
]
|
|
2944
|
+
|> maybeHtml (0 < List.length model.selected)
|
|
2945
|
+
|
|
2946
|
+
-- , H.span []
|
|
2947
|
+
-- [ H.text "snap"
|
|
2948
|
+
-- , H.input
|
|
2949
|
+
-- [ HA.type_ "number"
|
|
2950
|
+
-- , HA.value <| String.fromInt model.snap
|
|
2951
|
+
-- , HA.class "narrow"
|
|
2952
|
+
-- , HA.min "0"
|
|
2953
|
+
-- , HA.max "8"
|
|
2954
|
+
-- , E.onInput UpdateSnap
|
|
2955
|
+
-- ]
|
|
2956
|
+
-- []
|
|
2957
|
+
-- ]
|
|
2958
|
+
-- , H.text (status model)
|
|
2959
|
+
, selectedViews
|
|
2960
|
+
model
|
|
2961
|
+
]
|
|
2962
|
+
|> H.div
|
|
2963
|
+
[ HA.id "inspector"
|
|
2964
|
+
]
|
|
2965
|
+
|
|
2966
|
+
|
|
2967
|
+
footerView : Model -> Html Msg
|
|
2968
|
+
footerView model =
|
|
2969
|
+
H.div
|
|
2970
|
+
[ HA.id "footer" ]
|
|
2971
|
+
[ H.div []
|
|
2972
|
+
[ H.button
|
|
2973
|
+
[ E.onClick Remove
|
|
2974
|
+
, HA.style "color" "var(--red)"
|
|
2975
|
+
, HA.classList
|
|
2976
|
+
[ ( "inactive", List.isEmpty model.selected )
|
|
2977
|
+
]
|
|
2978
|
+
]
|
|
2979
|
+
[ H.text "remove" ]
|
|
2980
|
+
, H.button
|
|
2981
|
+
[ E.onClick Undo
|
|
2982
|
+
, HA.classList
|
|
2983
|
+
[ ( "inactive"
|
|
2984
|
+
, 0 == List.length model.undos
|
|
2985
|
+
)
|
|
2986
|
+
]
|
|
2987
|
+
, HA.title "Undo"
|
|
2988
|
+
]
|
|
2989
|
+
[ H.text "↺"
|
|
2990
|
+
]
|
|
2991
|
+
|
|
2992
|
+
-- [ H.text <| "undo [" ++ (String.fromInt <| List.length model.undos) ++ "]"
|
|
2993
|
+
]
|
|
2994
|
+
, H.form
|
|
2995
|
+
[ E.onSubmit Spawn ]
|
|
2996
|
+
[ H.input
|
|
2997
|
+
[ E.onInput TypeCommand
|
|
2998
|
+
, HA.value model.command
|
|
2999
|
+
, HA.title "spawn cards with codes like 'ac 9d k'"
|
|
3000
|
+
, HA.placeholder "spawn cards like '6c ad qh'"
|
|
3001
|
+
]
|
|
3002
|
+
[]
|
|
3003
|
+
]
|
|
3004
|
+
, H.form
|
|
3005
|
+
[ E.onSubmit SpawnStamp ]
|
|
3006
|
+
[ H.input
|
|
3007
|
+
[ E.onInput TypeStamp
|
|
3008
|
+
, HA.value model.stampInput
|
|
3009
|
+
, HA.title "spawn a stamps with characters like '◬ '"
|
|
3010
|
+
, HA.placeholder "spawn stamps with characters like '◬ '"
|
|
3011
|
+
]
|
|
3012
|
+
[]
|
|
3013
|
+
]
|
|
3014
|
+
, H.form
|
|
3015
|
+
[ E.onSubmit ApplySelector ]
|
|
3016
|
+
[ H.input
|
|
3017
|
+
[ E.onInput TypeSelector
|
|
3018
|
+
, HA.value model.selectedCardModels
|
|
3019
|
+
, HA.title "modify cards by changing their codes"
|
|
3020
|
+
, HA.placeholder "select some cards to edit..."
|
|
3021
|
+
]
|
|
3022
|
+
[]
|
|
3023
|
+
]
|
|
3024
|
+
, H.div []
|
|
3025
|
+
[ panelToggle Help "? Help"
|
|
3026
|
+
, panelToggle Settings "◍ Settings"
|
|
3027
|
+
, panelToggle ExportSettings "⤓ Download"
|
|
3028
|
+
, H.a [ HA.href "https://ko-fi.com/unlessgames" ] [ H.button [ HA.id "support-button" ] [ H.text "♥" ] ]
|
|
3029
|
+
]
|
|
3030
|
+
]
|
|
3031
|
+
|
|
3032
|
+
|
|
3033
|
+
view : Model -> Html Msg
|
|
3034
|
+
view model =
|
|
3035
|
+
H.div
|
|
3036
|
+
[ Attr.id "main"
|
|
3037
|
+
]
|
|
3038
|
+
[ headerView model
|
|
3039
|
+
, inspectorView model
|
|
3040
|
+
, svgView model
|
|
3041
|
+
, panelView model
|
|
3042
|
+
, footerView model
|
|
3043
|
+
]
|
|
3044
|
+
|
|
3045
|
+
|
|
3046
|
+
viewportAttr : Model -> Svg.Attribute Msg
|
|
3047
|
+
viewportAttr m =
|
|
3048
|
+
if m.boxed then
|
|
3049
|
+
Attr.clipPath (url <| viewportId m)
|
|
3050
|
+
|
|
3051
|
+
else
|
|
3052
|
+
Attr.visibility "visible"
|
|
3053
|
+
|
|
3054
|
+
|
|
3055
|
+
cull : Project -> (a -> Shape) -> a -> Bool
|
|
3056
|
+
cull p toShape s =
|
|
3057
|
+
s
|
|
3058
|
+
|> toShape
|
|
3059
|
+
|> shapeRect p
|
|
3060
|
+
|> overlaps (enlarge cardSize.x p.viewport)
|
|
3061
|
+
|
|
3062
|
+
|
|
3063
|
+
|
|
3064
|
+
-- TODO Card - different backs, choose red or blue for each card?
|
|
3065
|
+
|
|
3066
|
+
|
|
3067
|
+
cardsView : Model -> Svg Msg
|
|
3068
|
+
cardsView model =
|
|
3069
|
+
model.project.cards
|
|
3070
|
+
|> List.filter
|
|
3071
|
+
(cull model.project Project.Card)
|
|
3072
|
+
|> List.sortBy .z
|
|
3073
|
+
|> List.map (cardView model)
|
|
3074
|
+
|> Svg.Keyed.node "g" [ viewportAttr model ]
|
|
3075
|
+
|
|
3076
|
+
|
|
3077
|
+
arrowDefs : Svg msg
|
|
3078
|
+
arrowDefs =
|
|
3079
|
+
let
|
|
3080
|
+
w =
|
|
3081
|
+
3.0
|
|
3082
|
+
|
|
3083
|
+
h =
|
|
3084
|
+
1.5
|
|
3085
|
+
|
|
3086
|
+
w2 =
|
|
3087
|
+
w / 2.0
|
|
3088
|
+
in
|
|
3089
|
+
Svg.defs []
|
|
3090
|
+
[ Svg.marker
|
|
3091
|
+
[ Attr.id "arrow"
|
|
3092
|
+
, num Attr.markerWidth h
|
|
3093
|
+
, num Attr.markerHeight w
|
|
3094
|
+
, num Attr.refX (h * 0.5)
|
|
3095
|
+
, num Attr.refY w2
|
|
3096
|
+
, Attr.orient "auto"
|
|
3097
|
+
, Attr.markerUnits "strokeWidth"
|
|
3098
|
+
, Attr.fill "context-stroke"
|
|
3099
|
+
]
|
|
3100
|
+
[ Svg.polygon
|
|
3101
|
+
[ Attr.fill "context-stroke"
|
|
3102
|
+
, Attr.points <| "0,0 0," ++ String.fromFloat w ++ " " ++ String.fromFloat h ++ "," ++ String.fromFloat w2
|
|
3103
|
+
]
|
|
3104
|
+
[]
|
|
3105
|
+
]
|
|
3106
|
+
|
|
3107
|
+
-- , Svg.filter
|
|
3108
|
+
-- [ Attr.id "shadow" ]
|
|
3109
|
+
-- [ Svg.node "feDropShadow"
|
|
3110
|
+
-- [ Attr.dx "4px"
|
|
3111
|
+
-- , Attr.dy "4px"
|
|
3112
|
+
-- , Attr.floodColor "black"
|
|
3113
|
+
-- , Attr.stdDeviation "4"
|
|
3114
|
+
-- ]
|
|
3115
|
+
-- []
|
|
3116
|
+
-- ]
|
|
3117
|
+
]
|
|
3118
|
+
|
|
3119
|
+
|
|
3120
|
+
dragEvent : Id -> Svg.Attribute Msg
|
|
3121
|
+
dragEvent id =
|
|
3122
|
+
Draggable.customMouseTrigger id (positionDecoder "offset") (StartDragWithPos id)
|
|
3123
|
+
|
|
3124
|
+
|
|
3125
|
+
|
|
3126
|
+
-- TODO customize arrow cap or distance?
|
|
3127
|
+
-- TODO respond to pivot and angle
|
|
3128
|
+
|
|
3129
|
+
|
|
3130
|
+
arcPath : Float -> ( Vec2, Vec2 ) -> String
|
|
3131
|
+
arcPath f ( a, b ) =
|
|
3132
|
+
let
|
|
3133
|
+
p v =
|
|
3134
|
+
String.fromFloat v.x ++ " " ++ String.fromFloat v.y
|
|
3135
|
+
|
|
3136
|
+
m =
|
|
3137
|
+
Vec.sub b a
|
|
3138
|
+
|> Vec.scale 0.5
|
|
3139
|
+
|
|
3140
|
+
c =
|
|
3141
|
+
Vec.vec2 0.0 f
|
|
3142
|
+
|> Vec.rotate pi
|
|
3143
|
+
in
|
|
3144
|
+
"M " ++ p a ++ " Q " ++ p c ++ " " ++ p b
|
|
3145
|
+
|
|
3146
|
+
|
|
3147
|
+
arrowView : Model -> ( Vec2, Vec2 ) -> Arrow -> ( String, Svg Msg )
|
|
3148
|
+
arrowView model ( from, to ) a =
|
|
3149
|
+
let
|
|
3150
|
+
( rect, pivot, angle ) =
|
|
3151
|
+
arrowTransform a ( from, to )
|
|
3152
|
+
|
|
3153
|
+
c =
|
|
3154
|
+
center rect
|
|
3155
|
+
|
|
3156
|
+
dir =
|
|
3157
|
+
Vec.sub from to
|
|
3158
|
+
|
|
3159
|
+
l =
|
|
3160
|
+
Vec.length dir
|
|
3161
|
+
* 0.4
|
|
3162
|
+
in
|
|
3163
|
+
( a.id
|
|
3164
|
+
, Svg.g
|
|
3165
|
+
[ Attr.transform <|
|
|
3166
|
+
pivotRotate (Vec.vec2 0.5 0.5) rect.pos rect.size angle
|
|
3167
|
+
++ translateXY c
|
|
3168
|
+
]
|
|
3169
|
+
[ Svg.line
|
|
3170
|
+
([ num Attr.x1 <| l
|
|
3171
|
+
, num Attr.y1 0.0
|
|
3172
|
+
, num Attr.x2 <| -l
|
|
3173
|
+
, num Attr.y2 0.0
|
|
3174
|
+
, num Attr.strokeWidth a.width
|
|
3175
|
+
, Attr.fill <| getColor model a.fill
|
|
3176
|
+
, Attr.stroke <| getColor model a.fill
|
|
3177
|
+
, Attr.strokeLinecap "butt"
|
|
3178
|
+
, preventContextMenu
|
|
3179
|
+
, dragEvent a.id
|
|
3180
|
+
|
|
3181
|
+
-- , num Attr.opacity 0.0
|
|
3182
|
+
-- , E.onDoubleClick (Mod (Project.Remove model.selected))
|
|
3183
|
+
]
|
|
3184
|
+
++ (case a.head of
|
|
3185
|
+
Triangle ->
|
|
3186
|
+
[ Attr.markerEnd (url "arrow") ]
|
|
3187
|
+
|
|
3188
|
+
-- TODO Arrow - cross head!
|
|
3189
|
+
_ ->
|
|
3190
|
+
[]
|
|
3191
|
+
)
|
|
3192
|
+
)
|
|
3193
|
+
[]
|
|
3194
|
+
|
|
3195
|
+
-- TODO Arrow - curved path
|
|
3196
|
+
-- , Svg.path
|
|
3197
|
+
-- [ Attr.d <| arcPath 100.0 ( Vec.vec2 l 0.0, Vec.vec2 -l 0.0 )
|
|
3198
|
+
-- , num Attr.strokeWidth a.width
|
|
3199
|
+
-- , Attr.fill <| "transparent"
|
|
3200
|
+
-- , Attr.stroke <| getColor model a.fill
|
|
3201
|
+
-- , Attr.strokeLinecap "butt"
|
|
3202
|
+
-- , Attr.markerEnd (url "arrow")
|
|
3203
|
+
-- ]
|
|
3204
|
+
-- []
|
|
3205
|
+
, Svg.text_
|
|
3206
|
+
[ HA.style "font-size" "32px"
|
|
3207
|
+
, Attr.fill "magenta"
|
|
3208
|
+
]
|
|
3209
|
+
[ Svg.text <| a.id ]
|
|
3210
|
+
|> maybeHtml model.debug
|
|
3211
|
+
]
|
|
3212
|
+
)
|
|
3213
|
+
|
|
3214
|
+
|
|
3215
|
+
arrowsView : Model -> Svg Msg
|
|
3216
|
+
arrowsView model =
|
|
3217
|
+
model.project.arrows
|
|
3218
|
+
|> List.filter
|
|
3219
|
+
(cull model.project Project.Arrow)
|
|
3220
|
+
|> List.map
|
|
3221
|
+
(\a ->
|
|
3222
|
+
let
|
|
3223
|
+
points =
|
|
3224
|
+
case ( getShapeById model.project a.from, getShapeById model.project a.to ) of
|
|
3225
|
+
( Just from, Just to ) ->
|
|
3226
|
+
arrowPoints ( draggedTransform model from, draggedTransform model to )
|
|
3227
|
+
|
|
3228
|
+
_ ->
|
|
3229
|
+
( Vec.zero, Vec.zero )
|
|
3230
|
+
in
|
|
3231
|
+
arrowView model points a
|
|
3232
|
+
)
|
|
3233
|
+
|> Svg.Keyed.node "g" [ viewportAttr model, Attr.id "arrows" ]
|
|
3234
|
+
|
|
3235
|
+
|
|
3236
|
+
stampView : Model -> Stamp -> ( String, Svg Msg )
|
|
3237
|
+
stampView m s =
|
|
3238
|
+
let
|
|
3239
|
+
( rect, _, _ ) =
|
|
3240
|
+
draggedTransform m (Project.Stamp s)
|
|
3241
|
+
in
|
|
3242
|
+
( s.id
|
|
3243
|
+
, Svg.g
|
|
3244
|
+
[ Attr.transform <| translate (rect.pos.x + s.radius) (rect.pos.y + s.radius)
|
|
3245
|
+
, preventContextMenu
|
|
3246
|
+
, dragEvent s.id
|
|
3247
|
+
, onMouseUp EndDrag
|
|
3248
|
+
]
|
|
3249
|
+
[ Svg.circle
|
|
3250
|
+
[ Attr.r (String.fromFloat s.radius ++ "px")
|
|
3251
|
+
, Attr.fill <| getColor m s.fill
|
|
3252
|
+
]
|
|
3253
|
+
[]
|
|
3254
|
+
, Svg.text_
|
|
3255
|
+
[ Attr.fill <| getColor m s.text.color
|
|
3256
|
+
, Attr.fontSize <| String.fromInt s.text.size ++ "px"
|
|
3257
|
+
, Attr.textAnchor "middle"
|
|
3258
|
+
, Attr.dominantBaseline
|
|
3259
|
+
(if (s.text.label |> String.toList |> List.foldl (\c b -> b && Char.isAlphaNum c) True) && String.toLower s.text.label == s.text.label then
|
|
3260
|
+
"middle"
|
|
3261
|
+
|
|
3262
|
+
else
|
|
3263
|
+
"central"
|
|
3264
|
+
)
|
|
3265
|
+
]
|
|
3266
|
+
[ Svg.text s.text.label ]
|
|
3267
|
+
]
|
|
3268
|
+
)
|
|
3269
|
+
|
|
3270
|
+
|
|
3271
|
+
stampsView : Model -> Svg Msg
|
|
3272
|
+
stampsView model =
|
|
3273
|
+
model.project.stamps
|
|
3274
|
+
|> List.filter
|
|
3275
|
+
(cull model.project Project.Stamp)
|
|
3276
|
+
|> List.map
|
|
3277
|
+
(\s ->
|
|
3278
|
+
stampView model s
|
|
3279
|
+
)
|
|
3280
|
+
|> Svg.Keyed.node "g" [ viewportAttr model, Attr.id "stamps" ]
|
|
3281
|
+
|
|
3282
|
+
|
|
3283
|
+
incompleteArrow : Model -> Svg Msg
|
|
3284
|
+
incompleteArrow model =
|
|
3285
|
+
case model.dragState of
|
|
3286
|
+
Nothing ->
|
|
3287
|
+
Svg.g [] []
|
|
3288
|
+
|
|
3289
|
+
Just { state, mode } ->
|
|
3290
|
+
case state.shape of
|
|
3291
|
+
Nothing ->
|
|
3292
|
+
Svg.g [] []
|
|
3293
|
+
|
|
3294
|
+
Just shape ->
|
|
3295
|
+
case mode of
|
|
3296
|
+
Connect ->
|
|
3297
|
+
let
|
|
3298
|
+
pos =
|
|
3299
|
+
state.start |> Vec.add state.offset
|
|
3300
|
+
in
|
|
3301
|
+
let
|
|
3302
|
+
( rect, _, _ ) =
|
|
3303
|
+
shapeTransform model.project shape
|
|
3304
|
+
in
|
|
3305
|
+
if Rect.hasPoint pos <| rect then
|
|
3306
|
+
Svg.g [] []
|
|
3307
|
+
|
|
3308
|
+
else
|
|
3309
|
+
let
|
|
3310
|
+
points =
|
|
3311
|
+
arrowPointsToPos model.project shape pos
|
|
3312
|
+
in
|
|
3313
|
+
Svg.Keyed.node "g"
|
|
3314
|
+
[ viewportAttr model
|
|
3315
|
+
, Attr.pointerEvents "none"
|
|
3316
|
+
]
|
|
3317
|
+
[ arrowView model points (makeArrow model.arrowPreset (shapeId shape) "" "") ]
|
|
3318
|
+
|
|
3319
|
+
_ ->
|
|
3320
|
+
Svg.g [] []
|
|
3321
|
+
|
|
3322
|
+
|
|
3323
|
+
getColor : Model -> Color -> String
|
|
3324
|
+
getColor m c =
|
|
3325
|
+
case c of
|
|
3326
|
+
Transparent ->
|
|
3327
|
+
"transparent"
|
|
3328
|
+
|
|
3329
|
+
_ ->
|
|
3330
|
+
"var(--" ++ colorAsString c ++ ")"
|
|
3331
|
+
|
|
3332
|
+
|
|
3333
|
+
|
|
3334
|
+
-- Palette.getColor m.palette c
|
|
3335
|
+
|
|
3336
|
+
|
|
3337
|
+
customFont : Bool -> H.Attribute msg
|
|
3338
|
+
customFont primary =
|
|
3339
|
+
HA.style "font-family" <|
|
|
3340
|
+
"var(--desck-"
|
|
3341
|
+
++ (if primary then
|
|
3342
|
+
"primary"
|
|
3343
|
+
|
|
3344
|
+
else
|
|
3345
|
+
"secondary"
|
|
3346
|
+
)
|
|
3347
|
+
++ "-font)"
|
|
3348
|
+
|
|
3349
|
+
|
|
3350
|
+
zoneView : Model -> Bool -> Zone -> ( String, Html Msg )
|
|
3351
|
+
zoneView model active z =
|
|
3352
|
+
let
|
|
3353
|
+
( r, pivot, angle ) =
|
|
3354
|
+
draggedTransform model (Project.Zone z)
|
|
3355
|
+
|
|
3356
|
+
border =
|
|
3357
|
+
Vec.scale (toFloat <| z.border.width // 2) Vec.one
|
|
3358
|
+
|
|
3359
|
+
rect =
|
|
3360
|
+
{ r | pos = Vec.sub r.pos border, size = Vec.add r.size (Vec.scale 2.0 border) }
|
|
3361
|
+
|
|
3362
|
+
-- TODO make label pos dependent on border width
|
|
3363
|
+
textRectSize =
|
|
3364
|
+
rect.size
|
|
3365
|
+
|
|
3366
|
+
( textPos, ( textAnchor, dominantBaseline ), lineOffset ) =
|
|
3367
|
+
textAttributes textRectSize z.text
|
|
3368
|
+
|
|
3369
|
+
lines =
|
|
3370
|
+
String.split "\n" z.text.label
|
|
3371
|
+
|
|
3372
|
+
svgLines attrs =
|
|
3373
|
+
attrs
|
|
3374
|
+
|> List.map
|
|
3375
|
+
(\atts ->
|
|
3376
|
+
Svg.line
|
|
3377
|
+
(List.append atts
|
|
3378
|
+
[ Attr.stroke <| getColor model z.border.color
|
|
3379
|
+
, Attr.strokeWidth <| String.fromInt z.border.width
|
|
3380
|
+
, Attr.strokeDasharray <| String.fromInt z.border.dash
|
|
3381
|
+
]
|
|
3382
|
+
)
|
|
3383
|
+
[]
|
|
3384
|
+
)
|
|
3385
|
+
|
|
3386
|
+
horizontalLines w =
|
|
3387
|
+
if w > 1 then
|
|
3388
|
+
List.range 1 (w - 1)
|
|
3389
|
+
|> List.map
|
|
3390
|
+
(\x ->
|
|
3391
|
+
let
|
|
3392
|
+
cw =
|
|
3393
|
+
rect.size.x / toFloat w
|
|
3394
|
+
|
|
3395
|
+
px =
|
|
3396
|
+
toFloat x * cw
|
|
3397
|
+
in
|
|
3398
|
+
[ num Attr.x1 <| px
|
|
3399
|
+
, num Attr.y1 <| 0
|
|
3400
|
+
, num Attr.x2 <| px
|
|
3401
|
+
, num Attr.y2 <| rect.size.y
|
|
3402
|
+
]
|
|
3403
|
+
)
|
|
3404
|
+
|> svgLines
|
|
3405
|
+
|
|
3406
|
+
else
|
|
3407
|
+
[]
|
|
3408
|
+
|
|
3409
|
+
verticalLines h =
|
|
3410
|
+
if h > 1 then
|
|
3411
|
+
List.range 1 (h - 1)
|
|
3412
|
+
|> List.map
|
|
3413
|
+
(\y ->
|
|
3414
|
+
let
|
|
3415
|
+
cw =
|
|
3416
|
+
rect.size.y / toFloat h
|
|
3417
|
+
|
|
3418
|
+
py =
|
|
3419
|
+
toFloat y * cw
|
|
3420
|
+
in
|
|
3421
|
+
[ num Attr.x1 <| 0
|
|
3422
|
+
, num Attr.y1 <| py
|
|
3423
|
+
, num Attr.x2 <| rect.size.x
|
|
3424
|
+
, num Attr.y2 <| py
|
|
3425
|
+
]
|
|
3426
|
+
)
|
|
3427
|
+
|> svgLines
|
|
3428
|
+
|
|
3429
|
+
else
|
|
3430
|
+
[]
|
|
3431
|
+
in
|
|
3432
|
+
( z.id
|
|
3433
|
+
, Svg.g
|
|
3434
|
+
(List.concat
|
|
3435
|
+
[ [ Attr.transform <|
|
|
3436
|
+
pivotRotate pivot rect.pos rect.size angle
|
|
3437
|
+
++ translateXY rect.pos
|
|
3438
|
+
]
|
|
3439
|
+
, if active then
|
|
3440
|
+
[ preventContextMenu
|
|
3441
|
+
, dragEvent z.id
|
|
3442
|
+
, onMouseUp EndDrag
|
|
3443
|
+
|
|
3444
|
+
-- , E.onDoubleClick (Mod (Project.Remove model.selected))
|
|
3445
|
+
]
|
|
3446
|
+
|
|
3447
|
+
else
|
|
3448
|
+
[ Attr.pointerEvents "none" ]
|
|
3449
|
+
]
|
|
3450
|
+
)
|
|
3451
|
+
<|
|
|
3452
|
+
List.concat
|
|
3453
|
+
[ [ Svg.rect
|
|
3454
|
+
[ num Attr.width rect.size.x
|
|
3455
|
+
, num Attr.height rect.size.y
|
|
3456
|
+
, Attr.rx cornerRadius
|
|
3457
|
+
, Attr.class "zone"
|
|
3458
|
+
, Attr.fill <| getColor model z.fill
|
|
3459
|
+
, Attr.stroke <| getColor model z.border.color
|
|
3460
|
+
, Attr.strokeWidth <| String.fromInt z.border.width
|
|
3461
|
+
, Attr.strokeDasharray <| String.fromInt z.border.dash
|
|
3462
|
+
]
|
|
3463
|
+
[]
|
|
3464
|
+
]
|
|
3465
|
+
, List.append
|
|
3466
|
+
(if z.grid.columns > 1 then
|
|
3467
|
+
horizontalLines z.grid.columns
|
|
3468
|
+
|
|
3469
|
+
else
|
|
3470
|
+
[]
|
|
3471
|
+
)
|
|
3472
|
+
(if z.grid.rows > 1 then
|
|
3473
|
+
verticalLines z.grid.rows
|
|
3474
|
+
|
|
3475
|
+
else
|
|
3476
|
+
[]
|
|
3477
|
+
)
|
|
3478
|
+
, [ Svg.text_
|
|
3479
|
+
[ Attr.class "zone-label"
|
|
3480
|
+
, Attr.pointerEvents "none"
|
|
3481
|
+
, Attr.fontSize <| String.fromInt z.text.size
|
|
3482
|
+
, customFont z.text.primary
|
|
3483
|
+
, Attr.fill <| getColor model z.text.color
|
|
3484
|
+
, num Attr.x textPos.x
|
|
3485
|
+
, num Attr.y textPos.y
|
|
3486
|
+
, Attr.textAnchor textAnchor
|
|
3487
|
+
, Attr.dominantBaseline dominantBaseline
|
|
3488
|
+
]
|
|
3489
|
+
(lines
|
|
3490
|
+
|> List.indexedMap
|
|
3491
|
+
(\i t ->
|
|
3492
|
+
Svg.tspan
|
|
3493
|
+
[ Attr.dy <| lineOffset i t
|
|
3494
|
+
, num Attr.x textPos.x
|
|
3495
|
+
, customFont z.text.primary
|
|
3496
|
+
]
|
|
3497
|
+
[ Svg.text <|
|
|
3498
|
+
if t == "" then
|
|
3499
|
+
" "
|
|
3500
|
+
|
|
3501
|
+
else
|
|
3502
|
+
t
|
|
3503
|
+
]
|
|
3504
|
+
)
|
|
3505
|
+
)
|
|
3506
|
+
, Svg.text_ [ HA.style "font-size" "32px", Attr.fill "magenta" ] [ Svg.text <| z.id ]
|
|
3507
|
+
|> maybeHtml model.debug
|
|
3508
|
+
]
|
|
3509
|
+
]
|
|
3510
|
+
)
|
|
3511
|
+
|
|
3512
|
+
|
|
3513
|
+
zonesView : Model -> Svg Msg
|
|
3514
|
+
zonesView model =
|
|
3515
|
+
model.project.zones
|
|
3516
|
+
|> List.filter
|
|
3517
|
+
(cull model.project Project.Zone)
|
|
3518
|
+
|> List.map (zoneView model model.unlockedLayers.zones)
|
|
3519
|
+
|> Svg.Keyed.node "g" [ viewportAttr model ]
|
|
3520
|
+
|
|
3521
|
+
|
|
3522
|
+
inactiveFilter : String
|
|
3523
|
+
inactiveFilter =
|
|
3524
|
+
"filter: brightness(50%)"
|
|
3525
|
+
|
|
3526
|
+
|
|
3527
|
+
draggedLeaderTransform : Model -> Shape -> ( Rect, Vec2, Float )
|
|
3528
|
+
draggedLeaderTransform m s =
|
|
3529
|
+
let
|
|
3530
|
+
trans =
|
|
3531
|
+
shapeTransform m.project s
|
|
3532
|
+
|
|
3533
|
+
( rect, pivot, angle ) =
|
|
3534
|
+
trans
|
|
3535
|
+
in
|
|
3536
|
+
case m.dragState of
|
|
3537
|
+
Nothing ->
|
|
3538
|
+
trans
|
|
3539
|
+
|
|
3540
|
+
Just { state, mode } ->
|
|
3541
|
+
case mode of
|
|
3542
|
+
Move ->
|
|
3543
|
+
( { rect
|
|
3544
|
+
| pos =
|
|
3545
|
+
Vec.add rect.pos state.offset
|
|
3546
|
+
|> snapPos m
|
|
3547
|
+
}
|
|
3548
|
+
, pivot
|
|
3549
|
+
, angle
|
|
3550
|
+
)
|
|
3551
|
+
|
|
3552
|
+
Turn ->
|
|
3553
|
+
( rect, pivot, angle + state.offset.x )
|
|
3554
|
+
|
|
3555
|
+
_ ->
|
|
3556
|
+
trans
|
|
3557
|
+
|
|
3558
|
+
|
|
3559
|
+
|
|
3560
|
+
-- shapeOffset : Model -> Shape -> Shape -> (Rect, Vec2, Float)
|
|
3561
|
+
-- shapeOffset m from to =
|
|
3562
|
+
-- let
|
|
3563
|
+
-- (frect, )
|
|
3564
|
+
|
|
3565
|
+
|
|
3566
|
+
draggedFollowerTransform : Model -> Drag -> Shape -> Shape -> ( Rect, Vec2, Float )
|
|
3567
|
+
draggedFollowerTransform m drag leader follower =
|
|
3568
|
+
let
|
|
3569
|
+
( lrect, _, _ ) =
|
|
3570
|
+
shapeTransform m.project leader
|
|
3571
|
+
|
|
3572
|
+
( ldrect, _, _ ) =
|
|
3573
|
+
draggedLeaderTransform m leader
|
|
3574
|
+
|
|
3575
|
+
offset =
|
|
3576
|
+
Vec.sub rect.pos lrect.pos
|
|
3577
|
+
|
|
3578
|
+
trans =
|
|
3579
|
+
shapeTransform m.project follower
|
|
3580
|
+
|
|
3581
|
+
( rect, pivot, angle ) =
|
|
3582
|
+
trans
|
|
3583
|
+
in
|
|
3584
|
+
case drag.mode of
|
|
3585
|
+
Move ->
|
|
3586
|
+
( { rect | pos = Vec.add ldrect.pos offset }, pivot, angle )
|
|
3587
|
+
|
|
3588
|
+
Turn ->
|
|
3589
|
+
( rect, pivot, angle + drag.state.offset.x )
|
|
3590
|
+
|
|
3591
|
+
_ ->
|
|
3592
|
+
trans
|
|
3593
|
+
|
|
3594
|
+
|
|
3595
|
+
draggedTransform : Model -> Shape -> ( Rect, Vec2, Float )
|
|
3596
|
+
draggedTransform m s =
|
|
3597
|
+
let
|
|
3598
|
+
affected =
|
|
3599
|
+
case s of
|
|
3600
|
+
Project.Arrow a ->
|
|
3601
|
+
List.member a.id m.selected
|
|
3602
|
+
|| List.member a.from m.selected
|
|
3603
|
+
|| List.member a.to m.selected
|
|
3604
|
+
|
|
3605
|
+
_ ->
|
|
3606
|
+
List.member (shapeId s) m.selected
|
|
3607
|
+
|
|
3608
|
+
trans =
|
|
3609
|
+
shapeTransform m.project s
|
|
3610
|
+
|
|
3611
|
+
( rect, pivot, angle ) =
|
|
3612
|
+
trans
|
|
3613
|
+
in
|
|
3614
|
+
if not affected then
|
|
3615
|
+
trans
|
|
3616
|
+
|
|
3617
|
+
else
|
|
3618
|
+
case m.dragState of
|
|
3619
|
+
Nothing ->
|
|
3620
|
+
trans
|
|
3621
|
+
|
|
3622
|
+
Just { state, mode } ->
|
|
3623
|
+
case mode of
|
|
3624
|
+
Move ->
|
|
3625
|
+
case state.shape of
|
|
3626
|
+
Just l ->
|
|
3627
|
+
if shapeId l == shapeId s then
|
|
3628
|
+
draggedLeaderTransform m s
|
|
3629
|
+
|
|
3630
|
+
else
|
|
3631
|
+
draggedFollowerTransform m { state = state, mode = mode } l s
|
|
3632
|
+
|
|
3633
|
+
Nothing ->
|
|
3634
|
+
( { rect | pos = Vec.add rect.pos state.offset }, pivot, angle )
|
|
3635
|
+
|
|
3636
|
+
Turn ->
|
|
3637
|
+
( rect, pivot, angle + state.offset.x )
|
|
3638
|
+
|
|
3639
|
+
_ ->
|
|
3640
|
+
trans
|
|
3641
|
+
|
|
3642
|
+
|
|
3643
|
+
cardView : Model -> Card -> ( String, Svg Msg )
|
|
3644
|
+
cardView m card =
|
|
3645
|
+
let
|
|
3646
|
+
{ model, front, id, highlight } =
|
|
3647
|
+
card
|
|
3648
|
+
|
|
3649
|
+
( rect, pivot, angle ) =
|
|
3650
|
+
draggedTransform m (Project.Card card)
|
|
3651
|
+
in
|
|
3652
|
+
( id
|
|
3653
|
+
, Svg.g
|
|
3654
|
+
[ preventContextMenu
|
|
3655
|
+
, dragEvent id
|
|
3656
|
+
, onMouseUp EndDrag
|
|
3657
|
+
|
|
3658
|
+
-- TODO card shadow settings
|
|
3659
|
+
-- , Attr.filter "url(#shadow)"
|
|
3660
|
+
-- , E.onDoubleClick (Mod (Project.Remove m.selected))
|
|
3661
|
+
, Attr.class "card"
|
|
3662
|
+
, Attr.class
|
|
3663
|
+
(if not card.highlight then
|
|
3664
|
+
"card-disabled"
|
|
3665
|
+
|
|
3666
|
+
else
|
|
3667
|
+
""
|
|
3668
|
+
)
|
|
3669
|
+
, HA.attribute "data-using" (encode 0 (list string (modelUsing model)))
|
|
3670
|
+
, Attr.style
|
|
3671
|
+
(if highlight then
|
|
3672
|
+
""
|
|
3673
|
+
|
|
3674
|
+
else
|
|
3675
|
+
inactiveFilter
|
|
3676
|
+
)
|
|
3677
|
+
, Attr.transform <|
|
|
3678
|
+
pivotRotate pivot rect.pos rect.size angle
|
|
3679
|
+
++ translateXY rect.pos
|
|
3680
|
+
]
|
|
3681
|
+
<|
|
|
3682
|
+
List.concat
|
|
3683
|
+
[ if front then
|
|
3684
|
+
frontface rect.size.x rect.size.y model
|
|
3685
|
+
|
|
3686
|
+
else
|
|
3687
|
+
backface rect.size.x rect.size.y model
|
|
3688
|
+
, [ Svg.text_ [ HA.style "font-size" "32px", Attr.fill "magenta" ] [ Svg.text <| card.id ]
|
|
3689
|
+
|> maybeHtml m.debug
|
|
3690
|
+
]
|
|
3691
|
+
]
|
|
3692
|
+
)
|
|
3693
|
+
|
|
3694
|
+
|
|
3695
|
+
preventContextMenu : Svg.Attribute Msg
|
|
3696
|
+
preventContextMenu =
|
|
3697
|
+
Svg.Events.preventDefaultOn "contextmenu" (Decode.succeed ( Noop "", True ))
|
|
3698
|
+
|
|
3699
|
+
|
|
3700
|
+
removeOnExport : Svg.Attribute msg
|
|
3701
|
+
removeOnExport =
|
|
3702
|
+
Attr.class "remove-on-export"
|
|
3703
|
+
|
|
3704
|
+
|
|
3705
|
+
background : Model -> Svg Msg
|
|
3706
|
+
background model =
|
|
3707
|
+
let
|
|
3708
|
+
id =
|
|
3709
|
+
"background"
|
|
3710
|
+
in
|
|
3711
|
+
Svg.g
|
|
3712
|
+
[ Attr.class
|
|
3713
|
+
(if model.project.background == Transparent then
|
|
3714
|
+
"remove-on-export"
|
|
3715
|
+
|
|
3716
|
+
else
|
|
3717
|
+
""
|
|
3718
|
+
)
|
|
3719
|
+
]
|
|
3720
|
+
[ Svg.rect
|
|
3721
|
+
[ Attr.id id
|
|
3722
|
+
, Attr.strokeWidth (String.fromFloat <| model.project.viewport.size.x * 0.005)
|
|
3723
|
+
, num Attr.x model.project.viewport.pos.x
|
|
3724
|
+
, num Attr.y model.project.viewport.pos.y
|
|
3725
|
+
, num Attr.width model.project.viewport.size.x
|
|
3726
|
+
, num Attr.height model.project.viewport.size.y
|
|
3727
|
+
, Attr.fill <| getColor model model.project.background
|
|
3728
|
+
, viewportAttr model
|
|
3729
|
+
]
|
|
3730
|
+
[]
|
|
3731
|
+
, Svg.rect
|
|
3732
|
+
[ Attr.strokeWidth (String.fromFloat <| model.project.viewport.size.x * 0.005)
|
|
3733
|
+
, num Attr.x model.project.viewport.pos.x
|
|
3734
|
+
, num Attr.y model.project.viewport.pos.y
|
|
3735
|
+
, num Attr.width model.project.viewport.size.x
|
|
3736
|
+
, num Attr.height model.project.viewport.size.y
|
|
3737
|
+
, Attr.fill "none"
|
|
3738
|
+
, Attr.stroke "#777"
|
|
3739
|
+
, removeOnExport
|
|
3740
|
+
, viewportAttr model
|
|
3741
|
+
]
|
|
3742
|
+
[]
|
|
3743
|
+
]
|