-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
-
350
-
351
-
352
-
353
-
354
-
355
-
356
-
357
-
358
-
359
-
360
-
361
-
362
-
363
-
364
-
365
-
366
-
367
-
368
-
369
-
370
-
371
-
372
-
373
-
374
-
375
-
376
-
377
-
378
-
379
-
380
-
381
-
382
-
383
-
384
-
385
-
386
-
387
-
388
-
389
-
390
-
391
-
392
-
393
-
394
-
395
-
396
-
397
-
398
-
399
-
400
-
401
-
402
-
403
-
404
-
405
-
406
-
407
-
408
-
409
-
410
-
411
-
412
-
413
-
414
-
415
-
416
-
417
-
418
-
419
-
420
-
421
-
422
-
423
-
424
-
425
-
426
-
427
-
428
-
429
-
430
-
431
-
432
-
433
-
434
-
435
-
436
-
437
-
438
-
439
-
440
-
441
-
442
-
443
-
444
-
445
-
446
-
447
-
448
-
449
-
450
-
451
-
452
-
453
-
454
-
455
-
456
-
457
-
458
-
459
-
460
-
461
-
462
-
463
-
464
-
465
-
466
-
467
-
468
-
469
-
470
-
471
-
472
-
473
-
474
-
475
-
476
-
477
-
478
-
479
-
480
-
481
-
482
-
483
-
484
-
485
-
486
-
487
-
488
-
489
-
490
-
491
-
492
-
493
-
494
-
495
-
496
-
497
-
498
-
499
-
500
-
501
-
502
-
503
-
504
-
505
-
506
-
507
-
508
-
509
-
510
-
511
-
512
-
513
-
514
-
515
-
516
-
517
-
518
-
519
-
520
-
521
-
522
-
523
-
524
-
525
-
526
-
527
-
528
-
529
-
530
-
531
-
532
-
533
-
534
-
535
-
536
-
537
-
538
-
539
-
540
-
541
-
542
-
543
-
544
-
545
-
546
-
547
-
548
-
549
-
550
-
551
-
552
-
553
-
554
-
555
-
556
-
557
-
558
-
559
-
560
-
561
-
562
-
563
-
564
-
565
-
566
-
567
-
568
-
569
-
570
-
571
-
572
-
573
-
574
-
575
-
576
-
577
-
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
-
586
-
587
-
588
-
589
-
590
-
591
-
592
-
593
-
594
-
595
-
596
-
597
-
598
-
599
-
600
-
601
-
602
-
603
-
604
-
605
-
606
-
607
-
608
-
609
-
610
-
611
-
612
-
613
-
614
-
615
-
616
-
617
-
618
-
619
-
620
-
621
-
622
-
623
-
624
-
625
-
626
-
627
-
628
-
629
-
630
-
631
-
632
-
633
-
634
-
635
-
636
-
637
-
638
-
639
-
640
-
641
-
642
-
643
-
644
-
645
-
646
-
647
-
648
-
649
-
650
-
651
-
652
-
653
-
654
-
655
-
656
-
657
-
658
-
659
-
660
-
661
-
662
-
663
-
664
-
665
-
666
-
667
-
668
-
669
-
670
-
671
-
672
-
673
-
674
-
675
-
676
-
677
-
678
-
679
-
680
-
681
-
682
-
683
-
684
-
685
-
686
-
687
-
688
-
689
-
690
-
691
-
692
-
693
-
694
-
695
-
696
-
697
-
698
-
699
-
700
-
701
-
702
-
703
-
704
-
705
-
706
-
707
-
708
-
709
-
710
-
711
-
712
-
713
-
714
-
715
-
716
-
717
-
718
-
719
-
720
-
721
-
722
-
723
-
724
-
725
-
726
-
727
-
728
-
729
-
730
-
731
-
732
-
733
-
734
-
735
-
736
-
737
-
738
-
739
-
740
-
741
-
742
-
743
-
744
-
745
-
746
-
747
-
748
-
749
-
750
-
751
-
752
-
753
-
754
-
755
-
756
-
757
-
758
-
759
-
760
-
761
-
762
-
763
-
764
-
765
-
766
-
767
-
768
-
769
-
770
-
771
-
772
-
773
-
774
-
775
-
776
-
777
-
778
-
779
-
780
-
781
-
782
-
783
-
784
-
785
-
786
-
787
-
788
-
789
-
790
-
791
-
792
-
793
-
794
-
795
-
796
-
797
-
798
-
799
-
800
-
801
-
802
-
803
-
804
-
805
-
806
-
807
-
808
-
809
-
810
-
811
-
812
-
813
-
814
-
815
-
816
-
817
-
818
-
819
-
820
-
821
-
822
-
823
-
824
-
825
-
826
-
827
-
828
-
829
-
830
-
831
-
832
-
833
-
834
-
835
-
836
-
837
-
838
-
839
-
840
-
841
-
842
-
843
-
844
-
845
-
846
-
847
-
848
-
849
-
850
-
851
-
852
-
853
-
854
-
855
-
856
-
857
-
858
-
859
-
860
-
861
-
862
-
863
-
864
-
865
-
866
-
867
-
868
-
869
-
870
-
871
-
872
-
873
-
874
-
875
-
876
-
877
-
878
-
879
-
880
-
881
-
882
-
883
-
884
-
885
-
886
-
887
-
888
-
889
-
890
-
891
-
892
-
893
-
894
-
895
-
896
-
897
-
898
-
899
-
900
-
901
-
902
-
903
-
904
-
905
-
906
-
907
-
908
-
909
-
910
-
911
-
912
-
913
-
914
-
915
-
916
-
917
-
918
-
919
-
920
-
921
-
922
-
923
-
924
-
925
-
926
-
927
-
928
-
929
-
930
-
931
-- Copyright 2026 Shota FUJI
--
-- This source code and the design it expresses are licensed under
-- the Creative Commons Attribution 4.0 International License.
--
-- SPDX-License-Identifier: CC-BY-4.0
module Models.PWS01.Template.Cuts exposing (cuts)
import Html.Attributes exposing (attribute)
import Length exposing (Length, toMM)
import Models.PWS01.Parameters exposing (LoopStyle(..), Parameters, Profile(..), TipStyle(..))
import Models.PWS01.Parameters.Key exposing (Key(..))
import Svg exposing (..)
import Svg.Attributes exposing (..)
import Svg.Path as Path exposing (..)
import Template.Layout.Container as Container exposing (aligned, columns, gapped, noGrow, padded, rows, sized)
import Template.Layout.Coordinate exposing (Request(..))
import Template.Layout.Item exposing (Item)
highlightStroke : Bool -> Svg.Attribute msg
highlightStroke enabled =
if enabled then
class "highlight-stroke"
else
class "highlight-none"
cuts : Parameters -> Maybe Key -> Item msg
cuts params highlighting =
-- TODO: Pack items more smartly, so it won't push infoArea out
rows
|> sized (AtLeast 0) (AtLeast 0)
|> aligned Container.Start
|> padded (toMM params.rendering.gap / 2)
|> gapped (toMM params.rendering.gap)
|> Container.build
([ columns
|> gapped (toMM params.rendering.gap)
|> Container.build
[ surfacePieces params highlighting |> noGrow
, liningPieces params highlighting |> noGrow
, paddings params highlighting |> noGrow
]
|> Just
, loops params highlighting
]
|> List.filterMap identity
|> List.map noGrow
)
surfacePieces : Parameters -> Maybe Key -> Item msg
surfacePieces params highlighting =
rows
|> aligned Container.Center
|> gapped (toMM params.rendering.gap / 2)
|> Container.build
[ Item
{ width = Exactly 50, height = Exactly 5 }
(\p size ->
text_
[ x (String.fromFloat (p.x + size.width / 2))
, y (String.fromFloat p.y)
, fontSize "5"
, fontWeight "100"
, textAnchor "middle"
, dominantBaseline "hanging"
, fill "currentColor"
]
[ text "Top pieces" ]
)
, columns
|> gapped (toMM params.rendering.gap)
|> Container.build
[ tailPiece
{ offset = 0
, highlighting = highlighting
, parameters = params
, renderFlap = True
, renderHoles = True
}
, headPiece
{ offset = 0
, parameters = params
, highlighting = highlighting
, renderFlaps = True
}
]
]
liningPieces : Parameters -> Maybe Key -> Item msg
liningPieces params highlighting =
rows
|> aligned Container.Center
|> gapped (toMM params.rendering.gap / 2)
|> Container.build
[ Item
{ width = Exactly 40, height = Exactly 5 }
(\p size ->
text_
[ x (String.fromFloat (p.x + size.width / 2))
, y (String.fromFloat p.y)
, fontSize "5"
, fontWeight "100"
, textAnchor "middle"
, dominantBaseline "hanging"
, fill "currentColor"
]
[ text "Linings" ]
)
, columns
|> gapped (toMM params.rendering.gap)
|> Container.build
[ tailPiece
{ offset = 0
, highlighting = highlighting
, parameters = params
, renderFlap = False
, renderHoles = True
}
, headPiece
{ offset = 0
, parameters = params
, highlighting = highlighting
, renderFlaps = False
}
]
]
guideStroke : List (Svg.Attribute msg)
guideStroke =
[ fill "none"
, stroke "currentColor"
, strokeWidth "0.1"
, strokeDasharray "0.5 1"
]
type alias TailPieceProps =
{ offset : Float
, highlighting : Maybe Key
, parameters : Parameters
, renderFlap : Bool
, renderHoles : Bool
}
tailPieceGraphicId : TailPieceProps -> String
tailPieceGraphicId { offset, renderFlap, renderHoles } =
[ "tail"
, offset * 100 |> ceiling |> String.fromInt
, if renderFlap then
"flap"
else
"noflap"
, if renderHoles then
"holes"
else
"noholes"
]
|> String.join "__"
tailPiece : TailPieceProps -> Item msg
tailPiece props =
let
{ offset, highlighting, parameters, renderFlap, renderHoles } =
props
shoulderWidth =
toMM parameters.shoulderWidth
length =
toMM parameters.longPiece.length
taperUntil =
Basics.min
(toMM parameters.longPiece.buckleHole.distance
- (toMM parameters.longPiece.buckleHole.interval * toFloat (parameters.longPiece.buckleHole.adjustments + 1))
)
(length / 2)
taper =
case parameters.profile of
Straight ->
0.0
Tapered to ->
shoulderWidth - toMM to
buckleWidth =
shoulderWidth - taper
curvedSectionLength =
case parameters.longPiece.tip of
Round ->
buckleWidth / 2
Pointed _ ->
buckleWidth
flap =
if renderFlap then
toMM parameters.caseSideFlap
else
0.0
localId : String -> String
localId body =
tailPieceGraphicId props ++ "__" ++ body
in
{ size =
{ width = Exactly (shoulderWidth - offset * 2)
, height = Exactly (length - offset + flap)
}
, element =
\{ x, y } _ ->
Svg.g
[]
[ defs
[]
[ Svg.path
[ id (localId "body")
, Path.d
(MoveTo Absolute ( x - offset, y - offset )
:: VerticalLineTo Relative flap
:: VerticalLineTo Relative offset
:: LineTo Relative ( taper / 2, taperUntil )
:: VerticalLineTo Relative (length - curvedSectionLength - taperUntil)
:: (case parameters.longPiece.tip of
Round ->
[ EllipticalArcCurve Relative
{ rx = buckleWidth / 2
, ry = buckleWidth / 2
, angle = 0.0
, largeArcFlag = LargeArc
, sweepFlag = Counterclockwise
, x = buckleWidth
, y = 0
}
[]
]
Pointed sharpness ->
let
sy =
buckleWidth * (toFloat (100 - sharpness) / 100)
in
[ CubicBezierCurve
Relative
( ( 0, sy )
, ( buckleWidth / 2, buckleWidth )
, ( buckleWidth / 2, buckleWidth )
)
[]
, CubicBezierCurve
Relative
( ( 0, 0 )
, ( buckleWidth / 2, -(buckleWidth - sy) )
, ( buckleWidth / 2, -buckleWidth )
)
[]
]
)
++ [ VerticalLineTo Relative -(length - curvedSectionLength - taperUntil)
, LineTo Relative ( taper / 2, -taperUntil )
, VerticalLineTo Relative -offset
, VerticalLineTo Relative -flap
, ClosePath
]
)
]
[]
]
, if offset > 0 then
g
[]
[ Svg.mask [ id (localId "mask"), attribute "mask-type" "luminance" ]
[ use
[ attribute "href" ("#" ++ localId "body")
, stroke "black"
, strokeWidth (offset * 2 |> String.fromFloat)
, fill "white"
]
[]
]
, use
[ attribute "href" ("#" ++ localId "body")
, Svg.Attributes.mask ("url(#" ++ localId "mask" ++ ")")
, fill "none"
, stroke "currentColor"
, strokeWidth (offset * 2 + toMM parameters.rendering.lineWidth * 2 |> String.fromFloat)
]
[]
]
else
use
[ attribute "href" ("#" ++ localId "body")
, fill "none"
, stroke "currentColor"
, strokeWidth (toMM parameters.rendering.lineWidth |> String.fromFloat)
, highlightStroke
(highlighting
== Just LongPieceLength
|| highlighting
== Just ShoulderWidth
|| highlighting
== Just Profile
|| highlighting
== Just TaperTo
|| (case parameters.profile of
Straight ->
False
Tapered _ ->
highlighting
== Just BuckleHoleDistance
|| highlighting
== Just BuckleHoleInterval
)
)
]
[]
, Svg.path
(Path.d
[ MoveTo Absolute ( x + shoulderWidth / 2 - offset, y )
, VerticalLineTo Relative (length - offset + flap)
]
:: highlightStroke (highlighting == Just LongPieceLength)
:: guideStroke
)
[]
, if renderFlap then
Svg.path
(Path.d
[ MoveTo Absolute ( x + offset, y + flap )
, HorizontalLineTo Relative shoulderWidth
]
:: guideStroke
)
[]
else
g [] []
, case parameters.longPiece.tip of
Round ->
g
[]
[]
Pointed _ ->
Svg.path
(Path.d
[ MoveTo Absolute ( x + taper / 2, y + (length - curvedSectionLength) + flap )
, HorizontalLineTo Relative (buckleWidth - offset * 2)
]
:: guideStroke
)
[]
, case parameters.profile of
Straight ->
g [] []
Tapered _ ->
Svg.path
(Path.d
[ MoveTo Absolute ( x + taper / 2, y + taperUntil + flap )
, HorizontalLineTo Relative (buckleWidth - offset * 2)
]
:: highlightStroke
(highlighting
== Just ShoulderWidth
|| highlighting
== Just TaperTo
|| highlighting
== Just BuckleHoleDistance
|| highlighting
== Just BuckleHoleAdjustments
|| highlighting
== Just BuckleHoleInterval
|| highlighting
== Just BuckleHoleDiameter
)
:: guideStroke
)
[]
, if renderHoles then
g
[]
(List.range 0 (parameters.longPiece.buckleHole.adjustments * 2)
|> List.map
(\index ->
let
{ buckleHole } =
parameters.longPiece
distance =
toMM buckleHole.distance
interval =
toMM buckleHole.interval
in
hole
parameters
buckleHole.diameter
( x + shoulderWidth / 2
, y + flap + distance + interval * toFloat (index - parameters.longPiece.buckleHole.adjustments)
)
[ highlightStroke
(highlighting
== Just BuckleHoleDiameter
|| highlighting
== Just BuckleHoleDistance
|| highlighting
== Just BuckleHoleInterval
|| highlighting
== Just BuckleHoleAdjustments
)
]
)
)
else
g [] []
]
}
hole : Parameters -> Length -> ( Float, Float ) -> List (Svg.Attribute msg) -> Svg msg
hole params diameter ( cx, cy ) attrs =
let
radius =
toMM diameter / 2
in
g
[]
[ circle
(Svg.Attributes.cx (String.fromFloat cx)
:: Svg.Attributes.cy (String.fromFloat cy)
:: r (String.fromFloat radius)
:: fill "none"
:: stroke "currentColor"
:: strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
:: attrs
)
[]
, g
[ fill "none"
, stroke "currentColor"
, strokeWidth "0.1"
]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( cx - radius / 2, cy )
, HorizontalLineTo Relative radius
]
]
[]
, Svg.path
[ Path.d
[ MoveTo Absolute ( cx, cy - radius / 2 )
, VerticalLineTo Relative radius
]
]
[]
]
]
type alias HeadPieceProps =
{ offset : Float
, highlighting : Maybe Key
, parameters : Parameters
, renderFlaps : Bool
}
headPieceGraphicId : HeadPieceProps -> String
headPieceGraphicId { offset, renderFlaps } =
[ "head"
, offset * 100 |> ceiling |> String.fromInt
, if renderFlaps then
"flap"
else
"noflap"
]
|> String.join "__"
headPiece : HeadPieceProps -> Item msg
headPiece props =
let
{ offset, highlighting, parameters, renderFlaps } =
props
shoulderWidth =
toMM parameters.shoulderWidth
length =
toMM parameters.shortPiece.length
taperUntil =
Basics.min
(toMM parameters.longPiece.buckleHole.distance
- (toMM parameters.longPiece.buckleHole.interval * toFloat (parameters.longPiece.buckleHole.adjustments + 1))
)
(length - 10)
taper =
case parameters.profile of
Straight ->
0.0
Tapered to ->
shoulderWidth - toMM to
caseFlap =
if renderFlaps then
toMM parameters.caseSideFlap
else
0.0
buckleFlap =
if renderFlaps then
toMM parameters.shortPiece.buckleSideFlap
else
0.0
localId : String -> String
localId body =
headPieceGraphicId props ++ "__" ++ body
in
{ size =
{ width = Exactly (shoulderWidth - offset * 2)
, height = Exactly (length + caseFlap + buckleFlap)
}
, element =
\{ x, y } _ ->
g
[]
[ defs []
[ Svg.path
[ id (localId "body")
, Path.d
[ MoveTo Absolute ( x - offset, y - offset )
, VerticalLineTo Relative caseFlap
, VerticalLineTo Relative offset
, LineTo Relative ( taper / 2, taperUntil )
, VerticalLineTo Relative (length - taperUntil)
, VerticalLineTo Relative offset
, VerticalLineTo Relative buckleFlap
, HorizontalLineTo Relative (shoulderWidth - taper)
, VerticalLineTo Relative -buckleFlap
, VerticalLineTo Relative -offset
, VerticalLineTo Relative -(length - taperUntil)
, LineTo Relative ( taper / 2, -taperUntil )
, VerticalLineTo Relative -offset
, VerticalLineTo Relative -caseFlap
, ClosePath
]
]
[]
]
, if offset > 0 then
g
[]
[ Svg.mask [ id (localId "mask"), attribute "mask-type" "luminance" ]
[ use
[ attribute "href" ("#" ++ localId "body")
, stroke "black"
, strokeWidth (offset * 2 |> String.fromFloat)
, fill "white"
]
[]
]
, use
[ attribute "href" ("#" ++ localId "body")
, Svg.Attributes.mask ("url(#" ++ localId "mask" ++ ")")
, fill "none"
, stroke "currentColor"
, strokeWidth (offset * 2 + toMM parameters.rendering.lineWidth * 2 |> String.fromFloat)
]
[]
]
else
use
[ attribute "href" ("#" ++ localId "body")
, fill "none"
, stroke "currentColor"
, strokeWidth (toMM parameters.rendering.lineWidth |> String.fromFloat)
, highlightStroke
(highlighting
== Just ShortPieceLength
|| highlighting
== Just ShoulderWidth
|| highlighting
== Just Profile
|| highlighting
== Just TaperTo
|| (case parameters.profile of
Straight ->
False
Tapered _ ->
highlighting
== Just BuckleHoleDistance
|| highlighting
== Just BuckleHoleInterval
)
)
]
[]
, Svg.path
(Path.d
[ MoveTo Absolute ( x + shoulderWidth / 2 - offset, y )
, VerticalLineTo Relative (length + caseFlap + buckleFlap)
]
:: highlightStroke (highlighting == Just ShortPieceLength)
:: guideStroke
)
[]
, if renderFlaps then
g
[]
[ Svg.path
(Path.d
[ MoveTo Absolute ( x, y + caseFlap )
, HorizontalLineTo Relative shoulderWidth
]
:: guideStroke
)
[]
, Svg.path
(Path.d
[ MoveTo Absolute ( x + taper / 2, y + length + caseFlap )
, HorizontalLineTo Relative (shoulderWidth - taper - offset * 2)
]
:: guideStroke
)
[]
]
else
g [] []
, case parameters.profile of
Straight ->
g [] []
Tapered _ ->
Svg.path
(Path.d
[ MoveTo Absolute ( x + taper / 2, y + caseFlap + taperUntil )
, HorizontalLineTo Relative (shoulderWidth - taper - offset * 2)
]
:: guideStroke
)
[]
]
}
loops : Parameters -> Maybe Key -> Maybe (Item msg)
loops params highlighting =
let
root =
params.shortPiece.loops
in
case ( root.fixed, root.free ) of
( Nothing, Nothing ) ->
Nothing
_ ->
rows
|> aligned Container.Center
|> gapped (toMM params.rendering.gap / 2)
|> Container.build
[ Item
{ width = Exactly 40, height = Exactly 5 }
(\p size ->
text_
[ x (String.fromFloat (p.x + size.width / 2))
, y (String.fromFloat p.y)
, fontSize "5"
, fontWeight "100"
, textAnchor "middle"
, dominantBaseline "hanging"
, fill "currentColor"
]
[ text "Loops" ]
)
, columns
|> gapped (toMM params.rendering.gap)
|> aligned Container.Center
|> Container.build
([ fixedLoop params highlighting
, freeLoop params highlighting
]
|> List.filterMap identity
)
]
|> Just
fixedLoop : Parameters -> Maybe Key -> Maybe (Item msg)
fixedLoop params highlighting =
Maybe.map
(\{ width, length } ->
{ size =
{ width = Exactly (toMM length)
, height =
Exactly
(case params.shortPiece.loops.style of
Simple ->
toMM width
Folded ->
toMM width * 2
)
}
, element =
\p size ->
g
[]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y )
, HorizontalLineTo Relative size.width
, VerticalLineTo Relative size.height
, HorizontalLineTo Relative -size.width
, ClosePath
]
, fill "none"
, stroke "currentColor"
, strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
, highlightStroke (highlighting == Just FixedLoopWidth || highlighting == Just FixedLoopLength)
]
[]
, case params.shortPiece.loops.style of
Simple ->
g [] []
Folded ->
g
[ fill "none"
, stroke "currentColor"
, strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
, strokeDasharray "1 0.5"
]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y + toMM width / 2 )
, HorizontalLineTo Relative size.width
]
]
[]
, Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y + toMM width * 1.5 )
, HorizontalLineTo Relative size.width
]
]
[]
]
]
}
)
params.shortPiece.loops.fixed
freeLoop : Parameters -> Maybe Key -> Maybe (Item msg)
freeLoop params highlighting =
Maybe.map
(\{ width, length, overlap } ->
{ size =
{ width = Exactly (toMM length + toMM overlap)
, height =
Exactly
(case params.shortPiece.loops.style of
Simple ->
toMM width
Folded ->
toMM width * 2
)
}
, element =
\p size ->
g
[]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y )
, HorizontalLineTo Relative size.width
, VerticalLineTo Relative size.height
, HorizontalLineTo Relative -size.width
, ClosePath
]
, fill "none"
, stroke "currentColor"
, strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
, highlightStroke
(highlighting
== Just FreeLoopWidth
|| highlighting
== Just FreeLoopLength
|| highlighting
== Just FreeLoopOverlap
)
]
[]
, g
[ fill "none"
, stroke "currentColor"
, strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
, strokeDasharray "1 0.5"
]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( p.x + toMM overlap, p.y )
, VerticalLineTo Relative size.height
]
]
[]
, Svg.path
[ Path.d
[ MoveTo Absolute ( p.x + size.width - toMM overlap, p.y )
, VerticalLineTo Relative size.height
]
]
[]
]
, case params.shortPiece.loops.style of
Simple ->
g [] []
Folded ->
g
[ fill "none"
, stroke "currentColor"
, strokeWidth (toMM params.rendering.lineWidth |> String.fromFloat)
, strokeDasharray "1 0.5"
]
[ Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y + toMM width / 2 )
, HorizontalLineTo Relative size.width
]
]
[]
, Svg.path
[ Path.d
[ MoveTo Absolute ( p.x, p.y + toMM width * 1.5 )
, HorizontalLineTo Relative size.width
]
]
[]
]
]
}
)
params.shortPiece.loops.free
paddings : Parameters -> Maybe Key -> Item msg
paddings params highlighting =
let
offset =
toMM params.paddingOffset
narrowestWidth =
case params.profile of
Straight ->
toMM params.shoulderWidth
Tapered to ->
toMM to
in
if offset == 0 || (offset * 2) >= narrowestWidth then
Item { width = Exactly 0, height = Exactly 0 } (\_ _ -> g [] [])
else
rows
|> aligned Container.Center
|> gapped (toMM params.rendering.gap / 2)
|> Container.build
[ Item
{ width = Exactly 21, height = Exactly 5 }
(\p size ->
text_
[ x (String.fromFloat (p.x + size.width / 2))
, y (String.fromFloat p.y)
, fontSize (String.fromFloat size.height)
, fontWeight "100"
, textAnchor "middle"
, dominantBaseline "hanging"
, fill "currentColor"
]
[ text "Paddings" ]
)
, columns
|> gapped (toMM params.rendering.gap)
|> Container.build
[ tailPiece
{ offset = offset
, parameters = params
, highlighting = highlighting
, renderFlap = False
, renderHoles = False
}
, headPiece
{ offset = offset
, parameters = params
, highlighting = highlighting
, renderFlaps = False
}
]
]