-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontraption.go
1816 lines (1618 loc) · 44 KB
/
contraption.go
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
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Contraption: a simple framework for user interfaces.
//
// A good user interface framework must be an engine for a word processing game.
//
// TODO:
// - Optimized paint
// - Two-pass Z-buffered drawing
// - Eliminate overdraw
// - First pass: opaque top-down, second pass: translucent bottom-up
// - Merging
// - Merge all text that was not overlayed above itself into one call
// - Merge all not overlayed shapes with the same fill into one call
// - Merge all not overlayed colors and (if possible) gradients into one call
// - Quadtree for pool
// - Needed for not redrawing the whole screen every time
// - Wall for errors.
// - Combine all pools into one struct so later nested crops/sequences can be implemented more easily.
// - Animations
// - All animations are specified by deadline, no implicit duration
// - Places burden to maintain animation times on user :grin:
// - Interpolate between two trees (reordering, scaling etc)
// - Anchors
// - For Curve aligner and other CAD-like features
// - Are just numbers
// - Textbox behavior
// - https://rxi.github.io/textbox_behaviour.html
// - Implemented over Sequence
// - TextSequence interface { Backspace(i, j), Delete(i, j), Insert(i, j), Copy(i, j) etc }
// - Sequence cropping will provide efficiency.
// - Editable() modifier
// - Aligner and Align will change the behavior.
// - Elements that are not in TextSequences can not be edited
// - But can be copied.
// - Activator stack
// - Word layout
// - Together with stretch creates a flexbox-like system
// - Together with laziness creates a universal layout framework, capable of word processing
// - Will be used for text.
// - func Hwords(perline func() Sorm) Sorm
// - func Vwords(perline func() Sorm) Sorm
// - Secondary axis limits influenced by perpendicular Void
// - wo.Text(io.RuneScanner) []Sorm
// - Returns Knuth-Plass-ready stream of boxes, Glues and Penalties.
// ? How to insert anything in between symbols?
// ? RuneScanner splitter?
// - wo.Cap(float64) (can't be negative)
// - wo.Lsp(float64)
// - Knuth-Plass
// ? Interpret negative sizes as glue.
// - func Hknuth(perline func() Sorm) Sorm
// - func Vknuth(perline func() Sorm) Sorm
// - func Glue(width, minus, plus float64) Sorm // Analogous to wo.Void() but undirectional.
// - func Penalty(replacewith func() Sorm, penalty float64) Sorm
// - Alt: Penalty as a builder on a target shape
// - Void(0, y) is already a “strut”
// - Investigate better ways to specify size values
// - Current: -1+20i — complex128, + concrete, - stretch, imaginary adds concrete change to stretchy
// + Almost transparent
// + Looks very good for static values
// ~ Simple
// - Variable stretch is done through complex() constructor
// - Arithmetic is static only
// - Strings: "200", "1s+20px"
// + Most powerful
// + Can implement deferred arithmetics
// + 1dp+8cm-20%/1s
// + Can be extendable by user
// + Parsing can be quite fast if done right
// - Construction from variables requires strconv
// + Or with printf-like construction: Sz("$dp+$cm-20%/$s", 1, 8, 1)
// - But this is equivalent to builders and not interesting
// - [N]int
// + Easily extendable
// - Looks gross and is not ergonomical
// - Builders: St(1).Add(Px(20))
// + Powerful
// + Can implement deferred arithmetics
// + Dp(1).Add(Cm(8)).Sub(Percent(20)).Div(St(1))
// - Less readable
// - Add another two values to every Sorm constructor
// - Requires major refactoring
// - Looks overcomplicated
// - Sequence buffer size hints
// - 16 elements on complex and large items
// - 128 on moderately complex and medium-sized items (messages in chat)
// - 1024 on small and simple elements (rows of data, histograms)
// + Round shape sizes inside aligners.
// + Noround modifier
// ? Smooth rounding hint — don't round if in motion
// - Probably when implementing this will be the best time to go for
// geom.Rect for storing xywh
// ? LimitOverride
// - Grid aligner
// - wo.Hgrid(cols int) + wo.Halign() — secondary alignment
// - wo.Vgrid(rows int) + wo.Valign()
// - Primary alignment won't work — makes no sense
// - Negative sizes in primary axis won't work.
// - No instructions for items like in CSS grid.
// - Negative sizes in secondary axis are distributed.
// - BufferSequence
// - MemoBufferSequence
// - RuneScannerSequence
// - Needs recording of the whole given RuneScanner, at least with current Sequence implementation.
// ± Imaginary sizes.
// - -1 + 20i — negative stretch, add 20 scaled by local transform pixels to size on layout step
// - -1 - 20i < -1 < -1 + 20i
// + Use: extending hitbox of elements without changing layout (together with Override)
// ± Works only on noaligner, sequences ignore it yet.
// - Progress reader for IO operations.
// - contraption.ProgressReader{ rd io.Reader; bytes, byteswritten int }
// - var _ io.Reader = &ProgressReader{}
// - (*ProgressReader).Remaining() float64 -> 0.0–0.1
// - (*ProgressReader).RemainingBytes() int
// - Display sinks on F2 view. Make F2 configurable also.
// - Stylus events
// - Touch(<50), Touch(>= 10) — threshold for pressure
// - File drag event: Drag(*.txt)
// - A companion for Drop — matches when the file is dragged above the area.
// - Needs changes in GLFW or changing input library. SDL supports this.
// - Interactive views for very large 1d and 2d data: waveforms, giant Minecraft maps, y-log STFT frames, etc.
// - Easy insertion of Sorms between the data. See https://www.youtube.com/watch?v=Cz0OvnR_aoY.
// - Probably very easy to implement by simply slicing the data.
// - Why? Try to display STFT of a music file using Matplotlib, then rescale the window. Enjoy the delay.
// + Sequence must be a special shape that pastes Sorms inside a compound, not being compound itself
// - So wo.Text(io.RuneReader) could be Sequence
// + Not clear how to reuse memory of pools in this case
// - Matching past in regexps and coords change [MAJOR TOPIC]
// - Easily solved with hitmaps — just draw a hitmap with all component's transformations
// - Could use per-event UV deltas, but 64x viewport memory overhead is too much
// - Use VDOM — retain, reconcile and feedback
// - Save matrix for every shape that looks behind, 64×8×16×[shape count] bytes of overhead
// - Simpler version: for every shape that has Cond/CondPaint, which is larger but still less than 10% of a tree
// - Laziness and scrolling [MAJOR TOPIC]
// + wo.Sequence(seq Sequence) — a window to infinity!
// + Every returned Sorm is included to the parent
// - Non-trivial layout
// - Timeline
// - See how clip names behave in almost every DAW.
// - A way to create uniformly sized buttons (as per tonsky)
// - Just create a special component for this, dumb ass
// - func Huniform(...Sorm) Sorm
// - func Vuniform(...Sorm) Sorm
// - Can use new negative value behavior? Just make needed widths/heights equal negative values.
// - Integer key to determine which sizes must be equal:
// - func Eqkey() Eqkey
// - func Hequal(Eqkey) Sorm
// - func Vequal(Eqkey) Sorm
// - Can be used to implement grid layout.
// - Other proposed names: Hequalize, Vequalize
// - H2Vfollow, V2Hfollow — stretch as one, lay out as another
// - Subworlds — layout inside canvases
// ? Modifier to shape position independence
// ? Fix paint interface
// - Remove bodges from layout (impossible)
// + Drag'n'drop
// - Vector boolean ops
// - Intersecton()
// - Union()
// - Subtraction()
// - Difference()
// - Sprite tiling
// - Tiled rect
// - Tiled path
// - Localization and internationalization guideline
// - Strict methodology of usage
// - func Retain(Sorm) (Sorm, struct{}) // Second returned value is needed only to restrict user from pasting
// - func Deploy(Sorm) (Sorm, struct{}) // it directly to Compound. Because it will break slices.
// - Other backends: Gio, software
// - https://rxi.github.io/cached_software_rendering.html
// ~ func Whereis(Sorm) Sorm — prints where object is on overlay for debug
// - func Target(onScreen *bool) Sorm
// - Commenting the interface
// - Rotations
// ~ Move -> Transform
// + Scale -> Pretransform
// ± Click and and get every line of code that tried to paint over that pixel.
// - Now make it less suck.
// + Separate update and render loops and add func (wo *World) Simulate(until time.Time)
// + Alignment
// + Positioning
// + Spacing
// + Key prop
// + Conditional fill and stroke, event handling
// + Bounds override
// + Draw mark
// + Canvas
// + Depth-first layout
// + Stretch
// + Draw order is not dependent on call order
// + Scissor (wo.Crop())
// + Autovoid container -> Between modifier
// + Fix scale modifier
// + wo.Max() (wo.Limit())
//
//
// Anti-todo:
// - Geometric constraints
// - 3D
// - Stylesheets
//
package contraption
import (
"encoding/gob"
"fmt"
"hash"
"hash/fnv"
"image"
"io"
"math"
"os"
"reflect"
"runtime"
"strings"
"unsafe"
"github.com/go-gl/gl/v2.1/gl"
"github.com/neputevshina/contraption/nanovgo"
"github.com/neputevshina/geom"
"golang.org/x/exp/slices"
)
const sequenceChunkSize = 10
type flagval uint
const (
flagSource flagval = 1 << iota
flagOvrx
flagOvry
flagBetweener
flagCrop
flagMark
flagFindme
flagHshrink
flagVshrink
flagSetStrokewidth
flagSequenceMark
flagSequenceSaved
flagBreakIteration
flagNegativeOvrx
flagNegativeOvry
flagNotCropped
flagIteratedScissor
flagNoround
flagRound
)
//go:generate stringer -type=tagkind -trimprefix=tag
type tagkind int
//go:generate stringer -type=alignerkind
type alignerkind int
const (
tagCompound tagkind = 0
)
const (
_ tagkind = iota
tagCircle
tagRect
tagRoundrect
tagVoid
tagEquation
tagText
tagCanvas
tagVectorText
tagTopDownText
tagBottomUpText
tagSequence
tagIllustration
)
const (
_ tagkind = -iota
tagHalign
tagValign
tagFill
tagStroke
tagStrokewidth
tagIdentity
tagCond
tagCondfill
tagCondstroke
tagBetween
tagScroll
tagSource
tagSink
tagHscroll
tagVscroll
)
const (
_ tagkind = -100 - iota
tagPosttransform
tagTransform
tagCrop
tagHshrink
tagVshrink
tagLimit // Limit must be executed after update of a matrix, but before aligners, because it influences size.
tagVfollow
tagHfollow
tagNoround
tagRound
)
const (
alignerNone alignerkind = iota
alignerVfollow
alignerHfollow
)
// NOTE This might actually be a single table, if needed.
var preActions [100]func(wo *World, compound, mod *Sorm)
var alignerActions [100]func(wo *World, compound *Sorm)
var shapeActions [100]func(wo *World, shape *Sorm)
var modActions [100]func(wo *World, compound, mod *Sorm)
// Before troubleshooting Sorm-returning methods, check this:
// - tagX -> xrun
// - (*World).X(...) sets tagX to Sorm
// - `stringer` was called
func init() {
shapeActions[tagCircle] = circlerun
shapeActions[tagRect] = rectrun
shapeActions[tagRoundrect] = roundrectrun
shapeActions[tagVoid] = voidrun
shapeActions[tagEquation] = equationrun
shapeActions[tagCanvas] = canvasrun
shapeActions[tagVectorText] = vectortextrun
shapeActions[tagText] = generaltextrun(tagText)
shapeActions[tagTopDownText] = generaltextrun(tagTopDownText)
shapeActions[tagBottomUpText] = generaltextrun(tagBottomUpText)
shapeActions[tagSequence] = sequencerun
shapeActions[tagIllustration] = illustrationrun
modActions[-tagHalign] = halignrun
modActions[-tagValign] = valignrun
modActions[-tagFill] = fillrun
modActions[-tagStroke] = strokerun
modActions[-tagStrokewidth] = strokewidthrun
modActions[-tagIdentity] = identityrun
modActions[-tagCond] = condrun
modActions[-tagCondfill] = condfillrun
modActions[-tagCondstroke] = condstrokerun
modActions[-tagBetween] = betweenrun
modActions[-tagSource] = sourcerun
modActions[-tagSink] = sinkrun
preActions[-100-tagPosttransform] = posttransformrun
preActions[-100-tagTransform] = transformrun
preActions[-100-tagCrop] = croprun
preActions[-100-tagVfollow] = vfollowrun
preActions[-100-tagHfollow] = hfollowrun
preActions[-100-tagHshrink] = hshrinkrun
preActions[-100-tagVshrink] = vshrinkrun
preActions[-100-tagLimit] = limitrun
preActions[-100-tagNoround] = noroundrun
preActions[-100-tagRound] = roundrun
alignerActions[alignerNone] = noaligner
alignerActions[alignerVfollow] = vfollowaligner
alignerActions[alignerHfollow] = hfollowaligner
}
type Eqn func(pt geom.Point) (dist float64)
type Equation interface {
Eqn(pt geom.Point) (dist float64)
Size() geom.Point
}
// TODO Nesting sequences and scissors with []pools
type pools struct {
nextn int
pool []Sorm
auxn int
auxpool []Sorm
prefix int
old []Sorm
auxold []Sorm
bufferstash []Sorm
cropped []*Sorm
cropping int
}
type World struct {
*Events
gen int
Window Window
Oscilloscope Oscilloscope
tmp []Sorm
pools
Vgo *Context
cctx any
Wwin, Hwin float64
eqnCache map[any][]geom.Point
eqnLife map[Equation]int
eqnWh map[Equation]geom.Point
nvgofontids []int
capmap map[int]float64
rend int
runepool []rune
keys map[any]*labelt
BeforeVgo func()
drag any
dragstart geom.Point
sinks []func(any)
dragEffects map[reflect.Type]func(interval [2]geom.Point, drag any) Sorm
showOutlines bool
f1 bool
alloc func(n int) (left, right int)
images map[io.Reader]imagestruct
hasher hash.Hash // Current tree hash
oldhash [16]byte // Previous tree hash
}
type imagestruct struct {
gen int
texid int
origsiz geom.Point
}
func AddDragEffect[T any](wo *World, convert func(interval [2]geom.Point, drag T) Sorm) {
var z T
wo.dragEffects[typeof(z)] = func(interval [2]geom.Point, drag any) Sorm {
return convert(interval, drag.(T))
}
}
func (wo *World) ResetDragEffects() {
for k := range wo.dragEffects {
delete(wo.dragEffects, k)
}
}
type Sorm struct {
z, z2, i, pi int
tag tagkind
flags flagval
Size point
p point // Position
l point // Limit
knowns point // Sum of kids with known sizes
props point // Total proportions of a compound
eprops point // Local proportions
add point // Imaginary sizes: adds to stretch
ialign point // Alignment of an image
r float64 // Radius or more
m, postm geom.Geom // Transformation matrices
aligner alignerkind
kidsl, kidsr,
modsl, modsr,
presl, presr int // Slices of a pool for every kids type
idx *Index
scrolld point
cropi int
cropr geom.Rectangle
fill nanovgo.Paint
stroke nanovgo.Paint
strokew float64
fontid int
vecfont *Font
// Some objects use key field for own purposes:
// - Equation stores an Equation object
// - Text stores a io.RuneReader
// - Compound stores an Identity, which also works
// out as Source's dropable object
// - Between stores func() Sorm
key any
condfill func(rect geom.Rectangle) nanovgo.Paint
condstroke func(rect geom.Rectangle) nanovgo.Paint
condfillstroke func(rect geom.Rectangle) (nanovgo.Paint, nanovgo.Paint)
cond func(m Matcher)
canvas func(vgo *Context, wt geom.Geom, rect geom.Rectangle)
sinkid int
callerline int
callerfile string
}
type Index struct {
I int
O float64
}
func (s Sorm) auxkids(wo *World) []Sorm {
return wo.auxpool[s.kidsl:s.kidsr]
}
func (s Sorm) kids2(wo *World) []Sorm {
return wo.pool[s.kidsl:s.kidsr]
}
func (s Sorm) mods(wo *World) []Sorm {
return wo.pool[s.modsl:s.modsr]
}
func (s Sorm) pres(wo *World) []Sorm {
return wo.pool[s.presl:s.presr]
}
// allocmain allocates new memory in pool and returns index range for an object.
func (wo *World) allocmain(n int) (left, right int) {
return alloc(&wo.pool, n)
}
// allocaux is wo.alloc for wo.auxpool.
func (wo *World) allocaux(n int) (left, right int) {
return alloc(&wo.auxpool, n)
}
func alloc(pool *[]Sorm, n int) (left, right int) {
if len(*pool)+n > cap(*pool) {
*pool = append(*pool, make([]Sorm, n)...)
} else {
*pool = (*pool)[0 : len(*pool)+n]
}
return len(*pool) - n, len(*pool)
}
// This allocator is not correct, because it gets more memory
// if capacity has changed, thus making all previously allocated slices
// be out of pool.
//
// But in this case it don't matter. These objects are not going to the pool.
//
// wo.alloc() which previously looked like this is fixed now by using indices in
// Sorms instead of slices.
func (wo *World) tmpalloc(n int) []Sorm {
if len(wo.tmp)+n > cap(wo.tmp) {
wo.tmp = append(wo.tmp, make([]Sorm, n)...)
} else {
wo.tmp = wo.tmp[0 : len(wo.tmp)+n]
}
return wo.tmp[len(wo.tmp)-n:]
}
func (wo *World) stash(s []Sorm) []Sorm {
dst := wo.tmpalloc(len(s))
copy(dst, s)
return dst
}
func (s Sorm) String() string {
type Paint struct {
xform nanovgo.TransformMatrix
extent [2]float32
radius float32
feather float32
innerColor nanovgo.Color
outerColor nanovgo.Color
image int
}
p := (*Paint)(unsafe.Pointer(&s.fill))
color := func(c nanovgo.Color) string {
return fmt.Sprintf("#%2.2x%2.2x%2.2x%2.2x", int(c.R*255), int(c.G*255), int(c.B*255), int(c.A*255))
}
f := func(f float64) string {
return fmt.Sprintf("%g", math.Ceil(f*100)/100)
}
var vals string
if s.tag < 0 {
vals = fmt.Sprint(f(s.Size.X), ", ", f(s.Size.Y), ", ", f(s.p.X), ", ", f(s.p.Y), ", _", f(s.l.X), ", _", f(s.l.Y))
} else {
vals = fmt.Sprint(f(s.Size.X), "×", f(s.Size.Y), ", ", f(s.p.X), "y", f(s.p.Y), ", ↓", f(s.l.X), "×", f(s.l.Y), " ", color(p.innerColor))
}
key := cond(s.key != nil, fmt.Sprint(" [", s.key, "]"), "")
ovrx := cond(s.flags&flagOvrx > 0, "↑X", " ")
ovry := cond(s.flags&flagOvry > 0, "↑Y", " ")
btw := cond(s.flags&flagBetweener > 0, "↔", " ")
seq := " "
z := ""
if s.flags&flagSequenceMark > 0 {
seq = "S"
z = sprint([2]int{s.z, s.z2})
} else {
z = sprint(s.z)
}
clip := " "
if s.flags&flagNotCropped == 0 && s.tag >= 0 {
clip = "C"
}
d := func(i int) int {
return int(math.Floor(math.Log10(float64(i))))
}
digits := d(s.z) + max(d(s.z2), 0)
if seq == "S" {
digits += 2 // "[" and "]"
}
crop := cond(s.cropr.Dx() > 0 && s.cropr.Dy() > 0, fmt.Sprint(s.cropr), "{/}")
return fmt.Sprint(z, strings.Repeat(" ", max(0, 10-digits)), seq, clip, ovrx, ovry, btw, " ", s.tag.String(), " ", vals, ` `, s.props, ` `, crop, key) //, " ", s.callerfile, ":", s.callerline)
}
func (s Sorm) decimate() Sorm {
// FIXME Decimation is now done inside layout.
if !s.decimated() {
return s
}
s.p.X = math.Floor(s.p.X)
s.p.Y = math.Floor(s.p.Y)
s.Size.X = math.Ceil(s.Size.X)
s.Size.Y = math.Ceil(s.Size.Y)
return s
}
func (s *Sorm) decimated() bool {
return s.flags&flagRound > 0 || !(s.flags&flagNoround > 0)
}
// BaseWorld returns itself.
// This method allows to access base World class from user worlds.
func (wo *World) BaseWorld() *World {
return wo
}
func (wo *World) beginsorm() (s Sorm) {
wo.nextn++
s = Sorm{
z: wo.nextn,
m: geom.Identity2d(),
postm: geom.Identity2d(),
}
if wo.f1 {
_, s.callerfile, s.callerline, _ = runtime.Caller(2)
}
virtual := wo.prefix > 0
if virtual {
s.z = wo.prefix
s.z2 = wo.nextn
s.flags = flagSequenceMark
}
return
}
func (wo *World) endsorm(s Sorm) {
// wr := wo.hasher.Write
// Note that at the moment endsorm() is called it is creating the tree description.
// So every value here is not processed in any way.
// Even CondFill/Stroke is not called yet.
// wr(asbs(s.z))
// wr(asbs(s.Size))
// wr(asbs(s.add))
// wr(asbs(s.fill))
// wr(asbs(s.stroke))
// wr(asbs(s.tag))
// wr(asbs(s.fontid))
// wr(asbs(s.r))
}
func (wo *World) allowed(s *Sorm) bool {
if s.flags&flagNotCropped > 0 && wo.cropping == 1 {
return false
}
if s.flags&flagNotCropped == 0 && wo.cropping == 0 {
return false
}
return true
}
func (wo *World) Prevkey(key any) Sorm {
for _, z := range wo.old {
if z.tag == 0 && z.key == key {
return z
}
}
return Sorm{}
}
func (s Sorm) Fill(p nanovgo.Paint) Sorm {
s.fill = p
return s
}
func (s Sorm) Stroke(p nanovgo.Paint) Sorm {
s.stroke = p
return s
}
func (s Sorm) Strokewidth(w float64) Sorm {
s.strokew = w
s.flags |= flagSetStrokewidth
return s
}
func (s Sorm) FillStroke(p nanovgo.Paint) Sorm {
s.stroke = p
s.fill = p
return s
}
func (s Sorm) CondFill(f func(rect geom.Rectangle) nanovgo.Paint) Sorm {
s.condfill = f
return s
}
func (s Sorm) CondStroke(f func(rect geom.Rectangle) nanovgo.Paint) Sorm {
s.condstroke = f
return s
}
func (s Sorm) CondFillStroke(f func(rect geom.Rectangle) (nanovgo.Paint, nanovgo.Paint)) Sorm {
s.condfillstroke = f
return s
}
func (s Sorm) Cond(f func(Matcher)) Sorm {
s.cond = f
return s
}
func (s Sorm) Lmb(wo *World, f func()) Sorm {
s.cond = func(m Matcher) {
if m.Nochoke().Match(`Click(1):in`) {
f()
}
}
return s
}
func (s Sorm) Override() Sorm {
s.flags |= flagOvrx | flagOvry
return s
}
func (s Sorm) Hoverride() Sorm {
s.flags |= flagOvrx
return s
}
func (s Sorm) Voverride() Sorm {
s.flags |= flagOvry
return s
}
func (s Sorm) Betweener() Sorm {
s.flags |= flagBetweener
return s
}
func (s Sorm) Resize(x, y float64) Sorm {
s.Size.X = x
s.Size.Y = y
return s
}
func (s Sorm) Rectangle() geom.Rectangle {
return geom.Rect(s.p.X, s.p.Y, s.p.X+s.Size.X, s.p.Y+s.Size.Y)
}
// Cat returns a Compound from two Sorm sources.
// Its intended usage is for defining user's own abstractions.
func (wo *World) Cat(a, b []Sorm) (s Sorm) {
tmp := wo.tmpalloc(len(a) + len(b))
copy(tmp[:len(a)], a)
copy(tmp[len(a):], b)
return wo.compound(wo.beginsorm(), tmp...)
}
// Compound is a shape container.
// It combines multiple Sorms into a single shape.
// Every other container is just a rebranded Compound.
func (wo *World) Compound(args ...Sorm) (s Sorm) {
s = wo.compound(wo.beginsorm(), args...)
wo.endsorm(s)
return
}
func (wo *World) realroot(args ...Sorm) Sorm {
return wo.compound2(wo.beginsorm(), true, args...)
}
func (wo *World) compound(s Sorm, args ...Sorm) Sorm {
return wo.compound2(s, false, args...)
}
func (wo *World) compound2(s Sorm, realroot bool, args ...Sorm) Sorm {
var kidc, modc, prec, btwc int
var void func() Sorm
if realroot {
s.l.X = wo.Wwin
s.l.Y = wo.Hwin
}
for _, a := range args {
if a.tag == tagBetween {
void = a.key.(func() Sorm)
}
switch {
case a.tag >= 0:
kidc++
if a.flags&flagBetweener > 0 {
btwc++
}
case a.tag < 0 && a.tag >= -100:
modc++
case a.tag < -100:
prec++
}
}
// TODO Allocate only what needed by index and limit.
// Between-shape allocation pass
tmpn := len(wo.tmp)
voidc := max(kidc-btwc-1, 0)
if void != nil {
kidc = kidc + voidc
// Place new voids into temporary storage so their allocations won't
// break breadth-first order of the pool.
for i := 0; i < voidc; i++ {
wo.tmp = append(wo.tmp, void())
}
}
s.tag = 0
// Shape pass
s.kidsl, s.kidsr = wo.alloc(kidc)
i := 0
for _, a := range args {
if a.tag >= 0 {
s.kids2(wo)[i] = a
i++
if void != nil && voidc > 0 && !(a.flags&flagBetweener > 0) {
s.kids2(wo)[i] = wo.tmp[tmpn]
voidc--
tmpn++
i++
}
}
}
// Modifier pass
s.presl, s.presr = wo.alloc(prec)
s.modsl, s.modsr = wo.alloc(modc)
i, j := 0, 0
for _, a := range args {
switch {
case a.tag < -100:
s.pres(wo)[i] = a
i++
case a.tag < 0:
s.mods(wo)[j] = a
j++
}
}
return s
}
func (wo *World) Root(s ...Sorm) {
wo.Compound(
wo.Void(complex(wo.Wwin, 0), complex(wo.Hwin, 0)),
func() Sorm {
if wo.dragEffects[typeof(wo.drag)] != nil && wo.drag != nil {
ps := [2]geom.Point{wo.dragstart, wo.Trace[0].Pt}
return wo.dragEffects[typeof(wo.drag)](ps, wo.drag)
}
return Sorm{}
}(),
wo.displayOscilloscope(),
wo.realroot(s...))
wo.rend = len(wo.pool)
}
func (wo *World) beginvirtual() (pool []Sorm) {
if sameslice(wo.pool, wo.auxpool) {
panic(`contraption: nested Sequences are not allowed`)
}
pool = wo.pool
wo.pool = wo.auxpool
wo.nextn, wo.auxn = wo.auxn, wo.nextn
return
}
func (wo *World) endvirtual(pool []Sorm) {
wo.auxpool = wo.pool
wo.pool = pool
wo.nextn, wo.auxn = wo.auxn, wo.nextn
}
type kiargs struct {
i Index
a alignerkind
firstloop bool
}
func (s *Sorm) kidsiter(wo *World, a kiargs, f func(k *Sorm)) {
// TODO Idea: take a limit in kidsiter, and if it is cropped, stop iteration when over it.
// It should work since cropped compounds can't stretch kids.
if s.tag == tagSequence {
return
}
kids := wo.pool[s.kidsl:s.kidsr]
out:
for i := range kids {
k := &kids[i]
q, ok := k.key.(Sequence)
if ok {
if k.flags&flagSequenceSaved == 0 {
reall := len(wo.auxpool)
// Treat the aux pool as the main pool and the sequence as the root compound.
pop := wo.beginvirtual()
wo.prefix = k.z
wo.bufferstash = wo.bufferstash[:0]
var buf [32]Sorm
out2:
for i := 0; i < q.Length(wo); i += len(buf) {
// println(i, len(buf), i+len(buf), q.Length(wo))
n := q.Get(wo, i, buf[:])
for j := 0; j < min(n, len(buf)); j++ {
f(&buf[j]) // (1)
if buf[j].flags&flagBreakIteration > 0 { // (2)
break out2
}
}
wo.bufferstash = append(wo.bufferstash, buf[:min(n, len(buf))]...)
}
wo.prefix = 0
wo.endvirtual(pop)
// Copy elements materialized from the sequence to the auxpool,
// treat them like arguments of (*World).Compound
l, r := wo.allocaux(len(wo.bufferstash))
copy(wo.auxpool[l:r], wo.bufferstash)
k.kidsl = reall
k.kidsr = r
// Save immediate kids.
k.presl = l
k.presr = r
k.flags |= flagSequenceSaved
} else {
aux := wo.auxpool[k.presl:k.presr]
for i := range aux {
k := &aux[i]
pop := wo.beginvirtual()
f(k) // (1)
wo.endvirtual(pop)
if k.flags&flagBreakIteration > 0 { // (2)
break out
}
}
}
} else {
f(k) // (1)
if k.flags&flagBreakIteration > 0 { // (2)
break out
}
}
}
}
func (wo *World) topbreadthiter(pool []Sorm, f func(s, _ *Sorm)) {
wo.breadthiter(pool, f, false)
}
func (wo *World) bottombreadthiter(pool []Sorm, f func(s, _ *Sorm)) {
wo.breadthiter(pool, f, true)
}
func (wo *World) breadthiter(pool []Sorm, f func(s, _ *Sorm), bottomup bool) {
z := func(pool []Sorm) int { return len(pool) - 1 }
p := func(i int, _ []Sorm) bool { return i >= 0 }
d := -1
if bottomup {
z = func(_ []Sorm) int { return 0 }
p = func(i int, pool []Sorm) bool { return i < len(pool) }